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