Flutter Engine
The Flutter Engine
ImageCacheBudgetBench.cpp
Go to the documentation of this file.
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 "bench/Benchmark.h"
15#include "tools/ToolUtils.h"
16
17
18#include <utility>
19
20/** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */
21
22//////////////////////////////////////////////////////////////////////////////
23
24// The width/height of the images to draw. The small size underestimates the value of a good
25// replacement strategy since the texture uploads are quite small. However, the effects are still
26// significant and this lets the benchmarks complete a lot faster, especially on mobile.
27static constexpr int kS = 25;
28
29static void make_images(sk_sp<SkImage> imgs[], int cnt) {
30 for (int i = 0; i < cnt; ++i) {
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
39 paint.setAlpha(0x10);
40 canvas->drawImage(img, 0, 0, SkSamplingOptions(), &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 auto context = canvas->recordingContext()->asDirectContext();
47 SkASSERT(context);
48 context->flushAndSubmit();
49 context->priv().getResourceCache()->purgeUnlockedResources(
52 make_images(&image, 1);
53 draw_image(canvas, image.get());
54 context->flushAndSubmit();
55 int baselineCount;
56 context->getResourceCacheUsage(&baselineCount, nullptr);
57 baselineCount -= 1; // for the image's textures.
58 context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
59 context->priv().getResourceCache()->purgeUnlockedResources(
61}
62
63//////////////////////////////////////////////////////////////////////////////
64
65/**
66 * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench
67 * run with different cache sizes and either repeat the image order each frame or use a random
68 * order. Every variation of this bench draws the same image set, only the budget and order of
69 * images differs. Since the total fill is the same they can be cross-compared.
70 */
72public:
73 /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
74 ImageCacheBudgetBench(int budgetSize, bool shuffle)
75 : fBudgetSize(budgetSize)
76 , fShuffle(shuffle)
77 , fIndices(nullptr) {
78 float imagesOverBudget = float(kImagesToDraw) / budgetSize;
79 // Make the benchmark name contain the percentage of the budget that is used in each
80 // simulated frame.
81 fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100,
82 (shuffle ? "_shuffle" : ""));
83 }
84
86
87protected:
88 const char* onGetName() override {
89 return fName.c_str();
90 }
91
92 void onPerCanvasPreDraw(SkCanvas* canvas) override {
93 auto context = canvas->recordingContext()->asDirectContext();
94 SkASSERT(context);
95 fOldBytes = context->getResourceCacheLimit();
96 set_cache_budget(canvas, fBudgetSize);
97 make_images(fImages, kImagesToDraw);
98 if (fShuffle) {
99 SkRandom random;
100 fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]);
101 for (int frame = 0; frame < kSimulatedFrames; ++frame) {
102 int* base = fIndices.get() + frame * kImagesToDraw;
103 for (int i = 0; i < kImagesToDraw; ++i) {
104 base[i] = i;
105 }
106 for (int i = 0; i < kImagesToDraw - 1; ++i) {
107 int other = random.nextULessThan(kImagesToDraw - i) + i;
108 using std::swap;
109 swap(base[i], base[other]);
110 }
111 }
112 }
113 }
114
115 void onPerCanvasPostDraw(SkCanvas* canvas) override {
116 auto context = canvas->recordingContext()->asDirectContext();
117 SkASSERT(context);
118 context->setResourceCacheLimit(fOldBytes);
119 for (int i = 0; i < kImagesToDraw; ++i) {
120 fImages[i].reset();
121 }
122 fIndices.reset(nullptr);
123 }
124
125 void onDraw(int loops, SkCanvas* canvas) override {
126 auto dContext = GrAsDirectContext(canvas->recordingContext());
127
128 for (int i = 0; i < loops; ++i) {
129 for (int frame = 0; frame < kSimulatedFrames; ++frame) {
130 for (int j = 0; j < kImagesToDraw; ++j) {
131 int idx;
132 if (fShuffle) {
133 idx = fIndices[frame * kImagesToDraw + j];
134 } else {
135 idx = j;
136 }
137 draw_image(canvas, fImages[idx].get());
138 }
139 // Simulate a frame boundary by flushing. This should notify GrResourceCache.
140 if (dContext) {
141 dContext->flush();
142 }
143 }
144 }
145 }
146
147private:
148 inline static constexpr int kImagesToDraw = 100;
149 inline static constexpr int kSimulatedFrames = 5;
150
151 int fBudgetSize;
152 bool fShuffle;
153 SkString fName;
154 sk_sp<SkImage> fImages[kImagesToDraw];
155 std::unique_ptr<int[]> fIndices;
156 size_t fOldBytes;
157
158 using INHERITED = Benchmark;
159};
160
161DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
162
163DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
164
165DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
166
167DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
168
169DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
170
171DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
172
173DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
174
175DEF_BENCH( return new ImageCacheBudgetBench(50, true); )
176
177//////////////////////////////////////////////////////////////////////////////
178
179/**
180 * Similar to above but changes between being over and under budget by varying the number of images
181 * rendered. This is not directly comparable to the non-dynamic benchmarks.
182 */
184public:
185 enum class Mode {
186 // Increase from min to max images drawn gradually over simulated frames and then back.
187 kPingPong,
188 // Alternate between under and over budget every other simulated frame.
189 kFlipFlop
190 };
191
193
194 bool isSuitableFor(Backend backend) override { return Backend::kGanesh == backend; }
195
196protected:
197 const char* onGetName() override {
198 switch (fMode) {
199 case Mode::kPingPong:
200 return "image_cache_budget_dynamic_ping_pong";
201 case Mode::kFlipFlop:
202 return "image_cache_budget_dynamic_flip_flop";
203 }
204 return "";
205 }
206
207 void onPerCanvasPreDraw(SkCanvas* canvas) override {
208 auto context = canvas->recordingContext()->asDirectContext();
209 SkASSERT(context);
210 context->getResourceCacheLimits(&fOldCount, &fOldBytes);
211 make_images(fImages, kMaxImagesToDraw);
212 set_cache_budget(canvas, kImagesInBudget);
213 }
214
215 void onPerCanvasPostDraw(SkCanvas* canvas) override {
216 auto context = canvas->recordingContext()->asDirectContext();
217 SkASSERT(context);
218 context->setResourceCacheLimits(fOldCount, fOldBytes);
219 for (int i = 0; i < kMaxImagesToDraw; ++i) {
220 fImages[i].reset();
221 }
222 }
223
224 void onDraw(int loops, SkCanvas* canvas) override {
225 auto dContext = GrAsDirectContext(canvas->recordingContext());
226
227 int delta = 0;
228 switch (fMode) {
229 case Mode::kPingPong:
230 delta = 1;
231 break;
232 case Mode::kFlipFlop:
233 delta = kMaxImagesToDraw - kMinImagesToDraw;
234 break;
235 }
236 for (int i = 0; i < loops; ++i) {
237 int imgsToDraw = kMinImagesToDraw;
238 for (int frame = 0; frame < kSimulatedFrames; ++frame) {
239 for (int j = 0; j < imgsToDraw; ++j) {
240 draw_image(canvas, fImages[j].get());
241 }
242 imgsToDraw += delta;
243 if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
244 delta = -delta;
245 imgsToDraw += 2 * delta;
246 }
247 // Simulate a frame boundary by flushing. This should notify GrResourceCache.
248 if (dContext) {
249 dContext->flush();
250 }
251 }
252 }
253 }
254
255private:
256 inline static constexpr int kImagesInBudget = 25;
257 inline static constexpr int kMinImagesToDraw = 15;
258 inline static constexpr int kMaxImagesToDraw = 35;
259 inline static constexpr int kSimulatedFrames = 80;
260
261 Mode fMode;
262 sk_sp<SkImage> fImages[kMaxImagesToDraw];
263 size_t fOldBytes;
264 int fOldCount;
265
266 using INHERITED = Benchmark;
267};
268
#define DEF_BENCH(code)
Definition: Benchmark.h:20
const char * backend
static GrDirectContext * GrAsDirectContext(GrContext_Base *base)
static void make_images(sk_sp< SkImage > imgs[], int cnt)
static void draw_image(SkCanvas *canvas, SkImage *img)
void set_cache_budget(SkCanvas *canvas, int approxImagesInBudget)
static constexpr int kS
#define SkASSERT(cond)
Definition: SkAssert.h:116
constexpr SkColor SK_ColorCYAN
Definition: SkColor.h:143
constexpr SkColor SK_ColorBLACK
Definition: SkColor.h:103
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
virtual GrDirectContext * asDirectContext()
void onDraw(int loops, SkCanvas *canvas) override
bool isSuitableFor(Backend backend) override
void onPerCanvasPreDraw(SkCanvas *canvas) override
void onPerCanvasPostDraw(SkCanvas *canvas) override
const char * onGetName() override
ImageCacheBudgetBench(int budgetSize, bool shuffle)
bool isSuitableFor(Backend backend) override
void onPerCanvasPostDraw(SkCanvas *canvas) override
void onPerCanvasPreDraw(SkCanvas *canvas) override
void onDraw(int loops, SkCanvas *canvas) override
virtual GrRecordingContext * recordingContext() const
Definition: SkCanvas.cpp:1637
void drawImage(const SkImage *image, SkScalar left, SkScalar top)
Definition: SkCanvas.h:1528
uint32_t nextULessThan(uint32_t count)
Definition: SkRandom.h:93
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:534
const char * c_str() const
Definition: SkString.h:133
T * get() const
Definition: SkRefCnt.h:303
void reset(T *ptr=nullptr)
Definition: SkRefCnt.h:310
const Paint & paint
Definition: color_source.cc:38
double frame
Definition: examples.cpp:31
sk_sp< const SkImage > image
Definition: SkRecords.h:269
sk_sp< SkImage > create_checkerboard_image(int w, int h, SkColor c1, SkColor c2, int checkSize)
Definition: ToolUtils.cpp:168
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
const myers::Point & get(const myers::Segment &)
SkSamplingOptions(SkFilterMode::kLinear))
SI Vec< sizeof...(Ix), T > shuffle(const Vec< N, T > &)
Definition: SkVx.h:667
SkBlendMode fMode
Definition: xfermodes.cpp:52