diff --git a/bench/BulkRectBench.cpp b/bench/BulkRectBench.cpp new file mode 100644 index 0000000000..c4a401f16f --- /dev/null +++ b/bench/BulkRectBench.cpp @@ -0,0 +1,271 @@ +/* + * 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/SkCanvas.h" +#include "include/core/SkImage.h" +#include "include/core/SkPaint.h" +#include "include/gpu/GrContext.h" +#include "include/utils/SkRandom.h" + +#include "src/gpu/GrClip.h" +#include "src/gpu/GrRenderTargetContext.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 +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 GrRenderTargetContext + return backend == kGPU_Backend; + } else { + return this->INHERITED::isSuitableFor(backend); + } + } + +protected: + SkRect fRects[kRectCount]; + sk_sp 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); + paint.setFilterQuality(kLow_SkFilterQuality); + + canvas->experimental_DrawEdgeAAImageSet(batch, kRectCount, nullptr, nullptr, &paint, + SkCanvas::kFast_SrcRectConstraint); + } + + void drawImagesRef(SkCanvas* canvas) const { + SkASSERT(kImageMode != ImageMode::kNone); + SkASSERT(kDrawMode == DrawMode::kRef); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setFilterQuality(kLow_SkFilterQuality); + + for (int i = 0; i < kRectCount; ++i) { + int imageIndex = kImageMode == ImageMode::kShared ? 0 : i; + SkIRect srcRect = SkIRect::MakeWH(fImages[imageIndex]->width(), + fImages[imageIndex]->height()); + canvas->drawImageRect(fImages[imageIndex].get(), srcRect, fRects[i], &paint, + SkCanvas::kFast_SrcRectConstraint); + } + } + + void drawSolidColorsBatch(SkCanvas* canvas) const { + SkASSERT(kImageMode == ImageMode::kNone); + SkASSERT(kDrawMode == DrawMode::kBatch); + + GrContext* context = canvas->getGrContext(); + SkASSERT(context); + + GrRenderTargetContext::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); + + GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext(); + SkMatrix view = canvas->getTotalMatrix(); + GrPaint grPaint; + SkPaintToGrPaint(context, rtc->colorInfo(), paint, view, &grPaint); + rtc->drawQuadSet(GrNoClip(), 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 + GrContext* context = canvas->getGrContext(); + for (int i = 0; i < kImageCount; ++i) { + SkBitmap bm; + bm.allocN32Pixels(256, 256); + bm.eraseColor(fColors[i].toSkColor()); + auto image = SkImage::MakeFromBitmap(bm); + + fImages[i] = context ? image->makeTextureImage(context) : std::move(image); + } + } + + 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 }; + } + + typedef Benchmark INHERITED; +}; + +// 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()); ) + +#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 diff --git a/gn/bench.gni b/gn/bench.gni index cd2ad907ed..cbd07d9dda 100644 --- a/gn/bench.gni +++ b/gn/bench.gni @@ -22,6 +22,7 @@ bench_sources = [ "$_bench/BlurImageFilterBench.cpp", "$_bench/BlurRectBench.cpp", "$_bench/BlurRectsBench.cpp", + "$_bench/BulkRectBench.cpp", "$_bench/CTConvertBench.cpp", "$_bench/ChartBench.cpp", "$_bench/ChecksumBench.cpp",