719239cd69
Change-Id: I238d29ba0250224fa593845ae65192653f58faff Reviewed-on: https://skia-review.googlesource.com/c/skia/+/528156 Reviewed-by: Kevin Lubick <kjlubick@google.com> Reviewed-by: Jim Van Verth <jvanverth@google.com> Commit-Queue: Greg Daniel <egdaniel@google.com>
269 lines
9.7 KiB
C++
269 lines
9.7 KiB
C++
/*
|
|
* 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 "bench/Benchmark.h"
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkImage.h"
|
|
#include "include/core/SkSurface.h"
|
|
#include "include/gpu/GrDirectContext.h"
|
|
#include "src/gpu/ganesh/GrDirectContextPriv.h"
|
|
#include "src/gpu/ganesh/GrResourceCache.h"
|
|
#include "tools/ToolUtils.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) {
|
|
imgs[i] = ToolUtils::create_checkerboard_image(kS, kS, SK_ColorBLACK, SK_ColorCYAN, 10);
|
|
}
|
|
}
|
|
|
|
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, SkSamplingOptions(), &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.
|
|
auto context = canvas->recordingContext()->asDirectContext();
|
|
SkASSERT(context);
|
|
context->flushAndSubmit();
|
|
context->priv().getResourceCache()->purgeUnlockedResources();
|
|
sk_sp<SkImage> image;
|
|
make_images(&image, 1);
|
|
draw_image(canvas, image.get());
|
|
context->flushAndSubmit();
|
|
int baselineCount;
|
|
context->getResourceCacheUsage(&baselineCount, nullptr);
|
|
baselineCount -= 1; // for the image's textures.
|
|
context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
|
|
context->priv().getResourceCache()->purgeUnlockedResources();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* 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 {
|
|
auto context = canvas->recordingContext()->asDirectContext();
|
|
SkASSERT(context);
|
|
fOldBytes = context->getResourceCacheLimit();
|
|
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 {
|
|
auto context = canvas->recordingContext()->asDirectContext();
|
|
SkASSERT(context);
|
|
context->setResourceCacheLimit(fOldBytes);
|
|
for (int i = 0; i < kImagesToDraw; ++i) {
|
|
fImages[i].reset();
|
|
}
|
|
fIndices.reset(nullptr);
|
|
}
|
|
|
|
void onDraw(int loops, SkCanvas* canvas) override {
|
|
auto dContext = GrAsDirectContext(canvas->recordingContext());
|
|
|
|
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.
|
|
if (dContext) {
|
|
dContext->flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
inline static constexpr int kImagesToDraw = 100;
|
|
inline static constexpr int kSimulatedFrames = 5;
|
|
|
|
int fBudgetSize;
|
|
bool fShuffle;
|
|
SkString fName;
|
|
sk_sp<SkImage> fImages[kImagesToDraw];
|
|
std::unique_ptr<int[]> fIndices;
|
|
size_t fOldBytes;
|
|
|
|
using INHERITED = Benchmark;
|
|
};
|
|
|
|
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 {
|
|
auto context = canvas->recordingContext()->asDirectContext();
|
|
SkASSERT(context);
|
|
context->getResourceCacheLimits(&fOldCount, &fOldBytes);
|
|
make_images(fImages, kMaxImagesToDraw);
|
|
set_cache_budget(canvas, kImagesInBudget);
|
|
}
|
|
|
|
void onPerCanvasPostDraw(SkCanvas* canvas) override {
|
|
auto context = canvas->recordingContext()->asDirectContext();
|
|
SkASSERT(context);
|
|
context->setResourceCacheLimits(fOldCount, fOldBytes);
|
|
for (int i = 0; i < kMaxImagesToDraw; ++i) {
|
|
fImages[i].reset();
|
|
}
|
|
}
|
|
|
|
void onDraw(int loops, SkCanvas* canvas) override {
|
|
auto dContext = GrAsDirectContext(canvas->recordingContext());
|
|
|
|
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.
|
|
if (dContext) {
|
|
dContext->flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
inline static constexpr int kImagesInBudget = 25;
|
|
inline static constexpr int kMinImagesToDraw = 15;
|
|
inline static constexpr int kMaxImagesToDraw = 35;
|
|
inline static constexpr int kSimulatedFrames = 80;
|
|
|
|
Mode fMode;
|
|
sk_sp<SkImage> fImages[kMaxImagesToDraw];
|
|
size_t fOldBytes;
|
|
int fOldCount;
|
|
|
|
using INHERITED = Benchmark;
|
|
};
|
|
|
|
DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
|
|
DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
|