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