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