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