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