1/*
2 * Copyright 2016 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 "Benchmark.h"
9#include "sk_tool_utils.h"
10#include "SkCanvas.h"
11#include "SkImage.h"
12#include "SkSurface.h"
13
14#if SK_SUPPORT_GPU
15
16#include "GrContext.h"
17
18/** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */
19
20//////////////////////////////////////////////////////////////////////////////
21
22// The width/height of the images to draw. The small size underestimates the value of a good
23// replacement strategy since the texture uploads are quite small. However, the effects are still
24// significant and this lets the benchmarks complete a lot faster, especially on mobile.
25static constexpr int kS = 25;
26
27static void make_images(sk_sp<SkImage> imgs[], int cnt) {
28    for (int i = 0; i < cnt; ++i) {
29        SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK,
30                                                                 SK_ColorCYAN, 10);
31        imgs[i] = SkImage::MakeFromBitmap(bmp);
32    }
33}
34
35static void draw_image(SkCanvas* canvas, SkImage* img) {
36    // Make the paint transparent to avoid any issues of deferred tiler blending
37    // optmizations
38    SkPaint paint;
39    paint.setAlpha(0x10);
40    canvas->drawImage(img, 0, 0, &paint);
41}
42
43void set_cache_budget(SkCanvas* canvas, int approxImagesInBudget) {
44    // This is inexact but we attempt to figure out a baseline number of resources GrContext needs
45    // to render an SkImage and add one additional resource for each image we'd like to fit.
46    GrContext* context =  canvas->getGrContext();
47    SkASSERT(context);
48    context->flush();
49    context->purgeAllUnlockedResources();
50    sk_sp<SkImage> image;
51    make_images(&image, 1);
52    draw_image(canvas, image.get());
53    context->flush();
54    int baselineCount;
55    context->getResourceCacheUsage(&baselineCount, nullptr);
56    baselineCount -= 1; // for the image's textures.
57    context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
58    context->purgeAllUnlockedResources();
59}
60
61//////////////////////////////////////////////////////////////////////////////
62
63/**
64 * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench
65 * run with different cache sizes and either repeat the image order each frame or use a random
66 * order. Every variation of this bench draws the same image set, only the budget and order of
67 * images differs. Since the total fill is the same they can be cross-compared.
68 */
69class ImageCacheBudgetBench : public Benchmark {
70public:
71    /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
72    ImageCacheBudgetBench(int budgetSize, bool shuffle)
73            : fBudgetSize(budgetSize)
74            , fShuffle(shuffle)
75            , fIndices(nullptr) {
76        float imagesOverBudget = float(kImagesToDraw) / budgetSize;
77        // Make the benchmark name contain the percentage of the budget that is used in each
78        // simulated frame.
79        fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100,
80                     (shuffle ? "_shuffle" : ""));
81    }
82
83    bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
84
85protected:
86    const char* onGetName() override {
87        return fName.c_str();
88    }
89
90    void onPerCanvasPreDraw(SkCanvas* canvas) override {
91        GrContext* context = canvas->getGrContext();
92        SkASSERT(context);
93        context->getResourceCacheLimits(&fOldCount, &fOldBytes);
94        set_cache_budget(canvas, fBudgetSize);
95        make_images(fImages, kImagesToDraw);
96        if (fShuffle) {
97            SkRandom random;
98            fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]);
99            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
100                int* base = fIndices.get() + frame * kImagesToDraw;
101                for (int i = 0; i < kImagesToDraw; ++i) {
102                    base[i] = i;
103                }
104                for (int i = 0; i < kImagesToDraw - 1; ++i) {
105                    int other = random.nextULessThan(kImagesToDraw - i) + i;
106                    SkTSwap(base[i], base[other]);
107                }
108            }
109        }
110    }
111
112    void onPerCanvasPostDraw(SkCanvas* canvas) override {
113        GrContext* context =  canvas->getGrContext();
114        SkASSERT(context);
115        context->setResourceCacheLimits(fOldCount, fOldBytes);
116        for (int i = 0; i < kImagesToDraw; ++i) {
117            fImages[i].reset();
118        }
119        fIndices.reset(nullptr);
120    }
121
122    void onDraw(int loops, SkCanvas* canvas) override {
123        for (int i = 0; i < loops; ++i) {
124            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
125                for (int j = 0; j < kImagesToDraw; ++j) {
126                    int idx;
127                    if (fShuffle) {
128                        idx = fIndices[frame * kImagesToDraw + j];
129                    } else {
130                        idx = j;
131                    }
132                    draw_image(canvas, fImages[idx].get());
133                }
134                // Simulate a frame boundary by flushing. This should notify GrResourceCache.
135                canvas->flush();
136           }
137        }
138    }
139
140private:
141    static constexpr int kImagesToDraw = 100;
142    static constexpr int kSimulatedFrames = 5;
143
144    int                         fBudgetSize;
145    bool                        fShuffle;
146    SkString                    fName;
147    sk_sp<SkImage>              fImages[kImagesToDraw];
148    std::unique_ptr<int[]>      fIndices;
149    size_t                      fOldBytes;
150    int                         fOldCount;
151
152    typedef Benchmark INHERITED;
153};
154
155DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
156
157DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
158
159DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
160
161DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
162
163DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
164
165DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
166
167DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
168
169DEF_BENCH( return new ImageCacheBudgetBench(50, true); )
170
171//////////////////////////////////////////////////////////////////////////////
172
173/**
174 * Similar to above but changes between being over and under budget by varying the number of images
175 * rendered. This is not directly comparable to the non-dynamic benchmarks.
176 */
177class ImageCacheBudgetDynamicBench : public Benchmark {
178public:
179    enum class Mode {
180        // Increase from min to max images drawn gradually over simulated frames and then back.
181        kPingPong,
182        // Alternate between under and over budget every other simulated frame.
183        kFlipFlop
184    };
185
186    ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
187
188    bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
189
190protected:
191    const char* onGetName() override {
192        switch (fMode) {
193            case Mode::kPingPong:
194                return "image_cache_budget_dynamic_ping_pong";
195            case Mode::kFlipFlop:
196                return "image_cache_budget_dynamic_flip_flop";
197        }
198        return "";
199    }
200
201    void onPerCanvasPreDraw(SkCanvas* canvas) override {
202        GrContext* context =  canvas->getGrContext();
203        SkASSERT(context);
204        context->getResourceCacheLimits(&fOldCount, &fOldBytes);
205        make_images(fImages, kMaxImagesToDraw);
206        set_cache_budget(canvas, kImagesInBudget);
207    }
208
209    void onPerCanvasPostDraw(SkCanvas* canvas) override {
210        GrContext* context =  canvas->getGrContext();
211        SkASSERT(context);
212        context->setResourceCacheLimits(fOldCount, fOldBytes);
213        for (int i = 0; i < kMaxImagesToDraw; ++i) {
214            fImages[i].reset();
215        }
216    }
217
218    void onDraw(int loops, SkCanvas* canvas) override {
219        int delta = 0;
220        switch (fMode) {
221            case Mode::kPingPong:
222                delta = 1;
223                break;
224            case Mode::kFlipFlop:
225                delta = kMaxImagesToDraw - kMinImagesToDraw;
226                break;
227        }
228        for (int i = 0; i < loops; ++i) {
229            int imgsToDraw = kMinImagesToDraw;
230            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
231                for (int j = 0; j < imgsToDraw; ++j) {
232                    draw_image(canvas, fImages[j].get());
233                }
234                imgsToDraw += delta;
235                if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
236                    delta = -delta;
237                    imgsToDraw += 2 * delta;
238                }
239                // Simulate a frame boundary by flushing. This should notify GrResourceCache.
240                canvas->flush();
241            }
242        }
243    }
244
245private:
246    static constexpr int kImagesInBudget  = 25;
247    static constexpr int kMinImagesToDraw = 15;
248    static constexpr int kMaxImagesToDraw = 35;
249    static constexpr int kSimulatedFrames = 80;
250
251    Mode                        fMode;
252    sk_sp<SkImage>              fImages[kMaxImagesToDraw];
253    size_t                      fOldBytes;
254    int                         fOldCount;
255
256    typedef Benchmark INHERITED;
257};
258
259DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
260DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
261
262#endif
263