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