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