skia2/tools/bench_record.cpp
mtklein 887f3979f0 Add EXPERIMENTAL_beginRecording() for SkRecord-based recording.
The interesting stuff is in SkPictureRecorder.{h,cpp}.  The rest is mostly moving SkRecord from its own directories into core to avoid circular dependencies in GYP.

After plumbing SkRecord all the way through in Picture, I'll delete its old entry point include/record/SkRecording.h.  For now it and record.gypi need to stay where they are to keep Chrome building.

BUG=skia:
R=reed@google.com, mtklein@google.com

Author: mtklein@chromium.org

Review URL: https://codereview.chromium.org/331573004
2014-06-17 12:08:16 -07:00

177 lines
5.9 KiB
C++

/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkCommandLineFlags.h"
#include "SkForceLinking.h"
#include "SkGraphics.h"
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
#include "SkStream.h"
#include "SkString.h"
#include "BenchTimer.h"
#include "LazyDecodeBitmap.h"
#include "Stats.h"
typedef WallTimer Timer;
__SK_FORCE_IMAGE_DECODER_LINKING;
DEFINE_string2(skps, r, "skps", "Directory containing SKPs to read and re-record.");
DEFINE_int32(samples, 10, "Number of times to re-record each SKP.");
DEFINE_int32(tileGridSize, 512, "Set the tile grid size. Has no effect if bbh is not set to tilegrid.");
DEFINE_string(bbh, "", "Turn on the bbh and select the type, one of rtree, tilegrid, quadtree");
DEFINE_bool(skr, false, "Record SKR instead of SKP.");
DEFINE_string(match, "", "The usual filters on file names of SKPs to bench.");
DEFINE_string(timescale, "us", "Print times in ms, us, or ns");
DEFINE_double(overheadGoal, 0.0001,
"Try to make timer overhead at most this fraction of our sample measurements.");
DEFINE_int32(verbose, 0, "0: print min sample; "
"1: print min, mean, max and noise indication "
"2: print all samples");
static double timescale() {
if (FLAGS_timescale.contains("us")) return 1000;
if (FLAGS_timescale.contains("ns")) return 1000000;
return 1;
}
static SkBBHFactory* parse_FLAGS_bbh() {
if (FLAGS_bbh.isEmpty()) {
return NULL;
}
if (FLAGS_bbh.contains("rtree")) {
return SkNEW(SkRTreeFactory);
}
if (FLAGS_bbh.contains("tilegrid")) {
SkTileGridFactory::TileGridInfo info;
info.fTileInterval.set(FLAGS_tileGridSize, FLAGS_tileGridSize);
info.fMargin.setEmpty();
info.fOffset.setZero();
return SkNEW_ARGS(SkTileGridFactory, (info));
}
if (FLAGS_bbh.contains("quadtree")) {
return SkNEW(SkQuadTreeFactory);
}
SkDebugf("Invalid bbh type %s, must be one of rtree, tilegrid, quadtree.\n", FLAGS_bbh[0]);
return NULL;
}
static void rerecord(const SkPicture& src, SkBBHFactory* bbhFactory) {
SkPictureRecorder recorder;
if (FLAGS_skr) {
src.draw(recorder.EXPERIMENTAL_beginRecording(src.width(), src.height(), bbhFactory));
} else {
src.draw(recorder.beginRecording(src.width(), src.height(), bbhFactory));
}
SkAutoTUnref<SkPicture> pic(recorder.endRecording());
}
static void bench_record(const SkPicture& src,
const double timerOverhead,
const char* name,
SkBBHFactory* bbhFactory) {
// Rerecord once to warm up any caches. Otherwise the first sample can be very noisy.
rerecord(src, bbhFactory);
// Rerecord once to see how many times we should loop to make timer overhead insignificant.
Timer timer;
do {
timer.start(timescale());
rerecord(src, bbhFactory);
timer.end();
} while (timer.fWall < timerOverhead); // Loop just in case something bizarre happens.
// We want (timer overhead / measurement) to be less than FLAGS_overheadGoal.
// So in each sample, we'll loop enough times to have made that true for our first measurement.
const int loops = (int)ceil(timerOverhead / timer.fWall / FLAGS_overheadGoal);
SkAutoTMalloc<double> samples(FLAGS_samples);
for (int i = 0; i < FLAGS_samples; i++) {
timer.start(timescale());
for (int j = 0; j < loops; j++) {
rerecord(src, bbhFactory);
}
timer.end();
samples[i] = timer.fWall / loops;
}
Stats stats(samples.get(), FLAGS_samples);
if (FLAGS_verbose == 0) {
printf("%g\t%s\n", stats.min, name);
} else if (FLAGS_verbose == 1) {
// Get a rough idea of how noisy the measurements were.
const double noisePercent = 100 * sqrt(stats.var) / stats.mean;
printf("%g\t%g\t%g\t±%.0f%%\t%s\n", stats.min, stats.mean, stats.max, noisePercent, name);
} else if (FLAGS_verbose == 2) {
printf("%s", name);
for (int i = 0; i < FLAGS_samples; i++) {
printf("\t%g", samples[i]);
}
printf("\n");
}
}
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
SkCommandLineFlags::Parse(argc, argv);
SkAutoGraphics autoGraphics;
if (FLAGS_bbh.count() > 1) {
SkDebugf("Multiple bbh arguments supplied.\n");
return 1;
}
SkAutoTDelete<SkBBHFactory> bbhFactory(parse_FLAGS_bbh());
// Each run will use this timer overhead estimate to guess how many times it should run.
static const int kOverheadLoops = 10000000;
Timer timer;
double overheadEstimate = 0.0;
for (int i = 0; i < kOverheadLoops; i++) {
timer.start(timescale());
timer.end();
overheadEstimate += timer.fWall;
}
overheadEstimate /= kOverheadLoops;
SkOSFile::Iter it(FLAGS_skps[0], ".skp");
SkString filename;
bool failed = false;
while (it.next(&filename)) {
if (SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) {
continue;
}
const SkString path = SkOSPath::SkPathJoin(FLAGS_skps[0], filename.c_str());
SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path.c_str()));
if (!stream) {
SkDebugf("Could not read %s.\n", path.c_str());
failed = true;
continue;
}
SkAutoTUnref<SkPicture> src(
SkPicture::CreateFromStream(stream, sk_tools::LazyDecodeBitmap));
if (!src) {
SkDebugf("Could not read %s as an SkPicture.\n", path.c_str());
failed = true;
continue;
}
bench_record(*src, overheadEstimate, filename.c_str(), bbhFactory.get());
}
return failed ? 1 : 0;
}
#if !defined SK_BUILD_FOR_IOS
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
#endif