/*
* 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 "sk_tool_utils.h"
#include "SkCanvas.h"
#include "SkImage.h"
#include "SkSurface.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include <utility>
/** 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->priv().testingOnly_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->priv().testingOnly_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;
using std::swap;
swap(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];
std::unique_ptr<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); )