| /* | 
 |  * Copyright 2014 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include <ctype.h> | 
 |  | 
 | #include "bench/nanobench.h" | 
 |  | 
 | #include "bench/AndroidCodecBench.h" | 
 | #include "bench/Benchmark.h" | 
 | #include "bench/CodecBench.h" | 
 | #include "bench/CodecBenchPriv.h" | 
 | #include "bench/GMBench.h" | 
 | #include "bench/MSKPBench.h" | 
 | #include "bench/RecordingBench.h" | 
 | #include "bench/ResultsWriter.h" | 
 | #include "bench/SKPAnimationBench.h" | 
 | #include "bench/SKPBench.h" | 
 | #include "bench/SkGlyphCacheBench.h" | 
 | #include "bench/SkSLBench.h" | 
 | #include "include/codec/SkAndroidCodec.h" | 
 | #include "include/codec/SkCodec.h" | 
 | #include "include/codec/SkJpegDecoder.h" | 
 | #include "include/codec/SkPngDecoder.h" | 
 | #include "include/core/SkBBHFactory.h" | 
 | #include "include/core/SkCanvas.h" | 
 | #include "include/core/SkData.h" | 
 | #include "include/core/SkGraphics.h" | 
 | #include "include/core/SkPictureRecorder.h" | 
 | #include "include/core/SkString.h" | 
 | #include "include/core/SkSurface.h" | 
 | #include "include/encode/SkPngEncoder.h" | 
 | #include "include/private/base/SkMacros.h" | 
 | #include "src/base/SkAutoMalloc.h" | 
 | #include "src/base/SkLeanWindows.h" | 
 | #include "src/base/SkTime.h" | 
 | #include "src/core/SkColorSpacePriv.h" | 
 | #include "src/core/SkOSFile.h" | 
 | #include "src/core/SkTaskGroup.h" | 
 | #include "src/core/SkTraceEvent.h" | 
 | #include "src/utils/SkJSONWriter.h" | 
 | #include "src/utils/SkOSPath.h" | 
 | #include "src/utils/SkShaderUtils.h" | 
 | #include "tools/AutoreleasePool.h" | 
 | #include "tools/CrashHandler.h" | 
 | #include "tools/MSKPPlayer.h" | 
 | #include "tools/ProcStats.h" | 
 | #include "tools/Stats.h" | 
 | #include "tools/ToolUtils.h" | 
 | #include "tools/flags/CommonFlags.h" | 
 | #include "tools/flags/CommonFlagsConfig.h" | 
 | #include "tools/fonts/FontToolUtils.h" | 
 | #include "tools/ios_utils.h" | 
 | #include "tools/trace/EventTracingPriv.h" | 
 | #include "tools/trace/SkDebugfTracer.h" | 
 |  | 
 | #if defined(SK_ENABLE_SVG) | 
 | #include "modules/skshaper/utils/FactoryHelpers.h" | 
 | #include "modules/svg/include/SkSVGDOM.h" | 
 | #include "modules/svg/include/SkSVGNode.h" | 
 | #endif | 
 |  | 
 | #ifdef SK_ENABLE_ANDROID_UTILS | 
 | #include "bench/BitmapRegionDecoderBench.h" | 
 | #include "client_utils/android/BitmapRegionDecoder.h" | 
 | #endif | 
 |  | 
 | #if defined(SK_GRAPHITE) | 
 | #include "include/gpu/graphite/Context.h" | 
 | #include "include/gpu/graphite/Recorder.h" | 
 | #include "include/gpu/graphite/Recording.h" | 
 | #include "include/gpu/graphite/Surface.h" | 
 | #include "tools/GpuToolUtils.h" | 
 | #include "tools/graphite/ContextFactory.h" | 
 | #include "tools/graphite/GraphiteTestContext.h" | 
 | #endif | 
 |  | 
 | #include <cinttypes> | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <stdlib.h> | 
 | #include <thread> | 
 |  | 
 | extern bool gSkForceRasterPipelineBlitter; | 
 | extern bool gForceHighPrecisionRasterPipeline; | 
 |  | 
 | #ifndef SK_BUILD_FOR_WIN | 
 | #include <unistd.h> | 
 | #endif | 
 |  | 
 | #include "include/gpu/GrDirectContext.h" | 
 | #include "include/gpu/ganesh/SkSurfaceGanesh.h" | 
 | #include "src/gpu/ganesh/GrCaps.h" | 
 | #include "src/gpu/ganesh/GrDirectContextPriv.h" | 
 | #include "src/gpu/ganesh/SkGr.h" | 
 | #include "tools/gpu/GrContextFactory.h" | 
 |  | 
 | using namespace skia_private; | 
 |  | 
 | using sk_gpu_test::ContextInfo; | 
 | using sk_gpu_test::GrContextFactory; | 
 | using sk_gpu_test::TestContext; | 
 |  | 
 | GrContextOptions grContextOpts; | 
 |  | 
 | static const int kAutoTuneLoops = 0; | 
 |  | 
 | static SkString loops_help_txt() { | 
 |     SkString help; | 
 |     help.printf("Number of times to run each bench. Set this to %d to auto-" | 
 |                 "tune for each bench. Timings are only reported when auto-tuning.", | 
 |                 kAutoTuneLoops); | 
 |     return help; | 
 | } | 
 |  | 
 | static SkString to_string(int n) { | 
 |     SkString str; | 
 |     str.appendS32(n); | 
 |     return str; | 
 | } | 
 |  | 
 | static DEFINE_int(loops, kAutoTuneLoops, loops_help_txt().c_str()); | 
 |  | 
 | static DEFINE_int(samples, 10, "Number of samples to measure for each bench."); | 
 | static DEFINE_int(ms, 0, "If >0, run each bench for this many ms instead of obeying --samples."); | 
 | static DEFINE_int(overheadLoops, 100000, "Loops to estimate timer overhead."); | 
 | static DEFINE_double(overheadGoal, 0.0001, | 
 |               "Loop until timer overhead is at most this fraction of our measurments."); | 
 | static DEFINE_double(gpuMs, 5, "Target bench time in millseconds for GPU."); | 
 | static DEFINE_int(gpuFrameLag, 5, | 
 |                     "If unknown, estimated maximum number of frames GPU allows to lag."); | 
 |  | 
 | static DEFINE_string(outResultsFile, "", "If given, write results here as JSON."); | 
 | static DEFINE_int(maxCalibrationAttempts, 3, | 
 |              "Try up to this many times to guess loops for a bench, or skip the bench."); | 
 | static DEFINE_int(maxLoops, 1000000, "Never run a bench more times than this."); | 
 | static DEFINE_string(clip, "0,0,1000,1000", "Clip for SKPs."); | 
 | static DEFINE_string(scales, "1.0", "Space-separated scales for SKPs."); | 
 | static DEFINE_string(zoom, "1.0,0", | 
 |                      "Comma-separated zoomMax,zoomPeriodMs factors for a periodic SKP zoom " | 
 |                      "function that ping-pongs between 1.0 and zoomMax."); | 
 | static DEFINE_bool(bbh, true, "Build a BBH for SKPs?"); | 
 | static DEFINE_bool(loopSKP, true, "Loop SKPs like we do for micro benches?"); | 
 | static DEFINE_int(flushEvery, 10, "Flush --outResultsFile every Nth run."); | 
 | static DEFINE_bool(gpuStats, false, "Print GPU stats after each gpu benchmark?"); | 
 | static DEFINE_bool(gpuStatsDump, false, "Dump GPU stats after each benchmark to json"); | 
 | static DEFINE_bool(dmsaaStatsDump, false, "Dump DMSAA stats after each benchmark to json"); | 
 | static DEFINE_bool(keepAlive, false, "Print a message every so often so that we don't time out"); | 
 | static DEFINE_bool(csv, false, "Print status in CSV format"); | 
 | static DEFINE_string(sourceType, "", | 
 |         "Apply usual --match rules to source type: bench, gm, skp, image, etc."); | 
 | static DEFINE_string(benchType,  "", | 
 |         "Apply usual --match rules to bench type: micro, recording, " | 
 |         "piping, playback, skcodec, etc."); | 
 |  | 
 | static DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter"); | 
 | static DEFINE_bool(forceRasterPipelineHP, false, "sets gSkForceRasterPipelineBlitter and gForceHighPrecisionRasterPipeline"); | 
 |  | 
 | static DEFINE_bool2(pre_log, p, false, | 
 |                     "Log before running each test. May be incomprehensible when threading"); | 
 |  | 
 | static DEFINE_bool(cpu, true, "Run CPU-bound work?"); | 
 | static DEFINE_bool(gpu, true, "Run GPU-bound work?"); | 
 | static DEFINE_bool(dryRun, false, | 
 |                    "just print the tests that would be run, without actually running them."); | 
 | static DEFINE_string(images, "", | 
 |                      "List of images and/or directories to decode. A directory with no images" | 
 |                      " is treated as a fatal error."); | 
 | static DEFINE_bool(simpleCodec, false, | 
 |                    "Runs of a subset of the codec tests, always N32, Premul or Opaque"); | 
 |  | 
 | static DEFINE_string2(match, m, nullptr, | 
 |                "[~][^]substring[$] [...] of name to run.\n" | 
 |                "Multiple matches may be separated by spaces.\n" | 
 |                "~ causes a matching name to always be skipped\n" | 
 |                "^ requires the start of the name to match\n" | 
 |                "$ requires the end of the name to match\n" | 
 |                "^ and $ requires an exact match\n" | 
 |                "If a name does not match any list entry,\n" | 
 |                "it is skipped unless some list entry starts with ~"); | 
 |  | 
 | static DEFINE_bool2(quiet, q, false, "if true, don't print status updates."); | 
 | static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver."); | 
 |  | 
 |  | 
 | static DEFINE_string(skps, "skps", "Directory to read skps from."); | 
 | static DEFINE_string(mskps, "mskps", "Directory to read mskps from."); | 
 | static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file."); | 
 | static DEFINE_string(texttraces, "", "Directory to read TextBlobTrace files from."); | 
 |  | 
 | static DEFINE_int_2(threads, j, -1, | 
 |                "Run threadsafe tests on a threadpool with this many extra threads, " | 
 |                "defaulting to one extra thread per core."); | 
 |  | 
 | static DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs."); | 
 |  | 
 | static DEFINE_string(key, "", | 
 |                      "Space-separated key/value pairs to add to JSON identifying this builder."); | 
 | static DEFINE_string(properties, "", | 
 |                      "Space-separated key/value pairs to add to JSON identifying this run."); | 
 |  | 
 | static DEFINE_bool(purgeBetweenBenches, false, | 
 |                    "Call SkGraphics::PurgeAllCaches() between each benchmark?"); | 
 |  | 
 | static DEFINE_bool(splitPerfettoTracesByBenchmark, true, | 
 |                   "Create separate perfetto trace files for each benchmark?\n" | 
 |                   "Will only take effect if perfetto tracing is enabled. See --trace."); | 
 |  | 
 | static DEFINE_bool(runtimeCPUDetection, true, "Skip runtime CPU detection and optimization"); | 
 |  | 
 | static double now_ms() { return SkTime::GetNSecs() * 1e-6; } | 
 |  | 
 | static SkString humanize(double ms) { | 
 |     if (FLAGS_verbose) return SkStringPrintf("%" PRIu64, (uint64_t)(ms*1e6)); | 
 |     return HumanizeMs(ms); | 
 | } | 
 | #define HUMANIZE(ms) humanize(ms).c_str() | 
 |  | 
 | bool Target::init(SkImageInfo info, Benchmark* bench) { | 
 |     if (Benchmark::Backend::kRaster == config.backend) { | 
 |         this->surface = SkSurfaces::Raster(info); | 
 |         if (!this->surface) { | 
 |             return false; | 
 |         } | 
 |     } | 
 |     return true; | 
 | } | 
 | bool Target::capturePixels(SkBitmap* bmp) { | 
 |     SkCanvas* canvas = this->getCanvas(); | 
 |     if (!canvas) { | 
 |         return false; | 
 |     } | 
 |     bmp->allocPixels(canvas->imageInfo()); | 
 |     if (!canvas->readPixels(*bmp, 0, 0)) { | 
 |         SkDebugf("Can't read canvas pixels.\n"); | 
 |         return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | struct GPUTarget : public Target { | 
 |     explicit GPUTarget(const Config& c) : Target(c) {} | 
 |     ContextInfo contextInfo; | 
 |     std::unique_ptr<GrContextFactory> factory; | 
 |  | 
 |     ~GPUTarget() override { | 
 |         // For Vulkan we need to release all our refs to the GrContext before destroy the vulkan | 
 |         // context which happens at the end of this destructor. Thus we need to release the surface | 
 |         // here which holds a ref to the GrContext. | 
 |         surface.reset(); | 
 |     } | 
 |  | 
 |     void onSetup() override { | 
 |         this->contextInfo.testContext()->makeCurrent(); | 
 |     } | 
 |     void endTiming() override { | 
 |         if (this->contextInfo.testContext()) { | 
 |             this->contextInfo.testContext()->flushAndWaitOnSync(contextInfo.directContext()); | 
 |         } | 
 |     } | 
 |     void submitWorkAndSyncCPU() override { | 
 |         if (this->contextInfo.testContext()) { | 
 |             this->contextInfo.testContext()->flushAndSyncCpu(contextInfo.directContext()); | 
 |         } | 
 |     } | 
 |  | 
 |     bool needsFrameTiming(int* maxFrameLag) const override { | 
 |         if (!this->contextInfo.testContext()->getMaxGpuFrameLag(maxFrameLag)) { | 
 |             // Frame lag is unknown. | 
 |             *maxFrameLag = FLAGS_gpuFrameLag; | 
 |         } | 
 |         return true; | 
 |     } | 
 |     bool init(SkImageInfo info, Benchmark* bench) override { | 
 |         GrContextOptions options = grContextOpts; | 
 |         bench->modifyGrContextOptions(&options); | 
 |         this->factory = std::make_unique<GrContextFactory>(options); | 
 |         SkSurfaceProps props(this->config.surfaceFlags, kRGB_H_SkPixelGeometry); | 
 |         this->surface = SkSurfaces::RenderTarget( | 
 |                 this->factory->get(this->config.ctxType, this->config.ctxOverrides), | 
 |                 skgpu::Budgeted::kNo, | 
 |                 info, | 
 |                 this->config.samples, | 
 |                 &props); | 
 |         this->contextInfo = | 
 |                 this->factory->getContextInfo(this->config.ctxType, this->config.ctxOverrides); | 
 |         if (!this->surface) { | 
 |             return false; | 
 |         } | 
 |         if (!this->contextInfo.testContext()->fenceSyncSupport()) { | 
 |             SkDebugf("WARNING: GL context for config \"%s\" does not support fence sync. " | 
 |                      "Timings might not be accurate.\n", this->config.name.c_str()); | 
 |         } | 
 |         return true; | 
 |     } | 
 |  | 
 |     void dumpStats() override { | 
 |         auto context = this->contextInfo.directContext(); | 
 |  | 
 |         context->priv().printCacheStats(); | 
 |         context->priv().printGpuStats(); | 
 |         context->priv().printContextStats(); | 
 |     } | 
 | }; | 
 |  | 
 | #if defined(SK_GRAPHITE) | 
 | struct GraphiteTarget : public Target { | 
 |     explicit GraphiteTarget(const Config& c) : Target(c) {} | 
 |     using TestContext = skiatest::graphite::GraphiteTestContext; | 
 |     using ContextFactory = skiatest::graphite::ContextFactory; | 
 |  | 
 |     std::unique_ptr<ContextFactory> factory; | 
 |  | 
 |     TestContext* testContext; | 
 |     skgpu::graphite::Context* context; | 
 |     std::unique_ptr<skgpu::graphite::Recorder> recorder; | 
 |  | 
 |     ~GraphiteTarget() override { | 
 |         // For Vulkan we need to release all our refs before we destroy the vulkan context which | 
 |         // happens at the end of this destructor. Thus we need to release the surface here which | 
 |         // holds a ref to the Graphite device | 
 |         surface.reset(); | 
 |     } | 
 |  | 
 |     void endTiming() override { | 
 |         if (context && recorder) { | 
 |             std::unique_ptr<skgpu::graphite::Recording> recording = this->recorder->snap(); | 
 |             if (recording) { | 
 |                 this->testContext->submitRecordingAndWaitOnSync(this->context, recording.get()); | 
 |             } | 
 |         } | 
 |     } | 
 |     void submitWorkAndSyncCPU() override { | 
 |         if (context && recorder) { | 
 |             // TODO: have a way to sync work with out submitting a Recording which is currently | 
 |             // required. Probably need to get to the point where the backend command buffers are | 
 |             // stored on the Context and not Recordings before this is feasible. | 
 |             std::unique_ptr<skgpu::graphite::Recording> recording = this->recorder->snap(); | 
 |             if (recording) { | 
 |                 skgpu::graphite::InsertRecordingInfo info; | 
 |                 info.fRecording = recording.get(); | 
 |                 this->context->insertRecording(info); | 
 |             } | 
 |             this->context->submit(skgpu::graphite::SyncToCpu::kYes); | 
 |         } | 
 |     } | 
 |  | 
 |     bool needsFrameTiming(int* maxFrameLag) const override { | 
 |         SkAssertResult(this->testContext->getMaxGpuFrameLag(maxFrameLag)); | 
 |         return true; | 
 |     } | 
 |     bool init(SkImageInfo info, Benchmark* bench) override { | 
 |         GrContextOptions options = grContextOpts; | 
 |         bench->modifyGrContextOptions(&options); | 
 |         // TODO: We should merge Ganesh and Graphite context options and then actually use the | 
 |         // context options when we make the factory here. | 
 |         this->factory = std::make_unique<ContextFactory>(); | 
 |  | 
 |         skiatest::graphite::ContextInfo ctxInfo = | 
 |                 this->factory->getContextInfo(this->config.ctxType); | 
 |         if (!ctxInfo.fContext) { | 
 |             return false; | 
 |         } | 
 |         this->testContext = ctxInfo.fTestContext; | 
 |         this->context = ctxInfo.fContext; | 
 |  | 
 |         this->recorder = this->context->makeRecorder(ToolUtils::CreateTestingRecorderOptions()); | 
 |         if (!this->recorder) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         this->surface = SkSurfaces::RenderTarget(this->recorder.get(), info); | 
 |         if (!this->surface) { | 
 |             return false; | 
 |         } | 
 |         // TODO: get fence stuff working | 
 | #if 0 | 
 |         if (!this->contextInfo.testContext()->fenceSyncSupport()) { | 
 |             SkDebugf("WARNING: GL context for config \"%s\" does not support fence sync. " | 
 |                      "Timings might not be accurate.\n", this->config.name.c_str()); | 
 |         } | 
 | #endif | 
 |         return true; | 
 |     } | 
 |  | 
 |     void dumpStats() override { | 
 |     } | 
 | }; | 
 | #endif // SK_GRAPHITE | 
 |  | 
 | static double time(int loops, Benchmark* bench, Target* target) { | 
 |     SkCanvas* canvas = target->getCanvas(); | 
 |     if (canvas) { | 
 |         canvas->clear(SK_ColorWHITE); | 
 |     } | 
 |     bench->preDraw(canvas); | 
 |     double start = now_ms(); | 
 |     canvas = target->beginTiming(canvas); | 
 |  | 
 |     bench->draw(loops, canvas); | 
 |  | 
 |     target->endTiming(); | 
 |     double elapsed = now_ms() - start; | 
 |     bench->postDraw(canvas); | 
 |     return elapsed; | 
 | } | 
 |  | 
 | static double estimate_timer_overhead() { | 
 |     double overhead = 0; | 
 |     for (int i = 0; i < FLAGS_overheadLoops; i++) { | 
 |         double start = now_ms(); | 
 |         overhead += now_ms() - start; | 
 |     } | 
 |     return overhead / FLAGS_overheadLoops; | 
 | } | 
 |  | 
 | static int detect_forever_loops(int loops) { | 
 |     // look for a magic run-forever value | 
 |     if (loops < 0) { | 
 |         loops = SK_MaxS32; | 
 |     } | 
 |     return loops; | 
 | } | 
 |  | 
 | static int clamp_loops(int loops) { | 
 |     if (loops < 1) { | 
 |         SkDebugf("ERROR: clamping loops from %d to 1. " | 
 |                  "There's probably something wrong with the bench.\n", loops); | 
 |         return 1; | 
 |     } | 
 |     if (loops > FLAGS_maxLoops) { | 
 |         SkDebugf("WARNING: clamping loops from %d to FLAGS_maxLoops, %d.\n", loops, FLAGS_maxLoops); | 
 |         return FLAGS_maxLoops; | 
 |     } | 
 |     return loops; | 
 | } | 
 |  | 
 | static bool write_canvas_png(Target* target, const SkString& filename) { | 
 |  | 
 |     if (filename.isEmpty()) { | 
 |         return false; | 
 |     } | 
 |     if (target->getCanvas() && | 
 |         kUnknown_SkColorType == target->getCanvas()->imageInfo().colorType()) { | 
 |         return false; | 
 |     } | 
 |  | 
 |     SkBitmap bmp; | 
 |  | 
 |     if (!target->capturePixels(&bmp)) { | 
 |         return false; | 
 |     } | 
 |  | 
 |     SkString dir = SkOSPath::Dirname(filename.c_str()); | 
 |     if (!sk_mkdir(dir.c_str())) { | 
 |         SkDebugf("Can't make dir %s.\n", dir.c_str()); | 
 |         return false; | 
 |     } | 
 |     SkFILEWStream stream(filename.c_str()); | 
 |     if (!stream.isValid()) { | 
 |         SkDebugf("Can't write %s.\n", filename.c_str()); | 
 |         return false; | 
 |     } | 
 |     if (!SkPngEncoder::Encode(&stream, bmp.pixmap(), {})) { | 
 |         SkDebugf("Can't encode a PNG.\n"); | 
 |         return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | static int kFailedLoops = -2; | 
 | static int setup_cpu_bench(const double overhead, Target* target, Benchmark* bench) { | 
 |     // First figure out approximately how many loops of bench it takes to make overhead negligible. | 
 |     double bench_plus_overhead = 0.0; | 
 |     int round = 0; | 
 |     int loops = bench->shouldLoop() ? FLAGS_loops : 1; | 
 |     if (kAutoTuneLoops == loops) { | 
 |         while (bench_plus_overhead < overhead) { | 
 |             if (round++ == FLAGS_maxCalibrationAttempts) { | 
 |                 SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n", | 
 |                          bench->getUniqueName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead)); | 
 |                 return kFailedLoops; | 
 |             } | 
 |             bench_plus_overhead = time(1, bench, target); | 
 |         } | 
 |     } | 
 |  | 
 |     // Later we'll just start and stop the timer once but loop N times. | 
 |     // We'll pick N to make timer overhead negligible: | 
 |     // | 
 |     //          overhead | 
 |     //  -------------------------  < FLAGS_overheadGoal | 
 |     //  overhead + N * Bench Time | 
 |     // | 
 |     // where bench_plus_overhead ~=~ overhead + Bench Time. | 
 |     // | 
 |     // Doing some math, we get: | 
 |     // | 
 |     //  (overhead / FLAGS_overheadGoal) - overhead | 
 |     //  ------------------------------------------  < N | 
 |     //       bench_plus_overhead - overhead) | 
 |     // | 
 |     // Luckily, this also works well in practice. :) | 
 |     if (kAutoTuneLoops == loops) { | 
 |         const double numer = overhead / FLAGS_overheadGoal - overhead; | 
 |         const double denom = bench_plus_overhead - overhead; | 
 |         loops = (int)ceil(numer / denom); | 
 |         loops = clamp_loops(loops); | 
 |     } else { | 
 |         loops = detect_forever_loops(loops); | 
 |     } | 
 |  | 
 |     return loops; | 
 | } | 
 |  | 
 | static int setup_gpu_bench(Target* target, Benchmark* bench, int maxGpuFrameLag) { | 
 |     // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs. | 
 |     int loops = bench->shouldLoop() ? FLAGS_loops : 1; | 
 |     if (kAutoTuneLoops == loops) { | 
 |         loops = 1; | 
 |         double elapsed = 0; | 
 |         do { | 
 |             if (1<<30 == loops) { | 
 |                 // We're about to wrap.  Something's wrong with the bench. | 
 |                 loops = 0; | 
 |                 break; | 
 |             } | 
 |             loops *= 2; | 
 |             // If the GPU lets frames lag at all, we need to make sure we're timing | 
 |             // _this_ round, not still timing last round. | 
 |             for (int i = 0; i < maxGpuFrameLag; i++) { | 
 |                 elapsed = time(loops, bench, target); | 
 |             } | 
 |         } while (elapsed < FLAGS_gpuMs); | 
 |  | 
 |         // We've overshot at least a little.  Scale back linearly. | 
 |         loops = (int)ceil(loops * FLAGS_gpuMs / elapsed); | 
 |         loops = clamp_loops(loops); | 
 |  | 
 |         // Make sure we're not still timing our calibration. | 
 |         target->submitWorkAndSyncCPU(); | 
 |     } else { | 
 |         loops = detect_forever_loops(loops); | 
 |     } | 
 |     // Pretty much the same deal as the calibration: do some warmup to make | 
 |     // sure we're timing steady-state pipelined frames. | 
 |     for (int i = 0; i < maxGpuFrameLag; i++) { | 
 |         time(loops, bench, target); | 
 |     } | 
 |  | 
 |     return loops; | 
 | } | 
 |  | 
 | #define kBogusContextType skgpu::ContextType::kGL | 
 | #define kBogusContextOverrides GrContextFactory::ContextOverrides::kNone | 
 |  | 
 | static std::optional<Config> create_config(const SkCommandLineConfig* config) { | 
 |     if (const auto* gpuConfig = config->asConfigGpu()) { | 
 |         if (!FLAGS_gpu) { | 
 |             SkDebugf("Skipping config '%s' as requested.\n", config->getTag().c_str()); | 
 |             return std::nullopt; | 
 |         } | 
 |  | 
 |         const auto ctxType = gpuConfig->getContextType(); | 
 |         const auto ctxOverrides = gpuConfig->getContextOverrides(); | 
 |         const auto sampleCount = gpuConfig->getSamples(); | 
 |         const auto colorType = gpuConfig->getColorType(); | 
 |         if (gpuConfig->getSurfType() != SkCommandLineConfigGpu::SurfType::kDefault) { | 
 |             SkDebugf("This tool only supports the default surface type."); | 
 |             return std::nullopt; | 
 |         } | 
 |  | 
 |         GrContextFactory factory(grContextOpts); | 
 |         if (const auto ctx = factory.get(ctxType, ctxOverrides)) { | 
 |             GrBackendFormat format = ctx->defaultBackendFormat(colorType, GrRenderable::kYes); | 
 |             int supportedSampleCount = | 
 |                     ctx->priv().caps()->getRenderTargetSampleCount(sampleCount, format); | 
 |             if (sampleCount != supportedSampleCount) { | 
 |                 SkDebugf("Configuration '%s' sample count %d is not a supported sample count.\n", | 
 |                          config->getTag().c_str(), | 
 |                          sampleCount); | 
 |                 return std::nullopt; | 
 |             } | 
 |         } else { | 
 |             SkDebugf("No context was available matching config '%s'.\n", config->getTag().c_str()); | 
 |             return std::nullopt; | 
 |         } | 
 |  | 
 |         return Config{gpuConfig->getTag(), | 
 |                       Benchmark::Backend::kGanesh, | 
 |                       colorType, | 
 |                       kPremul_SkAlphaType, | 
 |                       config->refColorSpace(), | 
 |                       sampleCount, | 
 |                       ctxType, | 
 |                       ctxOverrides, | 
 |                       gpuConfig->getSurfaceFlags()}; | 
 |     } | 
 | #if defined(SK_GRAPHITE) | 
 |     if (const auto* gpuConfig = config->asConfigGraphite()) { | 
 |         if (!FLAGS_gpu) { | 
 |             SkDebugf("Skipping config '%s' as requested.\n", config->getTag().c_str()); | 
 |             return std::nullopt; | 
 |         } | 
 |  | 
 |         const auto graphiteCtxType = gpuConfig->getContextType(); | 
 |         const auto sampleCount = 1; // TODO: gpuConfig->getSamples(); | 
 |         const auto colorType = gpuConfig->getColorType(); | 
 |  | 
 |         using ContextFactory = skiatest::graphite::ContextFactory; | 
 |  | 
 |         ContextFactory factory(gpuConfig->asConfigGraphite()->getOptions()); | 
 |         skiatest::graphite::ContextInfo ctxInfo = factory.getContextInfo(graphiteCtxType); | 
 |         skgpu::graphite::Context* ctx = ctxInfo.fContext; | 
 |         if (ctx) { | 
 |             // TODO: Add graphite ctx queries for supported sample count by color type. | 
 | #if 0 | 
 |             GrBackendFormat format = ctx->defaultBackendFormat(colorType, GrRenderable::kYes); | 
 |             int supportedSampleCount = | 
 |                     ctx->priv().caps()->getRenderTargetSampleCount(sampleCount, format); | 
 |             if (sampleCount != supportedSampleCount) { | 
 |                 SkDebugf("Configuration '%s' sample count %d is not a supported sample count.\n", | 
 |                          config->getTag().c_str(), | 
 |                          sampleCount); | 
 |                 return std::nullopt; | 
 |             } | 
 | #else | 
 |             if (sampleCount > 1) { | 
 |                 SkDebugf("Configuration '%s' sample count %d is not a supported sample count.\n", | 
 |                          config->getTag().c_str(), | 
 |                          sampleCount); | 
 |                 return std::nullopt; | 
 |             } | 
 | #endif | 
 |         } else { | 
 |             SkDebugf("No context was available matching config '%s'.\n", config->getTag().c_str()); | 
 |             return std::nullopt; | 
 |         } | 
 |  | 
 |         return Config{gpuConfig->getTag(), | 
 |                       Benchmark::Backend::kGraphite, | 
 |                       colorType, | 
 |                       kPremul_SkAlphaType, | 
 |                       config->refColorSpace(), | 
 |                       sampleCount, | 
 |                       graphiteCtxType, | 
 |                       kBogusContextOverrides, | 
 |                       0}; | 
 |     } | 
 | #endif | 
 |  | 
 | #define CPU_CONFIG(name, backend, color, alpha)                                         \ | 
 |     if (config->getBackend().equals(name)) {                                            \ | 
 |         if (!FLAGS_cpu) {                                                               \ | 
 |             SkDebugf("Skipping config '%s' as requested.\n", config->getTag().c_str()); \ | 
 |             return std::nullopt;                                                      \ | 
 |         }                                                                               \ | 
 |         return Config{SkString(name),                                                   \ | 
 |                       Benchmark::backend,                                               \ | 
 |                       color,                                                            \ | 
 |                       alpha,                                                            \ | 
 |                       config->refColorSpace(),                                          \ | 
 |                       0,                                                                \ | 
 |                       kBogusContextType,                                                \ | 
 |                       kBogusContextOverrides,                                           \ | 
 |                       0};                                                               \ | 
 |     } | 
 |  | 
 |     CPU_CONFIG("nonrendering", Backend::kNonRendering, kUnknown_SkColorType, kUnpremul_SkAlphaType) | 
 |  | 
 |     CPU_CONFIG("a8",    Backend::kRaster,    kAlpha_8_SkColorType, kPremul_SkAlphaType) | 
 |     CPU_CONFIG("565",   Backend::kRaster,    kRGB_565_SkColorType, kOpaque_SkAlphaType) | 
 |     CPU_CONFIG("8888",  Backend::kRaster,        kN32_SkColorType, kPremul_SkAlphaType) | 
 |     CPU_CONFIG("rgba",  Backend::kRaster,  kRGBA_8888_SkColorType, kPremul_SkAlphaType) | 
 |     CPU_CONFIG("bgra",  Backend::kRaster,  kBGRA_8888_SkColorType, kPremul_SkAlphaType) | 
 |     CPU_CONFIG("f16",   Backend::kRaster,   kRGBA_F16_SkColorType, kPremul_SkAlphaType) | 
 |     CPU_CONFIG("srgba", Backend::kRaster, kSRGBA_8888_SkColorType, kPremul_SkAlphaType) | 
 |  | 
 | #undef CPU_CONFIG | 
 |  | 
 |     SkDebugf("Unknown config '%s'.\n", config->getTag().c_str()); | 
 |     return std::nullopt; | 
 | } | 
 |  | 
 | // Append all configs that are enabled and supported. | 
 | void create_configs(TArray<Config>* configs) { | 
 |     SkCommandLineConfigArray array; | 
 |     ParseConfigs(FLAGS_config, &array); | 
 |     for (int i = 0; i < array.size(); ++i) { | 
 |         if (std::optional<Config> config = create_config(array[i].get())) { | 
 |             configs->push_back(*config); | 
 |         } | 
 |     } | 
 |  | 
 |     // If no just default configs were requested, then we're okay. | 
 |     if (array.size() == 0 || FLAGS_config.size() == 0 || | 
 |         // Otherwise, make sure that all specified configs have been created. | 
 |         array.size() == configs->size()) { | 
 |         return; | 
 |     } | 
 |     exit(1); | 
 | } | 
 |  | 
 | // disable warning : switch statement contains default but no 'case' labels | 
 | #if defined _WIN32 | 
 | #pragma warning ( push ) | 
 | #pragma warning ( disable : 4065 ) | 
 | #endif | 
 |  | 
 | // If bench is enabled for config, returns a Target* for it, otherwise nullptr. | 
 | static Target* is_enabled(Benchmark* bench, const Config& config) { | 
 |     if (!bench->isSuitableFor(config.backend)) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     SkImageInfo info = | 
 |             SkImageInfo::Make(bench->getSize(), config.color, config.alpha, config.colorSpace); | 
 |  | 
 |     Target* target = nullptr; | 
 |  | 
 |     switch (config.backend) { | 
 |     case Benchmark::Backend::kGanesh: | 
 |         target = new GPUTarget(config); | 
 |         break; | 
 | #if defined(SK_GRAPHITE) | 
 |     case Benchmark::Backend::kGraphite: | 
 |         target = new GraphiteTarget(config); | 
 |         break; | 
 | #endif | 
 |     default: | 
 |         target = new Target(config); | 
 |         break; | 
 |     } | 
 |  | 
 |     if (!target->init(info, bench)) { | 
 |         delete target; | 
 |         return nullptr; | 
 |     } | 
 |     return target; | 
 | } | 
 |  | 
 | #if defined _WIN32 | 
 | #pragma warning ( pop ) | 
 | #endif | 
 |  | 
 | #ifdef SK_ENABLE_ANDROID_UTILS | 
 | static bool valid_brd_bench(sk_sp<SkData> encoded, SkColorType colorType, uint32_t sampleSize, | 
 |         uint32_t minOutputSize, int* width, int* height) { | 
 |     auto brd = android::skia::BitmapRegionDecoder::Make(encoded); | 
 |     if (nullptr == brd) { | 
 |         // This is indicates that subset decoding is not supported for a particular image format. | 
 |         return false; | 
 |     } | 
 |  | 
 |     if (sampleSize * minOutputSize > (uint32_t) brd->width() || sampleSize * minOutputSize > | 
 |             (uint32_t) brd->height()) { | 
 |         // This indicates that the image is not large enough to decode a | 
 |         // minOutputSize x minOutputSize subset at the given sampleSize. | 
 |         return false; | 
 |     } | 
 |  | 
 |     // Set the image width and height.  The calling code will use this to choose subsets to decode. | 
 |     *width = brd->width(); | 
 |     *height = brd->height(); | 
 |     return true; | 
 | } | 
 | #endif | 
 |  | 
 | static void cleanup_run(Target* target) { | 
 |     delete target; | 
 | } | 
 |  | 
 | static void collect_files(const CommandLineFlags::StringArray& paths, | 
 |                           const char*                          ext, | 
 |                           TArray<SkString>*                  list) { | 
 |     for (int i = 0; i < paths.size(); ++i) { | 
 |         if (SkStrEndsWith(paths[i], ext)) { | 
 |             list->push_back(SkString(paths[i])); | 
 |         } else { | 
 |             SkOSFile::Iter it(paths[i], ext); | 
 |             SkString path; | 
 |             while (it.next(&path)) { | 
 |                 list->push_back(SkOSPath::Join(paths[i], path.c_str())); | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | class BenchmarkStream { | 
 | public: | 
 |     BenchmarkStream() : fBenches(BenchRegistry::Head()) | 
 |                       , fGMs(skiagm::GMRegistry::Head()) { | 
 |         collect_files(FLAGS_skps, ".skp", &fSKPs); | 
 |         collect_files(FLAGS_mskps, ".mskp", &fMSKPs); | 
 |         collect_files(FLAGS_svgs, ".svg", &fSVGs); | 
 |         collect_files(FLAGS_texttraces, ".trace", &fTextBlobTraces); | 
 |  | 
 |         if (4 != sscanf(FLAGS_clip[0], "%d,%d,%d,%d", | 
 |                         &fClip.fLeft, &fClip.fTop, &fClip.fRight, &fClip.fBottom)) { | 
 |             SkDebugf("Can't parse %s from --clip as an SkIRect.\n", FLAGS_clip[0]); | 
 |             exit(1); | 
 |         } | 
 |  | 
 |         for (int i = 0; i < FLAGS_scales.size(); i++) { | 
 |             if (1 != sscanf(FLAGS_scales[i], "%f", &fScales.push_back())) { | 
 |                 SkDebugf("Can't parse %s from --scales as an SkScalar.\n", FLAGS_scales[i]); | 
 |                 exit(1); | 
 |             } | 
 |         } | 
 |  | 
 |         if (2 != sscanf(FLAGS_zoom[0], "%f,%lf", &fZoomMax, &fZoomPeriodMs)) { | 
 |             SkDebugf("Can't parse %s from --zoom as a zoomMax,zoomPeriodMs.\n", FLAGS_zoom[0]); | 
 |             exit(1); | 
 |         } | 
 |  | 
 |         // Prepare the images for decoding | 
 |         if (!CommonFlags::CollectImages(FLAGS_images, &fImages)) { | 
 |             exit(1); | 
 |         } | 
 |  | 
 |         // Choose the candidate color types for image decoding | 
 |         fColorTypes.push_back(kN32_SkColorType); | 
 |         if (!FLAGS_simpleCodec) { | 
 |             fColorTypes.push_back(kRGB_565_SkColorType); | 
 |             fColorTypes.push_back(kAlpha_8_SkColorType); | 
 |             fColorTypes.push_back(kGray_8_SkColorType); | 
 |         } | 
 |     } | 
 |  | 
 |     static sk_sp<SkPicture> ReadPicture(const char* path) { | 
 |         // Not strictly necessary, as it will be checked again later, | 
 |         // but helps to avoid a lot of pointless work if we're going to skip it. | 
 |         if (CommandLineFlags::ShouldSkip(FLAGS_match, SkOSPath::Basename(path).c_str())) { | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(path); | 
 |         if (!stream) { | 
 |             SkDebugf("Could not read %s.\n", path); | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         return SkPicture::MakeFromStream(stream.get()); | 
 |     } | 
 |  | 
 |     static std::unique_ptr<MSKPPlayer> ReadMSKP(const char* path) { | 
 |         // Not strictly necessary, as it will be checked again later, | 
 |         // but helps to avoid a lot of pointless work if we're going to skip it. | 
 |         if (CommandLineFlags::ShouldSkip(FLAGS_match, SkOSPath::Basename(path).c_str())) { | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         std::unique_ptr<SkStreamSeekable> stream = SkStream::MakeFromFile(path); | 
 |         if (!stream) { | 
 |             SkDebugf("Could not read %s.\n", path); | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         return MSKPPlayer::Make(stream.get()); | 
 |     } | 
 |  | 
 |     static sk_sp<SkPicture> ReadSVGPicture(const char* path) { | 
 |         if (CommandLineFlags::ShouldSkip(FLAGS_match, SkOSPath::Basename(path).c_str())) { | 
 |             return nullptr; | 
 |         } | 
 |         sk_sp<SkData> data(SkData::MakeFromFileName(path)); | 
 |         if (!data) { | 
 |             SkDebugf("Could not read %s.\n", path); | 
 |             return nullptr; | 
 |         } | 
 |  | 
 | #if defined(SK_ENABLE_SVG) | 
 |         SkMemoryStream stream(std::move(data)); | 
 |         sk_sp<SkSVGDOM> svgDom = SkSVGDOM::Builder() | 
 |                                          .setFontManager(ToolUtils::TestFontMgr()) | 
 |                                          .setTextShapingFactory(SkShapers::BestAvailable()) | 
 |                                          .make(stream); | 
 |         if (!svgDom) { | 
 |             SkDebugf("Could not parse %s.\n", path); | 
 |             return nullptr; | 
 |         } | 
 |  | 
 |         // Use the intrinsic SVG size if available, otherwise fall back to a default value. | 
 |         static const SkSize kDefaultContainerSize = SkSize::Make(128, 128); | 
 |         if (svgDom->containerSize().isEmpty()) { | 
 |             svgDom->setContainerSize(kDefaultContainerSize); | 
 |         } | 
 |  | 
 |         SkPictureRecorder recorder; | 
 |         svgDom->render(recorder.beginRecording(svgDom->containerSize().width(), | 
 |                                                svgDom->containerSize().height())); | 
 |         return recorder.finishRecordingAsPicture(); | 
 | #else | 
 |         return nullptr; | 
 | #endif  // defined(SK_ENABLE_SVG) | 
 |     } | 
 |  | 
 |     Benchmark* next() { | 
 |         std::unique_ptr<Benchmark> bench; | 
 |         do { | 
 |             bench.reset(this->rawNext()); | 
 |             if (!bench) { | 
 |                 return nullptr; | 
 |             } | 
 |         } while (CommandLineFlags::ShouldSkip(FLAGS_sourceType, fSourceType) || | 
 |                  CommandLineFlags::ShouldSkip(FLAGS_benchType, fBenchType)); | 
 |         return bench.release(); | 
 |     } | 
 |  | 
 |     Benchmark* rawNext() { | 
 |         if (fBenches) { | 
 |             Benchmark* bench = fBenches->get()(nullptr); | 
 |             fBenches = fBenches->next(); | 
 |             fSourceType = "bench"; | 
 |             fBenchType  = "micro"; | 
 |             return bench; | 
 |         } | 
 |  | 
 |         while (fGMs) { | 
 |             std::unique_ptr<skiagm::GM> gm = fGMs->get()(); | 
 |             if (gm->isBazelOnly()) { | 
 |                 // We skip Bazel-only GMs because they might not be regular GMs. The Bazel build | 
 |                 // reuses the notion of GMs to replace the notion of DM sources of various kinds, | 
 |                 // such as codec sources and image generation sources. See comments in the | 
 |                 // skiagm::GM::isBazelOnly function declaration for context. | 
 |                 continue; | 
 |             } | 
 |             fGMs = fGMs->next(); | 
 |             if (gm->runAsBench()) { | 
 |                 fSourceType = "gm"; | 
 |                 fBenchType  = "micro"; | 
 |                 return new GMBench(std::move(gm)); | 
 |             } | 
 |         } | 
 |  | 
 |         while (fCurrentTextBlobTrace < fTextBlobTraces.size()) { | 
 |             SkString path = fTextBlobTraces[fCurrentTextBlobTrace++]; | 
 |             SkString basename = SkOSPath::Basename(path.c_str()); | 
 |             static constexpr char kEnding[] = ".trace"; | 
 |             if (basename.endsWith(kEnding)) { | 
 |                 basename.remove(basename.size() - strlen(kEnding), strlen(kEnding)); | 
 |             } | 
 |             fSourceType = "texttrace"; | 
 |             fBenchType  = "micro"; | 
 |             return CreateDiffCanvasBench( | 
 |                     SkStringPrintf("SkDiffBench-%s", basename.c_str()), | 
 |                     [path](){ return SkStream::MakeFromFile(path.c_str()); }); | 
 |         } | 
 |  | 
 |         // First add all .skps as RecordingBenches. | 
 |         while (fCurrentRecording < fSKPs.size()) { | 
 |             const SkString& path = fSKPs[fCurrentRecording++]; | 
 |             sk_sp<SkPicture> pic = ReadPicture(path.c_str()); | 
 |             if (!pic) { | 
 |                 continue; | 
 |             } | 
 |             SkString name = SkOSPath::Basename(path.c_str()); | 
 |             fSourceType = "skp"; | 
 |             fBenchType  = "recording"; | 
 |             fSKPBytes = static_cast<double>(pic->approximateBytesUsed()); | 
 |             fSKPOps   = pic->approximateOpCount(); | 
 |             return new RecordingBench(name.c_str(), pic.get(), FLAGS_bbh); | 
 |         } | 
 |  | 
 |         // Add all .skps as DeserializePictureBenchs. | 
 |         while (fCurrentDeserialPicture < fSKPs.size()) { | 
 |             const SkString& path = fSKPs[fCurrentDeserialPicture++]; | 
 |             sk_sp<SkData> data = SkData::MakeFromFileName(path.c_str()); | 
 |             if (!data) { | 
 |                 continue; | 
 |             } | 
 |             SkString name = SkOSPath::Basename(path.c_str()); | 
 |             fSourceType = "skp"; | 
 |             fBenchType  = "deserial"; | 
 |             fSKPBytes = static_cast<double>(data->size()); | 
 |             fSKPOps   = 0; | 
 |             return new DeserializePictureBench(name.c_str(), std::move(data)); | 
 |         } | 
 |  | 
 |         // Then once each for each scale as SKPBenches (playback). | 
 |         while (fCurrentScale < fScales.size()) { | 
 |             while (fCurrentSKP < fSKPs.size()) { | 
 |                 const SkString& path = fSKPs[fCurrentSKP++]; | 
 |                 sk_sp<SkPicture> pic = ReadPicture(path.c_str()); | 
 |                 if (!pic) { | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 if (FLAGS_bbh) { | 
 |                     // The SKP we read off disk doesn't have a BBH.  Re-record so it grows one. | 
 |                     SkRTreeFactory factory; | 
 |                     SkPictureRecorder recorder; | 
 |                     pic->playback(recorder.beginRecording(pic->cullRect().width(), | 
 |                                                           pic->cullRect().height(), | 
 |                                                           &factory)); | 
 |                     pic = recorder.finishRecordingAsPicture(); | 
 |                 } | 
 |                 SkString name = SkOSPath::Basename(path.c_str()); | 
 |                 fSourceType = "skp"; | 
 |                 fBenchType = "playback"; | 
 |                 return new SKPBench(name.c_str(), pic.get(), fClip, fScales[fCurrentScale], | 
 |                                     FLAGS_loopSKP); | 
 |             } | 
 |  | 
 |             while (fCurrentSVG < fSVGs.size()) { | 
 |                 const char* path = fSVGs[fCurrentSVG++].c_str(); | 
 |                 if (sk_sp<SkPicture> pic = ReadSVGPicture(path)) { | 
 |                     fSourceType = "svg"; | 
 |                     fBenchType = "playback"; | 
 |                     return new SKPBench(SkOSPath::Basename(path).c_str(), pic.get(), fClip, | 
 |                                         fScales[fCurrentScale], FLAGS_loopSKP); | 
 |                 } | 
 |             } | 
 |  | 
 |             fCurrentSKP = 0; | 
 |             fCurrentSVG = 0; | 
 |             fCurrentScale++; | 
 |         } | 
 |  | 
 |         // Now loop over each skp again if we have an animation | 
 |         if (fZoomMax != 1.0f && fZoomPeriodMs > 0) { | 
 |             while (fCurrentAnimSKP < fSKPs.size()) { | 
 |                 const SkString& path = fSKPs[fCurrentAnimSKP]; | 
 |                 sk_sp<SkPicture> pic = ReadPicture(path.c_str()); | 
 |                 if (!pic) { | 
 |                     fCurrentAnimSKP++; | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 fCurrentAnimSKP++; | 
 |                 SkString name = SkOSPath::Basename(path.c_str()); | 
 |                 sk_sp<SKPAnimationBench::Animation> animation = | 
 |                     SKPAnimationBench::MakeZoomAnimation(fZoomMax, fZoomPeriodMs); | 
 |                 return new SKPAnimationBench(name.c_str(), pic.get(), fClip, std::move(animation), | 
 |                                              FLAGS_loopSKP); | 
 |             } | 
 |         } | 
 |  | 
 |         // Read all MSKPs as benches | 
 |         while (fCurrentMSKP < fMSKPs.size()) { | 
 |             const SkString& path = fMSKPs[fCurrentMSKP++]; | 
 |             std::unique_ptr<MSKPPlayer> player = ReadMSKP(path.c_str()); | 
 |             if (!player) { | 
 |                 continue; | 
 |             } | 
 |             SkString name = SkOSPath::Basename(path.c_str()); | 
 |             fSourceType = "mskp"; | 
 |             fBenchType = "mskp"; | 
 |             return new MSKPBench(std::move(name), std::move(player)); | 
 |         } | 
 |  | 
 |         for (; fCurrentCodec < fImages.size(); fCurrentCodec++) { | 
 |             fSourceType = "image"; | 
 |             fBenchType = "skcodec"; | 
 |             const SkString& path = fImages[fCurrentCodec]; | 
 |             if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) { | 
 |                 continue; | 
 |             } | 
 |             sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str())); | 
 |             std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(encoded)); | 
 |             if (!codec) { | 
 |                 // Nothing to time. | 
 |                 SkDebugf("Cannot find codec for %s\n", path.c_str()); | 
 |                 continue; | 
 |             } | 
 |  | 
 |             while (fCurrentColorType < fColorTypes.size()) { | 
 |                 const SkColorType colorType = fColorTypes[fCurrentColorType]; | 
 |  | 
 |                 SkAlphaType alphaType = codec->getInfo().alphaType(); | 
 |                 if (FLAGS_simpleCodec) { | 
 |                     if (kUnpremul_SkAlphaType == alphaType) { | 
 |                         alphaType = kPremul_SkAlphaType; | 
 |                     } | 
 |  | 
 |                     fCurrentColorType++; | 
 |                 } else { | 
 |                     switch (alphaType) { | 
 |                         case kOpaque_SkAlphaType: | 
 |                             // We only need to test one alpha type (opaque). | 
 |                             fCurrentColorType++; | 
 |                             break; | 
 |                         case kUnpremul_SkAlphaType: | 
 |                         case kPremul_SkAlphaType: | 
 |                             if (0 == fCurrentAlphaType) { | 
 |                                 // Test unpremul first. | 
 |                                 alphaType = kUnpremul_SkAlphaType; | 
 |                                 fCurrentAlphaType++; | 
 |                             } else { | 
 |                                 // Test premul. | 
 |                                 alphaType = kPremul_SkAlphaType; | 
 |                                 fCurrentAlphaType = 0; | 
 |                                 fCurrentColorType++; | 
 |                             } | 
 |                             break; | 
 |                         default: | 
 |                             SkASSERT(false); | 
 |                             fCurrentColorType++; | 
 |                             break; | 
 |                     } | 
 |                 } | 
 |  | 
 |                 // Make sure we can decode to this color type and alpha type. | 
 |                 SkImageInfo info = | 
 |                         codec->getInfo().makeColorType(colorType).makeAlphaType(alphaType); | 
 |                 const size_t rowBytes = info.minRowBytes(); | 
 |                 SkAutoMalloc storage(info.computeByteSize(rowBytes)); | 
 |  | 
 |                 const SkCodec::Result result = codec->getPixels( | 
 |                         info, storage.get(), rowBytes); | 
 |                 switch (result) { | 
 |                     case SkCodec::kSuccess: | 
 |                     case SkCodec::kIncompleteInput: | 
 |                         return new CodecBench(SkOSPath::Basename(path.c_str()), | 
 |                                               encoded.get(), colorType, alphaType); | 
 |                     case SkCodec::kInvalidConversion: | 
 |                         // This is okay. Not all conversions are valid. | 
 |                         break; | 
 |                     default: | 
 |                         // This represents some sort of failure. | 
 |                         SkASSERT(false); | 
 |                         break; | 
 |                 } | 
 |             } | 
 |             fCurrentColorType = 0; | 
 |         } | 
 |  | 
 |         // Run AndroidCodecBenches | 
 |         const int sampleSizes[] = { 2, 4, 8 }; | 
 |         for (; fCurrentAndroidCodec < fImages.size(); fCurrentAndroidCodec++) { | 
 |             fSourceType = "image"; | 
 |             fBenchType = "skandroidcodec"; | 
 |  | 
 |             const SkString& path = fImages[fCurrentAndroidCodec]; | 
 |             if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) { | 
 |                 continue; | 
 |             } | 
 |             sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str())); | 
 |             std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromData(encoded)); | 
 |             if (!codec) { | 
 |                 // Nothing to time. | 
 |                 SkDebugf("Cannot find codec for %s\n", path.c_str()); | 
 |                 continue; | 
 |             } | 
 |  | 
 |             while (fCurrentSampleSize < (int) std::size(sampleSizes)) { | 
 |                 int sampleSize = sampleSizes[fCurrentSampleSize]; | 
 |                 fCurrentSampleSize++; | 
 |                 if (10 * sampleSize > std::min(codec->getInfo().width(), codec->getInfo().height())) { | 
 |                     // Avoid benchmarking scaled decodes of already small images. | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 return new AndroidCodecBench(SkOSPath::Basename(path.c_str()), | 
 |                                              encoded.get(), sampleSize); | 
 |             } | 
 |             fCurrentSampleSize = 0; | 
 |         } | 
 |  | 
 | #ifdef SK_ENABLE_ANDROID_UTILS | 
 |         // Run the BRDBenches | 
 |         // We intend to create benchmarks that model the use cases in | 
 |         // android/libraries/social/tiledimage.  In this library, an image is decoded in 512x512 | 
 |         // tiles.  The image can be translated freely, so the location of a tile may be anywhere in | 
 |         // the image.  For that reason, we will benchmark decodes in five representative locations | 
 |         // in the image.  Additionally, this use case utilizes power of two scaling, so we will | 
 |         // test on power of two sample sizes.  The output tile is always 512x512, so, when a | 
 |         // sampleSize is used, the size of the subset that is decoded is always | 
 |         // (sampleSize*512)x(sampleSize*512). | 
 |         // There are a few good reasons to only test on power of two sample sizes at this time: | 
 |         //     All use cases we are aware of only scale by powers of two. | 
 |         //     PNG decodes use the indicated sampling strategy regardless of the sample size, so | 
 |         //         these tests are sufficient to provide good coverage of our scaling options. | 
 |         const uint32_t brdSampleSizes[] = { 1, 2, 4, 8, 16 }; | 
 |         const uint32_t minOutputSize = 512; | 
 |         for (; fCurrentBRDImage < fImages.size(); fCurrentBRDImage++) { | 
 |             fSourceType = "image"; | 
 |             fBenchType = "BRD"; | 
 |  | 
 |             const SkString& path = fImages[fCurrentBRDImage]; | 
 |             if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             while (fCurrentColorType < fColorTypes.size()) { | 
 |                 while (fCurrentSampleSize < (int) std::size(brdSampleSizes)) { | 
 |                     while (fCurrentSubsetType <= kLastSingle_SubsetType) { | 
 |  | 
 |                         sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str())); | 
 |                         const SkColorType colorType = fColorTypes[fCurrentColorType]; | 
 |                         uint32_t sampleSize = brdSampleSizes[fCurrentSampleSize]; | 
 |                         int currentSubsetType = fCurrentSubsetType++; | 
 |  | 
 |                         int width = 0; | 
 |                         int height = 0; | 
 |                         if (!valid_brd_bench(encoded, colorType, sampleSize, minOutputSize, | 
 |                                 &width, &height)) { | 
 |                             break; | 
 |                         } | 
 |  | 
 |                         SkString basename = SkOSPath::Basename(path.c_str()); | 
 |                         SkIRect subset; | 
 |                         const uint32_t subsetSize = sampleSize * minOutputSize; | 
 |                         switch (currentSubsetType) { | 
 |                             case kTopLeft_SubsetType: | 
 |                                 basename.append("_TopLeft"); | 
 |                                 subset = SkIRect::MakeXYWH(0, 0, subsetSize, subsetSize); | 
 |                                 break; | 
 |                             case kTopRight_SubsetType: | 
 |                                 basename.append("_TopRight"); | 
 |                                 subset = SkIRect::MakeXYWH(width - subsetSize, 0, subsetSize, | 
 |                                         subsetSize); | 
 |                                 break; | 
 |                             case kMiddle_SubsetType: | 
 |                                 basename.append("_Middle"); | 
 |                                 subset = SkIRect::MakeXYWH((width - subsetSize) / 2, | 
 |                                         (height - subsetSize) / 2, subsetSize, subsetSize); | 
 |                                 break; | 
 |                             case kBottomLeft_SubsetType: | 
 |                                 basename.append("_BottomLeft"); | 
 |                                 subset = SkIRect::MakeXYWH(0, height - subsetSize, subsetSize, | 
 |                                         subsetSize); | 
 |                                 break; | 
 |                             case kBottomRight_SubsetType: | 
 |                                 basename.append("_BottomRight"); | 
 |                                 subset = SkIRect::MakeXYWH(width - subsetSize, | 
 |                                         height - subsetSize, subsetSize, subsetSize); | 
 |                                 break; | 
 |                             default: | 
 |                                 SkASSERT(false); | 
 |                         } | 
 |  | 
 |                         return new BitmapRegionDecoderBench(basename.c_str(), encoded.get(), | 
 |                                 colorType, sampleSize, subset); | 
 |                     } | 
 |                     fCurrentSubsetType = 0; | 
 |                     fCurrentSampleSize++; | 
 |                 } | 
 |                 fCurrentSampleSize = 0; | 
 |                 fCurrentColorType++; | 
 |             } | 
 |             fCurrentColorType = 0; | 
 |         } | 
 | #endif // SK_ENABLE_ANDROID_UTILS | 
 |  | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     void fillCurrentOptions(NanoJSONResultsWriter& log) const { | 
 |         log.appendCString("source_type", fSourceType); | 
 |         log.appendCString("bench_type",  fBenchType); | 
 |         if (0 == strcmp(fSourceType, "skp")) { | 
 |             log.appendString("clip", | 
 |                     SkStringPrintf("%d %d %d %d", fClip.fLeft, fClip.fTop, | 
 |                                                   fClip.fRight, fClip.fBottom)); | 
 |             SkASSERT_RELEASE(fCurrentScale < fScales.size());  // debugging paranoia | 
 |             log.appendString("scale", SkStringPrintf("%.2g", fScales[fCurrentScale])); | 
 |         } | 
 |     } | 
 |  | 
 |     void fillCurrentMetrics(NanoJSONResultsWriter& log) const { | 
 |         if (0 == strcmp(fBenchType, "recording")) { | 
 |             log.appendMetric("bytes", fSKPBytes); | 
 |             log.appendMetric("ops", fSKPOps); | 
 |         } | 
 |     } | 
 |  | 
 | private: | 
 | #ifdef SK_ENABLE_ANDROID_UTILS | 
 |     enum SubsetType { | 
 |         kTopLeft_SubsetType     = 0, | 
 |         kTopRight_SubsetType    = 1, | 
 |         kMiddle_SubsetType      = 2, | 
 |         kBottomLeft_SubsetType  = 3, | 
 |         kBottomRight_SubsetType = 4, | 
 |         kTranslate_SubsetType   = 5, | 
 |         kZoom_SubsetType        = 6, | 
 |         kLast_SubsetType        = kZoom_SubsetType, | 
 |         kLastSingle_SubsetType  = kBottomRight_SubsetType, | 
 |     }; | 
 | #endif | 
 |  | 
 |     const BenchRegistry* fBenches; | 
 |     const skiagm::GMRegistry* fGMs; | 
 |     SkIRect            fClip; | 
 |     TArray<SkScalar> fScales; | 
 |     TArray<SkString> fSKPs; | 
 |     TArray<SkString> fMSKPs; | 
 |     TArray<SkString> fSVGs; | 
 |     TArray<SkString> fTextBlobTraces; | 
 |     TArray<SkString> fImages; | 
 |     TArray<SkColorType, true> fColorTypes; | 
 |     SkScalar           fZoomMax; | 
 |     double             fZoomPeriodMs; | 
 |  | 
 |     double fSKPBytes, fSKPOps; | 
 |  | 
 |     const char* fSourceType;  // What we're benching: bench, GM, SKP, ... | 
 |     const char* fBenchType;   // How we bench it: micro, recording, playback, ... | 
 |     int fCurrentRecording = 0; | 
 |     int fCurrentDeserialPicture = 0; | 
 |     int fCurrentMSKP = 0; | 
 |     int fCurrentScale = 0; | 
 |     int fCurrentSKP = 0; | 
 |     int fCurrentSVG = 0; | 
 |     int fCurrentTextBlobTrace = 0; | 
 |     int fCurrentCodec = 0; | 
 |     int fCurrentAndroidCodec = 0; | 
 | #ifdef SK_ENABLE_ANDROID_UTILS | 
 |     int fCurrentBRDImage = 0; | 
 |     int fCurrentSubsetType = 0; | 
 | #endif | 
 |     int fCurrentColorType = 0; | 
 |     int fCurrentAlphaType = 0; | 
 |     int fCurrentSampleSize = 0; | 
 |     int fCurrentAnimSKP = 0; | 
 | }; | 
 |  | 
 | // Some runs (mostly, Valgrind) are so slow that the bot framework thinks we've hung. | 
 | // This prints something every once in a while so that it knows we're still working. | 
 | static void start_keepalive() { | 
 |     static std::thread* intentionallyLeaked = new std::thread([]{ | 
 |         for (;;) { | 
 |             static const int kSec = 1200; | 
 |         #if defined(SK_BUILD_FOR_WIN) | 
 |             Sleep(kSec * 1000); | 
 |         #else | 
 |             sleep(kSec); | 
 |         #endif | 
 |             SkDebugf("\nBenchmarks still running...\n"); | 
 |         } | 
 |     }); | 
 |     (void)intentionallyLeaked; | 
 |     SK_INTENTIONALLY_LEAKED(intentionallyLeaked); | 
 | } | 
 |  | 
 | class NanobenchShaderErrorHandler : public GrContextOptions::ShaderErrorHandler { | 
 |     void compileError(const char* shader, const char* errors) override { | 
 |         // Nanobench should abort if any shader can't compile. Failure is much better than | 
 |         // reporting meaningless performance metrics. | 
 |         std::string message = SkShaderUtils::BuildShaderErrorMessage(shader, errors); | 
 |         SK_ABORT("\n%s", message.c_str()); | 
 |     } | 
 | }; | 
 |  | 
 | int main(int argc, char** argv) { | 
 |     CommandLineFlags::Parse(argc, argv); | 
 |  | 
 |     initializeEventTracingForTools(); | 
 |  | 
 | #if defined(SK_BUILD_FOR_IOS) | 
 |     cd_Documents(); | 
 | #endif | 
 |     SetupCrashHandler(); | 
 |     if (FLAGS_runtimeCPUDetection) { | 
 |         SkGraphics::Init(); | 
 |     } | 
 |  | 
 |     // Our benchmarks only currently decode .png or .jpg files | 
 |     SkCodecs::Register(SkPngDecoder::Decoder()); | 
 |     SkCodecs::Register(SkJpegDecoder::Decoder()); | 
 |  | 
 |     SkTaskGroup::Enabler enabled(FLAGS_threads); | 
 |  | 
 |     CommonFlags::SetCtxOptions(&grContextOpts); | 
 |  | 
 |     NanobenchShaderErrorHandler errorHandler; | 
 |     grContextOpts.fShaderErrorHandler = &errorHandler; | 
 |  | 
 |     if (kAutoTuneLoops != FLAGS_loops) { | 
 |         FLAGS_samples     = 1; | 
 |         FLAGS_gpuFrameLag = 0; | 
 |     } | 
 |  | 
 |     if (!FLAGS_writePath.isEmpty()) { | 
 |         SkDebugf("Writing files to %s.\n", FLAGS_writePath[0]); | 
 |         if (!sk_mkdir(FLAGS_writePath[0])) { | 
 |             SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]); | 
 |             FLAGS_writePath.set(0, nullptr); | 
 |         } | 
 |     } | 
 |  | 
 |     std::unique_ptr<SkWStream> logStream(new SkNullWStream); | 
 |     if (!FLAGS_outResultsFile.isEmpty()) { | 
 | #if defined(SK_RELEASE) | 
 |         logStream.reset(new SkFILEWStream(FLAGS_outResultsFile[0])); | 
 | #else | 
 |         SkDebugf("I'm ignoring --outResultsFile because this is a Debug build."); | 
 |         return 1; | 
 | #endif | 
 |     } | 
 |     NanoJSONResultsWriter log(logStream.get(), SkJSONWriter::Mode::kPretty); | 
 |     log.beginObject(); // root | 
 |  | 
 |     if (1 == FLAGS_properties.size() % 2) { | 
 |         SkDebugf("ERROR: --properties must be passed with an even number of arguments.\n"); | 
 |         return 1; | 
 |     } | 
 |     for (int i = 1; i < FLAGS_properties.size(); i += 2) { | 
 |         log.appendCString(FLAGS_properties[i-1], FLAGS_properties[i]); | 
 |     } | 
 |  | 
 |     if (1 == FLAGS_key.size() % 2) { | 
 |         SkDebugf("ERROR: --key must be passed with an even number of arguments.\n"); | 
 |         return 1; | 
 |     } | 
 |     if (FLAGS_key.size()) { | 
 |         log.beginObject("key"); | 
 |         for (int i = 1; i < FLAGS_key.size(); i += 2) { | 
 |             log.appendCString(FLAGS_key[i - 1], FLAGS_key[i]); | 
 |         } | 
 |         log.endObject(); // key | 
 |     } | 
 |  | 
 |     const double overhead = estimate_timer_overhead(); | 
 |     if (!FLAGS_quiet && !FLAGS_csv) { | 
 |         SkDebugf("Timer overhead: %s\n", HUMANIZE(overhead)); | 
 |     } | 
 |  | 
 |     TArray<double> samples; | 
 |  | 
 |     if (kAutoTuneLoops != FLAGS_loops) { | 
 |         SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n"); | 
 |     } else if (FLAGS_quiet) { | 
 |         SkDebugf("! -> high variance, ? -> moderate variance\n"); | 
 |         SkDebugf("    micros   \tbench\n"); | 
 |     } else if (FLAGS_csv) { | 
 |         SkDebugf("min,median,mean,max,stddev,config,bench\n"); | 
 |     } else if (FLAGS_ms) { | 
 |         SkDebugf("curr/maxrss\tloops\tmin\tmedian\tmean\tmax\tstddev\tsamples\tconfig\tbench\n"); | 
 |     } else { | 
 |         SkDebugf("curr/maxrss\tloops\tmin\tmedian\tmean\tmax\tstddev\t%-*s\tconfig\tbench\n", | 
 |                  FLAGS_samples, "samples"); | 
 |     } | 
 |  | 
 |     GrRecordingContextPriv::DMSAAStats combinedDMSAAStats; | 
 |  | 
 |     TArray<Config> configs; | 
 |     create_configs(&configs); | 
 |  | 
 |     if (FLAGS_keepAlive) { | 
 |         start_keepalive(); | 
 |     } | 
 |  | 
 |     gSkForceRasterPipelineBlitter     = FLAGS_forceRasterPipelineHP || FLAGS_forceRasterPipeline; | 
 |     gForceHighPrecisionRasterPipeline = FLAGS_forceRasterPipelineHP; | 
 |  | 
 |     // The SkSL memory benchmark must run before any GPU painting occurs. SkSL allocates memory for | 
 |     // its modules the first time they are accessed, and this test is trying to measure the size of | 
 |     // those allocations. If a paint has already occurred, some modules will have already been | 
 |     // loaded, so we won't be able to capture a delta for them. | 
 |     log.beginObject("results"); | 
 |     RunSkSLModuleBenchmarks(&log); | 
 |  | 
 |     int runs = 0; | 
 |     BenchmarkStream benchStream; | 
 |     AutoreleasePool pool; | 
 |     while (Benchmark* b = benchStream.next()) { | 
 |         std::unique_ptr<Benchmark> bench(b); | 
 |         if (CommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName())) { | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (!configs.empty()) { | 
 |             log.beginBench( | 
 |                     bench->getUniqueName(), bench->getSize().width(), bench->getSize().height()); | 
 |             bench->delayedSetup(); | 
 |         } | 
 |         for (int i = 0; i < configs.size(); ++i) { | 
 |             Target* target = is_enabled(b, configs[i]); | 
 |             if (!target) { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             // During HWUI output this canvas may be nullptr. | 
 |             SkCanvas* canvas = target->getCanvas(); | 
 |             const char* config = target->config.name.c_str(); | 
 |  | 
 |             if (FLAGS_pre_log || FLAGS_dryRun) { | 
 |                 SkDebugf("Running %s\t%s\n" | 
 |                          , bench->getUniqueName() | 
 |                          , config); | 
 |                 if (FLAGS_dryRun) { | 
 |                     continue; | 
 |                 } | 
 |             } | 
 |  | 
 |             if (FLAGS_purgeBetweenBenches) { | 
 |                 SkGraphics::PurgeAllCaches(); | 
 |             } | 
 |  | 
 |             if (FLAGS_splitPerfettoTracesByBenchmark) { | 
 |                 TRACE_EVENT_API_NEW_TRACE_SECTION(TRACE_STR_COPY(bench->getUniqueName())); | 
 |             } | 
 |             TRACE_EVENT2("skia", "Benchmark", "name", TRACE_STR_COPY(bench->getUniqueName()), | 
 |                                               "config", TRACE_STR_COPY(config)); | 
 |  | 
 |             target->setup(); | 
 |             bench->perCanvasPreDraw(canvas); | 
 |  | 
 |             int maxFrameLag; | 
 |             int loops = target->needsFrameTiming(&maxFrameLag) | 
 |                 ? setup_gpu_bench(target, bench.get(), maxFrameLag) | 
 |                 : setup_cpu_bench(overhead, target, bench.get()); | 
 |  | 
 |             if (kFailedLoops == loops) { | 
 |                 // Can't be timed.  A warning note has already been printed. | 
 |                 cleanup_run(target); | 
 |                 continue; | 
 |             } | 
 |  | 
 |             if (runs == 0 && FLAGS_ms < 1000) { | 
 |                 // Run the first bench for 1000ms to warm up the nanobench if FLAGS_ms < 1000. | 
 |                 // Otherwise, the first few benches' measurements will be inaccurate. | 
 |                 auto stop = now_ms() + 1000; | 
 |                 do { | 
 |                     time(loops, bench.get(), target); | 
 |                     pool.drain(); | 
 |                 } while (now_ms() < stop); | 
 |             } | 
 |  | 
 |             if (FLAGS_ms) { | 
 |                 samples.clear(); | 
 |                 auto stop = now_ms() + FLAGS_ms; | 
 |                 do { | 
 |                     samples.push_back(time(loops, bench.get(), target) / loops); | 
 |                     pool.drain(); | 
 |                 } while (now_ms() < stop); | 
 |             } else { | 
 |                 samples.reset(FLAGS_samples); | 
 |                 for (int s = 0; s < FLAGS_samples; s++) { | 
 |                     samples[s] = time(loops, bench.get(), target) / loops; | 
 |                     pool.drain(); | 
 |                 } | 
 |             } | 
 |  | 
 |             // Scale each result to the benchmark's own units, time/unit. | 
 |             for (double& sample : samples) { | 
 |                 sample *= (1.0 / bench->getUnits()); | 
 |             } | 
 |  | 
 |             TArray<SkString> keys; | 
 |             TArray<double> values; | 
 |             if (configs[i].backend == Benchmark::Backend::kGanesh) { | 
 |                 if (FLAGS_gpuStatsDump) { | 
 |                     // TODO cache stats | 
 |                     bench->getGpuStats(canvas, &keys, &values); | 
 |                 } | 
 |                 if (FLAGS_dmsaaStatsDump && bench->getDMSAAStats(canvas->recordingContext())) { | 
 |                     const auto& dmsaaStats = canvas->recordingContext()->priv().dmsaaStats(); | 
 |                     dmsaaStats.dumpKeyValuePairs(&keys, &values); | 
 |                     dmsaaStats.dump(); | 
 |                     combinedDMSAAStats.merge(dmsaaStats); | 
 |                 } | 
 |             } | 
 |  | 
 |             bench->perCanvasPostDraw(canvas); | 
 |  | 
 |             if (Benchmark::Backend::kNonRendering != target->config.backend && | 
 |                 !FLAGS_writePath.isEmpty() && FLAGS_writePath[0]) { | 
 |                 SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config); | 
 |                 pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getUniqueName()); | 
 |                 pngFilename.append(".png"); | 
 |                 write_canvas_png(target, pngFilename); | 
 |             } | 
 |  | 
 |             // Building stats.plot often shows up in profiles, | 
 |             // so skip building it when we're not going to print it anyway. | 
 |             const bool want_plot = !FLAGS_quiet && !FLAGS_ms; | 
 |  | 
 |             Stats stats(samples, want_plot); | 
 |             log.beginObject(config); | 
 |  | 
 |             log.beginObject("options"); | 
 |             log.appendCString("name", bench->getName()); | 
 |             benchStream.fillCurrentOptions(log); | 
 |             log.endObject(); // options | 
 |  | 
 |             // Metrics | 
 |             log.appendMetric("min_ms", stats.min); | 
 |             log.appendMetric("min_ratio", sk_ieee_double_divide(stats.median, stats.min)); | 
 |             log.beginArray("samples"); | 
 |             for (double sample : samples) { | 
 |                 log.appendDoubleDigits(sample, 16); | 
 |             } | 
 |             log.endArray(); // samples | 
 |             benchStream.fillCurrentMetrics(log); | 
 |             if (!keys.empty()) { | 
 |                 // dump to json, only SKPBench currently returns valid keys / values | 
 |                 SkASSERT(keys.size() == values.size()); | 
 |                 for (int j = 0; j < keys.size(); j++) { | 
 |                     log.appendMetric(keys[j].c_str(), values[j]); | 
 |                 } | 
 |             } | 
 |  | 
 |             log.endObject(); // config | 
 |  | 
 |             if (runs++ % FLAGS_flushEvery == 0) { | 
 |                 log.flush(); | 
 |             } | 
 |  | 
 |             if (kAutoTuneLoops != FLAGS_loops) { | 
 |                 if (configs.size() == 1) { | 
 |                     config = ""; // Only print the config if we run the same bench on more than one. | 
 |                 } | 
 |                 SkDebugf("%4d/%-4dMB\t%s\t%s " | 
 |                          , sk_tools::getCurrResidentSetSizeMB() | 
 |                          , sk_tools::getMaxResidentSetSizeMB() | 
 |                          , bench->getUniqueName() | 
 |                          , config); | 
 |                 SkDebugf("\n"); | 
 |             } else if (FLAGS_quiet) { | 
 |                 const char* mark = " "; | 
 |                 const double stddev_percent = | 
 |                     sk_ieee_double_divide(100 * sqrt(stats.var), stats.mean); | 
 |                 if (stddev_percent >  5) mark = "?"; | 
 |                 if (stddev_percent > 10) mark = "!"; | 
 |  | 
 |                 SkDebugf("%10.2f %s\t%s\t%s\n", | 
 |                          stats.median*1e3, mark, bench->getUniqueName(), config); | 
 |             } else if (FLAGS_csv) { | 
 |                 const double stddev_percent = | 
 |                     sk_ieee_double_divide(100 * sqrt(stats.var), stats.mean); | 
 |                 SkDebugf("%g,%g,%g,%g,%g,%s,%s\n" | 
 |                          , stats.min | 
 |                          , stats.median | 
 |                          , stats.mean | 
 |                          , stats.max | 
 |                          , stddev_percent | 
 |                          , config | 
 |                          , bench->getUniqueName() | 
 |                          ); | 
 |             } else { | 
 |                 const double stddev_percent = | 
 |                     sk_ieee_double_divide(100 * sqrt(stats.var), stats.mean); | 
 |                 SkDebugf("%4d/%-4dMB\t%d\t%s\t%s\t%s\t%s\t%.0f%%\t%s\t%s\t%s\n" | 
 |                         , sk_tools::getCurrResidentSetSizeMB() | 
 |                         , sk_tools::getMaxResidentSetSizeMB() | 
 |                         , loops | 
 |                         , HUMANIZE(stats.min) | 
 |                         , HUMANIZE(stats.median) | 
 |                         , HUMANIZE(stats.mean) | 
 |                         , HUMANIZE(stats.max) | 
 |                         , stddev_percent | 
 |                         , FLAGS_ms ? to_string(samples.size()).c_str() : stats.plot.c_str() | 
 |                         , config | 
 |                         , bench->getUniqueName() | 
 |                         ); | 
 |             } | 
 |  | 
 |             if (FLAGS_gpuStats && Benchmark::Backend::kGanesh == configs[i].backend) { | 
 |                 target->dumpStats(); | 
 |             } | 
 |  | 
 |             if (FLAGS_verbose) { | 
 |                 SkDebugf("Samples:  "); | 
 |                 for (int j = 0; j < samples.size(); j++) { | 
 |                     SkDebugf("%s  ", HUMANIZE(samples[j])); | 
 |                 } | 
 |                 SkDebugf("%s\n", bench->getUniqueName()); | 
 |             } | 
 |             cleanup_run(target); | 
 |             pool.drain(); | 
 |         } | 
 |         if (!configs.empty()) { | 
 |             log.endBench(); | 
 |         } | 
 |     } | 
 |  | 
 |     if (FLAGS_dmsaaStatsDump) { | 
 |         SkDebugf("<<Total Combined DMSAA Stats>>\n"); | 
 |         combinedDMSAAStats.dump(); | 
 |     } | 
 |  | 
 |     SkGraphics::PurgeAllCaches(); | 
 |  | 
 |     log.beginBench("memory_usage", 0, 0); | 
 |     log.beginObject("meta"); // config | 
 |     log.appendS32("max_rss_mb", sk_tools::getMaxResidentSetSizeMB()); | 
 |     log.endObject(); // config | 
 |     log.endBench(); | 
 |  | 
 |     log.endObject(); // results | 
 |     log.endObject(); // root | 
 |     log.flush(); | 
 |  | 
 |     return 0; | 
 | } |