|  | /* | 
|  | * 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/GrOpsTypes.h" | 
|  | #include "src/gpu/SkGr.h" | 
|  | #include "src/gpu/v1/SurfaceDrawContext_v1.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); | 
|  | 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 |