bbh_shootout.cpp revision 890b826fa381643969a65792339f5274f75d5e9a
1/*
2 * Copyright 2013 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 "LazyDecodeBitmap.h"
10#include "PictureBenchmark.h"
11#include "PictureRenderer.h"
12#include "SkBenchmark.h"
13#include "SkForceLinking.h"
14#include "SkGraphics.h"
15#include "SkStream.h"
16#include "SkString.h"
17#include "SkTArray.h"
18#include "TimerData.h"
19
20static const int kNumNormalRecordings = 10;
21static const int kNumRTreeRecordings = 10;
22static const int kNumPlaybacks = 1;
23static const size_t kNumBaseBenchmarks = 3;
24static const size_t kNumTileSizes = 3;
25static const size_t kNumBbhPlaybackBenchmarks = 3;
26static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
27
28enum BenchmarkType {
29    kNormal_BenchmarkType = 0,
30    kRTree_BenchmarkType,
31};
32
33struct Histogram {
34    Histogram() {
35        // Make fCpuTime negative so that we don't mess with stats:
36        fCpuTime = SkIntToScalar(-1);
37    }
38    SkScalar fCpuTime;
39    SkString fPath;
40};
41
42
43////////////////////////////////////////////////////////////////////////////////
44// Defined below.
45struct BenchmarkControl;
46
47typedef void (*BenchmarkFunction)
48    (const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
49
50static void benchmark_playback(
51    const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
52static void benchmark_recording(
53    const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
54////////////////////////////////////////////////////////////////////////////////
55
56/**
57 * Acts as a POD containing information needed to run a benchmark.
58 * Provides static methods to poll benchmark info from an index.
59 */
60struct BenchmarkControl {
61    SkISize fTileSize;
62    BenchmarkType fType;
63    BenchmarkFunction fFunction;
64    SkString fName;
65
66    /**
67     * Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
68     */
69    static BenchmarkControl Make(size_t i) {
70        SkASSERT(kNumBenchmarks > i);
71        BenchmarkControl benchControl;
72        benchControl.fTileSize = GetTileSize(i);
73        benchControl.fType = GetBenchmarkType(i);
74        benchControl.fFunction = GetBenchmarkFunc(i);
75        benchControl.fName = GetBenchmarkName(i);
76        return benchControl;
77    }
78
79    enum BaseBenchmarks {
80        kNormalRecord = 0,
81        kRTreeRecord,
82        kNormalPlayback,
83    };
84
85    static SkISize fTileSizes[kNumTileSizes];
86
87    static SkISize GetTileSize(size_t i) {
88        // Two of the base benchmarks don't need a tile size. But to maintain simplicity
89        // down the pipeline we have to let a couple of values unused.
90        if (i < kNumBaseBenchmarks) {
91            return SkISize::Make(256, 256);
92        }
93        if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
94            return fTileSizes[i - kNumBaseBenchmarks];
95        }
96        SkASSERT(0);
97        return SkISize::Make(0, 0);
98    }
99
100    static BenchmarkType GetBenchmarkType(size_t i) {
101        if (i < kNumBaseBenchmarks) {
102            switch (i) {
103            case kNormalRecord:
104                return kNormal_BenchmarkType;
105            case kNormalPlayback:
106                return kNormal_BenchmarkType;
107            case kRTreeRecord:
108                return kRTree_BenchmarkType;
109            }
110        }
111        if (i < kNumBenchmarks) {
112            return kRTree_BenchmarkType;
113        }
114        SkASSERT(0);
115        return kRTree_BenchmarkType;
116    }
117
118    static BenchmarkFunction GetBenchmarkFunc(size_t i) {
119        // Base functions.
120        switch (i) {
121            case kNormalRecord:
122                return benchmark_recording;
123            case kNormalPlayback:
124                return benchmark_playback;
125            case kRTreeRecord:
126                return benchmark_recording;
127        }
128        // RTree playbacks
129        if (i < kNumBenchmarks) {
130            return benchmark_playback;
131        }
132        SkASSERT(0);
133        return NULL;
134    }
135
136    static SkString GetBenchmarkName(size_t i) {
137        // Base benchmark names
138        switch (i) {
139            case kNormalRecord:
140                return SkString("normal_recording");
141            case kNormalPlayback:
142                return SkString("normal_playback");
143            case kRTreeRecord:
144                return SkString("rtree_recording");
145        }
146        // RTree benchmark names.
147        if (i < kNumBenchmarks) {
148            SkASSERT(i >= kNumBaseBenchmarks);
149            SkString name;
150            name.printf("rtree_playback_%dx%d",
151                    fTileSizes[i - kNumBaseBenchmarks].fWidth,
152                    fTileSizes[i - kNumBaseBenchmarks].fHeight);
153            return name;
154
155        } else {
156            SkASSERT(0);
157        }
158        return SkString("");
159    }
160
161};
162
163SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
164    SkISize::Make(256, 256),
165    SkISize::Make(512, 512),
166    SkISize::Make(1024, 1024),
167};
168
169static SkPicture* pic_from_path(const char path[]) {
170    SkFILEStream stream(path);
171    if (!stream.isValid()) {
172        SkDebugf("-- Can't open '%s'\n", path);
173        return NULL;
174    }
175    return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
176}
177
178/**
179 * Returns true when a tiled renderer with no bounding box hierarchy produces the given bitmap.
180 */
181static bool compare_picture(const SkString& path, const SkBitmap& inBitmap, SkPicture* pic) {
182    SkBitmap* bitmap;
183    sk_tools::TiledPictureRenderer renderer;
184    renderer.setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
185    renderer.init(pic);
186    renderer.setup();
187    renderer.render(&path, &bitmap);
188    SkAutoTDelete<SkBitmap> bmDeleter(bitmap);
189    renderer.end();
190
191    if (bitmap->getSize() != inBitmap.getSize()) {
192        return false;
193    }
194    return !memcmp(bitmap->getPixels(), inBitmap.getPixels(), bitmap->getSize());
195}
196
197/**
198 * This function is the sink to which all work ends up going.
199 * Renders the picture into the renderer. It may or may not use an RTree.
200 * The renderer is chosen upstream. If we want to measure recording, we will
201 * use a RecordPictureRenderer. If we want to measure rendering, we will use a
202 * TiledPictureRenderer.
203 */
204static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
205        int benchmarkType, const SkString& path, SkPicture* pic,
206        const int numRepeats, const char *msg, BenchTimer* timer) {
207    SkString msgPrefix;
208
209    switch (benchmarkType){
210        case kNormal_BenchmarkType:
211            msgPrefix.set("Normal");
212            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
213            break;
214        case kRTree_BenchmarkType:
215            msgPrefix.set("RTree");
216            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
217            break;
218        default:
219            SkASSERT(0);
220            break;
221    }
222
223    renderer->init(pic);
224
225    /**
226     * If the renderer is not tiled, assume we are measuring recording.
227     */
228    bool isPlayback = (NULL != renderer->getTiledRenderer());
229    // Will be non-null during RTree picture playback. For correctness test.
230    SkBitmap* bitmap = NULL;
231
232    SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
233    for (int i = 0; i < numRepeats; ++i) {
234        // Set up the bitmap.
235        SkBitmap** out = NULL;
236        if (i == 0 && kRTree_BenchmarkType == benchmarkType && isPlayback) {
237            out = &bitmap;
238        }
239
240        renderer->setup();
241        // Render once to fill caches. Fill bitmap during the first iteration.
242        renderer->render(NULL, out);
243        // Render again to measure
244        timer->start();
245        bool result = renderer->render(NULL);
246        timer->end();
247
248        // We only care about a false result on playback. RecordPictureRenderer::render will always
249        // return false because we are passing a NULL file name on purpose; which is fine.
250        if (isPlayback && !result) {
251            SkDebugf("Error rendering during playback.\n");
252        }
253    }
254    if (bitmap) {
255        SkAutoTDelete<SkBitmap> bmDeleter(bitmap);
256        if (!compare_picture(path, *bitmap, pic)) {
257            SkDebugf("Error: RTree produced different bitmap\n");
258        }
259    }
260}
261
262/**
263 * Call do_benchmark_work with a tiled renderer using the default tile dimensions.
264 */
265static void benchmark_playback(
266        const BenchmarkControl& benchControl,
267        const SkString& path, SkPicture* pic, BenchTimer* timer) {
268    sk_tools::TiledPictureRenderer renderer;
269
270    SkString message("tiled_playback");
271    message.appendf("_%dx%d", benchControl.fTileSize.fWidth, benchControl.fTileSize.fHeight);
272    do_benchmark_work(&renderer, benchControl.fType,
273            path, pic, kNumPlaybacks, message.c_str(), timer);
274}
275
276/**
277 * Call do_benchmark_work with a RecordPictureRenderer.
278 */
279static void benchmark_recording(
280        const BenchmarkControl& benchControl,
281        const SkString& path, SkPicture* pic, BenchTimer* timer) {
282    sk_tools::RecordPictureRenderer renderer;
283    int numRecordings = 0;
284    switch(benchControl.fType) {
285        case kRTree_BenchmarkType:
286            numRecordings = kNumRTreeRecordings;
287            break;
288        case kNormal_BenchmarkType:
289            numRecordings = kNumNormalRecordings;
290            break;
291    }
292    do_benchmark_work(&renderer, benchControl.fType,
293            path, pic, numRecordings, "recording", timer);
294}
295
296/**
297 * Takes argc,argv along with one of the benchmark functions defined above.
298 * Will loop along all skp files and perform measurments.
299 */
300static void benchmark_loop(
301        int argc,
302        char **argv,
303        const BenchmarkControl& benchControl,
304        SkTArray<Histogram>& histogram) {
305    for (int index = 1; index < argc; ++index) {
306        BenchTimer timer;
307        SkString path(argv[index]);
308        SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str()));
309        if (NULL == pic) {
310            SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str());
311            continue;
312        }
313        benchControl.fFunction(benchControl, path, pic, &timer);
314        histogram[index - 1].fPath = path;
315        histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu);
316    }
317}
318
319int tool_main(int argc, char** argv);
320int tool_main(int argc, char** argv) {
321    SkAutoGraphics ag;
322    SkString usage;
323    usage.printf("Usage: filename [filename]*\n");
324
325    if (argc < 2) {
326        SkDebugf("%s\n", usage.c_str());
327        return -1;
328    }
329
330    SkTArray<Histogram> histograms[kNumBenchmarks];
331
332    for (size_t i = 0; i < kNumBenchmarks; ++i) {
333        histograms[i].reset(argc - 1);
334        benchmark_loop(argc, argv, BenchmarkControl::Make(i), histograms[i]);
335    }
336
337    // Output gnuplot readable histogram data..
338    const char* pbTitle = "bbh_shootout_playback.dat";
339    const char* recTitle = "bbh_shootout_record.dat";
340    SkFILEWStream playbackOut(pbTitle);
341    SkFILEWStream recordOut(recTitle);
342    recordOut.writeText("# ");
343    playbackOut.writeText("# ");
344    for (size_t i = 0; i < kNumBenchmarks; ++i) {
345        SkString out;
346        out.printf("%s ", BenchmarkControl::GetBenchmarkName(i).c_str());
347        if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_recording) {
348            recordOut.writeText(out.c_str());
349        }
350        if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_playback) {
351            playbackOut.writeText(out.c_str());
352        }
353    }
354    recordOut.writeText("\n");
355    playbackOut.writeText("\n");
356    // Write to file, and save recording averages.
357    SkScalar avgRecord = SkIntToScalar(0);
358    SkScalar avgPlayback = SkIntToScalar(0);
359    SkScalar avgRecordRTree = SkIntToScalar(0);
360    SkScalar avgPlaybackRTree = SkIntToScalar(0);
361    for (int i = 0; i < argc - 1; ++i) {
362        SkString pbLine;
363        SkString recLine;
364        // ==== Write record info
365        recLine.printf("%d ", i);
366        SkScalar cpuTime = histograms[BenchmarkControl::kNormalRecord][i].fCpuTime;
367        recLine.appendf("%f ", cpuTime);
368        avgRecord += cpuTime;
369        cpuTime = histograms[BenchmarkControl::kRTreeRecord][i].fCpuTime;
370        recLine.appendf("%f", cpuTime);
371        avgRecordRTree += cpuTime;
372        avgPlaybackRTree += cpuTime;
373
374        // ==== Write playback info
375        pbLine.printf("%d ", i);
376        pbLine.appendf("%f ", histograms[2][i].fCpuTime);  // Start with normal playback time.
377        avgPlayback += histograms[kNumBbhPlaybackBenchmarks - 1][i].fCpuTime;
378        avgPlaybackRTree += histograms[kNumBbhPlaybackBenchmarks][i].fCpuTime;
379        // Append all playback benchmark times.
380        for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) {
381            pbLine.appendf("%f ", histograms[j][i].fCpuTime);
382        }
383        pbLine.remove(pbLine.size() - 1, 1);  // Remove trailing space from line.
384        pbLine.appendf("\n");
385        recLine.appendf("\n");
386        playbackOut.writeText(pbLine.c_str());
387        recordOut.writeText(recLine.c_str());
388    }
389    avgRecord /= argc - 1;
390    avgRecordRTree /= argc - 1;
391    avgPlayback /= argc - 1;
392    avgPlaybackRTree /= argc - 1;
393    SkDebugf("Average base recording time: %.3fms\n", avgRecord);
394    SkDebugf("Average recording time with rtree: %.3fms\n", avgRecordRTree);
395    SkDebugf("Average base playback time: %.3fms\n", avgPlayback);
396    SkDebugf("Average playback time with rtree: %.3fms\n", avgPlaybackRTree);
397
398    SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle);
399
400    return 0;
401}
402
403#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
404int main(int argc, char** argv) {
405    return tool_main(argc, argv);
406}
407#endif
408