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