blob: 9de0bfcf828ab91a493307d2173beb9bfbdeb085 [file] [log] [blame]
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +00001/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "BenchTimer.h"
9#include "LazyDecodeBitmap.h"
10#include "PictureBenchmark.h"
11#include "PictureRenderer.h"
12#include "SkBenchmark.h"
13#include "SkForceLinking.h"
14#include "SkGraphics.h"
15#include "SkStream.h"
16#include "SkString.h"
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +000017#include "SkTArray.h"
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +000018#include "TimerData.h"
19
20static const int kNumNormalRecordings = SkBENCHLOOP(10);
21static const int kNumRTreeRecordings = SkBENCHLOOP(10);
22static const int kNumPlaybacks = SkBENCHLOOP(4);
23static const size_t kNumBaseBenchmarks = 3;
24static const size_t kNumTileSizes = 3;
25static const size_t kNumBbhPlaybackBenchmarks = 3;
26static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
27
28enum BenchmarkType {
29 kNormal_BenchmarkType = 0,
30 kRTree_BenchmarkType,
31};
32
33struct Histogram {
34 Histogram() {
35 // Make fCpuTime negative so that we don't mess with stats:
36 fCpuTime = SkIntToScalar(-1);
37 }
38 SkScalar fCpuTime;
39 SkString fPath;
40};
41
42typedef void (*BenchmarkFunction)
43 (BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
44
45// Defined below.
46static void benchmark_playback(
47 BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
48static void benchmark_recording(
49 BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
50
51/**
52 * Acts as a POD containing information needed to run a benchmark.
53 * Provides static methods to poll benchmark info from an index.
54 */
55struct BenchmarkControl {
56 SkISize fTileSize;
57 BenchmarkType fType;
58 BenchmarkFunction fFunction;
59 SkString fName;
60
61 /**
62 * Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
63 */
64 static BenchmarkControl Make(size_t i) {
65 SkASSERT(kNumBenchmarks > i);
66 BenchmarkControl benchControl;
67 benchControl.fTileSize = getTileSize(i);
68 benchControl.fType = getBenchmarkType(i);
69 benchControl.fFunction = getBenchmarkFunc(i);
70 benchControl.fName = getBenchmarkName(i);
71 return benchControl;
72 }
73
74 enum BaseBenchmarks {
75 kNormalRecord = 0,
76 kRTreeRecord,
77 kNormalPlayback,
78 };
79
80 static SkISize fTileSizes[kNumTileSizes];
81
82 static SkISize getTileSize(size_t i) {
83 // Two of the base benchmarks don't need a tile size. But to maintain simplicity
84 // down the pipeline we have to let a couple of values unused.
85 if (i < kNumBaseBenchmarks) {
86 return SkISize::Make(256, 256);
87 }
88 if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
89 return fTileSizes[i - kNumBaseBenchmarks];
90 }
91 SkASSERT(0);
92 return SkISize::Make(0, 0);
93 }
94
95 static BenchmarkType getBenchmarkType(size_t i) {
96 if (i < kNumBaseBenchmarks) {
97 switch (i) {
98 case kNormalRecord:
99 return kNormal_BenchmarkType;
100 case kNormalPlayback:
101 return kNormal_BenchmarkType;
102 case kRTreeRecord:
103 return kRTree_BenchmarkType;
104 }
105 }
106 if (i < kNumBenchmarks) {
107 return kRTree_BenchmarkType;
108 }
109 SkASSERT(0);
110 return kRTree_BenchmarkType;
111 }
112
113 static BenchmarkFunction getBenchmarkFunc(size_t i) {
114 // Base functions.
115 switch (i) {
116 case kNormalRecord:
117 return benchmark_recording;
118 case kNormalPlayback:
119 return benchmark_playback;
120 case kRTreeRecord:
121 return benchmark_recording;
122 }
123 // RTree playbacks
124 if (i < kNumBenchmarks) {
125 return benchmark_playback;
126 }
127 SkASSERT(0);
128 return NULL;
129 }
130
131 static SkString getBenchmarkName(size_t i) {
132 // Base benchmark names
133 switch (i) {
134 case kNormalRecord:
135 return SkString("normal_recording");
136 case kNormalPlayback:
137 return SkString("normal_playback");
138 case kRTreeRecord:
139 return SkString("rtree_recording");
140 }
141 // RTree benchmark names.
142 if (i < kNumBenchmarks) {
143 SkASSERT(i >= kNumBaseBenchmarks);
144 SkString name;
145 name.printf("rtree_playback_%dx%d",
146 fTileSizes[i - kNumBaseBenchmarks].fWidth,
147 fTileSizes[i - kNumBaseBenchmarks].fHeight);
148 return name;
149
150 } else {
151 SkASSERT(0);
152 }
153 return SkString("");
154 }
155
156};
157
158SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
159 SkISize::Make(256, 256),
160 SkISize::Make(512, 512),
161 SkISize::Make(1024, 1024),
162};
163
164static SkPicture* pic_from_path(const char path[]) {
165 SkFILEStream stream(path);
166 if (!stream.isValid()) {
167 SkDebugf("-- Can't open '%s'\n", path);
168 return NULL;
169 }
170 return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
171}
172
173/**
174 * This function is the sink to which all work ends up going.
175 * Renders the picture into the renderer. It may or may not use an RTree.
176 * The renderer is chosen upstream. If we want to measure recording, we will
177 * use a RecordPictureRenderer. If we want to measure rendering, we eill use a
178 * TiledPictureRenderer.
179 */
180static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
181 int benchmarkType, const SkString& path, SkPicture* pic,
182 const int numRepeats, const char *msg, BenchTimer* timer) {
183 SkString msgPrefix;
184
185 switch (benchmarkType){
186 case kNormal_BenchmarkType:
187 msgPrefix.set("Normal");
188 renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
189 break;
190 case kRTree_BenchmarkType:
191 msgPrefix.set("RTree");
192 renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
193 break;
194 default:
195 SkASSERT(0);
196 break;
197 }
198
199 renderer->init(pic);
200
201 /**
202 * If the renderer is not tiled, assume we are measuring recording.
203 */
204 bool isPlayback = (NULL != renderer->getTiledRenderer());
205
206 SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
207 for (int i = 0; i < numRepeats; ++i) {
208 renderer->setup();
209 // Render once to fill caches.
210 renderer->render(NULL);
211 // Render again to measure
212 timer->start();
213 bool result = renderer->render(NULL);
214 timer->end();
215 // We only care about a false result on playback. RecordPictureRenderer::render will always
216 // return false because we are passing a NULL file name on purpose; which is fine.
217 if(isPlayback && !result) {
218 SkDebugf("Error rendering during playback.\n");
219 }
220 }
221 renderer->end();
222}
223
224/**
225 * Call do_benchmark_work with a tiled renderer using the default tile dimensions.
226 */
227static void benchmark_playback(
228 BenchmarkType benchmarkType, const SkISize& tileSize,
229 const SkString& path, SkPicture* pic, BenchTimer* timer) {
230 sk_tools::TiledPictureRenderer renderer;
231
232 SkString message("tiled_playback");
233 message.appendf("_%dx%d", tileSize.fWidth, tileSize.fHeight);
234 do_benchmark_work(&renderer, benchmarkType,
235 path, pic, kNumPlaybacks, message.c_str(), timer);
236}
237
238/**
239 * Call do_benchmark_work with a RecordPictureRenderer.
240 */
241static void benchmark_recording(
242 BenchmarkType benchmarkType, const SkISize& tileSize,
243 const SkString& path, SkPicture* pic, BenchTimer* timer) {
244 sk_tools::RecordPictureRenderer renderer;
245 int numRecordings = 0;
246 switch(benchmarkType) {
247 case kRTree_BenchmarkType:
248 numRecordings = kNumRTreeRecordings;
249 break;
250 case kNormal_BenchmarkType:
251 numRecordings = kNumNormalRecordings;
252 break;
253 }
254 do_benchmark_work(&renderer, benchmarkType, path, pic, numRecordings, "recording", timer);
255}
256
257/**
258 * Takes argc,argv along with one of the benchmark functions defined above.
259 * Will loop along all skp files and perform measurments.
260 *
261 * Returns a SkScalar representing CPU time taken during benchmark.
262 * As a side effect, it spits the timer result to stdout.
263 * Will return -1.0 on error.
264 */
265static bool benchmark_loop(
266 int argc,
267 char **argv,
268 const BenchmarkControl& benchControl,
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +0000269 SkTArray<Histogram>& histogram) {
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000270 static const SkString timeFormat("%f");
commit-bot@chromium.org55fd6122013-07-31 20:00:56 +0000271 TimerData timerData(argc - 1);
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000272 for (int index = 1; index < argc; ++index) {
273 BenchTimer timer;
274 SkString path(argv[index]);
275 SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str()));
276 if (NULL == pic) {
277 SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str());
278 continue;
279 }
280 benchControl.fFunction(benchControl.fType, benchControl.fTileSize, path, pic, &timer);
commit-bot@chromium.org55fd6122013-07-31 20:00:56 +0000281 SkAssertResult(timerData.appendTimes(&timer));
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000282
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +0000283 histogram[index - 1].fPath = path;
284 histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu);
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000285 }
286
287 const SkString timerResult = timerData.getResult(
commit-bot@chromium.org55fd6122013-07-31 20:00:56 +0000288 /*doubleFormat = */ timeFormat.c_str(),
289 /*result = */ TimerData::kAvg_Result,
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000290 /*configName = */ benchControl.fName.c_str(),
commit-bot@chromium.org55fd6122013-07-31 20:00:56 +0000291 /*timerFlags = */ TimerData::kCpu_Flag);
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000292
293 const char findStr[] = "= ";
294 int pos = timerResult.find(findStr);
295 if (-1 == pos) {
296 SkDebugf("Unexpected output from TimerData::getResult(...). Unable to parse.");
297 return false;
298 }
299
300 SkScalar cpuTime = SkDoubleToScalar(atof(timerResult.c_str() + pos + sizeof(findStr) - 1));
301 if (cpuTime == 0) { // atof returns 0.0 on error.
302 SkDebugf("Unable to read value from timer result.\n");
303 return false;
304 }
305 return true;
306}
307
sglez@google.com1d38ae92013-07-19 20:03:57 +0000308int tool_main(int argc, char** argv);
309int tool_main(int argc, char** argv) {
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000310 SkAutoGraphics ag;
311 SkString usage;
312 usage.printf("Usage: filename [filename]*\n");
313
314 if (argc < 2) {
315 SkDebugf("%s\n", usage.c_str());
316 return -1;
317 }
318
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +0000319 SkTArray<Histogram> histograms[kNumBenchmarks];
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000320
321 for (size_t i = 0; i < kNumBenchmarks; ++i) {
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +0000322 histograms[i].reset(argc - 1);
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000323 bool success = benchmark_loop(
324 argc, argv,
325 BenchmarkControl::Make(i),
commit-bot@chromium.org49a07ad2013-07-22 19:28:40 +0000326 histograms[i]);
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000327 if (!success) {
328 SkDebugf("benchmark_loop failed at index %d", i);
329 }
330 }
331
332 // Output gnuplot readable histogram data..
333 const char* pbTitle = "bbh_shootout_playback.dat";
334 const char* recTitle = "bbh_shootout_record.dat";
335 SkFILEWStream playbackOut(pbTitle);
336 SkFILEWStream recordOut(recTitle);
337 recordOut.writeText("# ");
338 playbackOut.writeText("# ");
339 for (size_t i = 0; i < kNumBenchmarks; ++i) {
340 SkString out;
341 out.printf("%s ", BenchmarkControl::getBenchmarkName(i).c_str());
342 if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_recording) {
343 recordOut.writeText(out.c_str());
344 }
345 if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_playback) {
346 playbackOut.writeText(out.c_str());
347 }
348 }
349 recordOut.writeText("\n");
350 playbackOut.writeText("\n");
351
352 for (int i = 0; i < argc - 1; ++i) {
353 SkString pbLine;
354 SkString recLine;
355 // ==== Write record info
356 recLine.printf("%d ", i);
357 recLine.appendf("%f ", histograms[0][i].fCpuTime); // Append normal_record time
358 recLine.appendf("%f", histograms[1][i].fCpuTime); // Append rtree_record time
359
360 // ==== Write playback info
361 pbLine.printf("%d ", i);
362 pbLine.appendf("%f ", histograms[2][i].fCpuTime); // Start with normal playback time.
363 // Append all playback benchmark times.
364 for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) {
365 pbLine.appendf("%f ", histograms[j][i].fCpuTime);
366 }
367 pbLine.remove(pbLine.size() - 1, 1); // Remove trailing space from line.
368 pbLine.appendf("\n");
369 recLine.appendf("\n");
370 playbackOut.writeText(pbLine.c_str());
371 recordOut.writeText(recLine.c_str());
372 }
373 SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle);
374
375 return 0;
376}
377
sglez@google.com5f3f6812013-07-19 20:21:05 +0000378#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
commit-bot@chromium.org6645cde2013-07-19 18:54:04 +0000379int main(int argc, char** argv) {
380 return tool_main(argc, argv);
381}
sglez@google.com5f3f6812013-07-19 20:21:05 +0000382#endif