| /* |
| * 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 "src/base/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(); |
| SkSurfaceProps props; |
| GrPaint grPaint; |
| SkPaintToGrPaint(context, sdc->colorInfo(), paint, view, 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 |