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_int32(multi); 46 DECLARE_string(readPath); 47 DEFINE_int32(repeat, 1, "Set the number of times to repeat each test."); 48 DEFINE_bool(timeIndividualTiles, false, "Report times for drawing individual tiles, rather than " 49 "times for drawing the whole page. Requires tiled rendering."); 50 DEFINE_bool(purgeDecodedTex, false, "Purge decoded and GPU-uploaded textures " 51 "after each iteration."); 52 DEFINE_string(timers, "c", "[wcgWC]*: Display wall, cpu, gpu, truncated wall or truncated cpu time" 53 " for each picture."); 54 DEFINE_bool(trackDeferredCaching, false, "Only meaningful with --deferImageDecoding and " 55 "SK_LAZY_CACHE_STATS set to true. Report percentage of cache hits when using " 56 "deferred image decoding."); 57 58 DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before timing."); 59 60 static char const * const gFilterTypes[] = { 61 "paint", 62 "point", 63 "line", 64 "bitmap", 65 "rect", 66 "oval", 67 "path", 68 "text", 69 "all", 70 }; 71 72 static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]); 73 74 static char const * const gFilterFlags[] = { 75 "antiAlias", 76 "filterBitmap", 77 "dither", 78 "underlineText", 79 "strikeThruText", 80 "fakeBoldText", 81 "linearText", 82 "subpixelText", 83 "devKernText", 84 "LCDRenderText", 85 "embeddedBitmapText", 86 "autoHinting", 87 "verticalText", 88 "genA8FromLCD", 89 "blur", 90 "hinting", 91 "slightHinting", 92 "AAClip", 93 }; 94 95 static const size_t kFilterFlagsCount = sizeof(gFilterFlags) / sizeof(gFilterFlags[0]); 96 97 static SkString filtersName(sk_tools::PictureRenderer::DrawFilterFlags* drawFilters) { 98 int all = drawFilters[0]; 99 size_t tIndex; 100 for (tIndex = 1; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { 101 all &= drawFilters[tIndex]; 102 } 103 SkString result; 104 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { 105 SkString types; 106 if (all & (1 << fIndex)) { 107 types = gFilterTypes[SkDrawFilter::kTypeCount]; 108 } else { 109 for (tIndex = 0; tIndex < SkDrawFilter::kTypeCount; ++tIndex) { 110 if (drawFilters[tIndex] & (1 << fIndex)) { 111 types += gFilterTypes[tIndex]; 112 } 113 } 114 } 115 if (!types.size()) { 116 continue; 117 } 118 result += "_"; 119 result += types; 120 result += "."; 121 result += gFilterFlags[fIndex]; 122 } 123 return result; 124 } 125 126 static SkString filterTypesUsage() { 127 SkString result; 128 for (size_t index = 0; index < kFilterTypesCount; ++index) { 129 result += gFilterTypes[index]; 130 if (index < kFilterTypesCount - 1) { 131 result += " | "; 132 } 133 } 134 return result; 135 } 136 137 static SkString filterFlagsUsage() { 138 SkString result; 139 size_t len = 0; 140 for (size_t index = 0; index < kFilterFlagsCount; ++index) { 141 result += gFilterFlags[index]; 142 if (result.size() - len >= 72) { 143 result += "\n\t\t"; 144 len = result.size(); 145 } 146 if (index < kFilterFlagsCount - 1) { 147 result += " | "; 148 } 149 } 150 return result; 151 } 152 153 #if SK_LAZY_CACHE_STATS 154 static int32_t gTotalCacheHits; 155 static int32_t gTotalCacheMisses; 156 #endif 157 158 static bool run_single_benchmark(const SkString& inputPath, 159 sk_tools::PictureBenchmark& benchmark) { 160 SkFILEStream inputStream; 161 162 inputStream.setPath(inputPath.c_str()); 163 if (!inputStream.isValid()) { 164 SkString err; 165 err.printf("Could not open file %s\n", inputPath.c_str()); 166 gLogger.logError(err); 167 return false; 168 } 169 170 SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool(); 171 // Since the old picture has been deleted, all pixels should be cleared. 172 SkASSERT(pool->getRAMUsed() == 0); 173 if (FLAGS_countRAM) { 174 pool->setRAMBudget(SK_MaxU32); 175 // Set the limit to max, so all pixels will be kept 176 } 177 178 SkPicture::InstallPixelRefProc proc; 179 if (FLAGS_deferImageDecoding) { 180 proc = &sk_tools::LazyDecodeBitmap; 181 } else { 182 proc = &SkImageDecoder::DecodeMemory; 183 } 184 SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, proc)); 185 186 if (NULL == picture.get()) { 187 SkString err; 188 err.printf("Could not read an SkPicture from %s\n", inputPath.c_str()); 189 gLogger.logError(err); 190 return false; 191 } 192 193 SkString filename = SkOSPath::SkBasename(inputPath.c_str()); 194 195 gWriter.bench(filename.c_str(), picture->width(), picture->height()); 196 197 benchmark.run(picture); 198 199 #if SK_LAZY_CACHE_STATS 200 if (FLAGS_trackDeferredCaching) { 201 int cacheHits = pool->getCacheHits(); 202 int cacheMisses = pool->getCacheMisses(); 203 pool->resetCacheHitsAndMisses(); 204 SkString hitString; 205 hitString.printf("Cache hit rate: %f\n", (double) cacheHits / (cacheHits + cacheMisses)); 206 gLogger.logProgress(hitString); 207 gTotalCacheHits += cacheHits; 208 gTotalCacheMisses += cacheMisses; 209 } 210 #endif 211 if (FLAGS_countRAM) { 212 SkString ramCount("RAM used for bitmaps: "); 213 size_t bytes = pool->getRAMUsed(); 214 if (bytes > 1024) { 215 size_t kb = bytes / 1024; 216 if (kb > 1024) { 217 size_t mb = kb / 1024; 218 ramCount.appendf("%zi MB\n", mb); 219 } else { 220 ramCount.appendf("%zi KB\n", kb); 221 } 222 } else { 223 ramCount.appendf("%zi bytes\n", bytes); 224 } 225 gLogger.logProgress(ramCount); 226 } 227 228 return true; 229 } 230 231 static void setup_benchmark(sk_tools::PictureBenchmark* benchmark) { 232 sk_tools::PictureRenderer::DrawFilterFlags drawFilters[SkDrawFilter::kTypeCount]; 233 sk_bzero(drawFilters, sizeof(drawFilters)); 234 235 if (FLAGS_filter.count() > 0) { 236 const char* filters = FLAGS_filter[0]; 237 const char* colon = strchr(filters, ':'); 238 if (colon) { 239 int32_t type = -1; 240 size_t typeLen = colon - filters; 241 for (size_t tIndex = 0; tIndex < kFilterTypesCount; ++tIndex) { 242 if (typeLen == strlen(gFilterTypes[tIndex]) 243 && !strncmp(filters, gFilterTypes[tIndex], typeLen)) { 244 type = SkToS32(tIndex); 245 break; 246 } 247 } 248 if (type < 0) { 249 SkString err; 250 err.printf("Unknown type for --filter %s\n", filters); 251 gLogger.logError(err); 252 exit(-1); 253 } 254 int flag = -1; 255 size_t flagLen = strlen(filters) - typeLen - 1; 256 for (size_t fIndex = 0; fIndex < kFilterFlagsCount; ++fIndex) { 257 if (flagLen == strlen(gFilterFlags[fIndex]) 258 && !strncmp(colon + 1, gFilterFlags[fIndex], flagLen)) { 259 flag = 1 << fIndex; 260 break; 261 } 262 } 263 if (flag < 0) { 264 SkString err; 265 err.printf("Unknown flag for --filter %s\n", filters); 266 gLogger.logError(err); 267 exit(-1); 268 } 269 for (int index = 0; index < SkDrawFilter::kTypeCount; ++index) { 270 if (type != SkDrawFilter::kTypeCount && index != type) { 271 continue; 272 } 273 drawFilters[index] = (sk_tools::PictureRenderer::DrawFilterFlags) 274 (drawFilters[index] | flag); 275 } 276 } else { 277 SkString err; 278 err.printf("Unknown arg for --filter %s : missing colon\n", filters); 279 gLogger.logError(err); 280 exit(-1); 281 } 282 } 283 284 if (FLAGS_timers.count() > 0) { 285 size_t index = 0; 286 bool timerWall = false; 287 bool truncatedTimerWall = false; 288 bool timerCpu = false; 289 bool truncatedTimerCpu = false; 290 bool timerGpu = false; 291 while (index < strlen(FLAGS_timers[0])) { 292 switch (FLAGS_timers[0][index]) { 293 case 'w': 294 timerWall = true; 295 break; 296 case 'c': 297 timerCpu = true; 298 break; 299 case 'W': 300 truncatedTimerWall = true; 301 break; 302 case 'C': 303 truncatedTimerCpu = true; 304 break; 305 case 'g': 306 timerGpu = true; 307 break; 308 default: 309 SkDebugf("mystery character\n"); 310 break; 311 } 312 index++; 313 } 314 benchmark->setTimersToShow(timerWall, truncatedTimerWall, timerCpu, truncatedTimerCpu, 315 timerGpu); 316 } 317 318 SkString errorString; 319 SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, 320 kBench_PictureTool)); 321 322 if (errorString.size() > 0) { 323 gLogger.logError(errorString); 324 } 325 326 if (NULL == renderer.get()) { 327 exit(-1); 328 } 329 330 if (FLAGS_timeIndividualTiles) { 331 if (FLAGS_multi > 1) { 332 gLogger.logError("Cannot time individual tiles with more than one thread.\n"); 333 exit(-1); 334 } 335 sk_tools::TiledPictureRenderer* tiledRenderer = renderer->getTiledRenderer(); 336 if (NULL == tiledRenderer) { 337 gLogger.logError("--timeIndividualTiles requires tiled rendering.\n"); 338 exit(-1); 339 } 340 if (!tiledRenderer->supportsTimingIndividualTiles()) { 341 gLogger.logError("This renderer does not support --timeIndividualTiles.\n"); 342 exit(-1); 343 } 344 benchmark->setTimeIndividualTiles(true); 345 } 346 347 benchmark->setPurgeDecodedTex(FLAGS_purgeDecodedTex); 348 benchmark->setPreprocess(FLAGS_preprocess); 349 350 if (FLAGS_readPath.count() < 1) { 351 gLogger.logError(".skp files or directories are required.\n"); 352 exit(-1); 353 } 354 355 renderer->setDrawFilters(drawFilters, filtersName(drawFilters)); 356 if (FLAGS_logPerIter) { 357 benchmark->setTimerResultType(TimerData::kPerIter_Result); 358 } else if (FLAGS_min) { 359 benchmark->setTimerResultType(TimerData::kMin_Result); 360 } else { 361 benchmark->setTimerResultType(TimerData::kAvg_Result); 362 } 363 benchmark->setRenderer(renderer); 364 benchmark->setRepeats(FLAGS_repeat); 365 benchmark->setWriter(&gWriter); 366 } 367 368 static int process_input(const char* input, 369 sk_tools::PictureBenchmark& benchmark) { 370 SkString inputAsSkString(input); 371 SkOSFile::Iter iter(input, "skp"); 372 SkString inputFilename; 373 int failures = 0; 374 if (iter.next(&inputFilename)) { 375 do { 376 SkString inputPath = SkOSPath::SkPathJoin(input, inputFilename.c_str()); 377 if (!run_single_benchmark(inputPath, benchmark)) { 378 ++failures; 379 } 380 } while(iter.next(&inputFilename)); 381 } else if (SkStrEndsWith(input, ".skp")) { 382 if (!run_single_benchmark(inputAsSkString, benchmark)) { 383 ++failures; 384 } 385 } else { 386 SkString warning; 387 warning.printf("Warning: skipping %s\n", input); 388 gLogger.logError(warning); 389 } 390 return failures; 391 } 392 393 int tool_main(int argc, char** argv); 394 int tool_main(int argc, char** argv) { 395 SetupCrashHandler(); 396 SkString usage; 397 usage.printf("Time drawing .skp files.\n" 398 "\tPossible arguments for --filter: [%s]\n\t\t[%s]", 399 filterTypesUsage().c_str(), filterFlagsUsage().c_str()); 400 SkCommandLineFlags::SetUsage(usage.c_str()); 401 SkCommandLineFlags::Parse(argc, argv); 402 403 if (FLAGS_repeat < 1) { 404 SkString error; 405 error.printf("--repeats must be >= 1. Was %i\n", FLAGS_repeat); 406 gLogger.logError(error); 407 exit(-1); 408 } 409 410 if (FLAGS_logFile.count() == 1) { 411 if (!gLogger.SetLogFile(FLAGS_logFile[0])) { 412 SkString str; 413 str.printf("Could not open %s for writing.\n", FLAGS_logFile[0]); 414 gLogger.logError(str); 415 // TODO(borenet): We're disabling this for now, due to 416 // write-protected Android devices. The very short-term 417 // solution is to ignore the fact that we have no log file. 418 //exit(-1); 419 } 420 } 421 422 SkAutoTDelete<PictureJSONResultsWriter> jsonWriter; 423 if (FLAGS_jsonLog.count() == 1) { 424 jsonWriter.reset(SkNEW(PictureJSONResultsWriter(FLAGS_jsonLog[0]))); 425 gWriter.add(jsonWriter.get()); 426 } 427 428 gWriter.add(&gLogWriter); 429 430 431 #if SK_ENABLE_INST_COUNT 432 gPrintInstCount = true; 433 #endif 434 SkAutoGraphics ag; 435 436 sk_tools::PictureBenchmark benchmark; 437 438 setup_benchmark(&benchmark); 439 440 int failures = 0; 441 for (int i = 0; i < FLAGS_readPath.count(); ++i) { 442 failures += process_input(FLAGS_readPath[i], benchmark); 443 } 444 445 if (failures != 0) { 446 SkString err; 447 err.printf("Failed to run %i benchmarks.\n", failures); 448 gLogger.logError(err); 449 return 1; 450 } 451 #if SK_LAZY_CACHE_STATS 452 if (FLAGS_trackDeferredCaching) { 453 SkDebugf("Total cache hit rate: %f\n", 454 (double) gTotalCacheHits / (gTotalCacheHits + gTotalCacheMisses)); 455 } 456 #endif 457 gWriter.end(); 458 return 0; 459 } 460 461 #if !defined SK_BUILD_FOR_IOS 462 int main(int argc, char * const argv[]) { 463 return tool_main(argc, (char**) argv); 464 } 465 #endif 466