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