xref: /aosp_15_r20/external/skia/gm/compositor_quads.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2019 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 // This test only works with the GPU backend.
9 
10 #include "gm/gm.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkBlendMode.h"
13 #include "include/core/SkCanvas.h"
14 #include "include/core/SkColor.h"
15 #include "include/core/SkColorFilter.h"
16 #include "include/core/SkData.h"
17 #include "include/core/SkFont.h"
18 #include "include/core/SkImage.h"
19 #include "include/core/SkImageFilter.h"
20 #include "include/core/SkImageInfo.h"
21 #include "include/core/SkMaskFilter.h"
22 #include "include/core/SkMatrix.h"
23 #include "include/core/SkPaint.h"
24 #include "include/core/SkPoint.h"
25 #include "include/core/SkRect.h"
26 #include "include/core/SkRefCnt.h"
27 #include "include/core/SkScalar.h"
28 #include "include/core/SkShader.h"
29 #include "include/core/SkSize.h"
30 #include "include/core/SkString.h"
31 #include "include/core/SkTileMode.h"
32 #include "include/core/SkTypeface.h"
33 #include "include/core/SkTypes.h"
34 #include "include/effects/SkColorMatrix.h"
35 #include "include/effects/SkGradientShader.h"
36 #include "include/effects/SkImageFilters.h"
37 #include "include/effects/SkShaderMaskFilter.h"
38 #include "include/private/base/SkTArray.h"
39 #include "src/core/SkLineClipper.h"
40 #include "tools/DecodeUtils.h"
41 #include "tools/Resources.h"
42 #include "tools/ToolUtils.h"
43 #include "tools/fonts/FontToolUtils.h"
44 #include "tools/gpu/YUVUtils.h"
45 
46 #include <array>
47 #include <memory>
48 #include <utility>
49 
50 using namespace skia_private;
51 
52 class ClipTileRenderer;
53 using ClipTileRendererArray = TArray<sk_sp<ClipTileRenderer>>;
54 
55 // This GM mimics the draw calls used by complex compositors that focus on drawing rectangles
56 // and quadrilaterals with per-edge AA, with complex images, effects, and seamless tiling.
57 // It will be updated to reflect the patterns seen in Chromium's SkiaRenderer. It is currently
58 // restricted to adding draw ops directly in Ganesh since there is no fully-specified public API.
59 static constexpr SkScalar kTileWidth = 40;
60 static constexpr SkScalar kTileHeight = 30;
61 
62 static constexpr int kRowCount = 4;
63 static constexpr int kColCount = 3;
64 
65 // To mimic Chromium's BSP clipping strategy, a set of three lines formed by triangle edges
66 // of the below points are used to clip against the regular tile grid. The tile grid occupies
67 // a 120 x 120 rectangle (40px * 3 cols by 30px * 4 rows).
68 static constexpr SkPoint kClipP1 = {1.75f * kTileWidth, 0.8f * kTileHeight};
69 static constexpr SkPoint kClipP2 = {0.6f * kTileWidth, 2.f * kTileHeight};
70 static constexpr SkPoint kClipP3 = {2.9f * kTileWidth, 3.5f * kTileHeight};
71 
72 ///////////////////////////////////////////////////////////////////////////////////////////////
73 // Utilities for operating on lines and tiles
74 ///////////////////////////////////////////////////////////////////////////////////////////////
75 
76 // p0 and p1 form a segment contained the tile grid, so extends them by a large enough margin
77 // that the output points stored in 'line' are outside the tile grid (thus effectively infinite).
clipping_line_segment(const SkPoint & p0,const SkPoint & p1,SkPoint line[2])78 static void clipping_line_segment(const SkPoint& p0, const SkPoint& p1, SkPoint line[2]) {
79     SkVector v = p1 - p0;
80     // 10f was chosen as a balance between large enough to scale the currently set clip
81     // points outside of the tile grid, but small enough to preserve precision.
82     line[0] = p0 - v * 10.f;
83     line[1] = p1 + v * 10.f;
84 }
85 
86 // Returns true if line segment (p0-p1) intersects with line segment (l0-l1); if true is returned,
87 // the intersection point is stored in 'intersect'.
intersect_line_segments(const SkPoint & p0,const SkPoint & p1,const SkPoint & l0,const SkPoint & l1,SkPoint * intersect)88 static bool intersect_line_segments(const SkPoint& p0, const SkPoint& p1,
89                                     const SkPoint& l0, const SkPoint& l1, SkPoint* intersect) {
90     static constexpr SkScalar kHorizontalTolerance = 0.01f; // Pretty conservative
91 
92     // Use doubles for accuracy, since the clipping strategy used below can create T
93     // junctions, and lower precision could artificially create gaps
94     double pY = (double) p1.fY - (double) p0.fY;
95     double pX = (double) p1.fX - (double) p0.fX;
96     double lY = (double) l1.fY - (double) l0.fY;
97     double lX = (double) l1.fX - (double) l0.fX;
98     double plY = (double) p0.fY - (double) l0.fY;
99     double plX = (double) p0.fX - (double) l0.fX;
100     if (SkScalarNearlyZero(pY, kHorizontalTolerance)) {
101         if (SkScalarNearlyZero(lY, kHorizontalTolerance)) {
102             // Two horizontal lines
103             return false;
104         } else {
105             // Recalculate but swap p and l
106             return intersect_line_segments(l0, l1, p0, p1, intersect);
107         }
108     }
109 
110     // Up to now, the line segments do not form an invalid intersection
111     double lNumerator = plX * pY - plY * pX;
112     double lDenom = lX * pY - lY * pX;
113     if (SkScalarNearlyZero(lDenom)) {
114         // Parallel or identical
115         return false;
116     }
117 
118     // Calculate alphaL that provides the intersection point along (l0-l1), e.g. l0+alphaL*(l1-l0)
119     double alphaL = lNumerator / lDenom;
120     if (alphaL < 0.0 || alphaL > 1.0) {
121         // Outside of the l segment
122         return false;
123     }
124 
125     // Calculate alphaP from the valid alphaL (since it could be outside p segment)
126     // double alphaP = (alphaL * l.fY - pl.fY) / p.fY;
127     double alphaP = (alphaL * lY - plY) / pY;
128     if (alphaP < 0.0 || alphaP > 1.0) {
129         // Outside of p segment
130         return false;
131     }
132 
133     // Is valid, so calculate the actual intersection point
134     *intersect = l1 * SkScalar(alphaL) + l0 * SkScalar(1.0 - alphaL);
135     return true;
136 }
137 
138 // Draw a line through the two points, outset by a fixed length in screen space
draw_outset_line(SkCanvas * canvas,const SkMatrix & local,const SkPoint pts[2],const SkPaint & paint)139 static void draw_outset_line(SkCanvas* canvas, const SkMatrix& local, const SkPoint pts[2],
140                              const SkPaint& paint) {
141     static constexpr SkScalar kLineOutset = 10.f;
142     SkPoint mapped[2];
143     local.mapPoints(mapped, pts, 2);
144     SkVector v = mapped[1] - mapped[0];
145     v.setLength(v.length() + kLineOutset);
146     canvas->drawLine(mapped[1] - v, mapped[0] + v, paint);
147 }
148 
149 // Draw grid of red lines at interior tile boundaries.
draw_tile_boundaries(SkCanvas * canvas,const SkMatrix & local)150 static void draw_tile_boundaries(SkCanvas* canvas, const SkMatrix& local) {
151     SkPaint paint;
152     paint.setAntiAlias(true);
153     paint.setColor(SK_ColorRED);
154     paint.setStyle(SkPaint::kStroke_Style);
155     paint.setStrokeWidth(0.f);
156     for (int x = 1; x < kColCount; ++x) {
157         SkPoint pts[] = {{x * kTileWidth, 0}, {x * kTileWidth, kRowCount * kTileHeight}};
158         draw_outset_line(canvas, local, pts, paint);
159     }
160     for (int y = 1; y < kRowCount; ++y) {
161         SkPoint pts[] = {{0, y * kTileHeight}, {kTileWidth * kColCount, y * kTileHeight}};
162         draw_outset_line(canvas, local, pts, paint);
163     }
164 }
165 
166 // Draw the arbitrary clipping/split boundaries that intersect the tile grid as green lines
draw_clipping_boundaries(SkCanvas * canvas,const SkMatrix & local)167 static void draw_clipping_boundaries(SkCanvas* canvas, const SkMatrix& local) {
168     SkPaint paint;
169     paint.setAntiAlias(true);
170     paint.setColor(SK_ColorGREEN);
171     paint.setStyle(SkPaint::kStroke_Style);
172     paint.setStrokeWidth(0.f);
173 
174     // Clip the "infinite" line segments to a rectangular region outside the tile grid
175     SkRect border = SkRect::MakeWH(kTileWidth * kColCount, kTileHeight * kRowCount);
176 
177     // Draw p1 to p2
178     SkPoint line[2];
179     SkPoint clippedLine[2];
180     clipping_line_segment(kClipP1, kClipP2, line);
181     SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
182     draw_outset_line(canvas, local, clippedLine, paint);
183 
184     // Draw p2 to p3
185     clipping_line_segment(kClipP2, kClipP3, line);
186     SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
187     draw_outset_line(canvas, local, clippedLine, paint);
188 
189     // Draw p3 to p1
190     clipping_line_segment(kClipP3, kClipP1, line);
191     SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
192     draw_outset_line(canvas, local, clippedLine, paint);
193 }
194 
draw_text(SkCanvas * canvas,const char * text)195 static void draw_text(SkCanvas* canvas, const char* text) {
196     SkFont font(ToolUtils::DefaultPortableTypeface(), 12);
197     canvas->drawString(text, 0, 0, font, SkPaint());
198 }
199 
200 /////////////////////////////////////////////////////////////////////////////////////////////////
201 // Abstraction for rendering a possibly clipped tile, that can apply different effects to mimic
202 // the Chromium quad types, and a generic GM template to arrange renderers x transforms in a grid
203 /////////////////////////////////////////////////////////////////////////////////////////////////
204 
205 class ClipTileRenderer : public SkRefCntBase {
206 public:
207     // Draw the base rect, possibly clipped by 'clip' if that is not null. The edges to antialias
208     // are specified in 'edgeAA' (to make manipulation easier than an unsigned bitfield). 'tileID'
209     // represents the location of rect within the tile grid, 'quadID' is the unique ID of the clip
210     // region within the tile (reset for each tile).
211     //
212     // The edgeAA order matches that of clip, so it refers to top, right, bottom, left.
213     // Return draw count
214     virtual int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4],
215                           const bool edgeAA[4], int tileID, int quadID) = 0;
216 
217     virtual void drawBanner(SkCanvas* canvas) = 0;
218 
219     // Return draw count
drawTiles(SkCanvas * canvas)220     virtual int drawTiles(SkCanvas* canvas) {
221         // All three lines in a list
222         SkPoint lines[6];
223         clipping_line_segment(kClipP1, kClipP2, lines);
224         clipping_line_segment(kClipP2, kClipP3, lines + 2);
225         clipping_line_segment(kClipP3, kClipP1, lines + 4);
226 
227         bool edgeAA[4];
228         int tileID = 0;
229         int drawCount = 0;
230         for (int i = 0; i < kRowCount; ++i) {
231             for (int j = 0; j < kColCount; ++j) {
232                 // The unclipped tile geometry
233                 SkRect tile = SkRect::MakeXYWH(j * kTileWidth, i * kTileHeight,
234                                                kTileWidth, kTileHeight);
235                 // Base edge AA flags if there are no clips; clipped lines will only turn off edges
236                 edgeAA[0] = i == 0;             // Top
237                 edgeAA[1] = j == kColCount - 1; // Right
238                 edgeAA[2] = i == kRowCount - 1; // Bottom
239                 edgeAA[3] = j == 0;             // Left
240 
241                 // Now clip against the 3 lines formed by kClipPx and split into general purpose
242                 // quads as needed.
243                 int quadCount = 0;
244                 drawCount += this->clipTile(canvas, tileID, tile, nullptr, edgeAA, lines, 3,
245                                             &quadCount);
246                 tileID++;
247             }
248         }
249 
250         return drawCount;
251     }
252 
253 protected:
maskToFlags(const bool edgeAA[4]) const254     SkCanvas::QuadAAFlags maskToFlags(const bool edgeAA[4]) const {
255         unsigned flags = (edgeAA[0] * SkCanvas::kTop_QuadAAFlag) |
256                          (edgeAA[1] * SkCanvas::kRight_QuadAAFlag) |
257                          (edgeAA[2] * SkCanvas::kBottom_QuadAAFlag) |
258                          (edgeAA[3] * SkCanvas::kLeft_QuadAAFlag);
259         return static_cast<SkCanvas::QuadAAFlags>(flags);
260     }
261 
262     // Recursively splits the quadrilateral against the segments stored in 'lines', which must be
263     // 2 * lineCount long. Increments 'quadCount' for each split quadrilateral, and invokes the
264     // drawTile at leaves.
clipTile(SkCanvas * canvas,int tileID,const SkRect & baseRect,const SkPoint quad[4],const bool edgeAA[4],const SkPoint lines[],int lineCount,int * quadCount)265     int clipTile(SkCanvas* canvas, int tileID, const SkRect& baseRect, const SkPoint quad[4],
266                   const bool edgeAA[4], const SkPoint lines[], int lineCount, int* quadCount) {
267         if (lineCount == 0) {
268             // No lines, so end recursion by drawing the tile. If the tile was never split then
269             // 'quad' remains null so that drawTile() can differentiate how it should draw.
270             int draws = this->drawTile(canvas, baseRect, quad, edgeAA, tileID, *quadCount);
271             *quadCount = *quadCount + 1;
272             return draws;
273         }
274 
275         static constexpr int kTL = 0; // Top-left point index in points array
276         static constexpr int kTR = 1; // Top-right point index in points array
277         static constexpr int kBR = 2; // Bottom-right point index in points array
278         static constexpr int kBL = 3; // Bottom-left point index in points array
279         static constexpr int kS0 = 4; // First split point index in points array
280         static constexpr int kS1 = 5; // Second split point index in points array
281 
282         SkPoint points[6];
283         if (quad) {
284             // Copy the original 4 points into set of points to consider
285             for (int i = 0; i < 4; ++i) {
286                 points[i] = quad[i];
287             }
288         } else {
289             //  Haven't been split yet, so fill in based on the rect
290             baseRect.toQuad(points);
291         }
292 
293         // Consider the first line against the 4 quad edges in tile, which should have 0,1, or 2
294         // intersection points since the tile is convex.
295         int splitIndices[2]; // Edge that was intersected
296         int intersectionCount = 0;
297         for (int i = 0; i < 4; ++i) {
298             SkPoint intersect;
299             if (intersect_line_segments(points[i], points[i == 3 ? 0 : i + 1],
300                                         lines[0], lines[1], &intersect)) {
301                 // If the intersected point is the same as the last found intersection, the line
302                 // runs through a vertex, so don't double count it
303                 bool duplicate = false;
304                 for (int j = 0; j < intersectionCount; ++j) {
305                     if (SkScalarNearlyZero((intersect - points[kS0 + j]).length())) {
306                         duplicate = true;
307                         break;
308                     }
309                 }
310                 if (!duplicate) {
311                     points[kS0 + intersectionCount] = intersect;
312                     splitIndices[intersectionCount] = i;
313                     intersectionCount++;
314                 }
315             }
316         }
317 
318         if (intersectionCount < 2) {
319             // Either the first line never intersected the quad (count == 0), or it intersected at a
320             // single vertex without going through quad area (count == 1), so check next line
321             return this->clipTile(
322                     canvas, tileID, baseRect, quad, edgeAA, lines + 2, lineCount - 1, quadCount);
323         }
324 
325         SkASSERT(intersectionCount == 2);
326         // Split the tile points into 2+ sub quads and recurse to the next lines, which may or may
327         // not further split the tile. Since the configurations are relatively simple, the possible
328         // splits are hardcoded below; subtile quad orderings are such that the sub tiles remain in
329         // clockwise order and match expected edges for QuadAAFlags. subtile indices refer to the
330         // 6-element 'points' array.
331         STArray<3, std::array<int, 4>> subtiles;
332         int s2 = -1; // Index of an original vertex chosen for a artificial split
333         if (splitIndices[1] - splitIndices[0] == 2) {
334             // Opposite edges, so the split trivially forms 2 sub quads
335             if (splitIndices[0] == 0) {
336                 subtiles.push_back({{kTL, kS0, kS1, kBL}});
337                 subtiles.push_back({{kS0, kTR, kBR, kS1}});
338             } else {
339                 subtiles.push_back({{kTL, kTR, kS0, kS1}});
340                 subtiles.push_back({{kS1, kS0, kBR, kBL}});
341             }
342         } else {
343             // Adjacent edges, which makes for a more complicated split, since it forms a degenerate
344             // quad (triangle) and a pentagon that must be artificially split. The pentagon is split
345             // using one of the original vertices (remembered in 's2'), which adds an additional
346             // degenerate quad, but ensures there are no T-junctions.
347             switch(splitIndices[0]) {
348                 case 0:
349                     // Could be connected to edge 1 or edge 3
350                     if (splitIndices[1] == 1) {
351                         s2 = kBL;
352                         subtiles.push_back({{kS0, kTR, kS1, kS0}}); // degenerate
353                         subtiles.push_back({{kTL, kS0, edgeAA[0] ? kS0 : kBL, kBL}}); // degenerate
354                         subtiles.push_back({{kS0, kS1, kBR, kBL}});
355                     } else {
356                         SkASSERT(splitIndices[1] == 3);
357                         s2 = kBR;
358                         subtiles.push_back({{kTL, kS0, kS1, kS1}}); // degenerate
359                         subtiles.push_back({{kS1, edgeAA[3] ? kS1 : kBR, kBR, kBL}}); // degenerate
360                         subtiles.push_back({{kS0, kTR, kBR, kS1}});
361                     }
362                     break;
363                 case 1:
364                     // Edge 0 handled above, should only be connected to edge 2
365                     SkASSERT(splitIndices[1] == 2);
366                     s2 = kTL;
367                     subtiles.push_back({{kS0, kS0, kBR, kS1}}); // degenerate
368                     subtiles.push_back({{kTL, kTR, kS0, edgeAA[1] ? kS0 : kTL}}); // degenerate
369                     subtiles.push_back({{kTL, kS0, kS1, kBL}});
370                     break;
371                 case 2:
372                     // Edge 1 handled above, should only be connected to edge 3
373                     SkASSERT(splitIndices[1] == 3);
374                     s2 = kTR;
375                     subtiles.push_back({{kS1, kS0, kS0, kBL}}); // degenerate
376                     subtiles.push_back({{edgeAA[2] ? kS0 : kTR, kTR, kBR, kS0}}); // degenerate
377                     subtiles.push_back({{kTL, kTR, kS0, kS1}});
378                     break;
379                 case 3:
380                     // Fall through, an adjacent edge split that hits edge 3 should have first found
381                     // been found with edge 0 or edge 2 for the other end
382                 default:
383                     SkASSERT(false);
384                     return 0;
385             }
386         }
387 
388         SkPoint sub[4];
389         bool subAA[4];
390         int draws = 0;
391         for (int i = 0; i < subtiles.size(); ++i) {
392             // Fill in the quad points and update edge AA rules for new interior edges
393             for (int j = 0; j < 4; ++j) {
394                 int p = subtiles[i][j];
395                 sub[j] = points[p];
396 
397                 int np = j == 3 ? subtiles[i][0] : subtiles[i][j + 1];
398                 // The "new" edges are the edges that connect between the two split points or
399                 // between a split point and the chosen s2 point. Otherwise the edge remains aligned
400                 // with the original shape, so should preserve the AA setting.
401                 if ((p >= kS0 && (np == s2 || np >= kS0)) ||
402                     ((np >= kS0) && (p == s2 || p >= kS0))) {
403                     // New edge
404                     subAA[j] = false;
405                 } else {
406                     // The subtiles indices were arranged so that their edge ordering was still top,
407                     // right, bottom, left so 'j' can be used to access edgeAA
408                     subAA[j] = edgeAA[j];
409                 }
410             }
411 
412             // Split the sub quad with the next line
413             draws += this->clipTile(canvas, tileID, baseRect, sub, subAA, lines + 2, lineCount - 1,
414                                     quadCount);
415         }
416         return draws;
417     }
418 };
419 
420 static constexpr int kMatrixCount = 5;
421 
422 class CompositorGM : public skiagm::GM {
423 public:
CompositorGM(const char * name,std::function<ClipTileRendererArray ()> makeRendererFn)424     CompositorGM(const char* name, std::function<ClipTileRendererArray()> makeRendererFn)
425             : fMakeRendererFn(std::move(makeRendererFn))
426             , fName(name) {}
427 
428 protected:
getISize()429     SkISize getISize() override {
430         // Initialize the array of renderers.
431         this->onceBeforeDraw();
432 
433         // The GM draws a grid of renderers (rows) x transforms (col). Within each cell, the
434         // renderer draws the transformed tile grid, which is approximately
435         // (kColCount*kTileWidth, kRowCount*kTileHeight), although it has additional line
436         // visualizations and can be transformed outside of those rectangular bounds (i.e. persp),
437         // so pad the cell dimensions to be conservative. Must also account for the banner text.
438         static constexpr SkScalar kCellWidth = 1.3f * kColCount * kTileWidth;
439         static constexpr SkScalar kCellHeight = 1.3f * kRowCount * kTileHeight;
440         return SkISize::Make(SkScalarRoundToInt(kCellWidth * kMatrixCount + 175.f),
441                              SkScalarRoundToInt(kCellHeight * fRenderers.size() + 75.f));
442     }
443 
getName() const444     SkString getName() const override {
445         SkString fullName;
446         fullName.appendf("compositor_quads_%s", fName.c_str());
447         return fullName;
448     }
449 
onOnceBeforeDraw()450     void onOnceBeforeDraw() override {
451         fRenderers = fMakeRendererFn();
452         this->configureMatrices();
453     }
454 
onDraw(SkCanvas * canvas)455     void onDraw(SkCanvas* canvas) override {
456         static constexpr SkScalar kGap = 40.f;
457         static constexpr SkScalar kBannerWidth = 120.f;
458         static constexpr SkScalar kOffset = 15.f;
459 
460         TArray<int> drawCounts(fRenderers.size());
461         drawCounts.push_back_n(fRenderers.size(), 0);
462 
463         canvas->save();
464         canvas->translate(kOffset + kBannerWidth, kOffset);
465         for (int i = 0; i < fMatrices.size(); ++i) {
466             canvas->save();
467             draw_text(canvas, fMatrixNames[i].c_str());
468 
469             canvas->translate(0.f, kGap);
470             for (int j = 0; j < fRenderers.size(); ++j) {
471                 canvas->save();
472                 draw_tile_boundaries(canvas, fMatrices[i]);
473                 draw_clipping_boundaries(canvas, fMatrices[i]);
474 
475                 canvas->concat(fMatrices[i]);
476                 drawCounts[j] += fRenderers[j]->drawTiles(canvas);
477 
478                 canvas->restore();
479                 // And advance to the next row
480                 canvas->translate(0.f, kGap + kRowCount * kTileHeight);
481             }
482             // Reset back to the left edge
483             canvas->restore();
484             // And advance to the next column
485             canvas->translate(kGap + kColCount * kTileWidth, 0.f);
486         }
487         canvas->restore();
488 
489         // Print a row header, with total draw counts
490         canvas->save();
491         canvas->translate(kOffset, kGap + 0.5f * kRowCount * kTileHeight);
492         for (int j = 0; j < fRenderers.size(); ++j) {
493             fRenderers[j]->drawBanner(canvas);
494             canvas->translate(0.f, 15.f);
495             draw_text(canvas, SkStringPrintf("Draws = %d", drawCounts[j]).c_str());
496             canvas->translate(0.f, kGap + kRowCount * kTileHeight);
497         }
498         canvas->restore();
499     }
500 
501 private:
502     std::function<ClipTileRendererArray()> fMakeRendererFn;
503     ClipTileRendererArray fRenderers;
504     TArray<SkMatrix> fMatrices;
505     TArray<SkString> fMatrixNames;
506 
507     SkString fName;
508 
configureMatrices()509     void configureMatrices() {
510         fMatrices.clear();
511         fMatrixNames.clear();
512         fMatrices.push_back_n(kMatrixCount);
513 
514         // Identity
515         fMatrices[0].setIdentity();
516         fMatrixNames.push_back(SkString("Identity"));
517 
518         // Translate/scale
519         fMatrices[1].setTranslate(5.5f, 20.25f);
520         fMatrices[1].postScale(.9f, .7f);
521         fMatrixNames.push_back(SkString("T+S"));
522 
523         // Rotation
524         fMatrices[2].setRotate(20.0f);
525         fMatrices[2].preTranslate(15.f, -20.f);
526         fMatrixNames.push_back(SkString("Rotate"));
527 
528         // Skew
529         fMatrices[3].setSkew(.5f, .25f);
530         fMatrices[3].preTranslate(-30.f, 0.f);
531         fMatrixNames.push_back(SkString("Skew"));
532 
533         // Perspective
534         SkPoint src[4];
535         SkRect::MakeWH(kColCount * kTileWidth, kRowCount * kTileHeight).toQuad(src);
536         SkPoint dst[4] = {{0, 0},
537                           {kColCount * kTileWidth + 10.f, 15.f},
538                           {kColCount * kTileWidth - 28.f, kRowCount * kTileHeight + 40.f},
539                           {25.f, kRowCount * kTileHeight - 15.f}};
540         SkAssertResult(fMatrices[4].setPolyToPoly(src, dst, 4));
541         fMatrices[4].preTranslate(0.f, 10.f);
542         fMatrixNames.push_back(SkString("Perspective"));
543 
544         SkASSERT(fMatrices.size() == fMatrixNames.size());
545     }
546 
547     using INHERITED = skiagm::GM;
548 };
549 
550 ////////////////////////////////////////////////////////////////////////////////////////////////
551 // Implementations of TileRenderer that color the clipped tiles in various ways
552 ////////////////////////////////////////////////////////////////////////////////////////////////
553 
554 class DebugTileRenderer : public ClipTileRenderer {
555 public:
556 
Make()557     static sk_sp<ClipTileRenderer> Make() {
558         // Since aa override is disabled, the quad flags arg doesn't matter.
559         return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kAll_QuadAAFlags, false));
560     }
561 
MakeAA()562     static sk_sp<ClipTileRenderer> MakeAA() {
563         return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kAll_QuadAAFlags, true));
564     }
565 
MakeNonAA()566     static sk_sp<ClipTileRenderer> MakeNonAA() {
567         return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kNone_QuadAAFlags, true));
568     }
569 
drawTile(SkCanvas * canvas,const SkRect & rect,const SkPoint clip[4],const bool edgeAA[4],int tileID,int quadID)570     int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
571                   int tileID, int quadID) override {
572         // Colorize the tile based on its grid position and quad ID
573         int i = tileID / kColCount;
574         int j = tileID % kColCount;
575 
576         SkColor4f c = {(i + 1.f) / kRowCount, (j + 1.f) / kColCount, .4f, 1.f};
577         float alpha = quadID / 10.f;
578         c.fR = c.fR * (1 - alpha) + alpha;
579         c.fG = c.fG * (1 - alpha) + alpha;
580         c.fB = c.fB * (1 - alpha) + alpha;
581         c.fA = c.fA * (1 - alpha) + alpha;
582 
583         SkCanvas::QuadAAFlags aaFlags = fEnableAAOverride ? fAAOverride : this->maskToFlags(edgeAA);
584         canvas->experimental_DrawEdgeAAQuad(
585                 rect, clip, aaFlags, c.toSkColor(), SkBlendMode::kSrcOver);
586         return 1;
587     }
588 
drawBanner(SkCanvas * canvas)589     void drawBanner(SkCanvas* canvas) override {
590         draw_text(canvas, "Edge AA");
591         canvas->translate(0.f, 15.f);
592 
593         SkString config;
594         constexpr char kFormat[] = "Ext(%s) - Int(%s)";
595         if (fEnableAAOverride) {
596             SkASSERT(fAAOverride == SkCanvas::kAll_QuadAAFlags ||
597                      fAAOverride == SkCanvas::kNone_QuadAAFlags);
598             if (fAAOverride == SkCanvas::kAll_QuadAAFlags) {
599                 config.appendf(kFormat, "yes", "yes");
600             } else {
601                 config.appendf(kFormat, "no", "no");
602             }
603         } else {
604             config.appendf(kFormat, "yes", "no");
605         }
606         draw_text(canvas, config.c_str());
607     }
608 
609 private:
610     SkCanvas::QuadAAFlags fAAOverride;
611     bool fEnableAAOverride;
612 
DebugTileRenderer(SkCanvas::QuadAAFlags aa,bool enableAAOverrde)613     DebugTileRenderer(SkCanvas::QuadAAFlags aa, bool enableAAOverrde)
614             : fAAOverride(aa)
615             , fEnableAAOverride(enableAAOverrde) {}
616 
617     using INHERITED = ClipTileRenderer;
618 };
619 
620 // Tests tmp_drawEdgeAAQuad
621 class SolidColorRenderer : public ClipTileRenderer {
622 public:
623 
Make(const SkColor4f & color)624     static sk_sp<ClipTileRenderer> Make(const SkColor4f& color) {
625         return sk_sp<ClipTileRenderer>(new SolidColorRenderer(color));
626     }
627 
drawTile(SkCanvas * canvas,const SkRect & rect,const SkPoint clip[4],const bool edgeAA[4],int tileID,int quadID)628     int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
629                   int tileID, int quadID) override {
630         canvas->experimental_DrawEdgeAAQuad(rect, clip, this->maskToFlags(edgeAA),
631                                             fColor.toSkColor(), SkBlendMode::kSrcOver);
632         return 1;
633     }
634 
drawBanner(SkCanvas * canvas)635     void drawBanner(SkCanvas* canvas) override {
636         draw_text(canvas, "Solid Color");
637     }
638 
639 private:
640     SkColor4f fColor;
641 
SolidColorRenderer(const SkColor4f & color)642     SolidColorRenderer(const SkColor4f& color) : fColor(color) {}
643 
644     using INHERITED = ClipTileRenderer;
645 };
646 
647 // Tests drawEdgeAAImageSet(), but can batch the entries together in different ways
648 class TextureSetRenderer : public ClipTileRenderer {
649 public:
650 
MakeUnbatched(sk_sp<SkImage> image)651     static sk_sp<ClipTileRenderer> MakeUnbatched(sk_sp<SkImage> image) {
652         return Make("Texture", "", std::move(image), nullptr, nullptr, nullptr, nullptr,
653                     1.f, true, 0);
654     }
655 
MakeBatched(sk_sp<SkImage> image,int transformCount)656     static sk_sp<ClipTileRenderer> MakeBatched(sk_sp<SkImage> image, int transformCount) {
657         const char* subtitle = transformCount == 0 ? "" : "w/ xforms";
658         return Make("Texture Set", subtitle, std::move(image), nullptr, nullptr, nullptr, nullptr,
659                     1.f, false, transformCount);
660     }
661 
MakeShader(const char * name,sk_sp<SkImage> image,sk_sp<SkShader> shader,bool local)662     static sk_sp<ClipTileRenderer> MakeShader(const char* name, sk_sp<SkImage> image,
663                                               sk_sp<SkShader> shader, bool local) {
664         return Make("Shader", name, std::move(image), std::move(shader),
665                     nullptr, nullptr, nullptr, 1.f, local, 0);
666     }
667 
MakeColorFilter(const char * name,sk_sp<SkImage> image,sk_sp<SkColorFilter> filter)668     static sk_sp<ClipTileRenderer> MakeColorFilter(const char* name, sk_sp<SkImage> image,
669                                                    sk_sp<SkColorFilter> filter) {
670         return Make("Color Filter", name, std::move(image), nullptr, std::move(filter), nullptr,
671                     nullptr, 1.f, false, 0);
672     }
673 
MakeImageFilter(const char * name,sk_sp<SkImage> image,sk_sp<SkImageFilter> filter)674     static sk_sp<ClipTileRenderer> MakeImageFilter(const char* name, sk_sp<SkImage> image,
675                                                    sk_sp<SkImageFilter> filter) {
676         return Make("Image Filter", name, std::move(image), nullptr, nullptr, std::move(filter),
677                     nullptr, 1.f, false, 0);
678     }
679 
MakeMaskFilter(const char * name,sk_sp<SkImage> image,sk_sp<SkMaskFilter> filter)680     static sk_sp<ClipTileRenderer> MakeMaskFilter(const char* name, sk_sp<SkImage> image,
681                                                   sk_sp<SkMaskFilter> filter) {
682         return Make("Mask Filter", name, std::move(image), nullptr, nullptr, nullptr,
683                     std::move(filter), 1.f, false, 0);
684     }
685 
MakeAlpha(sk_sp<SkImage> image,SkScalar alpha)686     static sk_sp<ClipTileRenderer> MakeAlpha(sk_sp<SkImage> image, SkScalar alpha) {
687         return Make("Alpha", SkStringPrintf("a = %.2f", alpha).c_str(), std::move(image), nullptr,
688                     nullptr, nullptr, nullptr, alpha, false, 0);
689     }
690 
Make(const char * topBanner,const char * bottomBanner,sk_sp<SkImage> image,sk_sp<SkShader> shader,sk_sp<SkColorFilter> colorFilter,sk_sp<SkImageFilter> imageFilter,sk_sp<SkMaskFilter> maskFilter,SkScalar paintAlpha,bool resetAfterEachQuad,int transformCount)691     static sk_sp<ClipTileRenderer> Make(const char* topBanner, const char* bottomBanner,
692                                         sk_sp<SkImage> image, sk_sp<SkShader> shader,
693                                         sk_sp<SkColorFilter> colorFilter,
694                                         sk_sp<SkImageFilter> imageFilter,
695                                         sk_sp<SkMaskFilter> maskFilter, SkScalar paintAlpha,
696                                         bool resetAfterEachQuad, int transformCount) {
697         return sk_sp<ClipTileRenderer>(new TextureSetRenderer(topBanner, bottomBanner,
698                 std::move(image), std::move(shader), std::move(colorFilter), std::move(imageFilter),
699                 std::move(maskFilter), paintAlpha, resetAfterEachQuad, transformCount));
700     }
701 
drawTiles(SkCanvas * canvas)702     int drawTiles(SkCanvas* canvas) override {
703         int draws = this->INHERITED::drawTiles(canvas);
704         // Push the last tile set
705         draws += this->drawAndReset(canvas);
706         return draws;
707     }
708 
drawTile(SkCanvas * canvas,const SkRect & rect,const SkPoint clip[4],const bool edgeAA[4],int tileID,int quadID)709     int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
710                   int tileID, int quadID) override {
711         // Now don't actually draw the tile, accumulate it in the growing entry set
712         bool hasClip = false;
713         if (clip) {
714             // Record the four points into fDstClips
715             fDstClips.push_back_n(4, clip);
716             hasClip = true;
717         }
718 
719         int matrixIdx = -1;
720         if (!fResetEachQuad && fTransformBatchCount > 0) {
721             // Handle transform batching. This works by capturing the CTM of the first tile draw,
722             // and then calculate the difference between that and future CTMs for later tiles.
723             if (fPreViewMatrices.size() == 0) {
724                 fBaseCTM = canvas->getTotalMatrix();
725                 fPreViewMatrices.push_back(SkMatrix::I());
726                 matrixIdx = 0;
727             } else {
728                 // Calculate matrix s.t. getTotalMatrix() = fBaseCTM * M
729                 SkMatrix invBase;
730                 if (!fBaseCTM.invert(&invBase)) {
731                     SkDebugf("Cannot invert CTM, transform batching will not be correct.\n");
732                 } else {
733                     SkMatrix preView = SkMatrix::Concat(invBase, canvas->getTotalMatrix());
734                     if (preView != fPreViewMatrices[fPreViewMatrices.size() - 1]) {
735                         // Add the new matrix
736                         fPreViewMatrices.push_back(preView);
737                     } // else re-use the last matrix
738                     matrixIdx = fPreViewMatrices.size() - 1;
739                 }
740             }
741         }
742 
743         // This acts like the whole image is rendered over the entire tile grid, so derive local
744         // coordinates from 'rect', based on the grid to image transform.
745         SkMatrix gridToImage = SkMatrix::RectToRect(SkRect::MakeWH(kColCount * kTileWidth,
746                                                                    kRowCount * kTileHeight),
747                                                     SkRect::MakeWH(fImage->width(),
748                                                                    fImage->height()));
749         SkRect localRect = gridToImage.mapRect(rect);
750 
751         // drawTextureSet automatically derives appropriate local quad from localRect if clipPtr
752         // is not null.
753         fSetEntries.push_back(
754                 {fImage, localRect, rect, matrixIdx, 1.f, this->maskToFlags(edgeAA), hasClip});
755 
756         if (fResetEachQuad) {
757             // Only ever draw one entry at a time
758             return this->drawAndReset(canvas);
759         } else {
760             return 0;
761         }
762     }
763 
drawBanner(SkCanvas * canvas)764     void drawBanner(SkCanvas* canvas) override {
765         if (fTopBanner.size() > 0) {
766             draw_text(canvas, fTopBanner.c_str());
767         }
768         canvas->translate(0.f, 15.f);
769         if (fBottomBanner.size() > 0) {
770             draw_text(canvas, fBottomBanner.c_str());
771         }
772     }
773 
774 private:
775     SkString fTopBanner;
776     SkString fBottomBanner;
777 
778     sk_sp<SkImage> fImage;
779     sk_sp<SkShader> fShader;
780     sk_sp<SkColorFilter> fColorFilter;
781     sk_sp<SkImageFilter> fImageFilter;
782     sk_sp<SkMaskFilter> fMaskFilter;
783     SkScalar fPaintAlpha;
784 
785     // Batching rules
786     bool fResetEachQuad;
787     int fTransformBatchCount;
788 
789     TArray<SkPoint> fDstClips;
790     TArray<SkMatrix> fPreViewMatrices;
791     TArray<SkCanvas::ImageSetEntry> fSetEntries;
792 
793     SkMatrix fBaseCTM;
794     int fBatchCount;
795 
TextureSetRenderer(const char * topBanner,const char * bottomBanner,sk_sp<SkImage> image,sk_sp<SkShader> shader,sk_sp<SkColorFilter> colorFilter,sk_sp<SkImageFilter> imageFilter,sk_sp<SkMaskFilter> maskFilter,SkScalar paintAlpha,bool resetEachQuad,int transformBatchCount)796     TextureSetRenderer(const char* topBanner,
797                        const char* bottomBanner,
798                        sk_sp<SkImage> image,
799                        sk_sp<SkShader> shader,
800                        sk_sp<SkColorFilter> colorFilter,
801                        sk_sp<SkImageFilter> imageFilter,
802                        sk_sp<SkMaskFilter> maskFilter,
803                        SkScalar paintAlpha,
804                        bool resetEachQuad,
805                        int transformBatchCount)
806             : fTopBanner(topBanner)
807             , fBottomBanner(bottomBanner)
808             , fImage(std::move(image))
809             , fShader(std::move(shader))
810             , fColorFilter(std::move(colorFilter))
811             , fImageFilter(std::move(imageFilter))
812             , fMaskFilter(std::move(maskFilter))
813             , fPaintAlpha(paintAlpha)
814             , fResetEachQuad(resetEachQuad)
815             , fTransformBatchCount(transformBatchCount)
816             , fBatchCount(0) {
817         SkASSERT(transformBatchCount >= 0 && (!resetEachQuad || transformBatchCount == 0));
818     }
819 
configureTilePaint(const SkRect & rect,SkPaint * paint) const820     void configureTilePaint(const SkRect& rect, SkPaint* paint) const {
821         paint->setAntiAlias(true);
822         paint->setBlendMode(SkBlendMode::kSrcOver);
823 
824         // Send non-white RGB, that should be ignored
825         paint->setColor4f({1.f, 0.4f, 0.25f, fPaintAlpha}, nullptr);
826 
827 
828         if (fShader) {
829             if (fResetEachQuad) {
830                 // Apply a local transform in the shader to map from the tile rectangle to (0,0,w,h)
831                 static const SkRect kTarget = SkRect::MakeWH(kTileWidth, kTileHeight);
832                 SkMatrix local = SkMatrix::RectToRect(kTarget, rect);
833                 paint->setShader(fShader->makeWithLocalMatrix(local));
834             } else {
835                 paint->setShader(fShader);
836             }
837         }
838 
839         paint->setColorFilter(fColorFilter);
840         paint->setImageFilter(fImageFilter);
841         paint->setMaskFilter(fMaskFilter);
842     }
843 
drawAndReset(SkCanvas * canvas)844     int drawAndReset(SkCanvas* canvas) {
845         // Early out if there's nothing to draw
846         if (fSetEntries.size() == 0) {
847             SkASSERT(fDstClips.size() == 0 && fPreViewMatrices.size() == 0);
848             return 0;
849         }
850 
851         if (!fResetEachQuad && fTransformBatchCount > 0) {
852             // A batch is completed
853             fBatchCount++;
854             if (fBatchCount < fTransformBatchCount) {
855                 // Haven't hit the point to submit yet, but end the current tile
856                 return 0;
857             }
858 
859             // Submitting all tiles back to where fBaseCTM was the canvas' matrix, while the
860             // canvas currently has the CTM of the last tile batch, so reset it.
861             canvas->setMatrix(fBaseCTM);
862         }
863 
864 #ifdef SK_DEBUG
865         int expectedDstClipCount = 0;
866         for (int i = 0; i < fSetEntries.size(); ++i) {
867             expectedDstClipCount += 4 * fSetEntries[i].fHasClip;
868             SkASSERT(fSetEntries[i].fMatrixIndex < 0 ||
869                      fSetEntries[i].fMatrixIndex < fPreViewMatrices.size());
870         }
871         SkASSERT(expectedDstClipCount == fDstClips.size());
872 #endif
873 
874         SkPaint paint;
875         SkRect lastTileRect = fSetEntries[fSetEntries.size() - 1].fDstRect;
876         this->configureTilePaint(lastTileRect, &paint);
877 
878         canvas->experimental_DrawEdgeAAImageSet(
879                 fSetEntries.begin(), fSetEntries.size(), fDstClips.begin(),
880                 fPreViewMatrices.begin(), SkSamplingOptions(SkFilterMode::kLinear),
881                 &paint, SkCanvas::kFast_SrcRectConstraint);
882 
883         // Reset for next tile
884         fDstClips.clear();
885         fPreViewMatrices.clear();
886         fSetEntries.clear();
887         fBatchCount = 0;
888 
889         return 1;
890     }
891 
892     using INHERITED = ClipTileRenderer;
893 };
894 
895 class YUVTextureSetRenderer : public ClipTileRenderer {
896 public:
MakeFromJPEG(sk_sp<SkData> imageData)897     static sk_sp<ClipTileRenderer> MakeFromJPEG(sk_sp<SkData> imageData) {
898         return sk_sp<ClipTileRenderer>(new YUVTextureSetRenderer(std::move(imageData)));
899     }
900 
drawTiles(SkCanvas * canvas)901     int drawTiles(SkCanvas* canvas) override {
902         // Refresh the SkImage at the start, so that it's not attempted for every set entry
903         if (fYUVData) {
904             fImage = fYUVData->refImage(canvas->recordingContext(),
905                                         sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
906             if (!fImage) {
907                 return 0;
908             }
909         }
910 
911         int draws = this->INHERITED::drawTiles(canvas);
912         // Push the last tile set
913         draws += this->drawAndReset(canvas);
914         return draws;
915     }
916 
drawTile(SkCanvas * canvas,const SkRect & rect,const SkPoint clip[4],const bool edgeAA[4],int tileID,int quadID)917     int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
918                   int tileID, int quadID) override {
919         SkASSERT(fImage);
920         // Now don't actually draw the tile, accumulate it in the growing entry set
921         bool hasClip = false;
922         if (clip) {
923             // Record the four points into fDstClips
924             fDstClips.push_back_n(4, clip);
925             hasClip = true;
926         }
927 
928         // This acts like the whole image is rendered over the entire tile grid, so derive local
929         // coordinates from 'rect', based on the grid to image transform.
930         SkMatrix gridToImage = SkMatrix::RectToRect(SkRect::MakeWH(kColCount * kTileWidth,
931                                                                    kRowCount * kTileHeight),
932                                                     SkRect::MakeWH(fImage->width(),
933                                                                    fImage->height()));
934         SkRect localRect = gridToImage.mapRect(rect);
935 
936         // drawTextureSet automatically derives appropriate local quad from localRect if clipPtr
937         // is not null. Also exercise per-entry alpha combined with YUVA images.
938         fSetEntries.push_back(
939                 {fImage, localRect, rect, -1, .5f, this->maskToFlags(edgeAA), hasClip});
940         return 0;
941     }
942 
drawBanner(SkCanvas * canvas)943     void drawBanner(SkCanvas* canvas) override {
944         draw_text(canvas, "Texture");
945         canvas->translate(0.f, 15.f);
946         draw_text(canvas, "YUV + alpha - GPU Only");
947     }
948 
949 private:
950     std::unique_ptr<sk_gpu_test::LazyYUVImage> fYUVData;
951     // The last accessed SkImage from fYUVData, held here for easy access by drawTile
952     sk_sp<SkImage> fImage;
953 
954     TArray<SkPoint> fDstClips;
955     TArray<SkCanvas::ImageSetEntry> fSetEntries;
956 
YUVTextureSetRenderer(sk_sp<SkData> jpegData)957     YUVTextureSetRenderer(sk_sp<SkData> jpegData)
958             : fYUVData(sk_gpu_test::LazyYUVImage::Make(std::move(jpegData)))
959             , fImage(nullptr) {}
960 
drawAndReset(SkCanvas * canvas)961     int drawAndReset(SkCanvas* canvas) {
962         // Early out if there's nothing to draw
963         if (fSetEntries.size() == 0) {
964             SkASSERT(fDstClips.size() == 0);
965             return 0;
966         }
967 
968 #ifdef SK_DEBUG
969         int expectedDstClipCount = 0;
970         for (int i = 0; i < fSetEntries.size(); ++i) {
971             expectedDstClipCount += 4 * fSetEntries[i].fHasClip;
972         }
973         SkASSERT(expectedDstClipCount == fDstClips.size());
974 #endif
975 
976         SkPaint paint;
977         paint.setAntiAlias(true);
978         paint.setBlendMode(SkBlendMode::kSrcOver);
979 
980         canvas->experimental_DrawEdgeAAImageSet(
981                 fSetEntries.begin(), fSetEntries.size(), fDstClips.begin(), nullptr,
982                 SkSamplingOptions(SkFilterMode::kLinear), &paint,
983                 SkCanvas::kFast_SrcRectConstraint);
984 
985         // Reset for next tile
986         fDstClips.clear();
987         fSetEntries.clear();
988 
989         return 1;
990     }
991 
992     using INHERITED = ClipTileRenderer;
993 };
994 
make_debug_renderers()995 static ClipTileRendererArray make_debug_renderers() {
996     return ClipTileRendererArray{DebugTileRenderer::Make(),
997                                  DebugTileRenderer::MakeAA(),
998                                  DebugTileRenderer::MakeNonAA()};
999 }
1000 
make_solid_color_renderers()1001 static ClipTileRendererArray make_solid_color_renderers() {
1002     return ClipTileRendererArray{SolidColorRenderer::Make({.2f, .8f, .3f, 1.f})};
1003 }
1004 
make_shader_renderers()1005 static ClipTileRendererArray make_shader_renderers() {
1006     static constexpr SkPoint kPts[] = { {0.f, 0.f}, {0.25f * kTileWidth, 0.25f * kTileHeight} };
1007     static constexpr SkColor kColors[] = { SK_ColorBLUE, SK_ColorWHITE };
1008     auto gradient = SkGradientShader::MakeLinear(kPts, kColors, nullptr, 2,
1009                                                  SkTileMode::kMirror);
1010 
1011     auto info = SkImageInfo::Make(1, 1, kAlpha_8_SkColorType, kOpaque_SkAlphaType);
1012     SkBitmap bm;
1013     bm.allocPixels(info);
1014     bm.eraseColor(SK_ColorWHITE);
1015     sk_sp<SkImage> image = bm.asImage();
1016 
1017     return ClipTileRendererArray{
1018                TextureSetRenderer::MakeShader("Gradient", image, gradient, false),
1019                TextureSetRenderer::MakeShader("Local Gradient", image, gradient, true)};
1020 }
1021 
make_image_renderers()1022 static ClipTileRendererArray make_image_renderers() {
1023     sk_sp<SkImage> mandrill = ToolUtils::GetResourceAsImage("images/mandrill_512.png");
1024     sk_sp<SkData> mandrillJpeg = GetResourceAsData("images/mandrill_h1v1.jpg");
1025     return ClipTileRendererArray{TextureSetRenderer::MakeUnbatched(mandrill),
1026                                  TextureSetRenderer::MakeBatched(mandrill, 0),
1027                                  TextureSetRenderer::MakeBatched(mandrill, kMatrixCount),
1028                                  YUVTextureSetRenderer::MakeFromJPEG(mandrillJpeg)};
1029 }
1030 
make_filtered_renderers()1031 static ClipTileRendererArray make_filtered_renderers() {
1032     sk_sp<SkImage> mandrill = ToolUtils::GetResourceAsImage("images/mandrill_512.png");
1033 
1034     SkColorMatrix cm;
1035     cm.setSaturation(10);
1036     sk_sp<SkColorFilter> colorFilter = SkColorFilters::Matrix(cm);
1037     sk_sp<SkImageFilter> imageFilter = SkImageFilters::Dilate(8, 8, nullptr);
1038 
1039     static constexpr SkColor kAlphas[] = { SK_ColorTRANSPARENT, SK_ColorBLACK };
1040     auto alphaGradient = SkGradientShader::MakeRadial(
1041             {0.5f * kTileWidth * kColCount, 0.5f * kTileHeight * kRowCount},
1042             0.25f * kTileWidth * kColCount, kAlphas, nullptr, 2, SkTileMode::kClamp);
1043     sk_sp<SkMaskFilter> maskFilter = SkShaderMaskFilter::Make(std::move(alphaGradient));
1044 
1045     return ClipTileRendererArray{
1046                TextureSetRenderer::MakeAlpha(mandrill, 0.5f),
1047                TextureSetRenderer::MakeColorFilter("Saturation", mandrill, std::move(colorFilter)),
1048 
1049     // NOTE: won't draw correctly until SkCanvas' AutoLoopers are used to handle image filters
1050                TextureSetRenderer::MakeImageFilter("Dilate", mandrill, std::move(imageFilter)),
1051 
1052     // NOTE: blur mask filters do work (tested locally), but visually they don't make much
1053     // sense, since each quad is blurred independently
1054                TextureSetRenderer::MakeMaskFilter("Shader", mandrill, std::move(maskFilter))};
1055 }
1056 
1057 DEF_GM(return new CompositorGM("debug",  make_debug_renderers);)
1058 DEF_GM(return new CompositorGM("color",  make_solid_color_renderers);)
1059 DEF_GM(return new CompositorGM("shader", make_shader_renderers);)
1060 DEF_GM(return new CompositorGM("image",  make_image_renderers);)
1061 DEF_GM(return new CompositorGM("filter", make_filtered_renderers);)
1062