• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "GrContext.h"
15 #include "GrContextPriv.h"
16 
17 #include <utility>
18 
19 /** 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.
26 static constexpr int kS = 25;
27 
make_images(sk_sp<SkImage> imgs[],int cnt)28 static void make_images(sk_sp<SkImage> imgs[], int cnt) {
29     for (int i = 0; i < cnt; ++i) {
30         SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK,
31                                                                  SK_ColorCYAN, 10);
32         imgs[i] = SkImage::MakeFromBitmap(bmp);
33     }
34 }
35 
draw_image(SkCanvas * canvas,SkImage * img)36 static 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 
set_cache_budget(SkCanvas * canvas,int approxImagesInBudget)44 void 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);
49     context->flush();
50     context->contextPriv().purgeAllUnlockedResources_ForTesting();
51     sk_sp<SkImage> image;
52     make_images(&image, 1);
53     draw_image(canvas, image.get());
54     context->flush();
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->contextPriv().purgeAllUnlockedResources_ForTesting();
60 }
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  */
70 class ImageCacheBudgetBench : public Benchmark {
71 public:
72     /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
ImageCacheBudgetBench(int budgetSize,bool shuffle)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 
isSuitableFor(Backend backend)84     bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
85 
86 protected:
onGetName()87     const char* onGetName() override {
88         return fName.c_str();
89     }
90 
onPerCanvasPreDraw(SkCanvas * canvas)91     void onPerCanvasPreDraw(SkCanvas* canvas) override {
92         GrContext* context = canvas->getGrContext();
93         SkASSERT(context);
94         context->getResourceCacheLimits(&fOldCount, &fOldBytes);
95         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;
107                     using std::swap;
108                     swap(base[i], base[other]);
109                 }
110             }
111         }
112     }
113 
onPerCanvasPostDraw(SkCanvas * canvas)114     void onPerCanvasPostDraw(SkCanvas* canvas) override {
115         GrContext* context =  canvas->getGrContext();
116         SkASSERT(context);
117         context->setResourceCacheLimits(fOldCount, fOldBytes);
118         for (int i = 0; i < kImagesToDraw; ++i) {
119             fImages[i].reset();
120         }
121         fIndices.reset(nullptr);
122     }
123 
onDraw(int loops,SkCanvas * canvas)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 
142 private:
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];
150     std::unique_ptr<int[]>      fIndices;
151     size_t                      fOldBytes;
152     int                         fOldCount;
153 
154     typedef Benchmark INHERITED;
155 };
156 
157 DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
158 
159 DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
160 
161 DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
162 
163 DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
164 
165 DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
166 
167 DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
168 
169 DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
170 
171 DEF_BENCH( return new ImageCacheBudgetBench(50, true); )
172 
173 //////////////////////////////////////////////////////////////////////////////
174 
175 /**
176  * Similar to above but changes between being over and under budget by varying the number of images
177  * rendered. This is not directly comparable to the non-dynamic benchmarks.
178  */
179 class ImageCacheBudgetDynamicBench : public Benchmark {
180 public:
181     enum class Mode {
182         // Increase from min to max images drawn gradually over simulated frames and then back.
183         kPingPong,
184         // Alternate between under and over budget every other simulated frame.
185         kFlipFlop
186     };
187 
ImageCacheBudgetDynamicBench(Mode mode)188     ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
189 
isSuitableFor(Backend backend)190     bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
191 
192 protected:
onGetName()193     const char* onGetName() override {
194         switch (fMode) {
195             case Mode::kPingPong:
196                 return "image_cache_budget_dynamic_ping_pong";
197             case Mode::kFlipFlop:
198                 return "image_cache_budget_dynamic_flip_flop";
199         }
200         return "";
201     }
202 
onPerCanvasPreDraw(SkCanvas * canvas)203     void onPerCanvasPreDraw(SkCanvas* canvas) override {
204         GrContext* context =  canvas->getGrContext();
205         SkASSERT(context);
206         context->getResourceCacheLimits(&fOldCount, &fOldBytes);
207         make_images(fImages, kMaxImagesToDraw);
208         set_cache_budget(canvas, kImagesInBudget);
209     }
210 
onPerCanvasPostDraw(SkCanvas * canvas)211     void onPerCanvasPostDraw(SkCanvas* canvas) override {
212         GrContext* context =  canvas->getGrContext();
213         SkASSERT(context);
214         context->setResourceCacheLimits(fOldCount, fOldBytes);
215         for (int i = 0; i < kMaxImagesToDraw; ++i) {
216             fImages[i].reset();
217         }
218     }
219 
onDraw(int loops,SkCanvas * canvas)220     void onDraw(int loops, SkCanvas* canvas) override {
221         int delta = 0;
222         switch (fMode) {
223             case Mode::kPingPong:
224                 delta = 1;
225                 break;
226             case Mode::kFlipFlop:
227                 delta = kMaxImagesToDraw - kMinImagesToDraw;
228                 break;
229         }
230         for (int i = 0; i < loops; ++i) {
231             int imgsToDraw = kMinImagesToDraw;
232             for (int frame = 0; frame < kSimulatedFrames; ++frame) {
233                 for (int j = 0; j < imgsToDraw; ++j) {
234                     draw_image(canvas, fImages[j].get());
235                 }
236                 imgsToDraw += delta;
237                 if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
238                     delta = -delta;
239                     imgsToDraw += 2 * delta;
240                 }
241                 // Simulate a frame boundary by flushing. This should notify GrResourceCache.
242                 canvas->flush();
243             }
244         }
245     }
246 
247 private:
248     static constexpr int kImagesInBudget  = 25;
249     static constexpr int kMinImagesToDraw = 15;
250     static constexpr int kMaxImagesToDraw = 35;
251     static constexpr int kSimulatedFrames = 80;
252 
253     Mode                        fMode;
254     sk_sp<SkImage>              fImages[kMaxImagesToDraw];
255     size_t                      fOldBytes;
256     int                         fOldCount;
257 
258     typedef Benchmark INHERITED;
259 };
260 
261 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
262 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
263