282 lines
11 KiB
C++
282 lines
11 KiB
C++
/*
|
|
* 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/GrSurfaceDrawContext.h"
|
|
#include "src/gpu/SkGr.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");
|
|
|
|
static constexpr int kWidth = 1024;
|
|
static constexpr int kHeight = 1024;
|
|
|
|
// There will either be 0 images, 1 image, or 1 image per rect
|
|
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 GrSurfaceDrawContext
|
|
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);
|
|
|
|
GrSurfaceDrawContext::QuadSetEntry 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);
|
|
|
|
GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
|
|
SkMatrix view = canvas->getLocalToDeviceAs3x3();
|
|
SkSimpleMatrixProvider matrixProvider(view);
|
|
GrPaint grPaint;
|
|
SkPaintToGrPaint(context, sdc->colorInfo(), paint, matrixProvider, &grPaint);
|
|
sdc->drawQuadSet(nullptr, std::move(grPaint), GrAA::kYes, 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
|