ImageCacheBudgetBench.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. #include "bench/Benchmark.h"
  8. #include "include/core/SkCanvas.h"
  9. #include "include/core/SkImage.h"
  10. #include "include/core/SkSurface.h"
  11. #include "tools/ToolUtils.h"
  12. #include "include/gpu/GrContext.h"
  13. #include "src/gpu/GrContextPriv.h"
  14. #include <utility>
  15. /** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */
  16. //////////////////////////////////////////////////////////////////////////////
  17. // The width/height of the images to draw. The small size underestimates the value of a good
  18. // replacement strategy since the texture uploads are quite small. However, the effects are still
  19. // significant and this lets the benchmarks complete a lot faster, especially on mobile.
  20. static constexpr int kS = 25;
  21. static void make_images(sk_sp<SkImage> imgs[], int cnt) {
  22. for (int i = 0; i < cnt; ++i) {
  23. SkBitmap bmp =
  24. ToolUtils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK, SK_ColorCYAN, 10);
  25. imgs[i] = SkImage::MakeFromBitmap(bmp);
  26. }
  27. }
  28. static void draw_image(SkCanvas* canvas, SkImage* img) {
  29. // Make the paint transparent to avoid any issues of deferred tiler blending
  30. // optmizations
  31. SkPaint paint;
  32. paint.setAlpha(0x10);
  33. canvas->drawImage(img, 0, 0, &paint);
  34. }
  35. void set_cache_budget(SkCanvas* canvas, int approxImagesInBudget) {
  36. // This is inexact but we attempt to figure out a baseline number of resources GrContext needs
  37. // to render an SkImage and add one additional resource for each image we'd like to fit.
  38. GrContext* context = canvas->getGrContext();
  39. SkASSERT(context);
  40. context->flush();
  41. context->priv().testingOnly_purgeAllUnlockedResources();
  42. sk_sp<SkImage> image;
  43. make_images(&image, 1);
  44. draw_image(canvas, image.get());
  45. context->flush();
  46. int baselineCount;
  47. context->getResourceCacheUsage(&baselineCount, nullptr);
  48. baselineCount -= 1; // for the image's textures.
  49. context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
  50. context->priv().testingOnly_purgeAllUnlockedResources();
  51. }
  52. //////////////////////////////////////////////////////////////////////////////
  53. /**
  54. * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench
  55. * run with different cache sizes and either repeat the image order each frame or use a random
  56. * order. Every variation of this bench draws the same image set, only the budget and order of
  57. * images differs. Since the total fill is the same they can be cross-compared.
  58. */
  59. class ImageCacheBudgetBench : public Benchmark {
  60. public:
  61. /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
  62. ImageCacheBudgetBench(int budgetSize, bool shuffle)
  63. : fBudgetSize(budgetSize)
  64. , fShuffle(shuffle)
  65. , fIndices(nullptr) {
  66. float imagesOverBudget = float(kImagesToDraw) / budgetSize;
  67. // Make the benchmark name contain the percentage of the budget that is used in each
  68. // simulated frame.
  69. fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100,
  70. (shuffle ? "_shuffle" : ""));
  71. }
  72. bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
  73. protected:
  74. const char* onGetName() override {
  75. return fName.c_str();
  76. }
  77. void onPerCanvasPreDraw(SkCanvas* canvas) override {
  78. GrContext* context = canvas->getGrContext();
  79. SkASSERT(context);
  80. context->getResourceCacheLimits(&fOldCount, &fOldBytes);
  81. set_cache_budget(canvas, fBudgetSize);
  82. make_images(fImages, kImagesToDraw);
  83. if (fShuffle) {
  84. SkRandom random;
  85. fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]);
  86. for (int frame = 0; frame < kSimulatedFrames; ++frame) {
  87. int* base = fIndices.get() + frame * kImagesToDraw;
  88. for (int i = 0; i < kImagesToDraw; ++i) {
  89. base[i] = i;
  90. }
  91. for (int i = 0; i < kImagesToDraw - 1; ++i) {
  92. int other = random.nextULessThan(kImagesToDraw - i) + i;
  93. using std::swap;
  94. swap(base[i], base[other]);
  95. }
  96. }
  97. }
  98. }
  99. void onPerCanvasPostDraw(SkCanvas* canvas) override {
  100. GrContext* context = canvas->getGrContext();
  101. SkASSERT(context);
  102. context->setResourceCacheLimits(fOldCount, fOldBytes);
  103. for (int i = 0; i < kImagesToDraw; ++i) {
  104. fImages[i].reset();
  105. }
  106. fIndices.reset(nullptr);
  107. }
  108. void onDraw(int loops, SkCanvas* canvas) override {
  109. for (int i = 0; i < loops; ++i) {
  110. for (int frame = 0; frame < kSimulatedFrames; ++frame) {
  111. for (int j = 0; j < kImagesToDraw; ++j) {
  112. int idx;
  113. if (fShuffle) {
  114. idx = fIndices[frame * kImagesToDraw + j];
  115. } else {
  116. idx = j;
  117. }
  118. draw_image(canvas, fImages[idx].get());
  119. }
  120. // Simulate a frame boundary by flushing. This should notify GrResourceCache.
  121. canvas->flush();
  122. }
  123. }
  124. }
  125. private:
  126. static constexpr int kImagesToDraw = 100;
  127. static constexpr int kSimulatedFrames = 5;
  128. int fBudgetSize;
  129. bool fShuffle;
  130. SkString fName;
  131. sk_sp<SkImage> fImages[kImagesToDraw];
  132. std::unique_ptr<int[]> fIndices;
  133. size_t fOldBytes;
  134. int fOldCount;
  135. typedef Benchmark INHERITED;
  136. };
  137. DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
  138. DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
  139. DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
  140. DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
  141. DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
  142. DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
  143. DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
  144. DEF_BENCH( return new ImageCacheBudgetBench(50, true); )
  145. //////////////////////////////////////////////////////////////////////////////
  146. /**
  147. * Similar to above but changes between being over and under budget by varying the number of images
  148. * rendered. This is not directly comparable to the non-dynamic benchmarks.
  149. */
  150. class ImageCacheBudgetDynamicBench : public Benchmark {
  151. public:
  152. enum class Mode {
  153. // Increase from min to max images drawn gradually over simulated frames and then back.
  154. kPingPong,
  155. // Alternate between under and over budget every other simulated frame.
  156. kFlipFlop
  157. };
  158. ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
  159. bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
  160. protected:
  161. const char* onGetName() override {
  162. switch (fMode) {
  163. case Mode::kPingPong:
  164. return "image_cache_budget_dynamic_ping_pong";
  165. case Mode::kFlipFlop:
  166. return "image_cache_budget_dynamic_flip_flop";
  167. }
  168. return "";
  169. }
  170. void onPerCanvasPreDraw(SkCanvas* canvas) override {
  171. GrContext* context = canvas->getGrContext();
  172. SkASSERT(context);
  173. context->getResourceCacheLimits(&fOldCount, &fOldBytes);
  174. make_images(fImages, kMaxImagesToDraw);
  175. set_cache_budget(canvas, kImagesInBudget);
  176. }
  177. void onPerCanvasPostDraw(SkCanvas* canvas) override {
  178. GrContext* context = canvas->getGrContext();
  179. SkASSERT(context);
  180. context->setResourceCacheLimits(fOldCount, fOldBytes);
  181. for (int i = 0; i < kMaxImagesToDraw; ++i) {
  182. fImages[i].reset();
  183. }
  184. }
  185. void onDraw(int loops, SkCanvas* canvas) override {
  186. int delta = 0;
  187. switch (fMode) {
  188. case Mode::kPingPong:
  189. delta = 1;
  190. break;
  191. case Mode::kFlipFlop:
  192. delta = kMaxImagesToDraw - kMinImagesToDraw;
  193. break;
  194. }
  195. for (int i = 0; i < loops; ++i) {
  196. int imgsToDraw = kMinImagesToDraw;
  197. for (int frame = 0; frame < kSimulatedFrames; ++frame) {
  198. for (int j = 0; j < imgsToDraw; ++j) {
  199. draw_image(canvas, fImages[j].get());
  200. }
  201. imgsToDraw += delta;
  202. if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
  203. delta = -delta;
  204. imgsToDraw += 2 * delta;
  205. }
  206. // Simulate a frame boundary by flushing. This should notify GrResourceCache.
  207. canvas->flush();
  208. }
  209. }
  210. }
  211. private:
  212. static constexpr int kImagesInBudget = 25;
  213. static constexpr int kMinImagesToDraw = 15;
  214. static constexpr int kMaxImagesToDraw = 35;
  215. static constexpr int kSimulatedFrames = 80;
  216. Mode fMode;
  217. sk_sp<SkImage> fImages[kMaxImagesToDraw];
  218. size_t fOldBytes;
  219. int fOldCount;
  220. typedef Benchmark INHERITED;
  221. };
  222. DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
  223. DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )