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.
25 static constexpr int kS = 25;
26
make_images(sk_sp<SkImage> imgs[],int cnt)27 static 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
draw_image(SkCanvas * canvas,SkImage * img)35 static 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
set_cache_budget(SkCanvas * canvas,int approxImagesInBudget)43 void 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 */
69 class ImageCacheBudgetBench : public Benchmark {
70 public:
71 /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
ImageCacheBudgetBench(int budgetSize,bool shuffle)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
isSuitableFor(Backend backend)83 bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
84
85 protected:
onGetName()86 const char* onGetName() override {
87 return fName.c_str();
88 }
89
onPerCanvasPreDraw(SkCanvas * canvas)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
onPerCanvasPostDraw(SkCanvas * canvas)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
onDraw(int loops,SkCanvas * canvas)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
140 private:
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
155 DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
156
157 DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
158
159 DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
160
161 DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
162
163 DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
164
165 DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
166
167 DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
168
169 DEF_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 */
177 class ImageCacheBudgetDynamicBench : public Benchmark {
178 public:
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
ImageCacheBudgetDynamicBench(Mode mode)186 ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
187
isSuitableFor(Backend backend)188 bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
189
190 protected:
onGetName()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
onPerCanvasPreDraw(SkCanvas * canvas)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
onPerCanvasPostDraw(SkCanvas * canvas)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
onDraw(int loops,SkCanvas * canvas)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
245 private:
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
259 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
260 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
261
262 #endif
263