xref: /aosp_15_r20/external/skia/bench/CompositingImagesBench.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "bench/GpuTools.h"
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkColorSpace.h"
14 #include "include/core/SkImage.h"
15 #include "include/core/SkSurface.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 Backend::kGanesh == 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             skgpu::Flush(canvas->getSurface());
224         }
225         canvas->restore();
226     }
227 
228 private:
getTransform() const229     SkMatrix getTransform() const {
230         SkMatrix m;
231         switch(fTransformMode) {
232             case TransformMode::kNone:
233                 m.setIdentity();
234                 break;
235             case TransformMode::kSubpixel:
236                 m.setTranslate(0.5f, 0.5f);
237                 break;
238             case TransformMode::kRotated:
239                 m.setRotate(15.f);
240                 break;
241             case TransformMode::kPerspective: {
242                 m.setIdentity();
243                 m.setPerspY(0.001f);
244                 m.setSkewX(SkIntToScalar(8) / 25);
245                 break;
246             }
247         }
248         return m;
249     }
250 
onGetSize()251     SkISize onGetSize() override {
252         SkRect size = SkRect::MakeWH(1.25f * fTileSize.fWidth * fTileGridSize.fWidth,
253                                      1.25f * fTileSize.fHeight * fTileGridSize.fHeight);
254         this->getTransform().mapRect(&size);
255         return SkISize::Make(SkScalarCeilToInt(size.width()), SkScalarCeilToInt(size.height()));
256     }
257 
getEdgeFlags(int x,int y) const258     unsigned getEdgeFlags(int x, int y) const {
259         unsigned flags = SkCanvas::kNone_QuadAAFlags;
260         if (x == 0) {
261             flags |= SkCanvas::kLeft_QuadAAFlag;
262         } else if (x == fTileGridSize.fWidth - 1) {
263             flags |= SkCanvas::kRight_QuadAAFlag;
264         }
265 
266         if (y == 0) {
267             flags |= SkCanvas::kTop_QuadAAFlag;
268         } else if (y == fTileGridSize.fHeight - 1) {
269             flags |= SkCanvas::kBottom_QuadAAFlag;
270         }
271         return flags;
272     }
273 
getEntry(int x,int y,int layer) const274     SkCanvas::ImageSetEntry getEntry(int x, int y, int layer) const {
275         int imageIdx =
276                 fTileGridSize.fWidth * fTileGridSize.fHeight * layer + fTileGridSize.fWidth * y + x;
277         SkRect srcRect = SkRect::Make(fTileSize);
278         // Make a non-identity transform between src and dst so bilerp isn't disabled.
279         float dstWidth = srcRect.width() * 1.25f;
280         float dstHeight = srcRect.height() * 1.25f;
281         SkRect dstRect = SkRect::MakeXYWH(dstWidth * x, dstHeight * y, dstWidth, dstHeight);
282         return SkCanvas::ImageSetEntry(fImages[imageIdx], srcRect, dstRect, 1.f,
283                                        this->getEdgeFlags(x, y));
284     }
285 
getAdjustedEntry(int x,int y,int layer,SkPoint dstQuad[4]) const286     SkCanvas::ImageSetEntry getAdjustedEntry(int x, int y, int layer, SkPoint dstQuad[4]) const {
287         SkASSERT(x == fTileGridSize.fWidth - 1 || y == fTileGridSize.fHeight - 1);
288 
289         SkCanvas::ImageSetEntry entry = this->getEntry(x, y, layer);
290         SkRect contentRect = SkRect::Make(fImageSize);
291         if (x == fTileGridSize.fWidth - 1) {
292             // Right edge, so restrict horizontal content to tile width
293             contentRect.fRight = fTileSize.fWidth;
294         }
295         if (y == fTileGridSize.fHeight - 1) {
296             // Bottom edge, so restrict vertical content to tile height
297             contentRect.fBottom = fTileSize.fHeight;
298         }
299 
300         SkMatrix srcToDst = SkMatrix::RectToRect(entry.fSrcRect, entry.fDstRect);
301 
302         // Story entry's dstRect into dstQuad, and use contentRect and contentDst as its src and dst
303         entry.fDstRect.toQuad(dstQuad);
304         entry.fSrcRect = contentRect;
305         entry.fDstRect = srcToDst.mapRect(contentRect);
306         entry.fHasClip = true;
307 
308         return entry;
309     }
310 
311     std::unique_ptr<sk_sp<SkImage>[]> fImages;
312     SkString fName;
313     SkISize fImageSize;
314     SkISize fTileSize;
315     SkISize fTileGridSize;
316     ClampingMode fClampMode;
317     TransformMode fTransformMode;
318     int fLayerCnt;
319 
320     using INHERITED = Benchmark;
321 };
322 
323 // Subpixel = false; all of the draw commands align with integer pixels so AA will be automatically
324 // turned off within the operation
325 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1));
326 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1));
327 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1));
328 
329 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4));
330 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4));
331 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 4));
332 
333 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16));
334 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16));
335 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kNone, 16));
336 
337 // Subpixel = true; force the draw commands to not align with pixels exactly so AA remains on
338 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1));
339 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1));
340 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1));
341 
342 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4));
343 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4));
344 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 4));
345 
346 DEF_BENCH(return new CompositingImages({256, 256}, {256, 256}, {8, 8}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16));
347 DEF_BENCH(return new CompositingImages({512, 512}, {512, 512}, {4, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16));
348 DEF_BENCH(return new CompositingImages({1024, 512}, {1024, 512}, {2, 4}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 16));
349 
350 // Test different tiling scenarios inspired by Chrome's compositor
351 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kNone, 1));
352 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kNone, 1));
353 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kNone, 1));
354 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kNone, 1));
355 
356 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kSubpixel, 1));
357 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kSubpixel, 1));
358 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kSubpixel, 1));
359 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kSubpixel, 1));
360 
361 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kRotated, 1));
362 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kRotated, 1));
363 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kRotated, 1));
364 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kRotated, 1));
365 
366 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysFast, TransformMode::kPerspective, 1));
367 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kAlwaysStrict, TransformMode::kPerspective, 1));
368 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_RowMajor, TransformMode::kPerspective, 1));
369 DEF_BENCH(return new CompositingImages({512, 512}, {380, 380}, {5, 5}, ClampingMode::kChromeTiling_Optimal, TransformMode::kPerspective, 1));
370