blob: 9de0bfcf828ab91a493307d2173beb9bfbdeb085 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "BenchTimer.h"
#include "LazyDecodeBitmap.h"
#include "PictureBenchmark.h"
#include "PictureRenderer.h"
#include "SkBenchmark.h"
#include "SkForceLinking.h"
#include "SkGraphics.h"
#include "SkStream.h"
#include "SkString.h"
#include "SkTArray.h"
#include "TimerData.h"
static const int kNumNormalRecordings = SkBENCHLOOP(10);
static const int kNumRTreeRecordings = SkBENCHLOOP(10);
static const int kNumPlaybacks = SkBENCHLOOP(4);
static const size_t kNumBaseBenchmarks = 3;
static const size_t kNumTileSizes = 3;
static const size_t kNumBbhPlaybackBenchmarks = 3;
static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
enum BenchmarkType {
kNormal_BenchmarkType = 0,
kRTree_BenchmarkType,
};
struct Histogram {
Histogram() {
// Make fCpuTime negative so that we don't mess with stats:
fCpuTime = SkIntToScalar(-1);
}
SkScalar fCpuTime;
SkString fPath;
};
typedef void (*BenchmarkFunction)
(BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
// Defined below.
static void benchmark_playback(
BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
static void benchmark_recording(
BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
/**
* Acts as a POD containing information needed to run a benchmark.
* Provides static methods to poll benchmark info from an index.
*/
struct BenchmarkControl {
SkISize fTileSize;
BenchmarkType fType;
BenchmarkFunction fFunction;
SkString fName;
/**
* Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
*/
static BenchmarkControl Make(size_t i) {
SkASSERT(kNumBenchmarks > i);
BenchmarkControl benchControl;
benchControl.fTileSize = getTileSize(i);
benchControl.fType = getBenchmarkType(i);
benchControl.fFunction = getBenchmarkFunc(i);
benchControl.fName = getBenchmarkName(i);
return benchControl;
}
enum BaseBenchmarks {
kNormalRecord = 0,
kRTreeRecord,
kNormalPlayback,
};
static SkISize fTileSizes[kNumTileSizes];
static SkISize getTileSize(size_t i) {
// Two of the base benchmarks don't need a tile size. But to maintain simplicity
// down the pipeline we have to let a couple of values unused.
if (i < kNumBaseBenchmarks) {
return SkISize::Make(256, 256);
}
if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
return fTileSizes[i - kNumBaseBenchmarks];
}
SkASSERT(0);
return SkISize::Make(0, 0);
}
static BenchmarkType getBenchmarkType(size_t i) {
if (i < kNumBaseBenchmarks) {
switch (i) {
case kNormalRecord:
return kNormal_BenchmarkType;
case kNormalPlayback:
return kNormal_BenchmarkType;
case kRTreeRecord:
return kRTree_BenchmarkType;
}
}
if (i < kNumBenchmarks) {
return kRTree_BenchmarkType;
}
SkASSERT(0);
return kRTree_BenchmarkType;
}
static BenchmarkFunction getBenchmarkFunc(size_t i) {
// Base functions.
switch (i) {
case kNormalRecord:
return benchmark_recording;
case kNormalPlayback:
return benchmark_playback;
case kRTreeRecord:
return benchmark_recording;
}
// RTree playbacks
if (i < kNumBenchmarks) {
return benchmark_playback;
}
SkASSERT(0);
return NULL;
}
static SkString getBenchmarkName(size_t i) {
// Base benchmark names
switch (i) {
case kNormalRecord:
return SkString("normal_recording");
case kNormalPlayback:
return SkString("normal_playback");
case kRTreeRecord:
return SkString("rtree_recording");
}
// RTree benchmark names.
if (i < kNumBenchmarks) {
SkASSERT(i >= kNumBaseBenchmarks);
SkString name;
name.printf("rtree_playback_%dx%d",
fTileSizes[i - kNumBaseBenchmarks].fWidth,
fTileSizes[i - kNumBaseBenchmarks].fHeight);
return name;
} else {
SkASSERT(0);
}
return SkString("");
}
};
SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
SkISize::Make(256, 256),
SkISize::Make(512, 512),
SkISize::Make(1024, 1024),
};
static SkPicture* pic_from_path(const char path[]) {
SkFILEStream stream(path);
if (!stream.isValid()) {
SkDebugf("-- Can't open '%s'\n", path);
return NULL;
}
return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
}
/**
* This function is the sink to which all work ends up going.
* Renders the picture into the renderer. It may or may not use an RTree.
* The renderer is chosen upstream. If we want to measure recording, we will
* use a RecordPictureRenderer. If we want to measure rendering, we eill use a
* TiledPictureRenderer.
*/
static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
int benchmarkType, const SkString& path, SkPicture* pic,
const int numRepeats, const char *msg, BenchTimer* timer) {
SkString msgPrefix;
switch (benchmarkType){
case kNormal_BenchmarkType:
msgPrefix.set("Normal");
renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
break;
case kRTree_BenchmarkType:
msgPrefix.set("RTree");
renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
break;
default:
SkASSERT(0);
break;
}
renderer->init(pic);
/**
* If the renderer is not tiled, assume we are measuring recording.
*/
bool isPlayback = (NULL != renderer->getTiledRenderer());
SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
for (int i = 0; i < numRepeats; ++i) {
renderer->setup();
// Render once to fill caches.
renderer->render(NULL);
// Render again to measure
timer->start();
bool result = renderer->render(NULL);
timer->end();
// We only care about a false result on playback. RecordPictureRenderer::render will always
// return false because we are passing a NULL file name on purpose; which is fine.
if(isPlayback && !result) {
SkDebugf("Error rendering during playback.\n");
}
}
renderer->end();
}
/**
* Call do_benchmark_work with a tiled renderer using the default tile dimensions.
*/
static void benchmark_playback(
BenchmarkType benchmarkType, const SkISize& tileSize,
const SkString& path, SkPicture* pic, BenchTimer* timer) {
sk_tools::TiledPictureRenderer renderer;
SkString message("tiled_playback");
message.appendf("_%dx%d", tileSize.fWidth, tileSize.fHeight);
do_benchmark_work(&renderer, benchmarkType,
path, pic, kNumPlaybacks, message.c_str(), timer);
}
/**
* Call do_benchmark_work with a RecordPictureRenderer.
*/
static void benchmark_recording(
BenchmarkType benchmarkType, const SkISize& tileSize,
const SkString& path, SkPicture* pic, BenchTimer* timer) {
sk_tools::RecordPictureRenderer renderer;
int numRecordings = 0;
switch(benchmarkType) {
case kRTree_BenchmarkType:
numRecordings = kNumRTreeRecordings;
break;
case kNormal_BenchmarkType:
numRecordings = kNumNormalRecordings;
break;
}
do_benchmark_work(&renderer, benchmarkType, path, pic, numRecordings, "recording", timer);
}
/**
* Takes argc,argv along with one of the benchmark functions defined above.
* Will loop along all skp files and perform measurments.
*
* Returns a SkScalar representing CPU time taken during benchmark.
* As a side effect, it spits the timer result to stdout.
* Will return -1.0 on error.
*/
static bool benchmark_loop(
int argc,
char **argv,
const BenchmarkControl& benchControl,
SkTArray<Histogram>& histogram) {
static const SkString timeFormat("%f");
TimerData timerData(argc - 1);
for (int index = 1; index < argc; ++index) {
BenchTimer timer;
SkString path(argv[index]);
SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str()));
if (NULL == pic) {
SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str());
continue;
}
benchControl.fFunction(benchControl.fType, benchControl.fTileSize, path, pic, &timer);
SkAssertResult(timerData.appendTimes(&timer));
histogram[index - 1].fPath = path;
histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu);
}
const SkString timerResult = timerData.getResult(
/*doubleFormat = */ timeFormat.c_str(),
/*result = */ TimerData::kAvg_Result,
/*configName = */ benchControl.fName.c_str(),
/*timerFlags = */ TimerData::kCpu_Flag);
const char findStr[] = "= ";
int pos = timerResult.find(findStr);
if (-1 == pos) {
SkDebugf("Unexpected output from TimerData::getResult(...). Unable to parse.");
return false;
}
SkScalar cpuTime = SkDoubleToScalar(atof(timerResult.c_str() + pos + sizeof(findStr) - 1));
if (cpuTime == 0) { // atof returns 0.0 on error.
SkDebugf("Unable to read value from timer result.\n");
return false;
}
return true;
}
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
SkAutoGraphics ag;
SkString usage;
usage.printf("Usage: filename [filename]*\n");
if (argc < 2) {
SkDebugf("%s\n", usage.c_str());
return -1;
}
SkTArray<Histogram> histograms[kNumBenchmarks];
for (size_t i = 0; i < kNumBenchmarks; ++i) {
histograms[i].reset(argc - 1);
bool success = benchmark_loop(
argc, argv,
BenchmarkControl::Make(i),
histograms[i]);
if (!success) {
SkDebugf("benchmark_loop failed at index %d", i);
}
}
// Output gnuplot readable histogram data..
const char* pbTitle = "bbh_shootout_playback.dat";
const char* recTitle = "bbh_shootout_record.dat";
SkFILEWStream playbackOut(pbTitle);
SkFILEWStream recordOut(recTitle);
recordOut.writeText("# ");
playbackOut.writeText("# ");
for (size_t i = 0; i < kNumBenchmarks; ++i) {
SkString out;
out.printf("%s ", BenchmarkControl::getBenchmarkName(i).c_str());
if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_recording) {
recordOut.writeText(out.c_str());
}
if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_playback) {
playbackOut.writeText(out.c_str());
}
}
recordOut.writeText("\n");
playbackOut.writeText("\n");
for (int i = 0; i < argc - 1; ++i) {
SkString pbLine;
SkString recLine;
// ==== Write record info
recLine.printf("%d ", i);
recLine.appendf("%f ", histograms[0][i].fCpuTime); // Append normal_record time
recLine.appendf("%f", histograms[1][i].fCpuTime); // Append rtree_record time
// ==== Write playback info
pbLine.printf("%d ", i);
pbLine.appendf("%f ", histograms[2][i].fCpuTime); // Start with normal playback time.
// Append all playback benchmark times.
for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) {
pbLine.appendf("%f ", histograms[j][i].fCpuTime);
}
pbLine.remove(pbLine.size() - 1, 1); // Remove trailing space from line.
pbLine.appendf("\n");
recLine.appendf("\n");
playbackOut.writeText(pbLine.c_str());
recordOut.writeText(recLine.c_str());
}
SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle);
return 0;
}
#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
int main(int argc, char** argv) {
return tool_main(argc, argv);
}
#endif