compositor_quads.cpp 44 KB

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