2 * Copyright 2012 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "BenchTimer.h"
9 #include "CopyTilesRenderer.h"
10 #include "PictureBenchmark.h"
11 #include "SkBenchLogger.h"
13 #include "SkGraphics.h"
14 #include "SkImageDecoder.h"
17 #include "SkPicture.h"
20 #include "picture_utils.h"
22 const int DEFAULT_REPEATS = 1;
24 static char const * const gFilterTypes[] = {
35 static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]);
37 static char const * const gFilterFlags[] = {
59 static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]);
61 static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) {
62 int all = drawFilters[0];
64 for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
65 all &= drawFilters[tIndex];
68 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
70 if (all & (1 << fIndex)) {
71 types = gFilterTypes[SkDrawFilter::kTypeCount];
73 for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) {
74 if (drawFilters[tIndex] & (1 << fIndex)) {
75 types += gFilterTypes[tIndex];
85 result += gFilterFlags[fIndex];
90 static SkString filterTypesUsage() {
92 for (size_t index = 0; index < kFilterTypesCount; ++index) {
93 result += gFilterTypes[index];
94 if (index < kFilterTypesCount - 1) {
101 static SkString filterFlagsUsage() {
104 for (size_t index = 0; index < kFilterFlagsCount; ++index) {
105 result += gFilterFlags[index];
106 if (result.size() - len >= 72) {
110 if (index < kFilterFlagsCount - 1) {
117 static void usage(const char* argv0) {
118 SkDebugf("SkPicture benchmarking tool\n");
121 " %s <inputDir>...\n"
122 " [--logFile filename][--timers [wcgWC]*][--logPerIter 1|0][--min]\n"
123 " [--repeat][--timeIndividualTiles] \n"
124 " [--mode pow2tile minWidth height | record | simple\n"
125 " | tile width height | playbackCreation]\n"
128 " [--multi numThreads]\n"
129 " [--viewport width height]\n"
135 " [--filter [%s]:\n [%s]]\n"
136 , argv0, filterTypesUsage().c_str(), filterFlagsUsage().c_str());
139 " inputDir: A list of directories and files to use as input. Files are\n"
140 " expected to have the .skp extension.\n\n"
141 " --logFile filename : destination for writing log output, in addition to stdout.\n");
142 SkDebugf(" --logPerIter 1|0 : "
143 "Log each repeat timer instead of mean, default is disabled.\n");
144 SkDebugf(" --min : Print the minimum times (instead of average).\n");
145 SkDebugf(" --timers [wcgWC]* : "
146 "Display wall, cpu, gpu, truncated wall or truncated cpu time for each picture.\n");
147 SkDebugf(" --timeIndividualTiles : Report times for drawing individual tiles, rather than\n"
148 " times for drawing the whole page.\n"
149 " Requires --mode tile\n");
151 " --mode pow2tile minWidth height | copyTile width height | record | simple\n"
152 " | tile width height | playbackCreation:\n"
153 " Run in the corresponding mode.\n"
154 " Default is simple.\n");
156 " pow2tile minWidth height, Creates tiles with widths\n"
157 " that are all a power of two\n"
158 " such that they minimize the\n"
159 " amount of wasted tile space.\n"
160 " minWidth is the minimum width\n"
161 " of these tiles and must be a\n"
162 " power of two. Simple\n"
163 " rendering using these tiles\n"
164 " is benchmarked.\n");
166 " record, Benchmark picture to picture recording.\n");
168 " simple, Benchmark a simple rendering.\n");
170 " tile width height, Benchmark simple rendering using\n"
171 " tiles with the given dimensions.\n"
172 " copyTile width height, Draw the picture, then copy it into tiles.\n"
173 " Does not support percentages.\n"
174 " If the picture is large enough, breaks it into\n"
175 " larger tiles (and draws the picture once per\n"
176 " larger tile) to avoid creating a large canvas.\n"
177 " Add --tiles x y to specify the number of tiles\n"
178 " per larger tile in the x and y direction.\n"
181 " playbackCreation, Benchmark creation of the SkPicturePlayback.\n");
184 " --multi numThreads : Set the number of threads for multi threaded drawing. Must be greater\n"
185 " than 1. Only works with tiled rendering.\n"
186 " --viewport width height : Set the viewport.\n"
187 " --pipe: Benchmark SkGPipe rendering. Currently incompatible with \"mode\".\n");
189 " --bbh bbhType [width height]: Set the bounding box hierarchy type to\n"
190 " be used. Accepted values are: none, rtree, grid. Default\n"
191 " value is none. Not compatible with --pipe. With value\n"
192 " 'grid', width and height must be specified. 'grid' can\n"
193 " only be used with modes tile, record, and\n"
194 " playbackCreation.");
200 ": Use the corresponding device. Default is bitmap.\n");
202 " bitmap, Render to a bitmap.\n");
205 " gpu, Render to the GPU.\n");
210 "Set the number of times to repeat each test."
211 " Default is %i.\n", DEFAULT_REPEATS);
213 " --filter type:flag : Enable canvas filtering to disable a paint flag,\n"
214 " use no blur or low quality blur, or use no hinting or\n"
215 " slight hinting. For all flags except AAClip, specify the\n"
216 " type of primitive to effect, or choose all. for AAClip\n"
217 " alone, the filter affects all clips independent of type.\n");
220 SkBenchLogger gLogger;
222 static bool run_single_benchmark(const SkString& inputPath,
223 sk_tools::PictureBenchmark& benchmark) {
224 SkFILEStream inputStream;
226 inputStream.setPath(inputPath.c_str());
227 if (!inputStream.isValid()) {
229 err.printf("Could not open file %s\n", inputPath.c_str());
230 gLogger.logError(err);
234 bool success = false;
235 SkPicture picture(&inputStream, &success, &SkImageDecoder::DecodeStream);
238 err.printf("Could not read an SkPicture from %s\n", inputPath.c_str());
239 gLogger.logError(err);
244 sk_tools::get_basename(&filename, inputPath);
247 result.printf("running bench [%i %i] %s ", picture.width(),
248 picture.height(), filename.c_str());
249 gLogger.logProgress(result);
251 benchmark.run(&picture);
255 #define PRINT_USAGE_AND_EXIT \
261 static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* inputs,
262 sk_tools::PictureBenchmark* benchmark) {
263 const char* argv0 = argv[0];
264 char* const* stop = argv + argc;
266 int repeats = DEFAULT_REPEATS;
267 sk_tools::PictureRenderer::SkDeviceTypes deviceType =
268 sk_tools::PictureRenderer::kBitmap_DeviceType;
270 SkAutoTUnref<sk_tools::PictureRenderer> renderer(NULL);
272 // Create a string to show our current settings.
273 // TODO: Make it prettier. Currently it just repeats the command line.
274 SkString commandLine("bench_pictures:");
275 for (int i = 1; i < argc; i++) {
276 commandLine.appendf(" %s", *(argv+i));
278 commandLine.append("\n");
280 bool usePipe = false;
282 bool useTiles = false;
283 const char* widthString = NULL;
284 const char* heightString = NULL;
287 bool isPowerOf2Mode = false;
288 bool isCopyMode = false;
289 const char* xTilesString = NULL;
290 const char* yTilesString = NULL;
291 const char* mode = NULL;
292 bool gridSupported = false;
293 sk_tools::PictureRenderer::BBoxHierarchyType bbhType =
294 sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
295 sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount];
296 sk_bzero(drawFilters, sizeof(drawFilters));
299 for (++argv; argv < stop; ++argv) {
300 if (0 == strcmp(*argv, "--repeat")) {
303 repeats = atoi(*argv);
305 gLogger.logError("--repeat must be given a value > 0\n");
306 PRINT_USAGE_AND_EXIT;
309 gLogger.logError("Missing arg for --repeat\n");
310 PRINT_USAGE_AND_EXIT;
312 } else if (0 == strcmp(*argv, "--pipe")) {
314 } else if (0 == strcmp(*argv, "--logFile")) {
317 if (!gLogger.SetLogFile(*argv)) {
319 str.printf("Could not open %s for writing.", *argv);
320 gLogger.logError(str);
322 // TODO(borenet): We're disabling this for now, due to
323 // write-protected Android devices. The very short-term
324 // solution is to ignore the fact that we have no log file.
328 gLogger.logError("Missing arg for --logFile\n");
329 PRINT_USAGE_AND_EXIT;
331 } else if (0 == strcmp(*argv, "--multi")) {
334 gLogger.logError("Missing arg for --multi\n");
335 PRINT_USAGE_AND_EXIT;
337 numThreads = atoi(*argv);
338 if (numThreads < 2) {
339 gLogger.logError("Number of threads must be at least 2.\n");
340 PRINT_USAGE_AND_EXIT;
342 } else if (0 == strcmp(*argv, "--bbh")) {
345 gLogger.logError("Missing value for --bbh\n");
346 PRINT_USAGE_AND_EXIT;
348 if (0 == strcmp(*argv, "none")) {
349 bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
350 } else if (0 == strcmp(*argv, "rtree")) {
351 bbhType = sk_tools::PictureRenderer::kRTree_BBoxHierarchyType;
352 } else if (0 == strcmp(*argv, "grid")) {
353 bbhType = sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType;
356 gLogger.logError("Missing width for --bbh grid\n");
357 PRINT_USAGE_AND_EXIT;
359 gridWidth = atoi(*argv);
362 gLogger.logError("Missing height for --bbh grid\n");
363 PRINT_USAGE_AND_EXIT;
365 gridHeight = atoi(*argv);
368 err.printf("%s is not a valid value for --bbhType\n", *argv);
369 gLogger.logError(err);
370 PRINT_USAGE_AND_EXIT;
373 } else if (0 == strcmp(*argv, "--mode")) {
374 if (renderer.get() != NULL) {
375 SkDebugf("Cannot combine modes.\n");
376 PRINT_USAGE_AND_EXIT;
381 gLogger.logError("Missing mode for --mode\n");
382 PRINT_USAGE_AND_EXIT;
385 if (0 == strcmp(*argv, "record")) {
386 renderer.reset(SkNEW(sk_tools::RecordPictureRenderer));
387 gridSupported = true;
388 } else if (0 == strcmp(*argv, "clone")) {
389 renderer.reset(sk_tools::CreatePictureCloneRenderer());
390 } else if (0 == strcmp(*argv, "simple")) {
391 renderer.reset(SkNEW(sk_tools::SimplePictureRenderer));
392 } else if ((0 == strcmp(*argv, "tile")) || (0 == strcmp(*argv, "pow2tile"))
393 || 0 == strcmp(*argv, "copyTile")) {
397 if (0 == strcmp(*argv, "pow2tile")) {
398 isPowerOf2Mode = true;
399 } else if (0 == strcmp(*argv, "copyTile")) {
402 gridSupported = true;
408 err.printf("Missing width for --mode %s\n", mode);
409 gLogger.logError(err);
410 PRINT_USAGE_AND_EXIT;
417 err.appendf("Missing height for --mode %s\n", mode);
418 gLogger.logError(err);
419 PRINT_USAGE_AND_EXIT;
421 heightString = *argv;
422 } else if (0 == strcmp(*argv, "playbackCreation")) {
423 renderer.reset(SkNEW(sk_tools::PlaybackCreationRenderer));
424 gridSupported = true;
425 } else if (0 == strcmp(*argv, "gatherPixelRefs")) {
426 renderer.reset(sk_tools::CreateGatherPixelRefsRenderer());
429 err.printf("%s is not a valid mode for --mode\n", *argv);
430 gLogger.logError(err);
431 PRINT_USAGE_AND_EXIT;
433 } else if (0 == strcmp(*argv, "--viewport")) {
436 gLogger.logError("Missing width for --viewport\n");
437 PRINT_USAGE_AND_EXIT;
439 viewport.fWidth = atoi(*argv);
442 gLogger.logError("Missing height for --viewport\n");
443 PRINT_USAGE_AND_EXIT;
445 viewport.fHeight = atoi(*argv);
446 } else if (0 == strcmp(*argv, "--tiles")) {
449 gLogger.logError("Missing x for --tiles\n");
450 PRINT_USAGE_AND_EXIT;
452 xTilesString = *argv;
455 gLogger.logError("Missing y for --tiles\n");
456 PRINT_USAGE_AND_EXIT;
458 yTilesString = *argv;
459 } else if (0 == strcmp(*argv, "--device")) {
462 gLogger.logError("Missing mode for --device\n");
463 PRINT_USAGE_AND_EXIT;
466 if (0 == strcmp(*argv, "bitmap")) {
467 deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType;
470 else if (0 == strcmp(*argv, "gpu")) {
471 deviceType = sk_tools::PictureRenderer::kGPU_DeviceType;
476 err.printf("%s is not a valid mode for --device\n", *argv);
477 gLogger.logError(err);
478 PRINT_USAGE_AND_EXIT;
480 } else if (0 == strcmp(*argv, "--timers")) {
483 bool timerWall = false;
484 bool truncatedTimerWall = false;
485 bool timerCpu = false;
486 bool truncatedTimerCpu = false;
487 bool timerGpu = false;
488 for (char* t = *argv; *t; ++t) {
497 truncatedTimerWall = true;
500 truncatedTimerCpu = true;
510 benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu,
511 truncatedTimerCpu, timerGpu);
513 gLogger.logError("Missing arg for --timers\n");
514 PRINT_USAGE_AND_EXIT;
516 } else if (0 == strcmp(*argv, "--timeIndividualTiles")) {
517 benchmark->setTimeIndividualTiles(true);
518 } else if (0 == strcmp(*argv, "--min")) {
519 benchmark->setPrintMin(true);
520 } else if (0 == strcmp(*argv, "--logPerIter")) {
523 bool log = atoi(*argv) != 0;
524 benchmark->setLogPerIter(log);
526 gLogger.logError("Missing arg for --logPerIter\n");
527 PRINT_USAGE_AND_EXIT;
529 } else if (0 == strcmp(*argv, "--filter")) {
532 const char* colon = strchr(*argv, ':');
535 size_t typeLen = colon - *argv;
536 for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) {
537 if (typeLen == strlen(gFilterTypes[tIndex])
538 && !strncmp(*argv, gFilterTypes[tIndex], typeLen)) {
545 err.printf("Unknown type for --filter %s\n", *argv);
546 gLogger.logError(err);
547 PRINT_USAGE_AND_EXIT;
550 size_t flagLen = strlen(*argv) - typeLen - 1;
551 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) {
552 if (flagLen == strlen(gFilterFlags[fIndex])
553 && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) {
560 err.printf("Unknown flag for --filter %s\n", *argv);
561 gLogger.logError(err);
562 PRINT_USAGE_AND_EXIT;
564 for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) {
565 if (type != SkDrawFilter::kTypeCount && index != type) {
568 drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags)
569 (drawFilters[index] | flag);
573 err.printf("Unknown arg for --filter %s : missing colon\n", *argv);
574 gLogger.logError(err);
575 PRINT_USAGE_AND_EXIT;
578 gLogger.logError("Missing arg for --filter\n");
579 PRINT_USAGE_AND_EXIT;
581 } else if (0 == strcmp(*argv, "--help") || 0 == strcmp(*argv, "-h")) {
582 PRINT_USAGE_AND_EXIT;
584 inputs->push_back(SkString(*argv));
588 if (numThreads > 1 && !useTiles) {
589 gLogger.logError("Multithreaded drawing requires tiled rendering.\n");
590 PRINT_USAGE_AND_EXIT;
593 if (usePipe && sk_tools::PictureRenderer::kNone_BBoxHierarchyType != bbhType) {
594 gLogger.logError("--pipe and --bbh cannot be used together\n");
595 PRINT_USAGE_AND_EXIT;
598 if (sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType == bbhType &&
600 gLogger.logError("'--bbh grid' is not compatible with specified --mode.\n");
601 PRINT_USAGE_AND_EXIT;
605 SkASSERT(NULL == renderer);
606 sk_tools::TiledPictureRenderer* tiledRenderer;
609 if (xTilesString != NULL) {
610 SkASSERT(yTilesString != NULL);
611 x = atoi(xTilesString);
612 y = atoi(yTilesString);
613 if (x <= 0 || y <= 0) {
614 gLogger.logError("--tiles must be given values > 0\n");
615 PRINT_USAGE_AND_EXIT;
620 tiledRenderer = SkNEW_ARGS(sk_tools::CopyTilesRenderer, (x, y));
621 if (benchmark->timeIndividualTiles()) {
622 gLogger.logError("timeIndividualTiles is not compatible with copyTile\n");
623 PRINT_USAGE_AND_EXIT;
625 } else if (numThreads > 1) {
626 tiledRenderer = SkNEW_ARGS(sk_tools::MultiCorePictureRenderer, (numThreads));
628 tiledRenderer = SkNEW(sk_tools::TiledPictureRenderer);
630 if (isPowerOf2Mode) {
631 int minWidth = atoi(widthString);
632 if (!SkIsPow2(minWidth) || minWidth < 0) {
633 tiledRenderer->unref();
635 err.printf("-mode %s must be given a width"
636 " value that is a power of two\n", mode);
637 gLogger.logError(err);
638 PRINT_USAGE_AND_EXIT;
640 tiledRenderer->setTileMinPowerOf2Width(minWidth);
641 } else if (sk_tools::is_percentage(widthString)) {
643 tiledRenderer->unref();
645 err.printf("--mode %s does not support percentages.\n", mode);
646 gLogger.logError(err.c_str());
647 PRINT_USAGE_AND_EXIT;
649 tiledRenderer->setTileWidthPercentage(atof(widthString));
650 if (!(tiledRenderer->getTileWidthPercentage() > 0)) {
651 tiledRenderer->unref();
653 err.appendf("--mode %s must be given a width percentage > 0\n", mode);
654 gLogger.logError(err);
655 PRINT_USAGE_AND_EXIT;
658 tiledRenderer->setTileWidth(atoi(widthString));
659 if (!(tiledRenderer->getTileWidth() > 0)) {
660 tiledRenderer->unref();
662 err.appendf("--mode %s must be given a width > 0\n", mode);
663 gLogger.logError(err);
664 PRINT_USAGE_AND_EXIT;
668 if (sk_tools::is_percentage(heightString)) {
670 tiledRenderer->unref();
672 err.printf("--mode %s does not support percentages.\n", mode);
673 gLogger.logError(err.c_str());
674 PRINT_USAGE_AND_EXIT;
676 tiledRenderer->setTileHeightPercentage(atof(heightString));
677 if (!(tiledRenderer->getTileHeightPercentage() > 0)) {
678 tiledRenderer->unref();
680 err.appendf("--mode %s must be given a height percentage > 0\n", mode);
681 gLogger.logError(err);
682 PRINT_USAGE_AND_EXIT;
685 tiledRenderer->setTileHeight(atoi(heightString));
686 if (!(tiledRenderer->getTileHeight() > 0)) {
687 tiledRenderer->unref();
689 err.appendf("--mode %s must be given a height > 0\n", mode);
690 gLogger.logError(err);
691 PRINT_USAGE_AND_EXIT;
694 if (numThreads > 1) {
696 if (sk_tools::PictureRenderer::kGPU_DeviceType == deviceType) {
697 tiledRenderer->unref();
698 gLogger.logError("GPU not compatible with multithreaded tiling.\n");
699 PRINT_USAGE_AND_EXIT;
703 renderer.reset(tiledRenderer);
705 gLogger.logError("Pipe rendering is currently not compatible with tiling.\n"
706 "Turning off pipe.\n");
709 if (benchmark->timeIndividualTiles()) {
710 gLogger.logError("timeIndividualTiles requires tiled rendering.\n");
711 PRINT_USAGE_AND_EXIT;
714 if (renderer.get() != NULL) {
715 gLogger.logError("Pipe is incompatible with other modes.\n");
716 PRINT_USAGE_AND_EXIT;
718 renderer.reset(SkNEW(sk_tools::PipePictureRenderer));
721 if (inputs->count() < 1) {
722 PRINT_USAGE_AND_EXIT;
725 if (NULL == renderer) {
726 renderer.reset(SkNEW(sk_tools::SimplePictureRenderer));
729 renderer->setBBoxHierarchyType(bbhType);
730 renderer->setDrawFilters(drawFilters, filtersName(drawFilters));
731 renderer->setGridSize(gridWidth, gridHeight);
732 renderer->setViewport(viewport);
733 benchmark->setRenderer(renderer);
734 benchmark->setRepeats(repeats);
735 benchmark->setDeviceType(deviceType);
736 benchmark->setLogger(&gLogger);
737 // Report current settings:
738 gLogger.logProgress(commandLine);
741 static int process_input(const SkString& input,
742 sk_tools::PictureBenchmark& benchmark) {
743 SkOSFile::Iter iter(input.c_str(), "skp");
744 SkString inputFilename;
746 if (iter.next(&inputFilename)) {
749 sk_tools::make_filepath(&inputPath, input, inputFilename);
750 if (!run_single_benchmark(inputPath, benchmark)) {
753 } while(iter.next(&inputFilename));
754 } else if (SkStrEndsWith(input.c_str(), ".skp")) {
755 if (!run_single_benchmark(input, benchmark)) {
760 warning.printf("Warning: skipping %s\n", input.c_str());
761 gLogger.logError(warning);
766 int tool_main(int argc, char** argv);
767 int tool_main(int argc, char** argv) {
768 #ifdef SK_ENABLE_INST_COUNT
769 gPrintInstCount = true;
773 SkTArray<SkString> inputs;
774 sk_tools::PictureBenchmark benchmark;
776 parse_commandline(argc, argv, &inputs, &benchmark);
779 for (int i = 0; i < inputs.count(); ++i) {
780 failures += process_input(inputs[i], benchmark);
785 err.printf("Failed to run %i benchmarks.\n", failures);
786 gLogger.logError(err);
792 #if !defined SK_BUILD_FOR_IOS
793 int main(int argc, char * const argv[]) {
794 return tool_main(argc, (char**) argv);