1 /* 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" 9 #include "include/core/SkBitmap.h" 10 #include "include/core/SkCanvas.h" 11 #include "include/core/SkImage.h" 12 #include "include/core/SkPaint.h" 13 #include "include/gpu/GrDirectContext.h" 14 #include "src/base/SkRandom.h" 15 #include "src/core/SkCanvasPriv.h" 16 #include "src/gpu/ganesh/GrOpsTypes.h" 17 #include "src/gpu/ganesh/SkGr.h" 18 #include "src/gpu/ganesh/SurfaceDrawContext.h" 19 20 // Benchmarks that exercise the bulk image and solid color quad APIs, under a variety of patterns: 21 enum class ImageMode { 22 kShared, // 1. One shared image referenced by every rectangle 23 kUnique, // 2. Unique image for every rectangle 24 kNone // 3. No image, solid color shading per rectangle 25 }; 26 // X 27 enum class DrawMode { 28 kBatch, // Bulk API submission, one call to draw every rectangle 29 kRef, // One standard SkCanvas draw call per rectangle 30 kQuad // One experimental draw call per rectangle, only for solid color draws 31 }; 32 // X 33 enum class RectangleLayout { 34 kRandom, // Random overlapping rectangles 35 kGrid // Small, non-overlapping rectangles in a grid covering the output surface 36 }; 37 38 // Benchmark runner that can be configured by template arguments. 39 template<int kRectCount, RectangleLayout kLayout, ImageMode kImageMode, DrawMode kDrawMode> 40 class BulkRectBench : public Benchmark { 41 public: 42 static_assert(kImageMode == ImageMode::kNone || kDrawMode != DrawMode::kQuad, 43 "kQuad only supported for solid color draws"); 44 45 inline static constexpr int kWidth = 1024; 46 inline static constexpr int kHeight = 1024; 47 48 // There will either be 0 images, 1 image, or 1 image per rect 49 inline static constexpr int kImageCount = kImageMode == ImageMode::kShared ? 50 1 : (kImageMode == ImageMode::kNone ? 0 : kRectCount); 51 isSuitableFor(Backend backend)52 bool isSuitableFor(Backend backend) override { 53 if (kDrawMode == DrawMode::kBatch && kImageMode == ImageMode::kNone) { 54 // Currently the bulk color quad API is only available on skgpu::v1::SurfaceDrawContext 55 return backend == kGPU_Backend; 56 } else { 57 return this->INHERITED::isSuitableFor(backend); 58 } 59 } 60 61 protected: 62 SkRect fRects[kRectCount]; 63 sk_sp<SkImage> fImages[kImageCount]; 64 SkColor4f fColors[kRectCount]; 65 SkString fName; 66 computeName()67 void computeName() { 68 fName = "bulkrect"; 69 fName.appendf("_%d", kRectCount); 70 if (kLayout == RectangleLayout::kRandom) { 71 fName.append("_random"); 72 } else { 73 fName.append("_grid"); 74 } 75 if (kImageMode == ImageMode::kShared) { 76 fName.append("_sharedimage"); 77 } else if (kImageMode == ImageMode::kUnique) { 78 fName.append("_uniqueimages"); 79 } else { 80 fName.append("_solidcolor"); 81 } 82 if (kDrawMode == DrawMode::kBatch) { 83 fName.append("_batch"); 84 } else if (kDrawMode == DrawMode::kRef) { 85 fName.append("_ref"); 86 } else { 87 fName.append("_quad"); 88 } 89 } 90 drawImagesBatch(SkCanvas * canvas) const91 void drawImagesBatch(SkCanvas* canvas) const { 92 SkASSERT(kImageMode != ImageMode::kNone); 93 SkASSERT(kDrawMode == DrawMode::kBatch); 94 95 SkCanvas::ImageSetEntry batch[kRectCount]; 96 for (int i = 0; i < kRectCount; ++i) { 97 int imageIndex = kImageMode == ImageMode::kShared ? 0 : i; 98 batch[i].fImage = fImages[imageIndex]; 99 batch[i].fSrcRect = SkRect::MakeIWH(fImages[imageIndex]->width(), 100 fImages[imageIndex]->height()); 101 batch[i].fDstRect = fRects[i]; 102 batch[i].fAAFlags = SkCanvas::kAll_QuadAAFlags; 103 } 104 105 SkPaint paint; 106 paint.setAntiAlias(true); 107 108 canvas->experimental_DrawEdgeAAImageSet(batch, kRectCount, nullptr, nullptr, 109 SkSamplingOptions(SkFilterMode::kLinear), &paint, 110 SkCanvas::kFast_SrcRectConstraint); 111 } 112 drawImagesRef(SkCanvas * canvas) const113 void drawImagesRef(SkCanvas* canvas) const { 114 SkASSERT(kImageMode != ImageMode::kNone); 115 SkASSERT(kDrawMode == DrawMode::kRef); 116 117 SkPaint paint; 118 paint.setAntiAlias(true); 119 120 for (int i = 0; i < kRectCount; ++i) { 121 int imageIndex = kImageMode == ImageMode::kShared ? 0 : i; 122 SkRect srcRect = SkRect::MakeIWH(fImages[imageIndex]->width(), 123 fImages[imageIndex]->height()); 124 canvas->drawImageRect(fImages[imageIndex].get(), srcRect, fRects[i], 125 SkSamplingOptions(SkFilterMode::kLinear), &paint, 126 SkCanvas::kFast_SrcRectConstraint); 127 } 128 } 129 drawSolidColorsBatch(SkCanvas * canvas) const130 void drawSolidColorsBatch(SkCanvas* canvas) const { 131 SkASSERT(kImageMode == ImageMode::kNone); 132 SkASSERT(kDrawMode == DrawMode::kBatch); 133 134 auto context = canvas->recordingContext(); 135 SkASSERT(context); 136 137 GrQuadSetEntry batch[kRectCount]; 138 for (int i = 0; i < kRectCount; ++i) { 139 batch[i].fRect = fRects[i]; 140 batch[i].fColor = fColors[i].premul(); 141 batch[i].fLocalMatrix = SkMatrix::I(); 142 batch[i].fAAFlags = GrQuadAAFlags::kAll; 143 } 144 145 SkPaint paint; 146 paint.setColor(SK_ColorWHITE); 147 paint.setAntiAlias(true); 148 149 auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas); 150 SkMatrix view = canvas->getLocalToDeviceAs3x3(); 151 SkSurfaceProps props; 152 GrPaint grPaint; 153 SkPaintToGrPaint(context, sdc->colorInfo(), paint, view, props, &grPaint); 154 sdc->drawQuadSet(nullptr, std::move(grPaint), view, batch, kRectCount); 155 } 156 drawSolidColorsRef(SkCanvas * canvas) const157 void drawSolidColorsRef(SkCanvas* canvas) const { 158 SkASSERT(kImageMode == ImageMode::kNone); 159 SkASSERT(kDrawMode == DrawMode::kRef || kDrawMode == DrawMode::kQuad); 160 161 SkPaint paint; 162 paint.setAntiAlias(true); 163 for (int i = 0; i < kRectCount; ++i) { 164 if (kDrawMode == DrawMode::kRef) { 165 paint.setColor4f(fColors[i]); 166 canvas->drawRect(fRects[i], paint); 167 } else { 168 canvas->experimental_DrawEdgeAAQuad(fRects[i], nullptr, SkCanvas::kAll_QuadAAFlags, 169 fColors[i], SkBlendMode::kSrcOver); 170 } 171 } 172 } 173 onGetName()174 const char* onGetName() override { 175 if (fName.isEmpty()) { 176 this->computeName(); 177 } 178 return fName.c_str(); 179 } 180 onDelayedSetup()181 void onDelayedSetup() override { 182 static constexpr SkScalar kMinRectSize = 0.2f; 183 static constexpr SkScalar kMaxRectSize = 300.f; 184 185 SkRandom rand; 186 for (int i = 0; i < kRectCount; i++) { 187 if (kLayout == RectangleLayout::kRandom) { 188 SkScalar w = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize; 189 SkScalar h = rand.nextF() * (kMaxRectSize - kMinRectSize) + kMinRectSize; 190 191 SkScalar x = rand.nextF() * (kWidth - w); 192 SkScalar y = rand.nextF() * (kHeight - h); 193 194 fRects[i].setXYWH(x, y, w, h); 195 } else { 196 int gridSize = SkScalarCeilToInt(SkScalarSqrt(kRectCount)); 197 SkASSERT(gridSize * gridSize >= kRectCount); 198 199 SkScalar w = (kWidth - 1.f) / gridSize; 200 SkScalar h = (kHeight - 1.f) / gridSize; 201 202 SkScalar x = (i % gridSize) * w + 0.5f; // Offset to ensure AA doesn't get disabled 203 SkScalar y = (i / gridSize) * h + 0.5f; 204 205 fRects[i].setXYWH(x, y, w, h); 206 } 207 208 // Make sure we don't extend outside the render target, don't want to include clipping 209 // in the benchmark. 210 SkASSERT(SkRect::MakeWH(kWidth, kHeight).contains(fRects[i])); 211 212 fColors[i] = {rand.nextF(), rand.nextF(), rand.nextF(), 1.f}; 213 } 214 } 215 onPerCanvasPreDraw(SkCanvas * canvas)216 void onPerCanvasPreDraw(SkCanvas* canvas) override { 217 // Push the skimages to the GPU when using the GPU backend so that the texture creation is 218 // not part of the bench measurements. Always remake the images since they are so simple, 219 // and since they are context-specific, this works when the bench runs multiple GPU backends 220 auto direct = GrAsDirectContext(canvas->recordingContext()); 221 for (int i = 0; i < kImageCount; ++i) { 222 SkBitmap bm; 223 bm.allocN32Pixels(256, 256); 224 bm.eraseColor(fColors[i].toSkColor()); 225 auto image = bm.asImage(); 226 227 fImages[i] = direct ? image->makeTextureImage(direct) : std::move(image); 228 } 229 } 230 onPerCanvasPostDraw(SkCanvas * canvas)231 void onPerCanvasPostDraw(SkCanvas* canvas) override { 232 for (int i = 0; i < kImageCount; ++i) { 233 // For Vulkan we need to make sure the bench isn't holding onto any refs to the 234 // GrContext when we go to delete the vulkan context (which happens before the bench is 235 // deleted). So reset all the images here so they aren't holding GrContext refs. 236 fImages[i].reset(); 237 } 238 } 239 onDraw(int loops,SkCanvas * canvas)240 void onDraw(int loops, SkCanvas* canvas) override { 241 for (int i = 0; i < loops; i++) { 242 if (kImageMode == ImageMode::kNone) { 243 if (kDrawMode == DrawMode::kBatch) { 244 this->drawSolidColorsBatch(canvas); 245 } else { 246 this->drawSolidColorsRef(canvas); 247 } 248 } else { 249 if (kDrawMode == DrawMode::kBatch) { 250 this->drawImagesBatch(canvas); 251 } else { 252 this->drawImagesRef(canvas); 253 } 254 } 255 } 256 } 257 onGetSize()258 SkIPoint onGetSize() override { 259 return { kWidth, kHeight }; 260 } 261 262 using INHERITED = Benchmark; 263 }; 264 265 // constructor call is wrapped in () so the macro doesn't break parsing the commas in the template 266 #define ADD_BENCH(n, layout, imageMode, drawMode) \ 267 DEF_BENCH( return (new BulkRectBench<n, layout, imageMode, drawMode>()); ) 268 269 #define ADD_BENCH_FAMILY(n, layout) \ 270 ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kBatch) \ 271 ADD_BENCH(n, layout, ImageMode::kShared, DrawMode::kRef) \ 272 ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kBatch) \ 273 ADD_BENCH(n, layout, ImageMode::kUnique, DrawMode::kRef) \ 274 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kBatch) \ 275 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kRef) \ 276 ADD_BENCH(n, layout, ImageMode::kNone, DrawMode::kQuad) 277 278 ADD_BENCH_FAMILY(1000, RectangleLayout::kRandom) 279 ADD_BENCH_FAMILY(1000, RectangleLayout::kGrid) 280 281 #undef ADD_BENCH_FAMILY 282 #undef ADD_BENCH 283