blob: 0941c178a79d6f4ddb69228142af5f4803a0df7a [file] [log] [blame]
Michael Ludwig1beb8ae2019-10-28 13:22:11 -04001/*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "bench/Benchmark.h"
Mike Reedac9f0c92020-12-23 10:11:33 -05009#include "include/core/SkBitmap.h"
Michael Ludwig1beb8ae2019-10-28 13:22:11 -040010#include "include/core/SkCanvas.h"
11#include "include/core/SkImage.h"
12#include "include/core/SkPaint.h"
Robert Phillipsf0288102020-07-06 13:45:34 -040013#include "include/gpu/GrDirectContext.h"
Michael Ludwig1beb8ae2019-10-28 13:22:11 -040014#include "include/utils/SkRandom.h"
Brian Salomon8f7d9532020-12-23 09:16:59 -050015#include "src/core/SkCanvasPriv.h"
Brian Salomoneebe7352020-12-09 16:37:04 -050016#include "src/gpu/GrSurfaceDrawContext.h"
Michael Ludwig1beb8ae2019-10-28 13:22:11 -040017#include "src/gpu/SkGr.h"
18
19// Benchmarks that exercise the bulk image and solid color quad APIs, under a variety of patterns:
20enum class ImageMode {
21 kShared, // 1. One shared image referenced by every rectangle
22 kUnique, // 2. Unique image for every rectangle
23 kNone // 3. No image, solid color shading per rectangle
24};
25// X
26enum class DrawMode {
27 kBatch, // Bulk API submission, one call to draw every rectangle
28 kRef, // One standard SkCanvas draw call per rectangle
29 kQuad // One experimental draw call per rectangle, only for solid color draws
30};
31// X
32enum class RectangleLayout {
33 kRandom, // Random overlapping rectangles
34 kGrid // Small, non-overlapping rectangles in a grid covering the output surface
35};
36
37// Benchmark runner that can be configured by template arguments.
38template<int kRectCount, RectangleLayout kLayout, ImageMode kImageMode, DrawMode kDrawMode>
39class BulkRectBench : public Benchmark {
40public:
41 static_assert(kImageMode == ImageMode::kNone || kDrawMode != DrawMode::kQuad,
42 "kQuad only supported for solid color draws");
43
44 static constexpr int kWidth = 1024;
45 static constexpr int kHeight = 1024;
46
47 // There will either be 0 images, 1 image, or 1 image per rect
48 static constexpr int kImageCount = kImageMode == ImageMode::kShared ?
49 1 : (kImageMode == ImageMode::kNone ? 0 : kRectCount);
50
51 bool isSuitableFor(Backend backend) override {
52 if (kDrawMode == DrawMode::kBatch && kImageMode == ImageMode::kNone) {
Brian Salomoneebe7352020-12-09 16:37:04 -050053 // Currently the bulk color quad API is only available on GrSurfaceDrawContext
Michael Ludwig1beb8ae2019-10-28 13:22:11 -040054 return backend == kGPU_Backend;
55 } else {
56 return this->INHERITED::isSuitableFor(backend);
57 }
58 }
59
60protected:
61 SkRect fRects[kRectCount];
62 sk_sp<SkImage> fImages[kImageCount];
63 SkColor4f fColors[kRectCount];
64 SkString fName;
65
66 void computeName() {
67 fName = "bulkrect";
68 fName.appendf("_%d", kRectCount);
69 if (kLayout == RectangleLayout::kRandom) {
70 fName.append("_random");
71 } else {
72 fName.append("_grid");
73 }
74 if (kImageMode == ImageMode::kShared) {
75 fName.append("_sharedimage");
76 } else if (kImageMode == ImageMode::kUnique) {
77 fName.append("_uniqueimages");
78 } else {
79 fName.append("_solidcolor");
80 }
81 if (kDrawMode == DrawMode::kBatch) {
82 fName.append("_batch");
83 } else if (kDrawMode == DrawMode::kRef) {
84 fName.append("_ref");
85 } else {
86 fName.append("_quad");
87 }
88 }
89
90 void drawImagesBatch(SkCanvas* canvas) const {
91 SkASSERT(kImageMode != ImageMode::kNone);
92 SkASSERT(kDrawMode == DrawMode::kBatch);
93
94 SkCanvas::ImageSetEntry batch[kRectCount];
95 for (int i = 0; i < kRectCount; ++i) {
96 int imageIndex = kImageMode == ImageMode::kShared ? 0 : i;
97 batch[i].fImage = fImages[imageIndex];
98 batch[i].fSrcRect = SkRect::MakeIWH(fImages[imageIndex]->width(),
99 fImages[imageIndex]->height());
100 batch[i].fDstRect = fRects[i];
101 batch[i].fAAFlags = SkCanvas::kAll_QuadAAFlags;
102 }
103
104 SkPaint paint;
105 paint.setAntiAlias(true);
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400106
Mike Reed99116302021-01-25 11:37:10 -0500107 canvas->experimental_DrawEdgeAAImageSet(batch, kRectCount, nullptr, nullptr,
108 SkSamplingOptions(SkFilterMode::kLinear), &paint,
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400109 SkCanvas::kFast_SrcRectConstraint);
110 }
111
112 void drawImagesRef(SkCanvas* canvas) const {
113 SkASSERT(kImageMode != ImageMode::kNone);
114 SkASSERT(kDrawMode == DrawMode::kRef);
115
116 SkPaint paint;
117 paint.setAntiAlias(true);
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400118
119 for (int i = 0; i < kRectCount; ++i) {
120 int imageIndex = kImageMode == ImageMode::kShared ? 0 : i;
Mike Reed039f1362021-01-27 21:21:08 -0500121 SkRect srcRect = SkRect::MakeIWH(fImages[imageIndex]->width(),
122 fImages[imageIndex]->height());
123 canvas->drawImageRect(fImages[imageIndex].get(), srcRect, fRects[i],
124 SkSamplingOptions(SkFilterMode::kLinear), &paint,
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400125 SkCanvas::kFast_SrcRectConstraint);
126 }
127 }
128
129 void drawSolidColorsBatch(SkCanvas* canvas) const {
130 SkASSERT(kImageMode == ImageMode::kNone);
131 SkASSERT(kDrawMode == DrawMode::kBatch);
132
Robert Phillipsf0288102020-07-06 13:45:34 -0400133 auto context = canvas->recordingContext();
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400134 SkASSERT(context);
135
Brian Salomoneebe7352020-12-09 16:37:04 -0500136 GrSurfaceDrawContext::QuadSetEntry batch[kRectCount];
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400137 for (int i = 0; i < kRectCount; ++i) {
138 batch[i].fRect = fRects[i];
139 batch[i].fColor = fColors[i].premul();
140 batch[i].fLocalMatrix = SkMatrix::I();
141 batch[i].fAAFlags = GrQuadAAFlags::kAll;
142 }
143
144 SkPaint paint;
145 paint.setColor(SK_ColorWHITE);
146 paint.setAntiAlias(true);
147
Brian Salomon8f7d9532020-12-23 09:16:59 -0500148 GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
Mike Reed1a4140e2020-12-03 11:21:31 -0500149 SkMatrix view = canvas->getLocalToDeviceAs3x3();
Brian Osman449b1152020-04-15 16:43:00 -0400150 SkSimpleMatrixProvider matrixProvider(view);
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400151 GrPaint grPaint;
Brian Salomon8f7d9532020-12-23 09:16:59 -0500152 SkPaintToGrPaint(context, sdc->colorInfo(), paint, matrixProvider, &grPaint);
153 sdc->drawQuadSet(nullptr, std::move(grPaint), GrAA::kYes, view, batch, kRectCount);
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400154 }
155
156 void drawSolidColorsRef(SkCanvas* canvas) const {
157 SkASSERT(kImageMode == ImageMode::kNone);
158 SkASSERT(kDrawMode == DrawMode::kRef || kDrawMode == DrawMode::kQuad);
159
160 SkPaint paint;
161 paint.setAntiAlias(true);
162 for (int i = 0; i < kRectCount; ++i) {
163 if (kDrawMode == DrawMode::kRef) {
164 paint.setColor4f(fColors[i]);
165 canvas->drawRect(fRects[i], paint);
166 } else {
167 canvas->experimental_DrawEdgeAAQuad(fRects[i], nullptr, SkCanvas::kAll_QuadAAFlags,
168 fColors[i], SkBlendMode::kSrcOver);
169 }
170 }
171 }
172
173 const char* onGetName() override {
174 if (fName.isEmpty()) {
175 this->computeName();
176 }
177 return fName.c_str();
178 }
179
180 void onDelayedSetup() override {
181 static constexpr SkScalar kMinRectSize = 0.2f;
182 static constexpr SkScalar kMaxRectSize = 300.f;
183
184 SkRandom rand;
185 for (int i = 0; i < kRectCount; i++) {
186 if (kLayout == RectangleLayout::kRandom) {
187 SkScalar w = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize;
188 SkScalar h = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize;
189
190 SkScalar x = rand.nextF() * (kWidth - w);
191 SkScalar y = rand.nextF() * (kHeight - h);
192
193 fRects[i].setXYWH(x, y, w, h);
194 } else {
195 int gridSize = SkScalarCeilToInt(SkScalarSqrt(kRectCount));
196 SkASSERT(gridSize * gridSize >= kRectCount);
197
198 SkScalar w = (kWidth - 1.f) / gridSize;
199 SkScalar h = (kHeight - 1.f) / gridSize;
200
201 SkScalar x = (i % gridSize) * w + 0.5f; // Offset to ensure AA doesn't get disabled
202 SkScalar y = (i / gridSize) * h + 0.5f;
203
204 fRects[i].setXYWH(x, y, w, h);
205 }
206
207 // Make sure we don't extend outside the render target, don't want to include clipping
208 // in the benchmark.
209 SkASSERT(SkRect::MakeWH(kWidth, kHeight).contains(fRects[i]));
210
211 fColors[i] = {rand.nextF(), rand.nextF(), rand.nextF(), 1.f};
212 }
213 }
214
215 void onPerCanvasPreDraw(SkCanvas* canvas) override {
216 // Push the skimages to the GPU when using the GPU backend so that the texture creation is
217 // not part of the bench measurements. Always remake the images since they are so simple,
218 // and since they are context-specific, this works when the bench runs multiple GPU backends
Adlai Holler4caa9352020-07-16 10:58:58 -0400219 auto direct = GrAsDirectContext(canvas->recordingContext());
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400220 for (int i = 0; i < kImageCount; ++i) {
221 SkBitmap bm;
222 bm.allocN32Pixels(256, 256);
223 bm.eraseColor(fColors[i].toSkColor());
Mike Reeddc607e32020-12-23 11:50:36 -0500224 auto image = bm.asImage();
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400225
Adlai Holler4caa9352020-07-16 10:58:58 -0400226 fImages[i] = direct ? image->makeTextureImage(direct) : std::move(image);
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400227 }
228 }
229
Greg Daniel5ed3c112020-06-18 15:59:17 -0400230 void onPerCanvasPostDraw(SkCanvas* canvas) override {
231 for (int i = 0; i < kImageCount; ++i) {
232 // For Vulkan we need to make sure the bench isn't holding onto any refs to the
233 // GrContext when we go to delete the vulkan context (which happens before the bench is
234 // deleted). So reset all the images here so they aren't holding GrContext refs.
235 fImages[i].reset();
236 }
237 }
238
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400239 void onDraw(int loops, SkCanvas* canvas) override {
240 for (int i = 0; i < loops; i++) {
241 if (kImageMode == ImageMode::kNone) {
242 if (kDrawMode == DrawMode::kBatch) {
243 this->drawSolidColorsBatch(canvas);
244 } else {
245 this->drawSolidColorsRef(canvas);
246 }
247 } else {
248 if (kDrawMode == DrawMode::kBatch) {
249 this->drawImagesBatch(canvas);
250 } else {
251 this->drawImagesRef(canvas);
252 }
253 }
254 }
255 }
256
257 SkIPoint onGetSize() override {
258 return { kWidth, kHeight };
259 }
260
John Stiles7571f9e2020-09-02 22:42:33 -0400261 using INHERITED = Benchmark;
Michael Ludwig1beb8ae2019-10-28 13:22:11 -0400262};
263
264// constructor call is wrapped in () so the macro doesn't break parsing the commas in the template
265#define ADD_BENCH(n, layout, imageMode, drawMode) \
266 DEF_BENCH( return (new BulkRectBench<n, layout, imageMode, drawMode>()); )
267
268#define ADD_BENCH_FAMILY(n, layout) \
269 ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kBatch) \
270 ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kRef) \
271 ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kBatch) \
272 ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kRef) \
273 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kBatch) \
274 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kRef) \
275 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kQuad)
276
277ADD_BENCH_FAMILY(1000, RectangleLayout::kRandom)
278ADD_BENCH_FAMILY(1000, RectangleLayout::kGrid)
279
280#undef ADD_BENCH_FAMILY
281#undef ADD_BENCH