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