xref: /aosp_15_r20/external/skia/bench/ImageCacheBudgetBench.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkImage.h"
11 #include "include/core/SkSurface.h"
12 #include "include/gpu/ganesh/GrDirectContext.h"
13 #include "src/gpu/ganesh/GrDirectContextPriv.h"
14 #include "src/gpu/ganesh/GrResourceCache.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.
27 static constexpr int kS = 25;
28 
make_images(sk_sp<SkImage> imgs[],int cnt)29 static void make_images(sk_sp<SkImage> imgs[], int cnt) {
30     for (int i = 0; i < cnt; ++i) {
31         imgs[i] = ToolUtils::create_checkerboard_image(kS, kS, SK_ColorBLACK, SK_ColorCYAN, 10);
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, SkSamplingOptions(), &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     auto context =  canvas->recordingContext()->asDirectContext();
47     SkASSERT(context);
48     context->flushAndSubmit();
49     context->priv().getResourceCache()->purgeUnlockedResources(
50             GrPurgeResourceOptions::kAllResources);
51     sk_sp<SkImage> image;
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(
60             GrPurgeResourceOptions::kAllResources);
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  */
71 class ImageCacheBudgetBench : public Benchmark {
72 public:
73     /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
ImageCacheBudgetBench(int budgetSize,bool shuffle)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 
isSuitableFor(Backend backend)85     bool isSuitableFor(Backend backend) override { return Backend::kGanesh == backend; }
86 
87 protected:
onGetName()88     const char* onGetName() override {
89         return fName.c_str();
90     }
91 
onPerCanvasPreDraw(SkCanvas * canvas)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 
onPerCanvasPostDraw(SkCanvas * canvas)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 
onDraw(int loops,SkCanvas * canvas)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 
147 private:
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 
161 DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
162 
163 DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
164 
165 DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
166 
167 DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
168 
169 DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
170 
171 DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
172 
173 DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
174 
175 DEF_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  */
183 class ImageCacheBudgetDynamicBench : public Benchmark {
184 public:
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 
ImageCacheBudgetDynamicBench(Mode mode)192     ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
193 
isSuitableFor(Backend backend)194     bool isSuitableFor(Backend backend) override { return Backend::kGanesh == backend; }
195 
196 protected:
onGetName()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 
onPerCanvasPreDraw(SkCanvas * canvas)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 
onPerCanvasPostDraw(SkCanvas * canvas)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 
onDraw(int loops,SkCanvas * canvas)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 
255 private:
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 
269 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
270 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
271