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