aboutsummaryrefslogtreecommitdiffhomepage
path: root/bench/ImageCacheBudgetBench.cpp
blob: 29559450510e32ace80962320b7784d870437ff6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "Benchmark.h"
#include "sk_tool_utils.h"
#include "SkCanvas.h"
#include "SkImage.h"
#include "SkSurface.h"

#include "GrContext.h"
#include "GrContextPriv.h"

#include <utility>

/** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */

//////////////////////////////////////////////////////////////////////////////

// The width/height of the images to draw. The small size underestimates the value of a good
// replacement strategy since the texture uploads are quite small. However, the effects are still
// significant and this lets the benchmarks complete a lot faster, especially on mobile.
static constexpr int kS = 25;

static void make_images(sk_sp<SkImage> imgs[], int cnt) {
    for (int i = 0; i < cnt; ++i) {
        SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK,
                                                                 SK_ColorCYAN, 10);
        imgs[i] = SkImage::MakeFromBitmap(bmp);
    }
}

static void draw_image(SkCanvas* canvas, SkImage* img) {
    // Make the paint transparent to avoid any issues of deferred tiler blending
    // optmizations
    SkPaint paint;
    paint.setAlpha(0x10);
    canvas->drawImage(img, 0, 0, &paint);
}

void set_cache_budget(SkCanvas* canvas, int approxImagesInBudget) {
    // This is inexact but we attempt to figure out a baseline number of resources GrContext needs
    // to render an SkImage and add one additional resource for each image we'd like to fit.
    GrContext* context =  canvas->getGrContext();
    SkASSERT(context);
    context->flush();
    context->contextPriv().purgeAllUnlockedResources_ForTesting();
    sk_sp<SkImage> image;
    make_images(&image, 1);
    draw_image(canvas, image.get());
    context->flush();
    int baselineCount;
    context->getResourceCacheUsage(&baselineCount, nullptr);
    baselineCount -= 1; // for the image's textures.
    context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
    context->contextPriv().purgeAllUnlockedResources_ForTesting();
}

//////////////////////////////////////////////////////////////////////////////

/**
 * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench
 * run with different cache sizes and either repeat the image order each frame or use a random
 * order. Every variation of this bench draws the same image set, only the budget and order of
 * images differs. Since the total fill is the same they can be cross-compared.
 */
class ImageCacheBudgetBench : public Benchmark {
public:
    /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
    ImageCacheBudgetBench(int budgetSize, bool shuffle)
            : fBudgetSize(budgetSize)
            , fShuffle(shuffle)
            , fIndices(nullptr) {
        float imagesOverBudget = float(kImagesToDraw) / budgetSize;
        // Make the benchmark name contain the percentage of the budget that is used in each
        // simulated frame.
        fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100,
                     (shuffle ? "_shuffle" : ""));
    }

    bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }

protected:
    const char* onGetName() override {
        return fName.c_str();
    }

    void onPerCanvasPreDraw(SkCanvas* canvas) override {
        GrContext* context = canvas->getGrContext();
        SkASSERT(context);
        context->getResourceCacheLimits(&fOldCount, &fOldBytes);
        set_cache_budget(canvas, fBudgetSize);
        make_images(fImages, kImagesToDraw);
        if (fShuffle) {
            SkRandom random;
            fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]);
            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
                int* base = fIndices.get() + frame * kImagesToDraw;
                for (int i = 0; i < kImagesToDraw; ++i) {
                    base[i] = i;
                }
                for (int i = 0; i < kImagesToDraw - 1; ++i) {
                    int other = random.nextULessThan(kImagesToDraw - i) + i;
                    using std::swap;
                    swap(base[i], base[other]);
                }
            }
        }
    }

    void onPerCanvasPostDraw(SkCanvas* canvas) override {
        GrContext* context =  canvas->getGrContext();
        SkASSERT(context);
        context->setResourceCacheLimits(fOldCount, fOldBytes);
        for (int i = 0; i < kImagesToDraw; ++i) {
            fImages[i].reset();
        }
        fIndices.reset(nullptr);
    }

    void onDraw(int loops, SkCanvas* canvas) override {
        for (int i = 0; i < loops; ++i) {
            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
                for (int j = 0; j < kImagesToDraw; ++j) {
                    int idx;
                    if (fShuffle) {
                        idx = fIndices[frame * kImagesToDraw + j];
                    } else {
                        idx = j;
                    }
                    draw_image(canvas, fImages[idx].get());
                }
                // Simulate a frame boundary by flushing. This should notify GrResourceCache.
                canvas->flush();
           }
        }
    }

private:
    static constexpr int kImagesToDraw = 100;
    static constexpr int kSimulatedFrames = 5;

    int                         fBudgetSize;
    bool                        fShuffle;
    SkString                    fName;
    sk_sp<SkImage>              fImages[kImagesToDraw];
    std::unique_ptr<int[]>      fIndices;
    size_t                      fOldBytes;
    int                         fOldCount;

    typedef Benchmark INHERITED;
};

DEF_BENCH( return new ImageCacheBudgetBench(105, false); )

DEF_BENCH( return new ImageCacheBudgetBench(90, false); )

DEF_BENCH( return new ImageCacheBudgetBench(80, false); )

DEF_BENCH( return new ImageCacheBudgetBench(50, false); )

DEF_BENCH( return new ImageCacheBudgetBench(105, true); )

DEF_BENCH( return new ImageCacheBudgetBench(90, true); )

DEF_BENCH( return new ImageCacheBudgetBench(80, true); )

DEF_BENCH( return new ImageCacheBudgetBench(50, true); )

//////////////////////////////////////////////////////////////////////////////

/**
 * Similar to above but changes between being over and under budget by varying the number of images
 * rendered. This is not directly comparable to the non-dynamic benchmarks.
 */
class ImageCacheBudgetDynamicBench : public Benchmark {
public:
    enum class Mode {
        // Increase from min to max images drawn gradually over simulated frames and then back.
        kPingPong,
        // Alternate between under and over budget every other simulated frame.
        kFlipFlop
    };

    ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}

    bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }

protected:
    const char* onGetName() override {
        switch (fMode) {
            case Mode::kPingPong:
                return "image_cache_budget_dynamic_ping_pong";
            case Mode::kFlipFlop:
                return "image_cache_budget_dynamic_flip_flop";
        }
        return "";
    }

    void onPerCanvasPreDraw(SkCanvas* canvas) override {
        GrContext* context =  canvas->getGrContext();
        SkASSERT(context);
        context->getResourceCacheLimits(&fOldCount, &fOldBytes);
        make_images(fImages, kMaxImagesToDraw);
        set_cache_budget(canvas, kImagesInBudget);
    }

    void onPerCanvasPostDraw(SkCanvas* canvas) override {
        GrContext* context =  canvas->getGrContext();
        SkASSERT(context);
        context->setResourceCacheLimits(fOldCount, fOldBytes);
        for (int i = 0; i < kMaxImagesToDraw; ++i) {
            fImages[i].reset();
        }
    }

    void onDraw(int loops, SkCanvas* canvas) override {
        int delta = 0;
        switch (fMode) {
            case Mode::kPingPong:
                delta = 1;
                break;
            case Mode::kFlipFlop:
                delta = kMaxImagesToDraw - kMinImagesToDraw;
                break;
        }
        for (int i = 0; i < loops; ++i) {
            int imgsToDraw = kMinImagesToDraw;
            for (int frame = 0; frame < kSimulatedFrames; ++frame) {
                for (int j = 0; j < imgsToDraw; ++j) {
                    draw_image(canvas, fImages[j].get());
                }
                imgsToDraw += delta;
                if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
                    delta = -delta;
                    imgsToDraw += 2 * delta;
                }
                // Simulate a frame boundary by flushing. This should notify GrResourceCache.
                canvas->flush();
            }
        }
    }

private:
    static constexpr int kImagesInBudget  = 25;
    static constexpr int kMinImagesToDraw = 15;
    static constexpr int kMaxImagesToDraw = 35;
    static constexpr int kSimulatedFrames = 80;

    Mode                        fMode;
    sk_sp<SkImage>              fImages[kMaxImagesToDraw];
    size_t                      fOldBytes;
    int                         fOldCount;

    typedef Benchmark INHERITED;
};

DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )