blob: 12e579060e10a920ea61e89373372fea492869b9 [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#if SK_SUPPORT_GPU
15
16#include "GrContext.h"
17
bsalomonb3cb2142016-09-08 12:35:32 -070018/** 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];
Ben Wagner7ecc5962016-11-02 17:07:33 -0400148 std::unique_ptr<int[]> fIndices;
bsalomonb3cb2142016-09-08 12:35:32 -0700149 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); )
brianosman9f1f6e22016-09-15 08:33:02 -0700261
262#endif