1 /* 2 * Copyright 2018 Google Inc. 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 <memory> 9 10 #include "bench/Benchmark.h" 11 12 #include "include/core/SkCanvas.h" 13 #include "include/core/SkImage.h" 14 #include "include/core/SkSurface.h" 15 #include "include/gpu/GrDirectContext.h" 16 #include "include/private/SkTemplates.h" 17 #include "include/utils/SkRandom.h" 18 19 enum class ClampingMode { 20 // Submit image set entries with the fast constraint 21 kAlwaysFast, 22 // Submit image set entries with the strict constraint 23 kAlwaysStrict, 24 // Submit non-right/bottom tiles as fast, the bottom-right corner as strict, and bottom or right 25 // edge tiles as strict with geometry modification to match content area. These will be 26 // submitted from left-to-right, top-to-bottom so will necessarily be split into many batches. 27 kChromeTiling_RowMajor, 28 // As above, but group all fast tiles first, then bottom and right edge tiles in a second batch. 29 kChromeTiling_Optimal 30 }; 31 32 enum class TransformMode { 33 // Tiles will be axis aligned on integer pixels 34 kNone, 35 // Subpixel, tiles will be axis aligned but adjusted to subpixel coordinates 36 kSubpixel, 37 // Rotated, tiles will be rotated globally; they won't overlap but their device space bounds may 38 kRotated, 39 // Perspective, tiles will have global perspective 40 kPerspective 41 }; 42 43 /** 44 * Simulates drawing layers images in a grid a la a tile based compositor. 45 */ 46 class CompositingImages : public Benchmark { 47 public: CompositingImages(SkISize imageSize,SkISize tileSize,SkISize tileGridSize,ClampingMode clampMode,TransformMode transformMode,int layerCnt)48 CompositingImages(SkISize imageSize, SkISize tileSize, SkISize tileGridSize, 49 ClampingMode clampMode, TransformMode transformMode, int layerCnt) 50 : fImageSize(imageSize) 51 , fTileSize(tileSize) 52 , fTileGridSize(tileGridSize) 53 , fClampMode(clampMode) 54 , fTransformMode(transformMode) 55 , fLayerCnt(layerCnt) { 56 fName.appendf("compositing_images_tile_size_%dx%d_grid_%dx%d_layers_%d", 57 fTileSize.fWidth, fTileSize.fHeight, fTileGridSize.fWidth, 58 fTileGridSize.fHeight, fLayerCnt); 59 if (imageSize != tileSize) { 60 fName.appendf("_image_%dx%d", imageSize.fWidth, imageSize.fHeight); 61 } 62 switch(clampMode) { 63 case ClampingMode::kAlwaysFast: 64 fName.append("_fast"); 65 break; 66 case ClampingMode::kAlwaysStrict: 67 fName.append("_strict"); 68 break; 69 case ClampingMode::kChromeTiling_RowMajor: 70 fName.append("_chrome"); 71 break; 72 case ClampingMode::kChromeTiling_Optimal: 73 fName.append("_chrome_optimal"); 74 break; 75 } 76 switch(transformMode) { 77 case TransformMode::kNone: 78 break; 79 case TransformMode::kSubpixel: 80 fName.append("_subpixel"); 81 break; 82 case TransformMode::kRotated: 83 fName.append("_rotated"); 84 break; 85 case TransformMode::kPerspective: 86 fName.append("_persp"); 87 break; 88 } 89 } 90 isSuitableFor(Backend backend)91 bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; } 92 93 protected: onGetName()94 const char* onGetName() override { return fName.c_str(); } 95 onPerCanvasPreDraw(SkCanvas * canvas)96 void onPerCanvasPreDraw(SkCanvas* canvas) override { 97 // Use image size, which may be larger than the tile size (emulating how Chrome specifies 98 // their tiles). 99 auto ii = SkImageInfo::Make(fImageSize.fWidth, fImageSize.fHeight, kRGBA_8888_SkColorType, 100 kPremul_SkAlphaType, nullptr); 101 SkRandom random; 102 int numImages = fLayerCnt * fTileGridSize.fWidth * fTileGridSize.fHeight; 103 fImages = std::make_unique<sk_sp<SkImage>[]>(numImages); 104 for (int i = 0; i < numImages; ++i) { 105 auto surf = canvas->makeSurface(ii); 106 SkColor color = random.nextU(); 107 surf->getCanvas()->clear(color); 108 SkPaint paint; 109 paint.setColor(~color); 110 paint.setBlendMode(SkBlendMode::kSrc); 111 // While the image may be bigger than fTileSize, prepare its content as if fTileSize 112 // is what will be visible. 113 surf->getCanvas()->drawRect( 114 SkRect::MakeLTRB(3, 3, fTileSize.fWidth - 3, fTileSize.fHeight - 3), paint); 115 fImages[i] = surf->makeImageSnapshot(); 116 } 117 } 118 onPerCanvasPostDraw(SkCanvas *)119 void onPerCanvasPostDraw(SkCanvas*) override { fImages.reset(); } 120 onDraw(int loops,SkCanvas * canvas)121 void onDraw(int loops, SkCanvas* canvas) override { 122 SkPaint paint; 123 paint.setAntiAlias(true); 124 SkSamplingOptions sampling(SkFilterMode::kLinear); 125 126 canvas->save(); 127 canvas->concat(this->getTransform()); 128 129 for (int loop = 0; loop < loops; ++loop) { 130 for (int l = 0; l < fLayerCnt; ++l) { 131 SkAutoTArray<SkCanvas::ImageSetEntry> set( 132 fTileGridSize.fWidth * fTileGridSize.fHeight); 133 134 if (fClampMode == ClampingMode::kAlwaysFast || 135 fClampMode == ClampingMode::kAlwaysStrict) { 136 // Simple 2D for loop, submit everything as a single batch 137 int i = 0; 138 for (int y = 0; y < fTileGridSize.fHeight; ++y) { 139 for (int x = 0; x < fTileGridSize.fWidth; ++x) { 140 set[i++] = this->getEntry(x, y, l); 141 } 142 } 143 144 SkCanvas::SrcRectConstraint constraint = 145 fClampMode == ClampingMode::kAlwaysFast 146 ? SkCanvas::kFast_SrcRectConstraint 147 : SkCanvas::kStrict_SrcRectConstraint; 148 canvas->experimental_DrawEdgeAAImageSet(set.get(), i, nullptr, nullptr, 149 sampling, &paint, constraint); 150 } else if (fClampMode == ClampingMode::kChromeTiling_RowMajor) { 151 // Same tile order, but break batching between fast and strict sections, and 152 // adjust bottom and right tiles to encode content area distinct from src rect. 153 int i = 0; 154 for (int y = 0; y < fTileGridSize.fHeight - 1; ++y) { 155 int rowStart = i; 156 for (int x = 0; x < fTileGridSize.fWidth - 1; ++x) { 157 set[i++] = this->getEntry(x, y, l); 158 } 159 // Flush "fast" horizontal row 160 canvas->experimental_DrawEdgeAAImageSet(set.get() + rowStart, 161 fTileGridSize.fWidth - 1, nullptr, nullptr, sampling, &paint, 162 SkCanvas::kFast_SrcRectConstraint); 163 // Then flush a single adjusted entry for the right edge 164 SkPoint dstQuad[4]; 165 set[i++] = this->getAdjustedEntry(fTileGridSize.fWidth - 1, y, l, dstQuad); 166 canvas->experimental_DrawEdgeAAImageSet( 167 set.get() + fTileGridSize.fWidth - 1, 1, dstQuad, nullptr, sampling, 168 &paint, SkCanvas::kStrict_SrcRectConstraint); 169 } 170 // For last row, accumulate it as a single strict batch 171 int rowStart = i; 172 SkAutoTArray<SkPoint> dstQuads(4 * (fTileGridSize.fWidth - 1)); 173 for (int x = 0; x < fTileGridSize.fWidth - 1; ++x) { 174 set[i++] = this->getAdjustedEntry(x, fTileGridSize.fHeight - 1, l, 175 dstQuads.get() + x * 4); 176 } 177 // The corner can use conventional strict mode without geometric adjustment 178 set[i++] = this->getEntry( 179 fTileGridSize.fWidth - 1, fTileGridSize.fHeight - 1, l); 180 canvas->experimental_DrawEdgeAAImageSet(set.get() + rowStart, 181 fTileGridSize.fWidth, dstQuads.get(), nullptr, sampling, &paint, 182 SkCanvas::kStrict_SrcRectConstraint); 183 } else { 184 SkASSERT(fClampMode == ClampingMode::kChromeTiling_Optimal); 185 int i = 0; 186 // Interior fast tiles 187 for (int y = 0; y < fTileGridSize.fHeight - 1; ++y) { 188 for (int x = 0; x < fTileGridSize.fWidth - 1; ++x) { 189 set[i++] = this->getEntry(x, y, l); 190 } 191 } 192 canvas->experimental_DrawEdgeAAImageSet(set.get(), i, nullptr, nullptr, 193 sampling, &paint, 194 SkCanvas::kFast_SrcRectConstraint); 195 196 // Right edge 197 int strictStart = i; 198 SkAutoTArray<SkPoint> dstQuads( 199 4 * (fTileGridSize.fWidth + fTileGridSize.fHeight - 2)); 200 for (int y = 0; y < fTileGridSize.fHeight - 1; ++y) { 201 set[i++] = this->getAdjustedEntry(fTileGridSize.fWidth - 1, y, l, 202 dstQuads.get() + y * 4); 203 } 204 canvas->experimental_DrawEdgeAAImageSet(set.get() + strictStart, 205 i - strictStart, dstQuads.get(), nullptr, sampling, &paint, 206 SkCanvas::kStrict_SrcRectConstraint); 207 int quadStart = 4 * (fTileGridSize.fHeight - 1); 208 strictStart = i; 209 for (int x = 0; x < fTileGridSize.fWidth - 1; ++x) { 210 set[i++] = this->getAdjustedEntry(x, fTileGridSize.fHeight - 1, l, 211 dstQuads.get() + quadStart + x * 4); 212 } 213 set[i++] = this->getEntry( 214 fTileGridSize.fWidth - 1, fTileGridSize.fHeight - 1, l); 215 canvas->experimental_DrawEdgeAAImageSet(set.get() + strictStart, 216 i - strictStart, dstQuads.get() + quadStart, nullptr, sampling, &paint, 217 SkCanvas::kStrict_SrcRectConstraint); 218 } 219 } 220 // Prevent any batching between composited "frames". 221 auto surface = canvas->getSurface(); 222 if (surface) { 223 surface->flush(); 224 } 225 } 226 canvas->restore(); 227 } 228 229 private: getTransform() const230 SkMatrix getTransform() const { 231 SkMatrix m; 232 switch(fTransformMode) { 233 case TransformMode::kNone: 234 m.setIdentity(); 235 break; 236 case TransformMode::kSubpixel: 237 m.setTranslate(0.5f, 0.5f); 238 break; 239 case TransformMode::kRotated: 240 m.setRotate(15.f); 241 break; 242 case TransformMode::kPerspective: { 243 m.setIdentity(); 244 m.setPerspY(0.001f); 245 m.setSkewX(SkIntToScalar(8) / 25); 246 break; 247 } 248 } 249 return m; 250 } 251 onGetSize()252 SkIPoint onGetSize() override { 253 SkRect size = SkRect::MakeWH(1.25f * fTileSize.fWidth * fTileGridSize.fWidth, 254 1.25f * fTileSize.fHeight * fTileGridSize.fHeight); 255 this->getTransform().mapRect(&size); 256 return SkIPoint::Make(SkScalarCeilToInt(size.width()), SkScalarCeilToInt(size.height())); 257 } 258 getEdgeFlags(int x,int y) const259 unsigned getEdgeFlags(int x, int y) const { 260 unsigned flags = SkCanvas::kNone_QuadAAFlags; 261 if (x == 0) { 262 flags |= SkCanvas::kLeft_QuadAAFlag; 263 } else if (x == fTileGridSize.fWidth - 1) { 264 flags |= SkCanvas::kRight_QuadAAFlag; 265 } 266 267 if (y == 0) { 268 flags |= SkCanvas::kTop_QuadAAFlag; 269 } else if (y == fTileGridSize.fHeight - 1) { 270 flags |= SkCanvas::kBottom_QuadAAFlag; 271 } 272 return flags; 273 } 274 getEntry(int x,int y,int layer) const275 SkCanvas::ImageSetEntry getEntry(int x, int y, int layer) const { 276 int imageIdx = 277 fTileGridSize.fWidth * fTileGridSize.fHeight * layer + fTileGridSize.fWidth * y + x; 278 SkRect srcRect = SkRect::Make(fTileSize); 279 // Make a non-identity transform between src and dst so bilerp isn't disabled. 280 float dstWidth = srcRect.width() * 1.25f; 281 float dstHeight = srcRect.height() * 1.25f; 282 SkRect dstRect = SkRect::MakeXYWH(dstWidth * x, dstHeight * y, dstWidth, dstHeight); 283 return SkCanvas::ImageSetEntry(fImages[imageIdx], srcRect, dstRect, 1.f, 284 this->getEdgeFlags(x, y)); 285 } 286 getAdjustedEntry(int x,int y,int layer,SkPoint dstQuad[4]) const287 SkCanvas::ImageSetEntry getAdjustedEntry(int x, int y, int layer, SkPoint dstQuad[4]) const { 288 SkASSERT(x == fTileGridSize.fWidth - 1 || y == fTileGridSize.fHeight - 1); 289 290 SkCanvas::ImageSetEntry entry = this->getEntry(x, y, layer); 291 SkRect contentRect = SkRect::Make(fImageSize); 292 if (x == fTileGridSize.fWidth - 1) { 293 // Right edge, so restrict horizontal content to tile width 294 contentRect.fRight = fTileSize.fWidth; 295 } 296 if (y == fTileGridSize.fHeight - 1) { 297 // Bottom edge, so restrict vertical content to tile height 298 contentRect.fBottom = fTileSize.fHeight; 299 } 300 301 SkMatrix srcToDst = SkMatrix::RectToRect(entry.fSrcRect, entry.fDstRect); 302 303 // Story entry's dstRect into dstQuad, and use contentRect and contentDst as its src and dst 304 entry.fDstRect.toQuad(dstQuad); 305 entry.fSrcRect = contentRect; 306 entry.fDstRect = srcToDst.mapRect(contentRect); 307 entry.fHasClip = true; 308 309 return entry; 310 } 311 312 std::unique_ptr<sk_sp<SkImage>[]> fImages; 313 SkString fName; 314 SkISize fImageSize; 315 SkISize fTileSize; 316 SkISize fTileGridSize; 317 ClampingMode fClampMode; 318 TransformMode fTransformMode; 319 int fLayerCnt; 320 321 using INHERITED = Benchmark; 322 }; 323 324 // Subpixel = false; all of the draw commands align with integer pixels so AA will be automatically 325 // turned off within the operation 326 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1)); 327 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1)); 328 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1)); 329 330 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4)); 331 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4)); 332 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4)); 333 334 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16)); 335 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16)); 336 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16)); 337 338 // Subpixel = true; force the draw commands to not align with pixels exactly so AA remains on 339 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1)); 340 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1)); 341 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1)); 342 343 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4)); 344 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4)); 345 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4)); 346 347 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16)); 348 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16)); 349 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16)); 350 351 // Test different tiling scenarios inspired by Chrome's compositor 352 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1)); 353 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kNone, 1)); 354 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kNone, 1)); 355 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kNone, 1)); 356 357 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1)); 358 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kSubpixel, 1)); 359 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kSubpixel, 1)); 360 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kSubpixel, 1)); 361 362 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kRotated, 1)); 363 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kRotated, 1)); 364 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kRotated, 1)); 365 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kRotated, 1)); 366 367 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kPerspective, 1)); 368 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kPerspective, 1)); 369 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kPerspective, 1)); 370 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kPerspective, 1)); 371