1 /* 2 * Copyright 2012 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 "BenchLogger.h" 9 #include "Timer.h" 10 #include "CopyTilesRenderer.h" 11 #include "CrashHandler.h" 12 #include "LazyDecodeBitmap.h" 13 #include "PictureBenchmark.h" 14 #include "PictureRenderingFlags.h" 15 #include "PictureResultsWriter.h" 16 #include "SkCommandLineFlags.h" 17 #include "SkData.h" 18 #include "SkDiscardableMemoryPool.h" 19 #include "SkGraphics.h" 20 #include "SkImageDecoder.h" 21 #include "SkMath.h" 22 #include "SkOSFile.h" 23 #include "SkPicture.h" 24 #include "SkStream.h" 25 #include "picture_utils.h" 26 27 BenchLogger gLogger; 28 PictureResultsLoggerWriter gLogWriter(&gLogger); 29 PictureResultsMultiWriter gWriter; 30 31 // Flags used by this file, in alphabetical order. 32 DEFINE_bool(countRAM, false, "Count the RAM used for bitmap pixels in each skp file"); 33 DECLARE_bool(deferImageDecoding); 34 DEFINE_string(filter, "", 35 "type:flag : Enable canvas filtering to disable a paint flag, " 36 "use no blur or low quality blur, or use no hinting or " 37 "slight hinting. For all flags except AAClip, specify the " 38 "type of primitive to effect, or choose all. for AAClip " 39 "alone, the filter affects all clips independent of type. " 40 "Specific flags are listed above."); 41 DEFINE_string(logFile, "", "Destination for writing log output, in addition to stdout."); 42 DEFINE_bool(logPerIter, false, "Log each repeat timer instead of mean."); 43 DEFINE_string(jsonLog, "", "Destination for writing JSON data."); 44 DEFINE_bool(min, false, "Print the minimum times (instead of average)."); 45 DECLARE_string(readPath); 46 DEFINE_int32(repeat, 1, "Set the number of times to repeat each test."); 47 DEFINE_bool(timeIndividualTiles, false, "Report times for drawing individual tiles, rather than " 48 "times for drawing the whole page. Requires tiled rendering."); 49 DEFINE_bool(purgeDecodedTex, false, "Purge decoded and GPU-uploaded textures " 50 "after each iteration."); 51 DEFINE_string(timers, "c", "[wcgWC]*: Display wall, cpu, gpu, truncated wall or truncated cpu time" 52 " for each picture."); 53 DEFINE_bool(trackDeferredCaching, false, "Only meaningful with --deferImageDecoding and " 54 "SK_LAZY_CACHE_STATS set to true. Report percentage of cache hits when using " 55 "deferred image decoding."); 56 57 #if GR_GPU_STATS 58 DEFINE_bool(gpuStats, false, "Only meaningful with gpu configurations. " 59 "Report some GPU call statistics."); 60 #endif 61 62 DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before timing."); 63 64 // Buildbot-specific parameters 65 DEFINE_string(builderName, "", "Name of the builder this is running on."); 66 DEFINE_int32(buildNumber, -1, "Build number of the build this test is running on"); 67 DEFINE_int32(timestamp, 0, "Timestamp of the revision of Skia being tested."); 68 DEFINE_string(gitHash, "", "Commit hash of the revision of Skia being run."); 69 DEFINE_int32(gitNumber, -1, "Git number of the revision of Skia being run."); 70 71 72 static char const * const gFilterTypes[] = { 73 "paint", 74 "point", 75 "line", 76 "bitmap", 77 "rect", 78 "oval", 79 "path", 80 "text", 81 "all", 82 }; 83 84 static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]); 85 86 static char const * const gFilterFlags[] = { 87 "antiAlias", 88 "filterBitmap", 89 "dither", 90 "underlineText", 91 "strikeThruText", 92 "fakeBoldText", 93 "linearText", 94 "subpixelText", 95 "devKernText", 96 "LCDRenderText", 97 "embeddedBitmapText", 98 "autoHinting", 99 "verticalText", 100 "genA8FromLCD", 101 "blur", 102 "hinting", 103 "slightHinting", 104 "AAClip", 105 }; 106 107 static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]); 108 109 static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) { 110 int all = drawFilters[0]; 111 size_t tIndex; 112 for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { 113 all &= drawFilters[tIndex]; 114 } 115 SkString result; 116 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { 117 SkString types; 118 if (all & (1 << fIndex)) { 119 types = gFilterTypes[SkDrawFilter::kTypeCount]; 120 } else { 121 for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { 122 if (drawFilters[tIndex] & (1 << fIndex)) { 123 types += gFilterTypes[tIndex]; 124 } 125 } 126 } 127 if (!types.size()) { 128 continue; 129 } 130 result += "_"; 131 result += types; 132 result += "."; 133 result += gFilterFlags[fIndex]; 134 } 135 return result; 136 } 137 138 static SkString filterTypesUsage() { 139 SkString result; 140 for (size_t index = 0; index < kFilterTypesCount; ++index) { 141 result += gFilterTypes[index]; 142 if (index < kFilterTypesCount - 1) { 143 result += " | "; 144 } 145 } 146 return result; 147 } 148 149 static SkString filterFlagsUsage() { 150 SkString result; 151 size_t len = 0; 152 for (size_t index = 0; index < kFilterFlagsCount; ++index) { 153 result += gFilterFlags[index]; 154 if (result.size() - len >= 72) { 155 result += "\n\t\t"; 156 len = result.size(); 157 } 158 if (index < kFilterFlagsCount - 1) { 159 result += " | "; 160 } 161 } 162 return result; 163 } 164 165 #if SK_LAZY_CACHE_STATS 166 static int32_t gTotalCacheHits; 167 static int32_t gTotalCacheMisses; 168 #endif 169 170 static bool run_single_benchmark(const SkString& inputPath, 171 sk_tools::PictureBenchmark& benchmark) { 172 SkFILEStream inputStream; 173 174 inputStream.setPath(inputPath.c_str()); 175 if (!inputStream.isValid()) { 176 SkString err; 177 err.printf("Could not open file %s\n", inputPath.c_str()); 178 gLogger.logError(err); 179 return false; 180 } 181 182 SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool(); 183 // Since the old picture has been deleted, all pixels should be cleared. 184 SkASSERT(pool->getRAMUsed() == 0); 185 if (FLAGS_countRAM) { 186 pool->setRAMBudget(SK_MaxU32); 187 // Set the limit to max, so all pixels will be kept 188 } 189 190 SkPicture::InstallPixelRefProc proc; 191 if (FLAGS_deferImageDecoding) { 192 proc = &sk_tools::LazyDecodeBitmap; 193 } else { 194 proc = &SkImageDecoder::DecodeMemory; 195 } 196 SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, proc)); 197 198 if (NULL == picture.get()) { 199 SkString err; 200 err.printf("Could not read an SkPicture from %s\n", inputPath.c_str()); 201 gLogger.logError(err); 202 return false; 203 } 204 205 if (FLAGS_preprocess) { 206 // Because the GPU preprocessing step relies on the in-memory picture 207 // statistics we need to rerecord the picture here 208 SkPictureRecorder recorder; 209 picture->playback(recorder.beginRecording(picture->cullRect().width(), 210 picture->cullRect().height(), 211 NULL, 0)); 212 picture.reset(recorder.endRecording()); 213 } 214 215 SkString filename = SkOSPath::Basename(inputPath.c_str()); 216 217 gWriter.bench(filename.c_str(), 218 SkScalarCeilToInt(picture->cullRect().width()), 219 SkScalarCeilToInt(picture->cullRect().height())); 220 221 benchmark.run(picture); 222 223 #if SK_LAZY_CACHE_STATS 224 if (FLAGS_trackDeferredCaching) { 225 int cacheHits = pool->getCacheHits(); 226 int cacheMisses = pool->getCacheMisses(); 227 pool->resetCacheHitsAndMisses(); 228 SkString hitString; 229 hitString.printf("Cache hit rate: %f\n", (double) cacheHits / (cacheHits + cacheMisses)); 230 gLogger.logProgress(hitString); 231 gTotalCacheHits += cacheHits; 232 gTotalCacheMisses += cacheMisses; 233 } 234 #endif 235 if (FLAGS_countRAM) { 236 SkString ramCount("RAM used for bitmaps: "); 237 size_t bytes = pool->getRAMUsed(); 238 if (bytes > 1024) { 239 size_t kb = bytes / 1024; 240 if (kb > 1024) { 241 size_t mb = kb / 1024; 242 ramCount.appendf("%zi MB\n", mb); 243 } else { 244 ramCount.appendf("%zi KB\n", kb); 245 } 246 } else { 247 ramCount.appendf("%zi bytes\n", bytes); 248 } 249 gLogger.logProgress(ramCount); 250 } 251 252 return true; 253 } 254 255 static void setup_benchmark(sk_tools::PictureBenchmark* benchmark) { 256 sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount]; 257 sk_bzero(drawFilters, sizeof(drawFilters)); 258 259 if (FLAGS_filter.count() > 0) { 260 const char* filters = FLAGS_filter[0]; 261 const char* colon = strchr(filters, ':'); 262 if (colon) { 263 int32_t type = -1; 264 size_t typeLen = colon - filters; 265 for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) { 266 if (typeLen == strlen(gFilterTypes[tIndex]) 267 && !strncmp(filters, gFilterTypes[tIndex], typeLen)) { 268 type = SkToS32(tIndex); 269 break; 270 } 271 } 272 if (type < 0) { 273 SkString err; 274 err.printf("Unknown type for --filter %s\n", filters); 275 gLogger.logError(err); 276 exit(-1); 277 } 278 int flag = -1; 279 size_t flagLen = strlen(filters) - typeLen - 1; 280 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { 281 if (flagLen == strlen(gFilterFlags[fIndex]) 282 && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) { 283 flag = 1 << fIndex; 284 break; 285 } 286 } 287 if (flag < 0) { 288 SkString err; 289 err.printf("Unknown flag for --filter %s\n", filters); 290 gLogger.logError(err); 291 exit(-1); 292 } 293 for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) { 294 if (type != SkDrawFilter::kTypeCount && index != type) { 295 continue; 296 } 297 drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags) 298 (drawFilters[index] | flag); 299 } 300 } else { 301 SkString err; 302 err.printf("Unknown arg for --filter %s : missing colon\n", filters); 303 gLogger.logError(err); 304 exit(-1); 305 } 306 } 307 308 if (FLAGS_timers.count() > 0) { 309 size_t index = 0; 310 bool timerWall = false; 311 bool truncatedTimerWall = false; 312 bool timerCpu = false; 313 bool truncatedTimerCpu = false; 314 bool timerGpu = false; 315 while (index < strlen(FLAGS_timers[0])) { 316 switch (FLAGS_timers[0][index]) { 317 case 'w': 318 timerWall = true; 319 break; 320 case 'c': 321 timerCpu = true; 322 break; 323 case 'W': 324 truncatedTimerWall = true; 325 break; 326 case 'C': 327 truncatedTimerCpu = true; 328 break; 329 case 'g': 330 timerGpu = true; 331 break; 332 default: 333 SkDebugf("mystery character\n"); 334 break; 335 } 336 index++; 337 } 338 benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu, truncatedTimerCpu, 339 timerGpu); 340 } 341 342 SkString errorString; 343 SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, 344 kBench_PictureTool)); 345 346 if (errorString.size() > 0) { 347 gLogger.logError(errorString); 348 } 349 350 if (NULL == renderer.get()) { 351 exit(-1); 352 } 353 354 if (FLAGS_timeIndividualTiles) { 355 sk_tools::TiledPictureRenderer* tiledRenderer = renderer->getTiledRenderer(); 356 if (NULL == tiledRenderer) { 357 gLogger.logError("--timeIndividualTiles requires tiled rendering.\n"); 358 exit(-1); 359 } 360 if (!tiledRenderer->supportsTimingIndividualTiles()) { 361 gLogger.logError("This renderer does not support --timeIndividualTiles.\n"); 362 exit(-1); 363 } 364 benchmark->setTimeIndividualTiles(true); 365 } 366 367 benchmark->setPurgeDecodedTex(FLAGS_purgeDecodedTex); 368 benchmark->setPreprocess(FLAGS_preprocess); 369 370 if (FLAGS_readPath.count() < 1) { 371 gLogger.logError(".skp files or directories are required.\n"); 372 exit(-1); 373 } 374 375 renderer->setDrawFilters(drawFilters, filtersName(drawFilters)); 376 if (FLAGS_logPerIter) { 377 benchmark->setTimerResultType(TimerData::kPerIter_Result); 378 } else if (FLAGS_min) { 379 benchmark->setTimerResultType(TimerData::kMin_Result); 380 } else { 381 benchmark->setTimerResultType(TimerData::kAvg_Result); 382 } 383 benchmark->setRenderer(renderer); 384 benchmark->setRepeats(FLAGS_repeat); 385 benchmark->setWriter(&gWriter); 386 } 387 388 static int process_input(const char* input, 389 sk_tools::PictureBenchmark& benchmark) { 390 SkString inputAsSkString(input); 391 SkOSFile::Iter iter(input, "skp"); 392 SkString inputFilename; 393 int failures = 0; 394 if (iter.next(&inputFilename)) { 395 do { 396 SkString inputPath = SkOSPath::Join(input, inputFilename.c_str()); 397 if (!run_single_benchmark(inputPath, benchmark)) { 398 ++failures; 399 } 400 } while(iter.next(&inputFilename)); 401 } else if (SkStrEndsWith(input, ".skp")) { 402 if (!run_single_benchmark(inputAsSkString, benchmark)) { 403 ++failures; 404 } 405 } else { 406 SkString warning; 407 warning.printf("Warning: skipping %s\n", input); 408 gLogger.logError(warning); 409 } 410 return failures; 411 } 412 413 int tool_main(int argc, char** argv); 414 int tool_main(int argc, char** argv) { 415 SetupCrashHandler(); 416 SkString usage; 417 usage.printf("Time drawing .skp files.\n" 418 "\tPossible arguments for --filter: [%s]\n\t\t[%s]", 419 filterTypesUsage().c_str(), filterFlagsUsage().c_str()); 420 SkCommandLineFlags::SetUsage(usage.c_str()); 421 SkCommandLineFlags::Parse(argc, argv); 422 423 if (FLAGS_repeat < 1) { 424 SkString error; 425 error.printf("--repeats must be >= 1. Was %i\n", FLAGS_repeat); 426 gLogger.logError(error); 427 exit(-1); 428 } 429 430 if (FLAGS_logFile.count() == 1) { 431 if (!gLogger.SetLogFile(FLAGS_logFile[0])) { 432 SkString str; 433 str.printf("Could not open %s for writing.\n", FLAGS_logFile[0]); 434 gLogger.logError(str); 435 // TODO(borenet): We're disabling this for now, due to 436 // write-protected Android devices. The very short-term 437 // solution is to ignore the fact that we have no log file. 438 //exit(-1); 439 } 440 } 441 442 SkAutoTDelete<PictureJSONResultsWriter> jsonWriter; 443 if (FLAGS_jsonLog.count() == 1) { 444 SkASSERT(FLAGS_builderName.count() == 1 && FLAGS_gitHash.count() == 1); 445 jsonWriter.reset(SkNEW(PictureJSONResultsWriter( 446 FLAGS_jsonLog[0], 447 FLAGS_builderName[0], 448 FLAGS_buildNumber, 449 FLAGS_timestamp, 450 FLAGS_gitHash[0], 451 FLAGS_gitNumber))); 452 gWriter.add(jsonWriter.get()); 453 } 454 455 gWriter.add(&gLogWriter); 456 457 458 #if SK_ENABLE_INST_COUNT 459 gPrintInstCount = true; 460 #endif 461 SkAutoGraphics ag; 462 463 sk_tools::PictureBenchmark benchmark; 464 465 setup_benchmark(&benchmark); 466 467 int failures = 0; 468 for (int i = 0; i < FLAGS_readPath.count(); ++i) { 469 failures += process_input(FLAGS_readPath[i], benchmark); 470 } 471 472 if (failures != 0) { 473 SkString err; 474 err.printf("Failed to run %i benchmarks.\n", failures); 475 gLogger.logError(err); 476 return 1; 477 } 478 #if SK_LAZY_CACHE_STATS 479 if (FLAGS_trackDeferredCaching) { 480 SkDebugf("Total cache hit rate: %f\n", 481 (double) gTotalCacheHits / (gTotalCacheHits + gTotalCacheMisses)); 482 } 483 #endif 484 485 #if GR_GPU_STATS 486 if (FLAGS_gpuStats && benchmark.renderer()->isUsingGpuDevice()) { 487 GrContext* ctx = benchmark.renderer()->getGrContext(); 488 SkDebugf("RenderTarget Binds: %d\n", ctx->gpuStats()->renderTargetBinds()); 489 SkDebugf("Shader Compilations: %d\n", ctx->gpuStats()->shaderCompilations()); 490 } 491 #endif 492 493 gWriter.end(); 494 return 0; 495 } 496 497 #if !defined SK_BUILD_FOR_IOS 498 int main(int argc, char * const argv[]) { 499 return tool_main(argc, (char**) argv); 500 } 501 #endif 502