Index: bench/ImageCacheBudgetBench.cpp |
diff --git a/bench/ImageCacheBudgetBench.cpp b/bench/ImageCacheBudgetBench.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..32804bae13a45191e62f5ae3f2d7bf2e7b4a7d7e |
--- /dev/null |
+++ b/bench/ImageCacheBudgetBench.cpp |
@@ -0,0 +1,257 @@ |
+/* |
+ * Copyright 2016 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "Benchmark.h" |
+#include "GrContext.h" |
+#include "sk_tool_utils.h" |
+#include "SkCanvas.h" |
+#include "SkImage.h" |
+#include "SkSurface.h" |
+ |
+/** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */ |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
+// The width/height of the images to draw. The small size underestimates the value of a good |
+// replacement strategy since the texture uploads are quite small. However, the effects are still |
+// significant and this lets the benchmarks complete a lot faster, especially on mobile. |
+static constexpr int kS = 25; |
+ |
+static void make_images(sk_sp<SkImage> imgs[], int cnt) { |
+ for (int i = 0; i < cnt; ++i) { |
+ SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK, |
+ SK_ColorCYAN, 10); |
+ imgs[i] = SkImage::MakeFromBitmap(bmp); |
+ } |
+} |
+ |
+static void draw_image(SkCanvas* canvas, SkImage* img) { |
+ // Make the paint transparent to avoid any issues of deferred tiler blending |
+ // optmizations |
+ SkPaint paint; |
+ paint.setAlpha(0x10); |
+ canvas->drawImage(img, 0, 0, &paint); |
+} |
+ |
+void set_cache_budget(SkCanvas* canvas, int approxImagesInBudget) { |
+ // This is inexact but we attempt to figure out a baseline number of resources GrContext needs |
+ // to render an SkImage and add one additional resource for each image we'd like to fit. |
+ GrContext* context = canvas->getGrContext(); |
+ SkASSERT(context); |
+ context->flush(); |
+ context->purgeAllUnlockedResources(); |
+ sk_sp<SkImage> image; |
+ make_images(&image, 1); |
+ draw_image(canvas, image.get()); |
+ context->flush(); |
+ int baselineCount; |
+ context->getResourceCacheUsage(&baselineCount, nullptr); |
+ baselineCount -= 1; // for the image's textures. |
+ context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30); |
+ context->purgeAllUnlockedResources(); |
+} |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
+/** |
+ * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench |
+ * run with different cache sizes and either repeat the image order each frame or use a random |
+ * order. Every variation of this bench draws the same image set, only the budget and order of |
+ * images differs. Since the total fill is the same they can be cross-compared. |
+ */ |
+class ImageCacheBudgetBench : public Benchmark { |
+public: |
+ /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */ |
+ ImageCacheBudgetBench(int budgetSize, bool shuffle) |
+ : fBudgetSize(budgetSize) |
+ , fShuffle(shuffle) |
+ , fIndices(nullptr) { |
+ float imagesOverBudget = float(kImagesToDraw) / budgetSize; |
+ // Make the benchmark name contain the percentage of the budget that is used in each |
+ // simulated frame. |
+ fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100, |
+ (shuffle ? "_shuffle" : "")); |
+ } |
+ |
+ bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; } |
+ |
+protected: |
+ const char* onGetName() override { |
+ return fName.c_str(); |
+ } |
+ |
+ void onPerCanvasPreDraw(SkCanvas* canvas) override { |
+ GrContext* context = canvas->getGrContext(); |
+ SkASSERT(context); |
+ context->getResourceCacheLimits(&fOldCount, &fOldBytes); |
+ set_cache_budget(canvas, fBudgetSize); |
+ make_images(fImages, kImagesToDraw); |
+ if (fShuffle) { |
+ SkRandom random; |
+ fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]); |
+ for (int frame = 0; frame < kSimulatedFrames; ++frame) { |
+ int* base = fIndices.get() + frame * kImagesToDraw; |
+ for (int i = 0; i < kImagesToDraw; ++i) { |
+ base[i] = i; |
+ } |
+ for (int i = 0; i < kImagesToDraw - 1; ++i) { |
+ int other = random.nextULessThan(kImagesToDraw - i) + i; |
+ SkTSwap(base[i], base[other]); |
+ } |
+ } |
+ } |
+ } |
+ |
+ void onPerCanvasPostDraw(SkCanvas* canvas) override { |
+ GrContext* context = canvas->getGrContext(); |
+ SkASSERT(context); |
+ context->setResourceCacheLimits(fOldCount, fOldBytes); |
+ for (int i = 0; i < kImagesToDraw; ++i) { |
+ fImages[i].reset(); |
+ } |
+ fIndices.reset(nullptr); |
+ } |
+ |
+ void onDraw(int loops, SkCanvas* canvas) override { |
+ for (int i = 0; i < loops; ++i) { |
+ for (int frame = 0; frame < kSimulatedFrames; ++frame) { |
+ for (int j = 0; j < kImagesToDraw; ++j) { |
+ int idx; |
+ if (fShuffle) { |
+ idx = fIndices[frame * kImagesToDraw + j]; |
+ } else { |
+ idx = j; |
+ } |
+ draw_image(canvas, fImages[idx].get()); |
+ } |
+ // Simulate a frame boundary by flushing. This should notify GrResourceCache. |
+ canvas->flush(); |
+ } |
+ } |
+ } |
+ |
+private: |
+ static constexpr int kImagesToDraw = 100; |
+ static constexpr int kSimulatedFrames = 5; |
+ |
+ int fBudgetSize; |
+ bool fShuffle; |
+ SkString fName; |
+ sk_sp<SkImage> fImages[kImagesToDraw]; |
+ SkAutoTDeleteArray<int> fIndices; |
+ size_t fOldBytes; |
+ int fOldCount; |
+ |
+ typedef Benchmark INHERITED; |
+}; |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(105, false); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(90, false); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(80, false); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(50, false); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(105, true); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(90, true); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(80, true); ) |
+ |
+DEF_BENCH( return new ImageCacheBudgetBench(50, true); ) |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
+/** |
+ * Similar to above but changes between being over and under budget by varying the number of images |
+ * rendered. This is not directly comparable to the non-dynamic benchmarks. |
+ */ |
+class ImageCacheBudgetDynamicBench : public Benchmark { |
+public: |
+ enum class Mode { |
+ // Increase from min to max images drawn gradually over simulated frames and then back. |
+ kPingPong, |
+ // Alternate between under and over budget every other simulated frame. |
+ kFlipFlop |
+ }; |
+ |
+ ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {} |
+ |
+ bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; } |
+ |
+protected: |
+ const char* onGetName() override { |
+ switch (fMode) { |
+ case Mode::kPingPong: |
+ return "image_cache_budget_dynamic_ping_pong"; |
+ case Mode::kFlipFlop: |
+ return "image_cache_budget_dynamic_flip_flop"; |
+ } |
+ return ""; |
+ } |
+ |
+ void onPerCanvasPreDraw(SkCanvas* canvas) override { |
+ GrContext* context = canvas->getGrContext(); |
+ SkASSERT(context); |
+ context->getResourceCacheLimits(&fOldCount, &fOldBytes); |
+ make_images(fImages, kMaxImagesToDraw); |
+ set_cache_budget(canvas, kImagesInBudget); |
+ } |
+ |
+ void onPerCanvasPostDraw(SkCanvas* canvas) override { |
+ GrContext* context = canvas->getGrContext(); |
+ SkASSERT(context); |
+ context->setResourceCacheLimits(fOldCount, fOldBytes); |
+ for (int i = 0; i < kMaxImagesToDraw; ++i) { |
+ fImages[i].reset(); |
+ } |
+ } |
+ |
+ void onDraw(int loops, SkCanvas* canvas) override { |
+ int delta = 0; |
+ switch (fMode) { |
+ case Mode::kPingPong: |
+ delta = 1; |
+ break; |
+ case Mode::kFlipFlop: |
+ delta = kMaxImagesToDraw - kMinImagesToDraw; |
+ break; |
+ } |
+ for (int i = 0; i < loops; ++i) { |
+ int imgsToDraw = kMinImagesToDraw; |
+ for (int frame = 0; frame < kSimulatedFrames; ++frame) { |
+ for (int j = 0; j < imgsToDraw; ++j) { |
+ draw_image(canvas, fImages[j].get()); |
+ } |
+ imgsToDraw += delta; |
+ if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) { |
+ delta = -delta; |
+ imgsToDraw += 2 * delta; |
+ } |
+ // Simulate a frame boundary by flushing. This should notify GrResourceCache. |
+ canvas->flush(); |
+ } |
+ } |
+ } |
+ |
+private: |
+ static constexpr int kImagesInBudget = 25; |
+ static constexpr int kMinImagesToDraw = 15; |
+ static constexpr int kMaxImagesToDraw = 35; |
+ static constexpr int kSimulatedFrames = 80; |
+ |
+ Mode fMode; |
+ sk_sp<SkImage> fImages[kMaxImagesToDraw]; |
+ size_t fOldBytes; |
+ int fOldCount; |
+ |
+ typedef Benchmark INHERITED; |
+}; |
+ |
+DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); ) |
+DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); ) |