1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "GrQuadPerEdgeAA.h"
9 #include "GrQuad.h"
10 #include "GrVertexWriter.h"
11 #include "glsl/GrGLSLColorSpaceXformHelper.h"
12 #include "glsl/GrGLSLGeometryProcessor.h"
13 #include "glsl/GrGLSLPrimitiveProcessor.h"
14 #include "glsl/GrGLSLFragmentShaderBuilder.h"
15 #include "glsl/GrGLSLVarying.h"
16 #include "glsl/GrGLSLVertexGeoBuilder.h"
17 #include "SkNx.h"
18
19 #define AI SK_ALWAYS_INLINE
20
21 namespace {
22
23 // Helper data types since there is a lot of information that needs to be passed around to
24 // avoid recalculation in the different procedures for tessellating an AA quad.
25
26 struct Vertices {
27 // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f
28 Sk4f fX, fY, fW;
29 // U, V, and R coordinates representing local quad. Ignored depending on uvrCount (0, 1, 2).
30 Sk4f fU, fV, fR;
31 int fUVRCount;
32 };
33
34 struct QuadMetadata {
35 // Normalized edge vectors of the device space quad, ordered L, B, T, R (i.e. nextCCW(x) - x).
36 Sk4f fDX, fDY;
37 // 1 / edge length of the device space quad
38 Sk4f fInvLengths;
39 // Edge mask (set to all 1s if aa flags is kAll), otherwise 1.f if edge was AA, 0.f if non-AA.
40 Sk4f fMask;
41 };
42
43 struct Edges {
44 // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
45 Sk4f fA, fB, fC;
46 // Whether or not the edge normals had to be flipped to preserve positive distance on the inside
47 bool fFlipped;
48 };
49
50 static constexpr float kTolerance = 1e-2f;
51
fma(const Sk4f & f,const Sk4f & m,const Sk4f & a)52 static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) {
53 return SkNx_fma<4, float>(f, m, a);
54 }
55
56 // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
57 // order.
nextCW(const Sk4f & v)58 static AI Sk4f nextCW(const Sk4f& v) {
59 return SkNx_shuffle<2, 0, 3, 1>(v);
60 }
61
nextCCW(const Sk4f & v)62 static AI Sk4f nextCCW(const Sk4f& v) {
63 return SkNx_shuffle<1, 3, 0, 2>(v);
64 }
65
66 // Replaces zero-length 'bad' edge vectors with the reversed opposite edge vector.
67 // e3 may be null if only 2D edges need to be corrected for.
correct_bad_edges(const Sk4f & bad,Sk4f * e1,Sk4f * e2,Sk4f * e3)68 static AI void correct_bad_edges(const Sk4f& bad, Sk4f* e1, Sk4f* e2, Sk4f* e3) {
69 if (bad.anyTrue()) {
70 // Want opposite edges, L B T R -> R T B L but with flipped sign to preserve winding
71 *e1 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e1), *e1);
72 *e2 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e2), *e2);
73 if (e3) {
74 *e3 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e3), *e3);
75 }
76 }
77 }
78
79 // Replace 'bad' coordinates by rotating CCW to get the next point. c3 may be null for 2D points.
correct_bad_coords(const Sk4f & bad,Sk4f * c1,Sk4f * c2,Sk4f * c3)80 static AI void correct_bad_coords(const Sk4f& bad, Sk4f* c1, Sk4f* c2, Sk4f* c3) {
81 if (bad.anyTrue()) {
82 *c1 = bad.thenElse(nextCCW(*c1), *c1);
83 *c2 = bad.thenElse(nextCCW(*c2), *c2);
84 if (c3) {
85 *c3 = bad.thenElse(nextCCW(*c3), *c3);
86 }
87 }
88 }
89
get_metadata(const Vertices & vertices,GrQuadAAFlags aaFlags)90 static AI QuadMetadata get_metadata(const Vertices& vertices, GrQuadAAFlags aaFlags) {
91 Sk4f dx = nextCCW(vertices.fX) - vertices.fX;
92 Sk4f dy = nextCCW(vertices.fY) - vertices.fY;
93 Sk4f invLengths = fma(dx, dx, dy * dy).rsqrt();
94
95 Sk4f mask = aaFlags == GrQuadAAFlags::kAll ? Sk4f(1.f) :
96 Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
97 (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
98 (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
99 (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
100 return { dx * invLengths, dy * invLengths, invLengths, mask };
101 }
102
get_edge_equations(const QuadMetadata & metadata,const Vertices & vertices)103 static AI Edges get_edge_equations(const QuadMetadata& metadata, const Vertices& vertices) {
104 Sk4f dx = metadata.fDX;
105 Sk4f dy = metadata.fDY;
106 // Correct for bad edges by copying adjacent edge information into the bad component
107 correct_bad_edges(metadata.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr);
108
109 Sk4f c = fma(dx, vertices.fY, -dy * vertices.fX);
110 // Make sure normals point into the shape
111 Sk4f test = fma(dy, nextCW(vertices.fX), fma(-dx, nextCW(vertices.fY), c));
112 if ((test < -kTolerance).anyTrue()) {
113 return {-dy, dx, -c, true};
114 } else {
115 return {dy, -dx, c, false};
116 }
117 }
118
119 // Sets 'outset' to the magnitude of outset/inset to adjust each corner of a quad given the
120 // edge angles and lengths. If the quad is too small, has empty edges, or too sharp of angles,
121 // false is returned and the degenerate slow-path should be used.
get_optimized_outset(const QuadMetadata & metadata,bool rectilinear,Sk4f * outset)122 static bool get_optimized_outset(const QuadMetadata& metadata, bool rectilinear, Sk4f* outset) {
123 if (rectilinear) {
124 *outset = 0.5f;
125 // Stay in the fast path as long as all edges are at least a pixel long (so 1/len <= 1)
126 return (metadata.fInvLengths <= 1.f).allTrue();
127 }
128
129 if ((metadata.fInvLengths >= 1.f / kTolerance).anyTrue()) {
130 // Have an empty edge from a degenerate quad, so there's no hope
131 return false;
132 }
133
134 // The distance the point needs to move is 1/2sin(theta), where theta is the angle between the
135 // two edges at that point. cos(theta) is equal to dot(dxy, nextCW(dxy))
136 Sk4f cosTheta = fma(metadata.fDX, nextCW(metadata.fDX), metadata.fDY * nextCW(metadata.fDY));
137 // If the angle is too shallow between edges, go through the degenerate path, otherwise adding
138 // and subtracting very large vectors in almost opposite directions leads to float errors
139 if ((cosTheta.abs() >= 0.9f).anyTrue()) {
140 return false;
141 }
142 *outset = 0.5f * (1.f - cosTheta * cosTheta).rsqrt(); // 1/2sin(theta)
143
144 // When outsetting or insetting, the current edge's AA adds to the length:
145 // cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
146 // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
147 Sk4f halfTanTheta = -cosTheta * (*outset); // cos(pi - theta) = -cos(theta)
148 Sk4f edgeAdjust = metadata.fMask * (halfTanTheta + nextCCW(halfTanTheta)) +
149 nextCCW(metadata.fMask) * nextCCW(*outset) +
150 nextCW(metadata.fMask) * (*outset);
151 // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen negative
152 // then use the slow path
153 Sk4f threshold = 0.1f - metadata.fInvLengths.invert();
154 return (edgeAdjust > threshold).allTrue() && (edgeAdjust < -threshold).allTrue();
155 }
156
157 // Ignores the quad's fW, use outset_projected_vertices if it's known to need 3D.
outset_vertices(const Sk4f & outset,const QuadMetadata & metadata,Vertices * quad)158 static AI void outset_vertices(const Sk4f& outset, const QuadMetadata& metadata, Vertices* quad) {
159 // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
160 // both its points need to be moved along their other edge vectors.
161 auto maskedOutset = -outset * nextCW(metadata.fMask);
162 auto maskedOutsetCW = outset * metadata.fMask;
163 // x = x + outset * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
164 quad->fX += fma(maskedOutsetCW, nextCW(metadata.fDX), maskedOutset * metadata.fDX);
165 quad->fY += fma(maskedOutsetCW, nextCW(metadata.fDY), maskedOutset * metadata.fDY);
166 if (quad->fUVRCount > 0) {
167 // We want to extend the texture coords by the same proportion as the positions.
168 maskedOutset *= metadata.fInvLengths;
169 maskedOutsetCW *= nextCW(metadata.fInvLengths);
170 Sk4f du = nextCCW(quad->fU) - quad->fU;
171 Sk4f dv = nextCCW(quad->fV) - quad->fV;
172 quad->fU += fma(maskedOutsetCW, nextCW(du), maskedOutset * du);
173 quad->fV += fma(maskedOutsetCW, nextCW(dv), maskedOutset * dv);
174 if (quad->fUVRCount == 3) {
175 Sk4f dr = nextCCW(quad->fR) - quad->fR;
176 quad->fR += fma(maskedOutsetCW, nextCW(dr), maskedOutset * dr);
177 }
178 }
179 }
180
181 // Updates (x,y,w) to be at (x2d,y2d) once projected. Updates (u,v,r) to match if provided.
182 // Gracefully handles 2D content if *w holds all 1s.
outset_projected_vertices(const Sk4f & x2d,const Sk4f & y2d,GrQuadAAFlags aaFlags,Vertices * quad)183 static void outset_projected_vertices(const Sk4f& x2d, const Sk4f& y2d,
184 GrQuadAAFlags aaFlags, Vertices* quad) {
185 // Left to right, in device space, for each point
186 Sk4f e1x = SkNx_shuffle<2, 3, 2, 3>(quad->fX) - SkNx_shuffle<0, 1, 0, 1>(quad->fX);
187 Sk4f e1y = SkNx_shuffle<2, 3, 2, 3>(quad->fY) - SkNx_shuffle<0, 1, 0, 1>(quad->fY);
188 Sk4f e1w = SkNx_shuffle<2, 3, 2, 3>(quad->fW) - SkNx_shuffle<0, 1, 0, 1>(quad->fW);
189 correct_bad_edges(fma(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w);
190
191 // // Top to bottom, in device space, for each point
192 Sk4f e2x = SkNx_shuffle<1, 1, 3, 3>(quad->fX) - SkNx_shuffle<0, 0, 2, 2>(quad->fX);
193 Sk4f e2y = SkNx_shuffle<1, 1, 3, 3>(quad->fY) - SkNx_shuffle<0, 0, 2, 2>(quad->fY);
194 Sk4f e2w = SkNx_shuffle<1, 1, 3, 3>(quad->fW) - SkNx_shuffle<0, 0, 2, 2>(quad->fW);
195 correct_bad_edges(fma(e2x, e2x, e2y * e2y) < kTolerance * kTolerance, &e2x, &e2y, &e2w);
196
197 // Can only move along e1 and e2 to reach the new 2D point, so we have
198 // x2d = (x + a*e1x + b*e2x) / (w + a*e1w + b*e2w) and
199 // y2d = (y + a*e1y + b*e2y) / (w + a*e1w + b*e2w) for some a, b
200 // This can be rewritten to a*c1x + b*c2x + c3x = 0; a * c1y + b*c2y + c3y = 0, where
201 // the cNx and cNy coefficients are:
202 Sk4f c1x = e1w * x2d - e1x;
203 Sk4f c1y = e1w * y2d - e1y;
204 Sk4f c2x = e2w * x2d - e2x;
205 Sk4f c2y = e2w * y2d - e2y;
206 Sk4f c3x = quad->fW * x2d - quad->fX;
207 Sk4f c3y = quad->fW * y2d - quad->fY;
208
209 // Solve for a and b
210 Sk4f a, b, denom;
211 if (aaFlags == GrQuadAAFlags::kAll) {
212 // When every edge is outset/inset, each corner can use both edge vectors
213 denom = c1x * c2y - c2x * c1y;
214 a = (c2x * c3y - c3x * c2y) / denom;
215 b = (c3x * c1y - c1x * c3y) / denom;
216 } else {
217 // Force a or b to be 0 if that edge cannot be used due to non-AA
218 // FIXME requires the extra > 0.f, since Sk4f's thenElse only works if true values have
219 // all their bits set to 1.
220 Sk4f aMask = Sk4f((aaFlags & GrQuadAAFlags::kLeft) ? 1.f : 0.f,
221 (aaFlags & GrQuadAAFlags::kLeft) ? 1.f : 0.f,
222 (aaFlags & GrQuadAAFlags::kRight) ? 1.f : 0.f,
223 (aaFlags & GrQuadAAFlags::kRight) ? 1.f : 0.f) > 0.f;
224 Sk4f bMask = Sk4f((aaFlags & GrQuadAAFlags::kTop) ? 1.f : 0.f,
225 (aaFlags & GrQuadAAFlags::kBottom) ? 1.f : 0.f,
226 (aaFlags & GrQuadAAFlags::kTop) ? 1.f : 0.f,
227 (aaFlags & GrQuadAAFlags::kBottom) ? 1.f : 0.f) > 0.f;
228
229 // When aMask[i]&bMask[i], then a[i], b[i], denom[i] match the kAll case.
230 // When aMask[i]&!bMask[i], then b[i] = 0, a[i] = -c3x/c1x or -c3y/c1y, using better denom
231 // When !aMask[i]&bMask[i], then a[i] = 0, b[i] = -c3x/c2x or -c3y/c2y, ""
232 // When !aMask[i]&!bMask[i], then both a[i] = 0 and b[i] = 0
233 Sk4f useC1x = c1x.abs() > c1y.abs();
234 Sk4f useC2x = c2x.abs() > c2y.abs();
235 // -------- A & B ------ --------- A & !B ---------
236 denom = aMask.thenElse(bMask.thenElse(c1x * c2y - c2x * c1y, useC1x.thenElse(c1x, c1y)),
237 // ------- !A & B ---------- - !A & !B -
238 bMask.thenElse(useC2x.thenElse(c2x, c2y), 1.0f));
239 // -------- A & B ------ ---------- A & !B ----------
240 a = aMask.thenElse(bMask.thenElse(c2x * c3y - c3x * c2y, useC1x.thenElse(-c3x, -c3y)),
241 // - !A -
242 0.0f) / denom;
243 // -------- A & B ------ ---------- !A & B ----------
244 b = bMask.thenElse(aMask.thenElse(c3x * c1y - c1x * c3y, useC2x.thenElse(-c3x, -c3y)),
245 // - !B -
246 0.0f) / denom;
247 }
248
249 quad->fX += a * e1x + b * e2x;
250 quad->fY += a * e1y + b * e2y;
251 quad->fW += a * e1w + b * e2w;
252 correct_bad_coords(denom.abs() < kTolerance, &quad->fX, &quad->fY, &quad->fW);
253
254 if (quad->fUVRCount > 0) {
255 // Calculate R here so it can be corrected with U and V in case it's needed later
256 Sk4f e1u = SkNx_shuffle<2, 3, 2, 3>(quad->fU) - SkNx_shuffle<0, 1, 0, 1>(quad->fU);
257 Sk4f e1v = SkNx_shuffle<2, 3, 2, 3>(quad->fV) - SkNx_shuffle<0, 1, 0, 1>(quad->fV);
258 Sk4f e1r = SkNx_shuffle<2, 3, 2, 3>(quad->fR) - SkNx_shuffle<0, 1, 0, 1>(quad->fR);
259 correct_bad_edges(fma(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r);
260
261 Sk4f e2u = SkNx_shuffle<1, 1, 3, 3>(quad->fU) - SkNx_shuffle<0, 0, 2, 2>(quad->fU);
262 Sk4f e2v = SkNx_shuffle<1, 1, 3, 3>(quad->fV) - SkNx_shuffle<0, 0, 2, 2>(quad->fV);
263 Sk4f e2r = SkNx_shuffle<1, 1, 3, 3>(quad->fR) - SkNx_shuffle<0, 0, 2, 2>(quad->fR);
264 correct_bad_edges(fma(e2u, e2u, e2v * e2v) < kTolerance * kTolerance, &e2u, &e2v, &e2r);
265
266 quad->fU += a * e1u + b * e2u;
267 quad->fV += a * e1v + b * e2v;
268 if (quad->fUVRCount == 3) {
269 quad->fR += a * e1r + b * e2r;
270 correct_bad_coords(denom.abs() < kTolerance, &quad->fU, &quad->fV, &quad->fR);
271 } else {
272 correct_bad_coords(denom.abs() < kTolerance, &quad->fU, &quad->fV, nullptr);
273 }
274 }
275 }
276
277 // Calculate area of intersection between quad (xs, ys) and a pixel at 'pixelCenter'.
278 // a, b, c are edge equations of the quad, flipped is true if the line equations had their normals
279 // reversed to correct for matrix transforms.
get_exact_coverage(const SkPoint & pixelCenter,const Vertices & quad,const Edges & edges)280 static float get_exact_coverage(const SkPoint& pixelCenter, const Vertices& quad,
281 const Edges& edges) {
282 // Ordering of vertices given default tri-strip that produces CCW points
283 static const int kCCW[] = {0, 1, 3, 2};
284 // Ordering of vertices given inverted tri-strip that produces CCW
285 static const int kFlippedCCW[] = {0, 2, 3, 1};
286
287 // Edge boundaries of the pixel
288 float left = pixelCenter.fX - 0.5f;
289 float right = pixelCenter.fX + 0.5f;
290 float top = pixelCenter.fY - 0.5f;
291 float bot = pixelCenter.fY + 0.5f;
292
293 // Whether or not the 4 corners of the pixel are inside the quad geometry. Variable names are
294 // intentional to work easily with the helper macros.
295 bool topleftInside = ((edges.fA * left + edges.fB * top + edges.fC) >= 0.f).allTrue();
296 bool botleftInside = ((edges.fA * left + edges.fB * bot + edges.fC) >= 0.f).allTrue();
297 bool botrightInside = ((edges.fA * right + edges.fB * bot + edges.fC) >= 0.f).allTrue();
298 bool toprightInside = ((edges.fA * right + edges.fB * top + edges.fC) >= 0.f).allTrue();
299 if (topleftInside && botleftInside && botrightInside && toprightInside) {
300 // Quad fully contains the pixel, so we know the area will be 1.f
301 return 1.f;
302 }
303
304 // Track whether or not the quad vertices in (xs, ys) are on the proper sides of l, t, r, and b
305 Sk4i leftValid = SkNx_cast<int32_t>(quad.fX >= left);
306 Sk4i rightValid = SkNx_cast<int32_t>(quad.fX <= right);
307 Sk4i topValid = SkNx_cast<int32_t>(quad.fY >= top);
308 Sk4i botValid = SkNx_cast<int32_t>(quad.fY <= bot);
309
310 // Intercepts of quad lines with the 4 pixel edges
311 Sk4f leftCross = -(edges.fC + edges.fA * left) / edges.fB;
312 Sk4f rightCross = -(edges.fC + edges.fA * right) / edges.fB;
313 Sk4f topCross = -(edges.fC + edges.fB * top) / edges.fA;
314 Sk4f botCross = -(edges.fC + edges.fB * bot) / edges.fA;
315
316 // State for implicitly tracking the intersection boundary and area
317 SkPoint firstPoint = {0.f, 0.f};
318 SkPoint lastPoint = {0.f, 0.f};
319 bool intersected = false;
320 float area = 0.f;
321
322 // Adds a point to the intersection hull, remembering first point (for closing) and the
323 // current point, and updates the running area total.
324 // See http://mathworld.wolfram.com/PolygonArea.html
325 auto accumulate = [&](const SkPoint& p) {
326 if (intersected) {
327 float da = lastPoint.fX * p.fY - p.fX * lastPoint.fY;
328 area += da;
329 } else {
330 firstPoint = p;
331 intersected = true;
332 }
333 lastPoint = p;
334 };
335
336 // Used during iteration over the quad points to check if edge intersections are valid and
337 // should be accumulated.
338 #define ADD_EDGE_CROSSING_X(SIDE) \
339 do { \
340 if (SIDE##Cross[ei] >= top && SIDE##Cross[ei] <= bot) { \
341 accumulate({SIDE, SIDE##Cross[ei]}); \
342 addedIntersection = true; \
343 } \
344 } while(false)
345 #define ADD_EDGE_CROSSING_Y(SIDE) \
346 do { \
347 if (SIDE##Cross[ei] >= left && SIDE##Cross[ei] <= right) { \
348 accumulate({SIDE##Cross[ei], SIDE}); \
349 addedIntersection = true; \
350 } \
351 } while(false)
352 #define TEST_EDGES(SIDE, AXIS, I, NI) \
353 do { \
354 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
355 ADD_EDGE_CROSSING_##AXIS(SIDE); \
356 crossedEdges = true; \
357 } \
358 } while(false)
359 // Used during iteration over the quad points to check if a pixel corner should be included
360 // in the intersection boundary
361 #define ADD_CORNER(CHECK, SIDE_LR, SIDE_TB) \
362 if (!CHECK##Valid[i] || !CHECK##Valid[ni]) { \
363 if (SIDE_TB##SIDE_LR##Inside) { \
364 accumulate({SIDE_LR, SIDE_TB}); \
365 } \
366 }
367 #define TEST_CORNER_X(SIDE, I, NI) \
368 do { \
369 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
370 ADD_CORNER(top, SIDE, top) else ADD_CORNER(bot, SIDE, bot) \
371 } \
372 } while(false)
373 #define TEST_CORNER_Y(SIDE, I, NI) \
374 do { \
375 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
376 ADD_CORNER(left, left, SIDE) else ADD_CORNER(right, right, SIDE) \
377 } \
378 } while(false)
379
380 // Iterate over the 4 points of the quad, adding valid intersections with the pixel edges
381 // or adding interior pixel corners as it goes. This automatically keeps all accumulated points
382 // in CCW ordering so the area can be calculated on the fly and there's no need to store the
383 // list of hull points. This is somewhat inspired by the Sutherland-Hodgman algorithm but since
384 // there are only 4 points in each source polygon, there is no point list maintenance.
385 for (int j = 0; j < 4; ++j) {
386 // Current vertex
387 int i = edges.fFlipped ? kFlippedCCW[j] : kCCW[j];
388 // Moving to this vertex
389 int ni = edges.fFlipped ? kFlippedCCW[(j + 1) % 4] : kCCW[(j + 1) % 4];
390 // Index in edge vectors corresponding to move from i to ni
391 int ei = edges.fFlipped ? ni : i;
392
393 bool crossedEdges = false;
394 bool addedIntersection = false;
395
396 // First check if there are any outside -> inside edge crossings. There can be 0, 1, or 2.
397 // 2 can occur if one crossing is still outside the pixel, or if they both go through
398 // the corner (in which case a duplicate point is added, but that doesn't change area).
399
400 // Outside to inside crossing
401 TEST_EDGES(left, X, i, ni);
402 TEST_EDGES(right, X, i, ni);
403 TEST_EDGES(top, Y, i, ni);
404 TEST_EDGES(bot, Y, i, ni);
405 // Inside to outside crossing (swapping ni and i in the boolean test)
406 TEST_EDGES(left, X, ni, i);
407 TEST_EDGES(right, X, ni, i);
408 TEST_EDGES(top, Y, ni, i);
409 TEST_EDGES(bot, Y, ni, i);
410
411 // If we crossed edges but didn't add any intersections, check the corners of the pixel.
412 // If the pixel corners are inside the quad, include them in the boundary.
413 if (crossedEdges && !addedIntersection) {
414 // This can lead to repeated points, but those just accumulate zero area
415 TEST_CORNER_X(left, i, ni);
416 TEST_CORNER_X(right, i, ni);
417 TEST_CORNER_Y(top, i, ni);
418 TEST_CORNER_Y(bot, i, ni);
419
420 TEST_CORNER_X(left, ni, i);
421 TEST_CORNER_X(right, ni, i);
422 TEST_CORNER_Y(top, ni, i);
423 TEST_CORNER_Y(bot, ni, i);
424 }
425
426 // Lastly, if the next point is completely inside the pixel it gets included in the boundary
427 if (leftValid[ni] && rightValid[ni] && topValid[ni] && botValid[ni]) {
428 accumulate({quad.fX[ni], quad.fY[ni]});
429 }
430 }
431
432 #undef TEST_CORNER_Y
433 #undef TEST_CORNER_X
434 #undef ADD_CORNER
435
436 #undef TEST_EDGES
437 #undef ADD_EDGE_CROSSING_Y
438 #undef ADD_EDGE_CROSSING_X
439
440 // After all points have been considered, close the boundary to get final area. If we never
441 // added any points, it means the quad didn't intersect the pixel rectangle.
442 if (intersected) {
443 // Final equation for area of convex polygon is to multiply by -1/2 (minus since the points
444 // were in CCW order).
445 accumulate(firstPoint);
446 return -0.5f * area;
447 } else {
448 return 0.f;
449 }
450 }
451
452 // Outsets or insets xs/ys in place. To be used when the interior is very small, edges are near
453 // parallel, or edges are very short/zero-length. Returns coverage for each vertex.
454 // Requires (dx, dy) to already be fixed for empty edges.
compute_degenerate_quad(GrQuadAAFlags aaFlags,const Sk4f & mask,const Edges & edges,bool outset,Vertices * quad)455 static Sk4f compute_degenerate_quad(GrQuadAAFlags aaFlags, const Sk4f& mask, const Edges& edges,
456 bool outset, Vertices* quad) {
457 // Move the edge 1/2 pixel in or out depending on 'outset'.
458 Sk4f oc = edges.fC + mask * (outset ? 0.5f : -0.5f);
459
460 // There are 6 points that we care about to determine the final shape of the polygon, which
461 // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the
462 // 4 corners), and (e1, e2), (e0, e3) (representing the intersections of opposite edges).
463 Sk4f denom = edges.fA * nextCW(edges.fB) - edges.fB * nextCW(edges.fA);
464 Sk4f px = (edges.fB * nextCW(oc) - oc * nextCW(edges.fB)) / denom;
465 Sk4f py = (oc * nextCW(edges.fA) - edges.fA * nextCW(oc)) / denom;
466 correct_bad_coords(denom.abs() < kTolerance, &px, &py, nullptr);
467
468 // Calculate the signed distances from these 4 corners to the other two edges that did not
469 // define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and
470 // p(3) to e0,e2
471 Sk4f dists1 = px * SkNx_shuffle<3, 3, 0, 0>(edges.fA) +
472 py * SkNx_shuffle<3, 3, 0, 0>(edges.fB) +
473 SkNx_shuffle<3, 3, 0, 0>(oc);
474 Sk4f dists2 = px * SkNx_shuffle<1, 2, 1, 2>(edges.fA) +
475 py * SkNx_shuffle<1, 2, 1, 2>(edges.fB) +
476 SkNx_shuffle<1, 2, 1, 2>(oc);
477
478 // If all the distances are >= 0, the 4 corners form a valid quadrilateral, so use them as
479 // the 4 points. If any point is on the wrong side of both edges, the interior has collapsed
480 // and we need to use a central point to represent it. If all four points are only on the
481 // wrong side of 1 edge, one edge has crossed over another and we use a line to represent it.
482 // Otherwise, use a triangle that replaces the bad points with the intersections of
483 // (e1, e2) or (e0, e3) as needed.
484 Sk4f d1v0 = dists1 < kTolerance;
485 Sk4f d2v0 = dists2 < kTolerance;
486 // FIXME(michaelludwig): Sk4f has anyTrue() and allTrue(), but not & or |. Sk4i has & or | but
487 // not anyTrue() and allTrue(). Moving to SkVx from SkNx will clean this up.
488 Sk4i d1And2 = SkNx_cast<int32_t>(d1v0) & SkNx_cast<int32_t>(d2v0);
489 Sk4i d1Or2 = SkNx_cast<int32_t>(d1v0) | SkNx_cast<int32_t>(d2v0);
490
491 Sk4f coverage;
492 if (!d1Or2[0] && !d1Or2[1] && !d1Or2[2] && !d1Or2[3]) {
493 // Every dists1 and dists2 >= kTolerance so it's not degenerate, use all 4 corners as-is
494 // and use full coverage
495 coverage = 1.f;
496 } else if (d1And2[0] || d1And2[1] || d1And2[2] || d1And2[2]) {
497 // A point failed against two edges, so reduce the shape to a single point, which we take as
498 // the center of the original quad to ensure it is contained in the intended geometry. Since
499 // it has collapsed, we know the shape cannot cover a pixel so update the coverage.
500 SkPoint center = {0.25f * (quad->fX[0] + quad->fX[1] + quad->fX[2] + quad->fX[3]),
501 0.25f * (quad->fY[0] + quad->fY[1] + quad->fY[2] + quad->fY[3])};
502 coverage = get_exact_coverage(center, *quad, edges);
503 px = center.fX;
504 py = center.fY;
505 } else if (d1Or2[0] && d1Or2[1] && d1Or2[2] && d1Or2[3]) {
506 // Degenerates to a line. Compare p[2] and p[3] to edge 0. If they are on the wrong side,
507 // that means edge 0 and 3 crossed, and otherwise edge 1 and 2 crossed.
508 if (dists1[2] < kTolerance && dists1[3] < kTolerance) {
509 // Edges 0 and 3 have crossed over, so make the line from average of (p0,p2) and (p1,p3)
510 px = 0.5f * (SkNx_shuffle<0, 1, 0, 1>(px) + SkNx_shuffle<2, 3, 2, 3>(px));
511 py = 0.5f * (SkNx_shuffle<0, 1, 0, 1>(py) + SkNx_shuffle<2, 3, 2, 3>(py));
512 float mc02 = get_exact_coverage({px[0], py[0]}, *quad, edges);
513 float mc13 = get_exact_coverage({px[1], py[1]}, *quad, edges);
514 coverage = Sk4f(mc02, mc13, mc02, mc13);
515 } else {
516 // Edges 1 and 2 have crossed over, so make the line from average of (p0,p1) and (p2,p3)
517 px = 0.5f * (SkNx_shuffle<0, 0, 2, 2>(px) + SkNx_shuffle<1, 1, 3, 3>(px));
518 py = 0.5f * (SkNx_shuffle<0, 0, 2, 2>(py) + SkNx_shuffle<1, 1, 3, 3>(py));
519 float mc01 = get_exact_coverage({px[0], py[0]}, *quad, edges);
520 float mc23 = get_exact_coverage({px[2], py[2]}, *quad, edges);
521 coverage = Sk4f(mc01, mc01, mc23, mc23);
522 }
523 } else {
524 // This turns into a triangle. Replace corners as needed with the intersections between
525 // (e0,e3) and (e1,e2), which must now be calculated
526 Sk2f eDenom = SkNx_shuffle<0, 1>(edges.fA) * SkNx_shuffle<3, 2>(edges.fB) -
527 SkNx_shuffle<0, 1>(edges.fB) * SkNx_shuffle<3, 2>(edges.fA);
528 Sk2f ex = (SkNx_shuffle<0, 1>(edges.fB) * SkNx_shuffle<3, 2>(oc) -
529 SkNx_shuffle<0, 1>(oc) * SkNx_shuffle<3, 2>(edges.fB)) / eDenom;
530 Sk2f ey = (SkNx_shuffle<0, 1>(oc) * SkNx_shuffle<3, 2>(edges.fA) -
531 SkNx_shuffle<0, 1>(edges.fA) * SkNx_shuffle<3, 2>(oc)) / eDenom;
532
533 if (SkScalarAbs(eDenom[0]) > kTolerance) {
534 px = d1v0.thenElse(ex[0], px);
535 py = d1v0.thenElse(ey[0], py);
536 }
537 if (SkScalarAbs(eDenom[1]) > kTolerance) {
538 px = d2v0.thenElse(ex[1], px);
539 py = d2v0.thenElse(ey[1], py);
540 }
541
542 coverage = 1.f;
543 }
544
545 outset_projected_vertices(px, py, aaFlags, quad);
546 return coverage;
547 }
548
549 // Computes the vertices for the two nested quads used to create AA edges. The original single quad
550 // should be duplicated as input in 'inner' and 'outer', and the resulting quad frame will be
551 // stored in-place on return. Returns per-vertex coverage for the inner vertices.
compute_nested_quad_vertices(GrQuadAAFlags aaFlags,bool rectilinear,Vertices * inner,Vertices * outer)552 static Sk4f compute_nested_quad_vertices(GrQuadAAFlags aaFlags, bool rectilinear,
553 Vertices* inner, Vertices* outer) {
554 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
555 SkASSERT(outer->fUVRCount == inner->fUVRCount);
556
557 QuadMetadata metadata = get_metadata(*inner, aaFlags);
558
559 // When outsetting, we want the new edge to be .5px away from the old line, which means the
560 // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment
561 // is only computed if there are no empty edges, and it may signal going through the slow path.
562 Sk4f outset = 0.5f;
563 if (get_optimized_outset(metadata, rectilinear, &outset)) {
564 // Since it's not subpixel, outsetting and insetting are trivial vector additions.
565 outset_vertices(outset, metadata, outer);
566 outset_vertices(-outset, metadata, inner);
567 return 1.f;
568 }
569
570 // Only compute edge equations once since they are the same for inner and outer quads
571 Edges edges = get_edge_equations(metadata, *inner);
572
573 // Calculate both outset and inset, returning the coverage reported for the inset, since the
574 // outset will always have 0.0f.
575 compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer);
576 return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner);
577 }
578
579 // Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective
580 // division of the device coordinates, the original local coordinate value is at the original
581 // un-outset device position.
compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags,Vertices * inner,Vertices * outer)582 static Sk4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner,
583 Vertices* outer) {
584 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
585 SkASSERT(outer->fUVRCount == inner->fUVRCount);
586
587 // Calculate the projected 2D quad and use it to form projeccted inner/outer quads
588 // Don't use Sk4f.invert() here because it does not preserve 1/1 == 1, which creates rendering
589 // mismatches for 2D content that was batched into a 3D op, vs. 2D on its own.
590 Sk4f iw = 1.0f / inner->fW;
591 Sk4f x2d = inner->fX * iw;
592 Sk4f y2d = inner->fY * iw;
593
594 Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D
595 Vertices outer2D = inner2D;
596
597 Sk4f coverage = compute_nested_quad_vertices(aaFlags, /* rect */ false, &inner2D, &outer2D);
598
599 // Now map from the 2D inset/outset back to 3D and update the local coordinates as well
600 outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner);
601 outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer);
602
603 return coverage;
604 }
605
606 enum class CoverageMode {
607 kNone,
608 kWithPosition,
609 kWithColor
610 };
611
get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec & spec)612 static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
613 if (spec.usesCoverageAA()) {
614 if (spec.compatibleWithAlphaAsCoverage() && spec.hasVertexColors()) {
615 return CoverageMode::kWithColor;
616 } else {
617 return CoverageMode::kWithPosition;
618 }
619 } else {
620 return CoverageMode::kNone;
621 }
622 }
623
624 // Writes four vertices in triangle strip order, including the additional data for local
625 // coordinates, domain, color, and coverage as needed to satisfy the vertex spec.
write_quad(GrVertexWriter * vb,const GrQuadPerEdgeAA::VertexSpec & spec,CoverageMode mode,Sk4f coverage,SkPMColor4f color4f,const SkRect & domain,const Vertices & quad)626 static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
627 CoverageMode mode, Sk4f coverage, SkPMColor4f color4f, const SkRect& domain,
628 const Vertices& quad) {
629 static constexpr auto If = GrVertexWriter::If<float>;
630
631 for (int i = 0; i < 4; ++i) {
632 // save position, this is a float2 or float3 or float4 depending on the combination of
633 // perspective and coverage mode.
634 vb->write(quad.fX[i], quad.fY[i],
635 If(spec.deviceQuadType() == GrQuadType::kPerspective, quad.fW[i]),
636 If(mode == CoverageMode::kWithPosition, coverage[i]));
637
638 // save color
639 if (spec.hasVertexColors()) {
640 bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
641 vb->write(GrVertexColor(
642 color4f * (mode == CoverageMode::kWithColor ? coverage[i] : 1.f), wide));
643 }
644
645 // save local position
646 if (spec.hasLocalCoords()) {
647 vb->write(quad.fU[i], quad.fV[i],
648 If(spec.localQuadType() == GrQuadType::kPerspective, quad.fR[i]));
649 }
650
651 // save the domain
652 if (spec.hasDomain()) {
653 vb->write(domain);
654 }
655 }
656 }
657
658 GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
659
660 static const int kVertsPerAAFillRect = 8;
661 static const int kIndicesPerAAFillRect = 30;
662
get_index_buffer(GrResourceProvider * resourceProvider)663 static sk_sp<const GrGpuBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
664 GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
665
666 // clang-format off
667 static const uint16_t gFillAARectIdx[] = {
668 0, 1, 2, 1, 3, 2,
669 0, 4, 1, 4, 5, 1,
670 0, 6, 4, 0, 2, 6,
671 2, 3, 6, 3, 7, 6,
672 1, 5, 3, 3, 5, 7,
673 };
674 // clang-format on
675
676 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
677 return resourceProvider->findOrCreatePatternedIndexBuffer(
678 gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
679 kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
680 }
681
682 } // anonymous namespace
683
684 namespace GrQuadPerEdgeAA {
685
MinColorType(SkPMColor4f color)686 ColorType MinColorType(SkPMColor4f color) {
687 if (color == SK_PMColor4fWHITE) {
688 return ColorType::kNone;
689 } else if (color.fitsInBytes()) {
690 return ColorType::kByte;
691 } else {
692 return ColorType::kHalf;
693 }
694 }
695
696 ////////////////// Tessellate Implementation
697
Tessellate(void * vertices,const VertexSpec & spec,const GrPerspQuad & deviceQuad,const SkPMColor4f & color4f,const GrPerspQuad & localQuad,const SkRect & domain,GrQuadAAFlags aaFlags)698 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
699 const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
700 GrQuadAAFlags aaFlags) {
701 CoverageMode mode = get_mode_for_spec(spec);
702
703 // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
704 Vertices outer;
705 outer.fX = deviceQuad.x4f();
706 outer.fY = deviceQuad.y4f();
707 outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
708
709 // Load local position data into Sk4fs (either none, just u,v or all three)
710 outer.fUVRCount = spec.localDimensionality();
711 if (spec.hasLocalCoords()) {
712 outer.fU = localQuad.x4f();
713 outer.fV = localQuad.y4f();
714 outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
715 }
716
717 GrVertexWriter vb{vertices};
718 if (spec.usesCoverageAA()) {
719 SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
720 // Must calculate two new quads, an outset and inset by .5 in projected device space, so
721 // duplicate the original quad for the inner space
722 Vertices inner = outer;
723
724 Sk4f maxCoverage = 1.f;
725 if (spec.deviceQuadType() == GrQuadType::kPerspective) {
726 // For perspective, send quads with all edges non-AA through the tessellation to ensure
727 // their corners are processed the same as adjacent quads. This approach relies on
728 // solving edge equations to reconstruct corners, which can create seams if an inner
729 // fully non-AA quad is not similarly processed.
730 maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer);
731 } else if (aaFlags != GrQuadAAFlags::kNone) {
732 // In 2D, the simpler corner math does not cause issues with seaming against non-AA
733 // inner quads.
734 maxCoverage = compute_nested_quad_vertices(
735 aaFlags, spec.deviceQuadType() <= GrQuadType::kRectilinear, &inner, &outer);
736 }
737 // NOTE: could provide an even more optimized tessellation function for axis-aligned
738 // rects since the positions can be outset by constants without doing vector math,
739 // except it must handle identifying the winding of the quad vertices if the transform
740 // applied a mirror, etc. The current 2D case is already adequately fast.
741
742 // Write two quads for inner and outer, inner will use the
743 write_quad(&vb, spec, mode, maxCoverage, color4f, domain, inner);
744 write_quad(&vb, spec, mode, 0.f, color4f, domain, outer);
745 } else {
746 // No outsetting needed, just write a single quad with full coverage
747 SkASSERT(mode == CoverageMode::kNone);
748 write_quad(&vb, spec, mode, 1.f, color4f, domain, outer);
749 }
750
751 return vb.fPtr;
752 }
753
ConfigureMeshIndices(GrMeshDrawOp::Target * target,GrMesh * mesh,const VertexSpec & spec,int quadCount)754 bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
755 int quadCount) {
756 if (spec.usesCoverageAA()) {
757 // AA quads use 8 vertices, basically nested rectangles
758 sk_sp<const GrGpuBuffer> ibuffer = get_index_buffer(target->resourceProvider());
759 if (!ibuffer) {
760 return false;
761 }
762
763 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
764 mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
765 quadCount, kNumAAQuadsInIndexBuffer);
766 } else {
767 // Non-AA quads use 4 vertices, and regular triangle strip layout
768 if (quadCount > 1) {
769 sk_sp<const GrGpuBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
770 if (!ibuffer) {
771 return false;
772 }
773
774 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
775 mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
776 GrResourceProvider::QuadCountOfQuadBuffer());
777 } else {
778 mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
779 mesh->setNonIndexedNonInstanced(4);
780 }
781 }
782
783 return true;
784 }
785
786 ////////////////// VertexSpec Implementation
787
deviceDimensionality() const788 int VertexSpec::deviceDimensionality() const {
789 return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
790 }
791
localDimensionality() const792 int VertexSpec::localDimensionality() const {
793 return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
794 }
795
796 ////////////////// Geometry Processor Implementation
797
798 class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
799 public:
800
Make(const VertexSpec & spec)801 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
802 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
803 }
804
Make(const VertexSpec & vertexSpec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)805 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
806 GrTextureType textureType, GrPixelConfig textureConfig,
807 const GrSamplerState& samplerState,
808 uint32_t extraSamplerKey,
809 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
810 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
811 vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
812 std::move(textureColorSpaceXform)));
813 }
814
name() const815 const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
816
getGLSLProcessorKey(const GrShaderCaps &,GrProcessorKeyBuilder * b) const817 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
818 // domain, texturing, device-dimensions are single bit flags
819 uint32_t x = fDomain.isInitialized() ? 0 : 1;
820 x |= fSampler.isInitialized() ? 0 : 2;
821 x |= fNeedsPerspective ? 0 : 4;
822 // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
823 if (fLocalCoord.isInitialized()) {
824 x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
825 }
826 // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
827 if (fColor.isInitialized()) {
828 x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
829 }
830 // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor
831 if (fCoverageMode != CoverageMode::kNone) {
832 x |= CoverageMode::kWithPosition == fCoverageMode ? 128 : 256;
833 }
834
835 b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
836 b->add32(x);
837 }
838
createGLSLInstance(const GrShaderCaps & caps) const839 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
840 class GLSLProcessor : public GrGLSLGeometryProcessor {
841 public:
842 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
843 FPCoordTransformIter&& transformIter) override {
844 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
845 if (gp.fLocalCoord.isInitialized()) {
846 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
847 }
848 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
849 }
850
851 private:
852 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
853 using Interpolation = GrGLSLVaryingHandler::Interpolation;
854
855 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
856 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
857 gp.fTextureColorSpaceXform.get());
858
859 args.fVaryingHandler->emitAttributes(gp);
860
861 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
862 // Strip last channel from the vertex attribute to remove coverage and get the
863 // actual position
864 if (gp.fNeedsPerspective) {
865 args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
866 gp.fPosition.name());
867 } else {
868 args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
869 gp.fPosition.name());
870 }
871 gpArgs->fPositionVar = {"position",
872 gp.fNeedsPerspective ? kFloat3_GrSLType
873 : kFloat2_GrSLType,
874 GrShaderVar::kNone_TypeModifier};
875 } else {
876 // No coverage to eliminate
877 gpArgs->fPositionVar = gp.fPosition.asShaderVar();
878 }
879
880 // Handle local coordinates if they exist
881 if (gp.fLocalCoord.isInitialized()) {
882 // NOTE: If the only usage of local coordinates is for the inline texture fetch
883 // before FPs, then there are no registered FPCoordTransforms and this ends up
884 // emitting nothing, so there isn't a duplication of local coordinates
885 this->emitTransforms(args.fVertBuilder,
886 args.fVaryingHandler,
887 args.fUniformHandler,
888 gp.fLocalCoord.asShaderVar(),
889 args.fFPCoordTransformHandler);
890 }
891
892 // Solid color before any texturing gets modulated in
893 if (gp.fColor.isInitialized()) {
894 // The color cannot be flat if the varying coverage has been modulated into it
895 args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
896 gp.fCoverageMode == CoverageMode::kWithColor ?
897 Interpolation::kInterpolated : Interpolation::kCanBeFlat);
898 } else {
899 // Output color must be initialized to something
900 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
901 }
902
903 // If there is a texture, must also handle texture coordinates and reading from
904 // the texture in the fragment shader before continuing to fragment processors.
905 if (gp.fSampler.isInitialized()) {
906 // Texture coordinates clamped by the domain on the fragment shader; if the GP
907 // has a texture, it's guaranteed to have local coordinates
908 args.fFragBuilder->codeAppend("float2 texCoord;");
909 if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
910 // Can't do a pass through since we need to perform perspective division
911 GrGLSLVarying v(gp.fLocalCoord.gpuType());
912 args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
913 args.fVertBuilder->codeAppendf("%s = %s;",
914 v.vsOut(), gp.fLocalCoord.name());
915 args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
916 v.fsIn(), v.fsIn());
917 } else {
918 args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
919 }
920
921 // Clamp the now 2D localCoordName variable by the domain if it is provided
922 if (gp.fDomain.isInitialized()) {
923 args.fFragBuilder->codeAppend("float4 domain;");
924 args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
925 Interpolation::kCanBeFlat);
926 args.fFragBuilder->codeAppend(
927 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
928 }
929
930 // Now modulate the starting output color by the texture lookup
931 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
932 args.fFragBuilder->appendTextureLookupAndModulate(
933 args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
934 &fTextureColorSpaceXformHelper);
935 args.fFragBuilder->codeAppend(";");
936 }
937
938 // And lastly, output the coverage calculation code
939 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
940 GrGLSLVarying coverage(kFloat_GrSLType);
941 args.fVaryingHandler->addVarying("coverage", &coverage);
942 if (gp.fNeedsPerspective) {
943 args.fVertBuilder->codeAppendf("%s = %s.w;",
944 coverage.vsOut(), gp.fPosition.name());
945 } else {
946 args.fVertBuilder->codeAppendf("%s = %s.z;",
947 coverage.vsOut(), gp.fPosition.name());
948 }
949
950 args.fFragBuilder->codeAppendf("%s = half4(half(%s));",
951 args.fOutputCoverage, coverage.fsIn());
952 } else {
953 // Set coverage to 1, since it's either non-AA or the coverage was already
954 // folded into the output color
955 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
956 }
957 }
958 GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
959 };
960 return new GLSLProcessor;
961 }
962
963 private:
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec)964 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
965 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
966 , fTextureColorSpaceXform(nullptr) {
967 SkASSERT(!spec.hasDomain());
968 this->initializeAttrs(spec);
969 this->setTextureSamplerCnt(0);
970 }
971
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)972 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
973 GrTextureType textureType, GrPixelConfig textureConfig,
974 const GrSamplerState& samplerState,
975 uint32_t extraSamplerKey,
976 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
977 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
978 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
979 , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
980 SkASSERT(spec.hasLocalCoords());
981 this->initializeAttrs(spec);
982 this->setTextureSamplerCnt(1);
983 }
984
initializeAttrs(const VertexSpec & spec)985 void initializeAttrs(const VertexSpec& spec) {
986 fNeedsPerspective = spec.deviceDimensionality() == 3;
987 fCoverageMode = get_mode_for_spec(spec);
988
989 if (fCoverageMode == CoverageMode::kWithPosition) {
990 if (fNeedsPerspective) {
991 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
992 } else {
993 fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
994 }
995 } else {
996 if (fNeedsPerspective) {
997 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
998 } else {
999 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1000 }
1001 }
1002
1003 int localDim = spec.localDimensionality();
1004 if (localDim == 3) {
1005 fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1006 } else if (localDim == 2) {
1007 fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1008 } // else localDim == 0 and attribute remains uninitialized
1009
1010 if (ColorType::kByte == spec.colorType()) {
1011 fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
1012 } else if (ColorType::kHalf == spec.colorType()) {
1013 fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
1014 }
1015
1016 if (spec.hasDomain()) {
1017 fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1018 }
1019
1020 this->setVertexAttributes(&fPosition, 4);
1021 }
1022
onTextureSampler(int) const1023 const TextureSampler& onTextureSampler(int) const override { return fSampler; }
1024
1025 Attribute fPosition; // May contain coverage as last channel
1026 Attribute fColor; // May have coverage modulated in if the FPs support it
1027 Attribute fLocalCoord;
1028 Attribute fDomain;
1029
1030 // The positions attribute may have coverage built into it, so float3 is an ambiguous type
1031 // and may mean 2d with coverage, or 3d with no coverage
1032 bool fNeedsPerspective;
1033 CoverageMode fCoverageMode;
1034
1035 // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
1036 // to skip texturing.
1037 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1038 TextureSampler fSampler;
1039
1040 typedef GrGeometryProcessor INHERITED;
1041 };
1042
MakeProcessor(const VertexSpec & spec)1043 sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
1044 return QuadPerEdgeAAGeometryProcessor::Make(spec);
1045 }
1046
MakeTexturedProcessor(const VertexSpec & spec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)1047 sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
1048 GrTextureType textureType, GrPixelConfig textureConfig,
1049 const GrSamplerState& samplerState, uint32_t extraSamplerKey,
1050 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
1051 return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
1052 samplerState, extraSamplerKey,
1053 std::move(textureColorSpaceXform));
1054 }
1055
1056 } // namespace GrQuadPerEdgeAA
1057