xref: /third_party/skia/gm/compositor_quads.cpp (revision cb93a386)
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
48class ClipTileRenderer;
49using 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.
55static constexpr SkScalar kTileWidth = 40;
56static constexpr SkScalar kTileHeight = 30;
57
58static constexpr int kRowCount = 4;
59static 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).
64static constexpr SkPoint kClipP1 = {1.75f * kTileWidth, 0.8f * kTileHeight};
65static constexpr SkPoint kClipP2 = {0.6f * kTileWidth, 2.f * kTileHeight};
66static 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).
74static 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'.
84static 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
135static 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.
146static 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
163static 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
191static 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
201class ClipTileRenderer : public SkRefCntBase {
202public:
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
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
249protected:
250    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.
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
416static constexpr int kMatrixCount = 5;
417
418class CompositorGM : public skiagm::GM {
419public:
420    CompositorGM(const char* name, std::function<ClipTileRendererArray()> makeRendererFn)
421            : fMakeRendererFn(std::move(makeRendererFn))
422            , fName(name) {}
423
424protected:
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
440    SkString onShortName() override {
441        SkString fullName;
442        fullName.appendf("compositor_quads_%s", fName.c_str());
443        return fullName;
444    }
445
446    void onOnceBeforeDraw() override {
447        fRenderers = fMakeRendererFn();
448        this->configureMatrices();
449    }
450
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
497private:
498    std::function<ClipTileRendererArray()> fMakeRendererFn;
499    ClipTileRendererArray fRenderers;
500    SkTArray<SkMatrix> fMatrices;
501    SkTArray<SkString> fMatrixNames;
502
503    SkString fName;
504
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
550class DebugTileRenderer : public ClipTileRenderer {
551public:
552
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
558    static sk_sp<ClipTileRenderer> MakeAA() {
559        return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kAll_QuadAAFlags, true));
560    }
561
562    static sk_sp<ClipTileRenderer> MakeNonAA() {
563        return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kNone_QuadAAFlags, true));
564    }
565
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
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
605private:
606    SkCanvas::QuadAAFlags fAAOverride;
607    bool fEnableAAOverride;
608
609    DebugTileRenderer(SkCanvas::QuadAAFlags aa, bool enableAAOverrde)
610            : fAAOverride(aa)
611            , fEnableAAOverride(enableAAOverrde) {}
612
613    using INHERITED = ClipTileRenderer;
614};
615
616// Tests tmp_drawEdgeAAQuad
617class SolidColorRenderer : public ClipTileRenderer {
618public:
619
620    static sk_sp<ClipTileRenderer> Make(const SkColor4f& color) {
621        return sk_sp<ClipTileRenderer>(new SolidColorRenderer(color));
622    }
623
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
631    void drawBanner(SkCanvas* canvas) override {
632        draw_text(canvas, "Solid Color");
633    }
634
635private:
636    SkColor4f fColor;
637
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
644class TextureSetRenderer : public ClipTileRenderer {
645public:
646
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
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
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
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
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
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
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
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
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
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
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
770private:
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
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
816    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
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
891class YUVTextureSetRenderer : public ClipTileRenderer {
892public:
893    static sk_sp<ClipTileRenderer> MakeFromJPEG(sk_sp<SkData> imageData) {
894        return sk_sp<ClipTileRenderer>(new YUVTextureSetRenderer(std::move(imageData)));
895    }
896
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
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
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
945private:
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
953    YUVTextureSetRenderer(sk_sp<SkData> jpegData)
954            : fYUVData(sk_gpu_test::LazyYUVImage::Make(std::move(jpegData)))
955            , fImage(nullptr) {}
956
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
991static ClipTileRendererArray make_debug_renderers() {
992    return ClipTileRendererArray{DebugTileRenderer::Make(),
993                                 DebugTileRenderer::MakeAA(),
994                                 DebugTileRenderer::MakeNonAA()};
995}
996
997static ClipTileRendererArray make_solid_color_renderers() {
998    return ClipTileRendererArray{SolidColorRenderer::Make({.2f, .8f, .3f, 1.f})};
999}
1000
1001static 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
1018static 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
1027static 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
1053DEF_GM(return new CompositorGM("debug",  make_debug_renderers);)
1054DEF_GM(return new CompositorGM("color",  make_solid_color_renderers);)
1055DEF_GM(return new CompositorGM("shader", make_shader_renderers);)
1056DEF_GM(return new CompositorGM("image",  make_image_renderers);)
1057DEF_GM(return new CompositorGM("filter", make_filtered_renderers);)
1058