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