blob: 425f7f0e58c964fa091dd985ee2d311bfccb6e3d [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* 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/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkImage.h"
#include "include/core/SkPaint.h"
#include "include/gpu/GrDirectContext.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkCanvasPriv.h"
#include "src/gpu/ganesh/GrOpsTypes.h"
#include "src/gpu/ganesh/SkGr.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
// Benchmarks that exercise the bulk image and solid color quad APIs, under a variety of patterns:
enum class ImageMode {
kShared, // 1. One shared image referenced by every rectangle
kUnique, // 2. Unique image for every rectangle
kNone // 3. No image, solid color shading per rectangle
};
// X
enum class DrawMode {
kBatch, // Bulk API submission, one call to draw every rectangle
kRef, // One standard SkCanvas draw call per rectangle
kQuad // One experimental draw call per rectangle, only for solid color draws
};
// X
enum class RectangleLayout {
kRandom, // Random overlapping rectangles
kGrid // Small, non-overlapping rectangles in a grid covering the output surface
};
// Benchmark runner that can be configured by template arguments.
template<int kRectCount, RectangleLayout kLayout, ImageMode kImageMode, DrawMode kDrawMode>
class BulkRectBench : public Benchmark {
public:
static_assert(kImageMode == ImageMode::kNone || kDrawMode != DrawMode::kQuad,
"kQuad only supported for solid color draws");
inline static constexpr int kWidth = 1024;
inline static constexpr int kHeight = 1024;
// There will either be 0 images, 1 image, or 1 image per rect
inline static constexpr int kImageCount = kImageMode == ImageMode::kShared ?
1 : (kImageMode == ImageMode::kNone ? 0 : kRectCount);
bool isSuitableFor(Backend backend) override {
if (kDrawMode == DrawMode::kBatch && kImageMode == ImageMode::kNone) {
// Currently the bulk color quad API is only available on skgpu::v1::SurfaceDrawContext
return backend == kGPU_Backend;
} else {
return this->INHERITED::isSuitableFor(backend);
}
}
protected:
SkRect fRects[kRectCount];
sk_sp<SkImage> fImages[kImageCount];
SkColor4f fColors[kRectCount];
SkString fName;
void computeName() {
fName = "bulkrect";
fName.appendf("_%d", kRectCount);
if (kLayout == RectangleLayout::kRandom) {
fName.append("_random");
} else {
fName.append("_grid");
}
if (kImageMode == ImageMode::kShared) {
fName.append("_sharedimage");
} else if (kImageMode == ImageMode::kUnique) {
fName.append("_uniqueimages");
} else {
fName.append("_solidcolor");
}
if (kDrawMode == DrawMode::kBatch) {
fName.append("_batch");
} else if (kDrawMode == DrawMode::kRef) {
fName.append("_ref");
} else {
fName.append("_quad");
}
}
void drawImagesBatch(SkCanvas* canvas) const {
SkASSERT(kImageMode != ImageMode::kNone);
SkASSERT(kDrawMode == DrawMode::kBatch);
SkCanvas::ImageSetEntry batch[kRectCount];
for (int i = 0; i < kRectCount; ++i) {
int imageIndex = kImageMode == ImageMode::kShared ? 0 : i;
batch[i].fImage = fImages[imageIndex];
batch[i].fSrcRect = SkRect::MakeIWH(fImages[imageIndex]->width(),
fImages[imageIndex]->height());
batch[i].fDstRect = fRects[i];
batch[i].fAAFlags = SkCanvas::kAll_QuadAAFlags;
}
SkPaint paint;
paint.setAntiAlias(true);
canvas->experimental_DrawEdgeAAImageSet(batch, kRectCount, nullptr, nullptr,
SkSamplingOptions(SkFilterMode::kLinear), &paint,
SkCanvas::kFast_SrcRectConstraint);
}
void drawImagesRef(SkCanvas* canvas) const {
SkASSERT(kImageMode != ImageMode::kNone);
SkASSERT(kDrawMode == DrawMode::kRef);
SkPaint paint;
paint.setAntiAlias(true);
for (int i = 0; i < kRectCount; ++i) {
int imageIndex = kImageMode == ImageMode::kShared ? 0 : i;
SkRect srcRect = SkRect::MakeIWH(fImages[imageIndex]->width(),
fImages[imageIndex]->height());
canvas->drawImageRect(fImages[imageIndex].get(), srcRect, fRects[i],
SkSamplingOptions(SkFilterMode::kLinear), &paint,
SkCanvas::kFast_SrcRectConstraint);
}
}
void drawSolidColorsBatch(SkCanvas* canvas) const {
SkASSERT(kImageMode == ImageMode::kNone);
SkASSERT(kDrawMode == DrawMode::kBatch);
auto context = canvas->recordingContext();
SkASSERT(context);
GrQuadSetEntry batch[kRectCount];
for (int i = 0; i < kRectCount; ++i) {
batch[i].fRect = fRects[i];
batch[i].fColor = fColors[i].premul();
batch[i].fLocalMatrix = SkMatrix::I();
batch[i].fAAFlags = GrQuadAAFlags::kAll;
}
SkPaint paint;
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(true);
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
SkMatrix view = canvas->getLocalToDeviceAs3x3();
SkMatrixProvider matrixProvider(view);
SkSurfaceProps props;
GrPaint grPaint;
SkPaintToGrPaint(context, sdc->colorInfo(), paint, matrixProvider, props, &grPaint);
sdc->drawQuadSet(nullptr, std::move(grPaint), view, batch, kRectCount);
}
void drawSolidColorsRef(SkCanvas* canvas) const {
SkASSERT(kImageMode == ImageMode::kNone);
SkASSERT(kDrawMode == DrawMode::kRef || kDrawMode == DrawMode::kQuad);
SkPaint paint;
paint.setAntiAlias(true);
for (int i = 0; i < kRectCount; ++i) {
if (kDrawMode == DrawMode::kRef) {
paint.setColor4f(fColors[i]);
canvas->drawRect(fRects[i], paint);
} else {
canvas->experimental_DrawEdgeAAQuad(fRects[i], nullptr, SkCanvas::kAll_QuadAAFlags,
fColors[i], SkBlendMode::kSrcOver);
}
}
}
const char* onGetName() override {
if (fName.isEmpty()) {
this->computeName();
}
return fName.c_str();
}
void onDelayedSetup() override {
static constexpr SkScalar kMinRectSize = 0.2f;
static constexpr SkScalar kMaxRectSize = 300.f;
SkRandom rand;
for (int i = 0; i < kRectCount; i++) {
if (kLayout == RectangleLayout::kRandom) {
SkScalar w = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize;
SkScalar h = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize;
SkScalar x = rand.nextF() * (kWidth - w);
SkScalar y = rand.nextF() * (kHeight - h);
fRects[i].setXYWH(x, y, w, h);
} else {
int gridSize = SkScalarCeilToInt(SkScalarSqrt(kRectCount));
SkASSERT(gridSize * gridSize >= kRectCount);
SkScalar w = (kWidth - 1.f) / gridSize;
SkScalar h = (kHeight - 1.f) / gridSize;
SkScalar x = (i % gridSize) * w + 0.5f; // Offset to ensure AA doesn't get disabled
SkScalar y = (i / gridSize) * h + 0.5f;
fRects[i].setXYWH(x, y, w, h);
}
// Make sure we don't extend outside the render target, don't want to include clipping
// in the benchmark.
SkASSERT(SkRect::MakeWH(kWidth, kHeight).contains(fRects[i]));
fColors[i] = {rand.nextF(), rand.nextF(), rand.nextF(), 1.f};
}
}
void onPerCanvasPreDraw(SkCanvas* canvas) override {
// Push the skimages to the GPU when using the GPU backend so that the texture creation is
// not part of the bench measurements. Always remake the images since they are so simple,
// and since they are context-specific, this works when the bench runs multiple GPU backends
auto direct = GrAsDirectContext(canvas->recordingContext());
for (int i = 0; i < kImageCount; ++i) {
SkBitmap bm;
bm.allocN32Pixels(256, 256);
bm.eraseColor(fColors[i].toSkColor());
auto image = bm.asImage();
fImages[i] = direct ? image->makeTextureImage(direct) : std::move(image);
}
}
void onPerCanvasPostDraw(SkCanvas* canvas) override {
for (int i = 0; i < kImageCount; ++i) {
// For Vulkan we need to make sure the bench isn't holding onto any refs to the
// GrContext when we go to delete the vulkan context (which happens before the bench is
// deleted). So reset all the images here so they aren't holding GrContext refs.
fImages[i].reset();
}
}
void onDraw(int loops, SkCanvas* canvas) override {
for (int i = 0; i < loops; i++) {
if (kImageMode == ImageMode::kNone) {
if (kDrawMode == DrawMode::kBatch) {
this->drawSolidColorsBatch(canvas);
} else {
this->drawSolidColorsRef(canvas);
}
} else {
if (kDrawMode == DrawMode::kBatch) {
this->drawImagesBatch(canvas);
} else {
this->drawImagesRef(canvas);
}
}
}
}
SkIPoint onGetSize() override {
return { kWidth, kHeight };
}
using INHERITED = Benchmark;
};
// constructor call is wrapped in () so the macro doesn't break parsing the commas in the template
#define ADD_BENCH(n, layout, imageMode, drawMode) \
DEF_BENCH( return (new BulkRectBench<n, layout, imageMode, drawMode>()); )
#define ADD_BENCH_FAMILY(n, layout) \
ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kBatch) \
ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kRef) \
ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kBatch) \
ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kRef) \
ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kBatch) \
ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kRef) \
ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kQuad)
ADD_BENCH_FAMILY(1000, RectangleLayout::kRandom)
ADD_BENCH_FAMILY(1000, RectangleLayout::kGrid)
#undef ADD_BENCH_FAMILY
#undef ADD_BENCH