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