benchmain.cpp revision 95c2003740c4cd01fd1b02ed93b9de7227b1d0f5
1/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "BenchTimer.h"
9#include "ResultsWriter.h"
10#include "SkBenchLogger.h"
11#include "SkBenchmark.h"
12#include "SkBitmapDevice.h"
13#include "SkCanvas.h"
14#include "SkColorPriv.h"
15#include "SkCommandLineFlags.h"
16#include "SkData.h"
17#include "SkDeferredCanvas.h"
18#include "SkGMBench.h"
19#include "SkGraphics.h"
20#include "SkImageEncoder.h"
21#include "SkOSFile.h"
22#include "SkPicture.h"
23#include "SkPictureRecorder.h"
24#include "SkString.h"
25#include "SkSurface.h"
26
27#if SK_SUPPORT_GPU
28#include "GrContext.h"
29#include "GrContextFactory.h"
30#include "GrRenderTarget.h"
31#include "SkGpuDevice.h"
32#include "gl/GrGLDefines.h"
33#else
34class GrContext;
35#endif // SK_SUPPORT_GPU
36
37#include <limits>
38
39enum BenchMode {
40    kNormal_BenchMode,
41    kDeferred_BenchMode,
42    kDeferredSilent_BenchMode,
43    kRecord_BenchMode,
44    kPictureRecord_BenchMode
45};
46const char* BenchMode_Name[] = {
47    "normal", "deferred", "deferredSilent", "record", "picturerecord"
48};
49
50static const char kDefaultsConfigStr[] = "defaults";
51
52///////////////////////////////////////////////////////////////////////////////
53
54class Iter {
55public:
56    Iter() : fBenches(BenchRegistry::Head()), fGMs(skiagm::GMRegistry::Head()) {}
57
58    SkBenchmark* next() {
59        if (fBenches) {
60            BenchRegistry::Factory f = fBenches->factory();
61            fBenches = fBenches->next();
62            return (*f)(NULL);
63        }
64
65        while (fGMs) {
66            SkAutoTDelete<skiagm::GM> gm(fGMs->factory()(NULL));
67            fGMs = fGMs->next();
68            if (gm->getFlags() & skiagm::GM::kAsBench_Flag) {
69                return SkNEW_ARGS(SkGMBench, (gm.detach()));
70            }
71        }
72
73        return NULL;
74    }
75
76private:
77    const BenchRegistry* fBenches;
78    const skiagm::GMRegistry* fGMs;
79};
80
81class AutoPrePostDraw {
82public:
83    AutoPrePostDraw(SkBenchmark* bench) : fBench(bench) {
84        fBench->preDraw();
85    }
86    ~AutoPrePostDraw() {
87        fBench->postDraw();
88    }
89private:
90    SkBenchmark* fBench;
91};
92
93static void make_filename(const char name[], SkString* path) {
94    path->set(name);
95    for (int i = 0; name[i]; i++) {
96        switch (name[i]) {
97            case '/':
98            case '\\':
99            case ' ':
100            case ':':
101                path->writable_str()[i] = '-';
102                break;
103            default:
104                break;
105        }
106    }
107}
108
109static void saveFile(const char name[], const char config[], const char dir[],
110                     const SkImage* image) {
111    SkAutoTUnref<SkData> data(image->encode(SkImageEncoder::kPNG_Type, 100));
112    if (NULL == data.get()) {
113        return;
114    }
115
116    SkString filename;
117    make_filename(name, &filename);
118    filename.appendf("_%s.png", config);
119    SkString path = SkOSPath::SkPathJoin(dir, filename.c_str());
120    ::remove(path.c_str());
121
122    SkFILEWStream   stream(path.c_str());
123    stream.write(data->data(), data->size());
124}
125
126static void perform_clip(SkCanvas* canvas, int w, int h) {
127    SkRect r;
128
129    r.set(SkIntToScalar(10), SkIntToScalar(10),
130          SkIntToScalar(w*2/3), SkIntToScalar(h*2/3));
131    canvas->clipRect(r, SkRegion::kIntersect_Op);
132
133    r.set(SkIntToScalar(w/3), SkIntToScalar(h/3),
134          SkIntToScalar(w-10), SkIntToScalar(h-10));
135    canvas->clipRect(r, SkRegion::kXOR_Op);
136}
137
138static void perform_rotate(SkCanvas* canvas, int w, int h) {
139    const SkScalar x = SkIntToScalar(w) / 2;
140    const SkScalar y = SkIntToScalar(h) / 2;
141
142    canvas->translate(x, y);
143    canvas->rotate(SkIntToScalar(35));
144    canvas->translate(-x, -y);
145}
146
147static void perform_scale(SkCanvas* canvas, int w, int h) {
148    const SkScalar x = SkIntToScalar(w) / 2;
149    const SkScalar y = SkIntToScalar(h) / 2;
150
151    canvas->translate(x, y);
152    // just enough so we can't take the sprite case
153    canvas->scale(SK_Scalar1 * 99/100, SK_Scalar1 * 99/100);
154    canvas->translate(-x, -y);
155}
156
157static SkSurface* make_surface(SkColorType colorType, const SkIPoint& size,
158                               SkBenchmark::Backend backend, int sampleCount,
159                               GrContext* context) {
160    SkSurface* surface = NULL;
161    SkImageInfo info = SkImageInfo::Make(size.fX, size.fY, colorType,
162                                         kPremul_SkAlphaType);
163
164    switch (backend) {
165        case SkBenchmark::kRaster_Backend:
166            surface = SkSurface::NewRaster(info);
167            surface->getCanvas()->clear(SK_ColorWHITE);
168            break;
169#if SK_SUPPORT_GPU
170        case SkBenchmark::kGPU_Backend: {
171            surface = SkSurface::NewRenderTarget(context, info, sampleCount);
172            break;
173        }
174#endif
175        case SkBenchmark::kPDF_Backend:
176        default:
177            SkDEBUGFAIL("unsupported");
178    }
179    return surface;
180}
181
182#if SK_SUPPORT_GPU
183GrContextFactory gContextFactory;
184typedef GrContextFactory::GLContextType GLContextType;
185static const GLContextType kNative = GrContextFactory::kNative_GLContextType;
186static const GLContextType kNVPR   = GrContextFactory::kNVPR_GLContextType;
187#if SK_ANGLE
188static const GLContextType kANGLE  = GrContextFactory::kANGLE_GLContextType;
189#endif
190static const GLContextType kDebug  = GrContextFactory::kDebug_GLContextType;
191static const GLContextType kNull   = GrContextFactory::kNull_GLContextType;
192#else
193typedef int GLContextType;
194static const GLContextType kNative = 0, kANGLE = 0, kDebug = 0, kNull = 0;
195#endif
196
197#ifdef SK_DEBUG
198static const bool kIsDebug = true;
199#else
200static const bool kIsDebug = false;
201#endif
202
203static const struct Config {
204    SkColorType         fColorType;
205    const char*         name;
206    int                 sampleCount;
207    SkBenchmark::Backend backend;
208    GLContextType       contextType;
209    bool                runByDefault;
210} gConfigs[] = {
211    { kN32_SkColorType,     "NONRENDERING", 0, SkBenchmark::kNonRendering_Backend, kNative, true},
212    { kN32_SkColorType,     "8888",         0, SkBenchmark::kRaster_Backend,       kNative, true},
213    { kRGB_565_SkColorType, "565",          0, SkBenchmark::kRaster_Backend,       kNative, true},
214#if SK_SUPPORT_GPU
215    { kN32_SkColorType,     "GPU",          0, SkBenchmark::kGPU_Backend,          kNative, true},
216    { kN32_SkColorType,     "MSAA4",        4, SkBenchmark::kGPU_Backend,          kNative, false},
217    { kN32_SkColorType,     "MSAA16",      16, SkBenchmark::kGPU_Backend,          kNative, false},
218    { kN32_SkColorType,     "NVPRMSAA4",    4, SkBenchmark::kGPU_Backend,          kNVPR,   true},
219    { kN32_SkColorType,     "NVPRMSAA16",  16, SkBenchmark::kGPU_Backend,          kNVPR,   false},
220#if SK_ANGLE
221    { kN32_SkColorType,     "ANGLE",        0, SkBenchmark::kGPU_Backend,          kANGLE,  true},
222#endif // SK_ANGLE
223    { kN32_SkColorType,     "Debug",        0, SkBenchmark::kGPU_Backend,          kDebug,  kIsDebug},
224    { kN32_SkColorType,     "NULLGPU",      0, SkBenchmark::kGPU_Backend,          kNull,   true},
225#endif // SK_SUPPORT_GPU
226};
227
228DEFINE_string(outDir, "", "If given, image of each bench will be put in outDir.");
229DEFINE_string(timers, "cg", "Timers to display. "
230              "Options: w(all) W(all, truncated) c(pu) C(pu, truncated) g(pu)");
231
232DEFINE_bool(rotate, false,  "Rotate canvas before bench run?");
233DEFINE_bool(scale,  false,  "Scale canvas before bench run?");
234DEFINE_bool(clip,   false,  "Clip canvas before bench run?");
235
236DEFINE_bool(forceAA,        true,     "Force anti-aliasing?");
237DEFINE_bool(forceFilter,    false,    "Force bitmap filtering?");
238DEFINE_string(forceDither, "default", "Force dithering: true, false, or default?");
239DEFINE_bool(forceBlend,     false,    "Force alpha blending?");
240
241DEFINE_int32(gpuCacheBytes, -1, "GPU cache size limit in bytes.  0 to disable cache.");
242DEFINE_int32(gpuCacheCount, -1, "GPU cache size limit in object count.  0 to disable cache.");
243
244DEFINE_bool2(leaks, l, false, "show leaked ref cnt'd objects.");
245DEFINE_string(match, "",  "[~][^]substring[$] [...] of test name to run.\n"
246                          "Multiple matches may be separated by spaces.\n"
247                          "~ causes a matching test to always be skipped\n"
248                          "^ requires the start of the test to match\n"
249                          "$ requires the end of the test to match\n"
250                          "^ and $ requires an exact match\n"
251                          "If a test does not match any list entry,\n"
252                          "it is skipped unless some list entry starts with ~\n");
253DEFINE_string(mode, "normal",
254             "normal:         draw to a normal canvas;\n"
255             "deferred:       draw to a deferred canvas;\n"
256             "deferredSilent: deferred with silent playback;\n"
257             "record:         draw to an SkPicture;\n"
258             "picturerecord:  draw from an SkPicture to an SkPicture.\n");
259DEFINE_string(config, kDefaultsConfigStr,
260              "Run configs given.  By default, runs the configs marked \"runByDefault\" in gConfigs.");
261DEFINE_string(logFile, "", "Also write stdout here.");
262DEFINE_int32(minMs, 20,  "Shortest time we'll allow a benchmark to run.");
263DEFINE_int32(maxMs, 4000, "Longest time we'll allow a benchmark to run.");
264DEFINE_bool(runOnce, kIsDebug, "Run each bench exactly once and don't report timings.");
265DEFINE_double(error, 0.01,
266              "Ratio of subsequent bench measurements must drop within 1±error to converge.");
267DEFINE_string(timeFormat, "%9.2f", "Format to print results, in milliseconds per 1000 loops.");
268DEFINE_bool2(verbose, v, false, "Print more.");
269DEFINE_string2(resourcePath, i, "resources", "directory for test resources.");
270DEFINE_string(outResultsFile, "", "If given, the results will be written to the file in JSON format.");
271
272DEFINE_bool(dryRun, false, "Don't actually run the tests, just print what would have been done.");
273
274// Has this bench converged?  First arguments are milliseconds / loop iteration,
275// last is overall runtime in milliseconds.
276static bool HasConverged(double prevPerLoop, double currPerLoop, double currRaw) {
277    if (currRaw < FLAGS_minMs) {
278        return false;
279    }
280    const double low = 1 - FLAGS_error, high = 1 + FLAGS_error;
281    const double ratio = currPerLoop / prevPerLoop;
282    return low < ratio && ratio < high;
283}
284
285int tool_main(int argc, char** argv);
286int tool_main(int argc, char** argv) {
287    SkCommandLineFlags::Parse(argc, argv);
288#if SK_ENABLE_INST_COUNT
289    if (FLAGS_leaks) {
290        gPrintInstCount = true;
291    }
292#endif
293    SkAutoGraphics ag;
294
295    // First, parse some flags.
296    SkBenchLogger logger;
297    if (FLAGS_logFile.count()) {
298        logger.SetLogFile(FLAGS_logFile[0]);
299    }
300
301    LoggerResultsWriter logWriter(logger, FLAGS_timeFormat[0]);
302    MultiResultsWriter writer;
303    writer.add(&logWriter);
304    SkAutoTDelete<JSONResultsWriter> jsonWriter;
305    if (FLAGS_outResultsFile.count()) {
306        jsonWriter.reset(SkNEW(JSONResultsWriter(FLAGS_outResultsFile[0])));
307        writer.add(jsonWriter.get());
308    }
309    // Instantiate after all the writers have been added to writer so that we
310    // call close() before their destructors are called on the way out.
311    CallEnd<MultiResultsWriter> ender(writer);
312
313    const uint8_t alpha = FLAGS_forceBlend ? 0x80 : 0xFF;
314    SkTriState::State dither = SkTriState::kDefault;
315    for (size_t i = 0; i < 3; i++) {
316        if (strcmp(SkTriState::Name[i], FLAGS_forceDither[0]) == 0) {
317            dither = static_cast<SkTriState::State>(i);
318        }
319    }
320
321    BenchMode benchMode = kNormal_BenchMode;
322    for (size_t i = 0; i < SK_ARRAY_COUNT(BenchMode_Name); i++) {
323        if (strcmp(FLAGS_mode[0], BenchMode_Name[i]) == 0) {
324            benchMode = static_cast<BenchMode>(i);
325        }
326    }
327
328    SkTDArray<int> configs;
329    bool runDefaultConfigs = false;
330    // Try user-given configs first.
331    for (int i = 0; i < FLAGS_config.count(); i++) {
332        for (int j = 0; j < static_cast<int>(SK_ARRAY_COUNT(gConfigs)); ++j) {
333            if (0 == strcmp(FLAGS_config[i], gConfigs[j].name)) {
334                *configs.append() = j;
335            } else if (0 == strcmp(FLAGS_config[i], kDefaultsConfigStr)) {
336                runDefaultConfigs = true;
337            }
338        }
339    }
340    // If there weren't any, fill in with defaults.
341    if (runDefaultConfigs) {
342        for (int i = 0; i < static_cast<int>(SK_ARRAY_COUNT(gConfigs)); ++i) {
343            if (gConfigs[i].runByDefault) {
344                *configs.append() = i;
345            }
346        }
347    }
348    // Filter out things we can't run.
349    if (kNormal_BenchMode != benchMode) {
350        // Non-rendering configs only run in normal mode
351        for (int i = 0; i < configs.count(); ++i) {
352            const Config& config = gConfigs[configs[i]];
353            if (SkBenchmark::kNonRendering_Backend == config.backend) {
354                configs.remove(i, 1);
355                --i;
356            }
357        }
358    }
359    // Set the resource path.
360    if (!FLAGS_resourcePath.isEmpty()) {
361        SkBenchmark::SetResourcePath(FLAGS_resourcePath[0]);
362    }
363
364#if SK_SUPPORT_GPU
365    for (int i = 0; i < configs.count(); ++i) {
366        const Config& config = gConfigs[configs[i]];
367
368        if (SkBenchmark::kGPU_Backend == config.backend) {
369            GrContext* context = gContextFactory.get(config.contextType);
370            if (NULL == context) {
371                SkDebugf("GrContext could not be created for config %s. Config will be skipped.\n",
372                    config.name);
373                configs.remove(i);
374                --i;
375                continue;
376            }
377            if (config.sampleCount > context->getMaxSampleCount()){
378                SkDebugf(
379                    "Sample count (%d) for config %s is not supported. Config will be skipped.\n",
380                    config.sampleCount, config.name);
381                configs.remove(i);
382                --i;
383                continue;
384            }
385        }
386    }
387#endif
388
389    // All flags should be parsed now.  Report our settings.
390    if (FLAGS_runOnce) {
391        logger.logError("bench was run with --runOnce, so we're going to hide the times."
392                        " It's for your own good!\n");
393    }
394    writer.option("mode", FLAGS_mode[0]);
395    writer.option("alpha", SkStringPrintf("0x%02X", alpha).c_str());
396    writer.option("antialias", SkStringPrintf("%d", FLAGS_forceAA).c_str());
397    writer.option("filter", SkStringPrintf("%d", FLAGS_forceFilter).c_str());
398    writer.option("dither",  SkTriState::Name[dither]);
399
400    writer.option("rotate", SkStringPrintf("%d", FLAGS_rotate).c_str());
401    writer.option("scale", SkStringPrintf("%d", FLAGS_scale).c_str());
402    writer.option("clip", SkStringPrintf("%d", FLAGS_clip).c_str());
403
404#if defined(SK_BUILD_FOR_WIN32)
405    writer.option("system", "WIN32");
406#elif defined(SK_BUILD_FOR_MAC)
407    writer.option("system", "MAC");
408#elif defined(SK_BUILD_FOR_ANDROID)
409    writer.option("system", "ANDROID");
410#elif defined(SK_BUILD_FOR_UNIX)
411    writer.option("system", "UNIX");
412#else
413    writer.option("system", "other");
414#endif
415
416#if defined(SK_DEBUG)
417    writer.option("build", "DEBUG");
418#else
419    writer.option("build", "RELEASE");
420#endif
421
422    // Set texture cache limits if non-default.
423    for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); ++i) {
424#if SK_SUPPORT_GPU
425        const Config& config = gConfigs[i];
426        if (SkBenchmark::kGPU_Backend != config.backend) {
427            continue;
428        }
429        GrContext* context = gContextFactory.get(config.contextType);
430        if (NULL == context) {
431            continue;
432        }
433
434        size_t bytes;
435        int count;
436        context->getResourceCacheLimits(&count, &bytes);
437        if (-1 != FLAGS_gpuCacheBytes) {
438            bytes = static_cast<size_t>(FLAGS_gpuCacheBytes);
439        }
440        if (-1 != FLAGS_gpuCacheCount) {
441            count = FLAGS_gpuCacheCount;
442        }
443        context->setResourceCacheLimits(count, bytes);
444#endif
445    }
446
447    // Run each bench in each configuration it supports and we asked for.
448    Iter iter;
449    SkBenchmark* bench;
450    while ((bench = iter.next()) != NULL) {
451        SkAutoTUnref<SkBenchmark> benchUnref(bench);
452        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getName())) {
453            continue;
454        }
455
456        bench->setForceAlpha(alpha);
457        bench->setForceAA(FLAGS_forceAA);
458        bench->setForceFilter(FLAGS_forceFilter);
459        bench->setDither(dither);
460        AutoPrePostDraw appd(bench);
461
462        bool loggedBenchName = false;
463        for (int i = 0; i < configs.count(); ++i) {
464            const int configIndex = configs[i];
465            const Config& config = gConfigs[configIndex];
466
467            if (!bench->isSuitableFor(config.backend)) {
468                continue;
469            }
470
471            GrContext* context = NULL;
472#if SK_SUPPORT_GPU
473            SkGLContextHelper* glContext = NULL;
474            if (SkBenchmark::kGPU_Backend == config.backend) {
475                context = gContextFactory.get(config.contextType);
476                if (NULL == context) {
477                    continue;
478                }
479                glContext = gContextFactory.getGLContext(config.contextType);
480            }
481#endif
482
483            SkAutoTUnref<SkCanvas> canvas;
484            SkAutoTUnref<SkPicture> recordFrom;
485            SkPictureRecorder recorderTo;
486            const SkIPoint dim = bench->getSize();
487
488            const SkPicture::RecordingFlags kRecordFlags =
489                SkPicture::kUsePathBoundsForClip_RecordingFlag;
490
491            SkAutoTUnref<SkSurface> surface;
492            if (SkBenchmark::kNonRendering_Backend != config.backend) {
493                surface.reset(make_surface(config.fColorType,
494                                           dim,
495                                           config.backend,
496                                           config.sampleCount,
497                                           context));
498                if (!surface.get()) {
499                    logger.logError(SkStringPrintf(
500                        "Device creation failure for config %s. Will skip.\n", config.name));
501                    continue;
502                }
503
504                switch(benchMode) {
505                    case kDeferredSilent_BenchMode:
506                    case kDeferred_BenchMode:
507                        canvas.reset(SkDeferredCanvas::Create(surface.get()));
508                        break;
509                    case kRecord_BenchMode:
510                        canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY,
511                                                                     NULL, kRecordFlags)));
512                        break;
513                    case kPictureRecord_BenchMode: {
514                        SkPictureRecorder recorderFrom;
515                        bench->draw(1, recorderFrom.beginRecording(dim.fX, dim.fY,
516                                                                   NULL, kRecordFlags));
517                        recordFrom.reset(recorderFrom.endRecording());
518                        canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY,
519                                                                     NULL, kRecordFlags)));
520                        break;
521                    }
522                    case kNormal_BenchMode:
523                        canvas.reset(SkRef(surface->getCanvas()));
524                        break;
525                    default:
526                        SkASSERT(false);
527                }
528            }
529
530            if (NULL != canvas) {
531                canvas->clear(SK_ColorWHITE);
532                if (FLAGS_clip)   {
533                    perform_clip(canvas, dim.fX, dim.fY);
534                }
535                if (FLAGS_scale)  {
536                    perform_scale(canvas, dim.fX, dim.fY);
537                }
538                if (FLAGS_rotate) {
539                    perform_rotate(canvas, dim.fX, dim.fY);
540                }
541            }
542
543            if (!loggedBenchName) {
544                loggedBenchName = true;
545                writer.bench(bench->getName(), dim.fX, dim.fY);
546            }
547
548#if SK_SUPPORT_GPU
549            SkGLContextHelper* contextHelper = NULL;
550            if (SkBenchmark::kGPU_Backend == config.backend) {
551                contextHelper = gContextFactory.getGLContext(config.contextType);
552            }
553            BenchTimer timer(contextHelper);
554#else
555            BenchTimer timer;
556#endif
557
558            double previous = std::numeric_limits<double>::infinity();
559            bool converged = false;
560
561            // variables used to compute loopsPerFrame
562            double frameIntervalTime = 0.0f;
563            int frameIntervalTotalLoops = 0;
564
565            bool frameIntervalComputed = false;
566            int loopsPerFrame = 0;
567            int loopsPerIter = 0;
568            if (FLAGS_verbose) { SkDebugf("%s %s: ", bench->getName(), config.name); }
569            if (!FLAGS_dryRun) {
570                do {
571                    // Ramp up 1 -> 2 -> 4 -> 8 -> 16 -> ... -> ~1 billion.
572                    loopsPerIter = (loopsPerIter == 0) ? 1 : loopsPerIter * 2;
573                    if (loopsPerIter >= (1<<30) || timer.fWall > FLAGS_maxMs) {
574                        // If you find it takes more than a billion loops to get up to 20ms of runtime,
575                        // you've got a computer clocked at several THz or have a broken benchmark.  ;)
576                        //     "1B ought to be enough for anybody."
577                        logger.logError(SkStringPrintf(
578                            "\nCan't get %s %s to converge in %dms (%d loops)",
579                             bench->getName(), config.name, FLAGS_maxMs, loopsPerIter));
580                        break;
581                    }
582
583                    if ((benchMode == kRecord_BenchMode || benchMode == kPictureRecord_BenchMode)) {
584                        // Clear the recorded commands so that they do not accumulate.
585                        canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY,
586                                                                     NULL, kRecordFlags)));
587                    }
588
589                    timer.start();
590                    // Inner loop that allows us to break the run into smaller
591                    // chunks (e.g. frames). This is especially useful for the GPU
592                    // as we can flush and/or swap buffers to keep the GPU from
593                    // queuing up too much work.
594                    for (int loopCount = loopsPerIter; loopCount > 0; ) {
595                        // Save and restore around each call to draw() to guarantee a pristine canvas.
596                        SkAutoCanvasRestore saveRestore(canvas, true/*also save*/);
597
598                        int loops;
599                        if (frameIntervalComputed && loopCount > loopsPerFrame) {
600                            loops = loopsPerFrame;
601                            loopCount -= loopsPerFrame;
602                        } else {
603                            loops = loopCount;
604                            loopCount = 0;
605                        }
606
607                        if (benchMode == kPictureRecord_BenchMode) {
608                            recordFrom->draw(canvas);
609                        } else {
610                            bench->draw(loops, canvas);
611                        }
612
613                        if (kDeferredSilent_BenchMode == benchMode) {
614                            static_cast<SkDeferredCanvas*>(canvas.get())->silentFlush();
615                        } else if (NULL != canvas) {
616                            canvas->flush();
617                        }
618
619    #if SK_SUPPORT_GPU
620                        // swap drawing buffers on each frame to prevent the GPU
621                        // from queuing up too much work
622                        if (NULL != glContext) {
623                            glContext->swapBuffers();
624                        }
625    #endif
626                    }
627
628
629
630                    // Stop truncated timers before GL calls complete, and stop the full timers after.
631                    timer.truncatedEnd();
632    #if SK_SUPPORT_GPU
633                    if (NULL != glContext) {
634                        context->flush();
635                        SK_GL(*glContext, Finish());
636                    }
637    #endif
638                    timer.end();
639
640                    // setup the frame interval for subsequent iterations
641                    if (!frameIntervalComputed) {
642                        frameIntervalTime += timer.fWall;
643                        frameIntervalTotalLoops += loopsPerIter;
644                        if (frameIntervalTime >= FLAGS_minMs) {
645                            frameIntervalComputed = true;
646                            loopsPerFrame =
647                              (int)(((double)frameIntervalTotalLoops / frameIntervalTime) * FLAGS_minMs);
648                            if (loopsPerFrame < 1) {
649                                loopsPerFrame = 1;
650                            }
651    //                        SkDebugf("  %s has %d loops in %f ms (normalized to %d)\n",
652    //                                 bench->getName(), frameIntervalTotalLoops,
653    //                                 timer.fWall, loopsPerFrame);
654                        }
655                    }
656
657                    const double current = timer.fWall / loopsPerIter;
658                    if (FLAGS_verbose && current > previous) { SkDebugf("↑"); }
659                    if (FLAGS_verbose) { SkDebugf("%.3g ", current); }
660                    converged = HasConverged(previous, current, timer.fWall);
661                    previous = current;
662                } while (!FLAGS_runOnce && !converged);
663            }
664            if (FLAGS_verbose) { SkDebugf("\n"); }
665
666            if (!FLAGS_dryRun && FLAGS_outDir.count() && SkBenchmark::kNonRendering_Backend != config.backend) {
667                SkAutoTUnref<SkImage> image(surface->newImageSnapshot());
668                if (image.get()) {
669                    saveFile(bench->getName(), config.name, FLAGS_outDir[0],
670                             image);
671                }
672            }
673
674            if (FLAGS_runOnce) {
675                // Let's not mislead ourselves by looking at Debug build or single iteration bench times!
676                continue;
677            }
678
679            // Normalize to ms per 1000 iterations.
680            const double normalize = 1000.0 / loopsPerIter;
681            const struct { char shortName; const char* longName; double ms; } times[] = {
682                {'w', "msecs",  normalize * timer.fWall},
683                {'W', "Wmsecs", normalize * timer.fTruncatedWall},
684                {'c', "cmsecs", normalize * timer.fCpu},
685                {'C', "Cmsecs", normalize * timer.fTruncatedCpu},
686                {'g', "gmsecs", normalize * timer.fGpu},
687            };
688
689            writer.config(config.name);
690            for (size_t i = 0; i < SK_ARRAY_COUNT(times); i++) {
691                if (strchr(FLAGS_timers[0], times[i].shortName) && times[i].ms > 0) {
692                    writer.timer(times[i].longName, times[i].ms);
693                }
694            }
695        }
696    }
697#if SK_SUPPORT_GPU
698    gContextFactory.destroyContexts();
699#endif
700    return 0;
701}
702
703#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
704int main(int argc, char * const argv[]) {
705    return tool_main(argc, (char**) argv);
706}
707#endif
708