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 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 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 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. */ 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 83 bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; } 84 85 protected: 86 const char* onGetName() override { 87 return fName.c_str(); 88 } 89 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 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 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 186 ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {} 187 188 bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; } 189 190 protected: 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 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 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 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