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