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