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