blob: 8b9aa349bf204a16f0762e2ef633031f1bdc5566 [file] [log] [blame]
Michael Ludwig460eb5e2018-10-29 11:09:29 -04001/*
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
Michael Ludwigfd4f4df2019-05-29 09:51:09 -04008#include "src/gpu/ops/GrQuadPerEdgeAA.h"
9
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/private/SkNx.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#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"
Michael Ludwig460eb5e2018-10-29 11:09:29 -040019
Michael Ludwigf995c052018-11-26 15:24:29 -050020#define AI SK_ALWAYS_INLINE
21
Michael Ludwig460eb5e2018-10-29 11:09:29 -040022namespace {
23
Michael Ludwige6266a22019-03-07 11:24:32 -050024// 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
Michael Ludwigb3461fa2019-04-30 11:50:55 -040027using V4f = skvx::Vec<4, float>;
28using M4f = skvx::Vec<4, int32_t>;
29
Michael Ludwige6266a22019-03-07 11:24:32 -050030struct Vertices {
31 // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f
Michael Ludwigb3461fa2019-04-30 11:50:55 -040032 V4f fX, fY, fW;
Michael Ludwige6266a22019-03-07 11:24:32 -050033 // U, V, and R coordinates representing local quad. Ignored depending on uvrCount (0, 1, 2).
Michael Ludwigb3461fa2019-04-30 11:50:55 -040034 V4f fU, fV, fR;
Michael Ludwige6266a22019-03-07 11:24:32 -050035 int fUVRCount;
36};
37
38struct QuadMetadata {
39 // Normalized edge vectors of the device space quad, ordered L, B, T, R (i.e. nextCCW(x) - x).
Michael Ludwigb3461fa2019-04-30 11:50:55 -040040 V4f fDX, fDY;
Michael Ludwige6266a22019-03-07 11:24:32 -050041 // 1 / edge length of the device space quad
Michael Ludwigb3461fa2019-04-30 11:50:55 -040042 V4f fInvLengths;
Michael Ludwige6266a22019-03-07 11:24:32 -050043 // Edge mask (set to all 1s if aa flags is kAll), otherwise 1.f if edge was AA, 0.f if non-AA.
Michael Ludwigb3461fa2019-04-30 11:50:55 -040044 V4f fMask;
Michael Ludwige6266a22019-03-07 11:24:32 -050045};
46
47struct Edges {
48 // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
Michael Ludwigb3461fa2019-04-30 11:50:55 -040049 V4f fA, fB, fC;
Michael Ludwige6266a22019-03-07 11:24:32 -050050 // Whether or not the edge normals had to be flipped to preserve positive distance on the inside
51 bool fFlipped;
52};
53
54static constexpr float kTolerance = 1e-2f;
Michael Ludwigb3461fa2019-04-30 11:50:55 -040055// True/false bit masks for initializing an M4f
56static constexpr int32_t kTrue = ~0;
57static constexpr int32_t kFalse = 0;
Michael Ludwige6266a22019-03-07 11:24:32 -050058
Michael Ludwigb3461fa2019-04-30 11:50:55 -040059static AI V4f fma(const V4f& f, const V4f& m, const V4f& a) {
60 return mad(f, m, a);
Michael Ludwigf995c052018-11-26 15:24:29 -050061}
62
63// These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
64// order.
Michael Ludwigb3461fa2019-04-30 11:50:55 -040065static AI V4f nextCW(const V4f& v) {
66 return skvx::shuffle<2, 0, 3, 1>(v);
Michael Ludwigf995c052018-11-26 15:24:29 -050067}
68
Michael Ludwigb3461fa2019-04-30 11:50:55 -040069static AI V4f nextCCW(const V4f& v) {
70 return skvx::shuffle<1, 3, 0, 2>(v);
Michael Ludwigf995c052018-11-26 15:24:29 -050071}
72
Michael Ludwige6266a22019-03-07 11:24:32 -050073// 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.
Michael Ludwigb3461fa2019-04-30 11:50:55 -040075static AI void correct_bad_edges(const M4f& bad, V4f* e1, V4f* e2, V4f* e3) {
76 if (any(bad)) {
Michael Ludwige6266a22019-03-07 11:24:32 -050077 // Want opposite edges, L B T R -> R T B L but with flipped sign to preserve winding
Michael Ludwigb3461fa2019-04-30 11:50:55 -040078 *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);
Michael Ludwige6266a22019-03-07 11:24:32 -050080 if (e3) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -040081 *e3 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e3), *e3);
Michael Ludwige6266a22019-03-07 11:24:32 -050082 }
83 }
Michael Ludwigf995c052018-11-26 15:24:29 -050084}
85
Michael Ludwige6266a22019-03-07 11:24:32 -050086// Replace 'bad' coordinates by rotating CCW to get the next point. c3 may be null for 2D points.
Michael Ludwigb3461fa2019-04-30 11:50:55 -040087static 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);
Michael Ludwige6266a22019-03-07 11:24:32 -050091 if (c3) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -040092 *c3 = if_then_else(bad, nextCCW(*c3), *c3);
Michael Ludwige6266a22019-03-07 11:24:32 -050093 }
94 }
Michael Ludwig4921dc32018-12-03 14:57:29 +000095}
96
Michael Ludwige6266a22019-03-07 11:24:32 -050097static AI QuadMetadata get_metadata(const Vertices& vertices, GrQuadAAFlags aaFlags) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -040098 V4f dx = nextCCW(vertices.fX) - vertices.fX;
99 V4f dy = nextCCW(vertices.fY) - vertices.fY;
100 V4f invLengths = rsqrt(fma(dx, dx, dy * dy));
Michael Ludwige6266a22019-03-07 11:24:32 -0500101
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400102 V4f mask = aaFlags == GrQuadAAFlags::kAll ? V4f(1.f) :
103 V4f{(GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
Michael Ludwige6266a22019-03-07 11:24:32 -0500104 (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
105 (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400106 (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f};
Michael Ludwige6266a22019-03-07 11:24:32 -0500107 return { dx * invLengths, dy * invLengths, invLengths, mask };
108}
109
110static AI Edges get_edge_equations(const QuadMetadata& metadata, const Vertices& vertices) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400111 V4f dx = metadata.fDX;
112 V4f dy = metadata.fDY;
Michael Ludwige6266a22019-03-07 11:24:32 -0500113 // 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
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400116 V4f c = fma(dx, vertices.fY, -dy * vertices.fX);
Michael Ludwige6266a22019-03-07 11:24:32 -0500117 // Make sure normals point into the shape
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400118 V4f test = fma(dy, nextCW(vertices.fX), fma(-dx, nextCW(vertices.fY), c));
119 if (any(test < -kTolerance)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500120 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.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400129static bool get_optimized_outset(const QuadMetadata& metadata, bool rectilinear, V4f* outset) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500130 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)
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400133 return all(metadata.fInvLengths <= 1.f);
Michael Ludwige6266a22019-03-07 11:24:32 -0500134 }
135
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400136 if (any(metadata.fInvLengths >= 1.f / kTolerance)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500137 // 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))
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400143 V4f cosTheta = fma(metadata.fDX, nextCW(metadata.fDX), metadata.fDY * nextCW(metadata.fDY));
Michael Ludwige6266a22019-03-07 11:24:32 -0500144 // 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
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400146 if (any(abs(cosTheta) >= 0.9f)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500147 return false;
148 }
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400149 *outset = 0.5f * rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta)
Michael Ludwige6266a22019-03-07 11:24:32 -0500150
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))
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400154 V4f halfTanTheta = -cosTheta * (*outset); // cos(pi - theta) = -cos(theta)
155 V4f edgeAdjust = metadata.fMask * (halfTanTheta + nextCCW(halfTanTheta)) +
Michael Ludwige6266a22019-03-07 11:24:32 -0500156 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
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400160 V4f threshold = 0.1f - (1.f / metadata.fInvLengths);
161 return all(edgeAdjust > threshold) && all(edgeAdjust < -threshold);
Michael Ludwige6266a22019-03-07 11:24:32 -0500162}
163
164// Ignores the quad's fW, use outset_projected_vertices if it's known to need 3D.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400165static AI void outset_vertices(const V4f& outset, const QuadMetadata& metadata, Vertices* quad) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500166 // 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.
Michael Ludwige6266a22019-03-07 11:24:32 -0500168 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) {
Michael Ludwigf995c052018-11-26 15:24:29 -0500174 // We want to extend the texture coords by the same proportion as the positions.
Michael Ludwige6266a22019-03-07 11:24:32 -0500175 maskedOutset *= metadata.fInvLengths;
176 maskedOutsetCW *= nextCW(metadata.fInvLengths);
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400177 V4f du = nextCCW(quad->fU) - quad->fU;
178 V4f dv = nextCCW(quad->fV) - quad->fV;
Michael Ludwige6266a22019-03-07 11:24:32 -0500179 quad->fU += fma(maskedOutsetCW, nextCW(du), maskedOutset * du);
180 quad->fV += fma(maskedOutsetCW, nextCW(dv), maskedOutset * dv);
181 if (quad->fUVRCount == 3) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400182 V4f dr = nextCCW(quad->fR) - quad->fR;
Michael Ludwige6266a22019-03-07 11:24:32 -0500183 quad->fR += fma(maskedOutsetCW, nextCW(dr), maskedOutset * dr);
Michael Ludwigf995c052018-11-26 15:24:29 -0500184 }
185 }
186}
187
Michael Ludwige6266a22019-03-07 11:24:32 -0500188// 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.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400190static void outset_projected_vertices(const V4f& x2d, const V4f& y2d,
Michael Ludwige6266a22019-03-07 11:24:32 -0500191 GrQuadAAFlags aaFlags, Vertices* quad) {
192 // Left to right, in device space, for each point
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400193 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);
Michael Ludwige6266a22019-03-07 11:24:32 -0500196 correct_bad_edges(fma(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w);
197
198 // // Top to bottom, in device space, for each point
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400199 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);
Michael Ludwige6266a22019-03-07 11:24:32 -0500202 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:
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400209 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;
Michael Ludwige6266a22019-03-07 11:24:32 -0500215
216 // Solve for a and b
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400217 V4f a, b, denom;
Michael Ludwige6266a22019-03-07 11:24:32 -0500218 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
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400225 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};
Michael Ludwige6266a22019-03-07 11:24:32 -0500233
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
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400238 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 */
Michael Ludwige6266a22019-03-07 11:24:32 -0500259 }
260
Michael Ludwig784184a2019-04-30 13:28:26 -0400261 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
Michael Ludwige6266a22019-03-07 11:24:32 -0500274 quad->fX += a * e1x + b * e2x;
275 quad->fY += a * e1y + b * e2y;
276 quad->fW += a * e1w + b * e2w;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400277 correct_bad_coords(abs(denom) < kTolerance, &quad->fX, &quad->fY, &quad->fW);
Michael Ludwige6266a22019-03-07 11:24:32 -0500278
279 if (quad->fUVRCount > 0) {
280 // Calculate R here so it can be corrected with U and V in case it's needed later
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400281 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);
Michael Ludwige6266a22019-03-07 11:24:32 -0500284 correct_bad_edges(fma(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r);
285
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400286 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);
Michael Ludwige6266a22019-03-07 11:24:32 -0500289 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;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400295 correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, &quad->fR);
Michael Ludwige6266a22019-03-07 11:24:32 -0500296 } else {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400297 correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, nullptr);
Michael Ludwigf995c052018-11-26 15:24:29 -0500298 }
299 }
300}
301
Michael Ludwige6266a22019-03-07 11:24:32 -0500302// Calculate area of intersection between quad (xs, ys) and a pixel at 'pixelCenter'.
303// a, b, c are edge equations of the quad, flipped is true if the line equations had their normals
304// reversed to correct for matrix transforms.
305static float get_exact_coverage(const SkPoint& pixelCenter, const Vertices& quad,
306 const Edges& edges) {
307 // Ordering of vertices given default tri-strip that produces CCW points
308 static const int kCCW[] = {0, 1, 3, 2};
309 // Ordering of vertices given inverted tri-strip that produces CCW
310 static const int kFlippedCCW[] = {0, 2, 3, 1};
311
312 // Edge boundaries of the pixel
313 float left = pixelCenter.fX - 0.5f;
314 float right = pixelCenter.fX + 0.5f;
315 float top = pixelCenter.fY - 0.5f;
316 float bot = pixelCenter.fY + 0.5f;
317
318 // Whether or not the 4 corners of the pixel are inside the quad geometry. Variable names are
319 // intentional to work easily with the helper macros.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400320 bool topleftInside = all((edges.fA * left + edges.fB * top + edges.fC) >= 0.f);
321 bool botleftInside = all((edges.fA * left + edges.fB * bot + edges.fC) >= 0.f);
322 bool botrightInside = all((edges.fA * right + edges.fB * bot + edges.fC) >= 0.f);
323 bool toprightInside = all((edges.fA * right + edges.fB * top + edges.fC) >= 0.f);
Michael Ludwige6266a22019-03-07 11:24:32 -0500324 if (topleftInside && botleftInside && botrightInside && toprightInside) {
325 // Quad fully contains the pixel, so we know the area will be 1.f
326 return 1.f;
327 }
328
329 // Track whether or not the quad vertices in (xs, ys) are on the proper sides of l, t, r, and b
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400330 M4f leftValid = quad.fX >= left;
331 M4f rightValid = quad.fX <= right;
332 M4f topValid = quad.fY >= top;
333 M4f botValid = quad.fY <= bot;
Michael Ludwige6266a22019-03-07 11:24:32 -0500334
335 // Intercepts of quad lines with the 4 pixel edges
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400336 V4f leftCross = -(edges.fC + edges.fA * left) / edges.fB;
337 V4f rightCross = -(edges.fC + edges.fA * right) / edges.fB;
338 V4f topCross = -(edges.fC + edges.fB * top) / edges.fA;
339 V4f botCross = -(edges.fC + edges.fB * bot) / edges.fA;
Michael Ludwige6266a22019-03-07 11:24:32 -0500340
341 // State for implicitly tracking the intersection boundary and area
342 SkPoint firstPoint = {0.f, 0.f};
343 SkPoint lastPoint = {0.f, 0.f};
344 bool intersected = false;
345 float area = 0.f;
346
347 // Adds a point to the intersection hull, remembering first point (for closing) and the
348 // current point, and updates the running area total.
349 // See http://mathworld.wolfram.com/PolygonArea.html
350 auto accumulate = [&](const SkPoint& p) {
351 if (intersected) {
352 float da = lastPoint.fX * p.fY - p.fX * lastPoint.fY;
353 area += da;
354 } else {
355 firstPoint = p;
356 intersected = true;
357 }
358 lastPoint = p;
359 };
360
361 // Used during iteration over the quad points to check if edge intersections are valid and
362 // should be accumulated.
363#define ADD_EDGE_CROSSING_X(SIDE) \
364 do { \
365 if (SIDE##Cross[ei] >= top && SIDE##Cross[ei] <= bot) { \
366 accumulate({SIDE, SIDE##Cross[ei]}); \
367 addedIntersection = true; \
368 } \
369 } while(false)
370#define ADD_EDGE_CROSSING_Y(SIDE) \
371 do { \
372 if (SIDE##Cross[ei] >= left && SIDE##Cross[ei] <= right) { \
373 accumulate({SIDE##Cross[ei], SIDE}); \
374 addedIntersection = true; \
375 } \
376 } while(false)
377#define TEST_EDGES(SIDE, AXIS, I, NI) \
378 do { \
379 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
380 ADD_EDGE_CROSSING_##AXIS(SIDE); \
381 crossedEdges = true; \
382 } \
383 } while(false)
384 // Used during iteration over the quad points to check if a pixel corner should be included
385 // in the intersection boundary
386#define ADD_CORNER(CHECK, SIDE_LR, SIDE_TB) \
387 if (!CHECK##Valid[i] || !CHECK##Valid[ni]) { \
388 if (SIDE_TB##SIDE_LR##Inside) { \
389 accumulate({SIDE_LR, SIDE_TB}); \
390 } \
391 }
392#define TEST_CORNER_X(SIDE, I, NI) \
393 do { \
394 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
395 ADD_CORNER(top, SIDE, top) else ADD_CORNER(bot, SIDE, bot) \
396 } \
397 } while(false)
398#define TEST_CORNER_Y(SIDE, I, NI) \
399 do { \
400 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
401 ADD_CORNER(left, left, SIDE) else ADD_CORNER(right, right, SIDE) \
402 } \
403 } while(false)
404
405 // Iterate over the 4 points of the quad, adding valid intersections with the pixel edges
406 // or adding interior pixel corners as it goes. This automatically keeps all accumulated points
407 // in CCW ordering so the area can be calculated on the fly and there's no need to store the
408 // list of hull points. This is somewhat inspired by the Sutherland-Hodgman algorithm but since
409 // there are only 4 points in each source polygon, there is no point list maintenance.
410 for (int j = 0; j < 4; ++j) {
411 // Current vertex
412 int i = edges.fFlipped ? kFlippedCCW[j] : kCCW[j];
413 // Moving to this vertex
414 int ni = edges.fFlipped ? kFlippedCCW[(j + 1) % 4] : kCCW[(j + 1) % 4];
415 // Index in edge vectors corresponding to move from i to ni
416 int ei = edges.fFlipped ? ni : i;
417
418 bool crossedEdges = false;
419 bool addedIntersection = false;
420
421 // First check if there are any outside -> inside edge crossings. There can be 0, 1, or 2.
422 // 2 can occur if one crossing is still outside the pixel, or if they both go through
423 // the corner (in which case a duplicate point is added, but that doesn't change area).
424
425 // Outside to inside crossing
426 TEST_EDGES(left, X, i, ni);
427 TEST_EDGES(right, X, i, ni);
428 TEST_EDGES(top, Y, i, ni);
429 TEST_EDGES(bot, Y, i, ni);
430 // Inside to outside crossing (swapping ni and i in the boolean test)
431 TEST_EDGES(left, X, ni, i);
432 TEST_EDGES(right, X, ni, i);
433 TEST_EDGES(top, Y, ni, i);
434 TEST_EDGES(bot, Y, ni, i);
435
436 // If we crossed edges but didn't add any intersections, check the corners of the pixel.
437 // If the pixel corners are inside the quad, include them in the boundary.
438 if (crossedEdges && !addedIntersection) {
439 // This can lead to repeated points, but those just accumulate zero area
440 TEST_CORNER_X(left, i, ni);
441 TEST_CORNER_X(right, i, ni);
442 TEST_CORNER_Y(top, i, ni);
443 TEST_CORNER_Y(bot, i, ni);
444
445 TEST_CORNER_X(left, ni, i);
446 TEST_CORNER_X(right, ni, i);
447 TEST_CORNER_Y(top, ni, i);
448 TEST_CORNER_Y(bot, ni, i);
449 }
450
451 // Lastly, if the next point is completely inside the pixel it gets included in the boundary
452 if (leftValid[ni] && rightValid[ni] && topValid[ni] && botValid[ni]) {
453 accumulate({quad.fX[ni], quad.fY[ni]});
454 }
455 }
456
457#undef TEST_CORNER_Y
458#undef TEST_CORNER_X
459#undef ADD_CORNER
460
461#undef TEST_EDGES
462#undef ADD_EDGE_CROSSING_Y
463#undef ADD_EDGE_CROSSING_X
464
465 // After all points have been considered, close the boundary to get final area. If we never
466 // added any points, it means the quad didn't intersect the pixel rectangle.
467 if (intersected) {
468 // Final equation for area of convex polygon is to multiply by -1/2 (minus since the points
469 // were in CCW order).
470 accumulate(firstPoint);
471 return -0.5f * area;
472 } else {
473 return 0.f;
474 }
475}
476
477// Outsets or insets xs/ys in place. To be used when the interior is very small, edges are near
478// parallel, or edges are very short/zero-length. Returns coverage for each vertex.
479// Requires (dx, dy) to already be fixed for empty edges.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400480static V4f compute_degenerate_quad(GrQuadAAFlags aaFlags, const V4f& mask, const Edges& edges,
Michael Ludwige6266a22019-03-07 11:24:32 -0500481 bool outset, Vertices* quad) {
482 // Move the edge 1/2 pixel in or out depending on 'outset'.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400483 V4f oc = edges.fC + mask * (outset ? 0.5f : -0.5f);
Michael Ludwige6266a22019-03-07 11:24:32 -0500484
485 // There are 6 points that we care about to determine the final shape of the polygon, which
486 // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the
487 // 4 corners), and (e1, e2), (e0, e3) (representing the intersections of opposite edges).
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400488 V4f denom = edges.fA * nextCW(edges.fB) - edges.fB * nextCW(edges.fA);
489 V4f px = (edges.fB * nextCW(oc) - oc * nextCW(edges.fB)) / denom;
490 V4f py = (oc * nextCW(edges.fA) - edges.fA * nextCW(oc)) / denom;
491 correct_bad_coords(abs(denom) < kTolerance, &px, &py, nullptr);
Michael Ludwige6266a22019-03-07 11:24:32 -0500492
493 // Calculate the signed distances from these 4 corners to the other two edges that did not
494 // define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and
495 // p(3) to e0,e2
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400496 V4f dists1 = px * skvx::shuffle<3, 3, 0, 0>(edges.fA) +
497 py * skvx::shuffle<3, 3, 0, 0>(edges.fB) +
498 skvx::shuffle<3, 3, 0, 0>(oc);
499 V4f dists2 = px * skvx::shuffle<1, 2, 1, 2>(edges.fA) +
500 py * skvx::shuffle<1, 2, 1, 2>(edges.fB) +
501 skvx::shuffle<1, 2, 1, 2>(oc);
Michael Ludwige6266a22019-03-07 11:24:32 -0500502
503 // If all the distances are >= 0, the 4 corners form a valid quadrilateral, so use them as
504 // the 4 points. If any point is on the wrong side of both edges, the interior has collapsed
505 // and we need to use a central point to represent it. If all four points are only on the
506 // wrong side of 1 edge, one edge has crossed over another and we use a line to represent it.
507 // Otherwise, use a triangle that replaces the bad points with the intersections of
508 // (e1, e2) or (e0, e3) as needed.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400509 M4f d1v0 = dists1 < kTolerance;
510 M4f d2v0 = dists2 < kTolerance;
511 M4f d1And2 = d1v0 & d2v0;
512 M4f d1Or2 = d1v0 | d2v0;
Michael Ludwige6266a22019-03-07 11:24:32 -0500513
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400514 V4f coverage;
515 if (!any(d1Or2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500516 // Every dists1 and dists2 >= kTolerance so it's not degenerate, use all 4 corners as-is
517 // and use full coverage
518 coverage = 1.f;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400519 } else if (any(d1And2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500520 // A point failed against two edges, so reduce the shape to a single point, which we take as
521 // the center of the original quad to ensure it is contained in the intended geometry. Since
522 // it has collapsed, we know the shape cannot cover a pixel so update the coverage.
523 SkPoint center = {0.25f * (quad->fX[0] + quad->fX[1] + quad->fX[2] + quad->fX[3]),
524 0.25f * (quad->fY[0] + quad->fY[1] + quad->fY[2] + quad->fY[3])};
525 coverage = get_exact_coverage(center, *quad, edges);
526 px = center.fX;
527 py = center.fY;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400528 } else if (all(d1Or2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500529 // Degenerates to a line. Compare p[2] and p[3] to edge 0. If they are on the wrong side,
530 // that means edge 0 and 3 crossed, and otherwise edge 1 and 2 crossed.
531 if (dists1[2] < kTolerance && dists1[3] < kTolerance) {
532 // Edges 0 and 3 have crossed over, so make the line from average of (p0,p2) and (p1,p3)
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400533 px = 0.5f * (skvx::shuffle<0, 1, 0, 1>(px) + skvx::shuffle<2, 3, 2, 3>(px));
534 py = 0.5f * (skvx::shuffle<0, 1, 0, 1>(py) + skvx::shuffle<2, 3, 2, 3>(py));
Michael Ludwige6266a22019-03-07 11:24:32 -0500535 float mc02 = get_exact_coverage({px[0], py[0]}, *quad, edges);
536 float mc13 = get_exact_coverage({px[1], py[1]}, *quad, edges);
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400537 coverage = V4f{mc02, mc13, mc02, mc13};
Michael Ludwige6266a22019-03-07 11:24:32 -0500538 } else {
539 // Edges 1 and 2 have crossed over, so make the line from average of (p0,p1) and (p2,p3)
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400540 px = 0.5f * (skvx::shuffle<0, 0, 2, 2>(px) + skvx::shuffle<1, 1, 3, 3>(px));
541 py = 0.5f * (skvx::shuffle<0, 0, 2, 2>(py) + skvx::shuffle<1, 1, 3, 3>(py));
Michael Ludwige6266a22019-03-07 11:24:32 -0500542 float mc01 = get_exact_coverage({px[0], py[0]}, *quad, edges);
543 float mc23 = get_exact_coverage({px[2], py[2]}, *quad, edges);
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400544 coverage = V4f{mc01, mc01, mc23, mc23};
Michael Ludwige6266a22019-03-07 11:24:32 -0500545 }
546 } else {
547 // This turns into a triangle. Replace corners as needed with the intersections between
548 // (e0,e3) and (e1,e2), which must now be calculated
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400549 using V2f = skvx::Vec<2, float>;
550 V2f eDenom = skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(edges.fB) -
551 skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(edges.fA);
552 V2f ex = (skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(oc) -
553 skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fB)) / eDenom;
554 V2f ey = (skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fA) -
555 skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(oc)) / eDenom;
Michael Ludwige6266a22019-03-07 11:24:32 -0500556
557 if (SkScalarAbs(eDenom[0]) > kTolerance) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400558 px = if_then_else(d1v0, V4f(ex[0]), px);
559 py = if_then_else(d1v0, V4f(ey[0]), py);
Michael Ludwige6266a22019-03-07 11:24:32 -0500560 }
561 if (SkScalarAbs(eDenom[1]) > kTolerance) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400562 px = if_then_else(d2v0, V4f(ex[1]), px);
563 py = if_then_else(d2v0, V4f(ey[1]), py);
Michael Ludwige6266a22019-03-07 11:24:32 -0500564 }
565
566 coverage = 1.f;
567 }
568
569 outset_projected_vertices(px, py, aaFlags, quad);
570 return coverage;
Michael Ludwigf995c052018-11-26 15:24:29 -0500571}
572
Michael Ludwig93aeba02018-12-21 09:50:31 -0500573// Computes the vertices for the two nested quads used to create AA edges. The original single quad
Michael Ludwige6266a22019-03-07 11:24:32 -0500574// should be duplicated as input in 'inner' and 'outer', and the resulting quad frame will be
575// stored in-place on return. Returns per-vertex coverage for the inner vertices.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400576static V4f compute_nested_quad_vertices(GrQuadAAFlags aaFlags, bool rectilinear,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400577 Vertices* inner, Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500578 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
579 SkASSERT(outer->fUVRCount == inner->fUVRCount);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400580
Michael Ludwige6266a22019-03-07 11:24:32 -0500581 QuadMetadata metadata = get_metadata(*inner, aaFlags);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400582
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400583 // Calculate domain first before updating vertices. It's only used when not rectilinear.
584 if (!rectilinear) {
585 SkASSERT(domain);
586 // The domain is the bounding box of the quad, outset by 0.5. Don't worry about edge masks
587 // since the FP only applies the domain on the exterior triangles, which are degenerate for
588 // non-AA edges.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400589 domain->fLeft = min(outer->fX) - 0.5f;
590 domain->fRight = max(outer->fX) + 0.5f;
591 domain->fTop = min(outer->fY) - 0.5f;
592 domain->fBottom = max(outer->fY) + 0.5f;
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400593 }
594
Michael Ludwig93aeba02018-12-21 09:50:31 -0500595 // When outsetting, we want the new edge to be .5px away from the old line, which means the
Michael Ludwige6266a22019-03-07 11:24:32 -0500596 // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment
597 // is only computed if there are no empty edges, and it may signal going through the slow path.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400598 V4f outset = 0.5f;
Michael Ludwige6266a22019-03-07 11:24:32 -0500599 if (get_optimized_outset(metadata, rectilinear, &outset)) {
600 // Since it's not subpixel, outsetting and insetting are trivial vector additions.
601 outset_vertices(outset, metadata, outer);
602 outset_vertices(-outset, metadata, inner);
603 return 1.f;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400604 }
Michael Ludwig4921dc32018-12-03 14:57:29 +0000605
Michael Ludwige6266a22019-03-07 11:24:32 -0500606 // Only compute edge equations once since they are the same for inner and outer quads
607 Edges edges = get_edge_equations(metadata, *inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500608
Michael Ludwige6266a22019-03-07 11:24:32 -0500609 // Calculate both outset and inset, returning the coverage reported for the inset, since the
610 // outset will always have 0.0f.
611 compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer);
612 return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500613}
614
Michael Ludwige6266a22019-03-07 11:24:32 -0500615// Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective
616// division of the device coordinates, the original local coordinate value is at the original
617// un-outset device position.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400618static V4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400619 Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500620 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
621 SkASSERT(outer->fUVRCount == inner->fUVRCount);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500622
Michael Ludwige6266a22019-03-07 11:24:32 -0500623 // Calculate the projected 2D quad and use it to form projeccted inner/outer quads
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400624 V4f iw = 1.0f / inner->fW;
625 V4f x2d = inner->fX * iw;
626 V4f y2d = inner->fY * iw;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500627
Michael Ludwige6266a22019-03-07 11:24:32 -0500628 Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D
629 Vertices outer2D = inner2D;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400630
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400631 V4f coverage = compute_nested_quad_vertices(
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400632 aaFlags, /* rect */ false, &inner2D, &outer2D, domain);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400633
Michael Ludwige6266a22019-03-07 11:24:32 -0500634 // Now map from the 2D inset/outset back to 3D and update the local coordinates as well
635 outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner);
636 outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500637
Michael Ludwige6266a22019-03-07 11:24:32 -0500638 return coverage;
Eric Boren98cb1592018-11-26 18:38:05 +0000639}
640
Michael Ludwig93aeba02018-12-21 09:50:31 -0500641enum class CoverageMode {
642 kNone,
643 kWithPosition,
644 kWithColor
645};
646
647static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
648 if (spec.usesCoverageAA()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400649 if (spec.compatibleWithCoverageAsAlpha() && spec.hasVertexColors() &&
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400650 !spec.requiresGeometryDomain()) {
651 // Using a geometric domain acts as a second source of coverage and folding the original
652 // coverage into color makes it impossible to apply the color's alpha to the geometric
653 // domain's coverage when the original shape is clipped.
Michael Ludwig93aeba02018-12-21 09:50:31 -0500654 return CoverageMode::kWithColor;
655 } else {
656 return CoverageMode::kWithPosition;
657 }
Michael Ludwig553e9a92018-11-29 12:38:35 -0500658 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500659 return CoverageMode::kNone;
Michael Ludwig553e9a92018-11-29 12:38:35 -0500660 }
Michael Ludwig93aeba02018-12-21 09:50:31 -0500661}
Michael Ludwig553e9a92018-11-29 12:38:35 -0500662
Michael Ludwig93aeba02018-12-21 09:50:31 -0500663// Writes four vertices in triangle strip order, including the additional data for local
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400664// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec
Michael Ludwig93aeba02018-12-21 09:50:31 -0500665static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400666 CoverageMode mode, const V4f& coverage, SkPMColor4f color4f,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400667 const SkRect& geomDomain, const SkRect& texDomain, const Vertices& quad) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500668 static constexpr auto If = GrVertexWriter::If<float>;
669
Michael Ludwig553e9a92018-11-29 12:38:35 -0500670 for (int i = 0; i < 4; ++i) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500671 // save position, this is a float2 or float3 or float4 depending on the combination of
672 // perspective and coverage mode.
Michael Ludwige6266a22019-03-07 11:24:32 -0500673 vb->write(quad.fX[i], quad.fY[i],
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400674 If(spec.deviceQuadType() == GrQuad::Type::kPerspective, quad.fW[i]),
Michael Ludwige6266a22019-03-07 11:24:32 -0500675 If(mode == CoverageMode::kWithPosition, coverage[i]));
Michael Ludwig4921dc32018-12-03 14:57:29 +0000676
Michael Ludwig93aeba02018-12-21 09:50:31 -0500677 // save color
678 if (spec.hasVertexColors()) {
Brian Salomon1d835422019-03-13 16:11:44 -0400679 bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
Michael Ludwige6266a22019-03-07 11:24:32 -0500680 vb->write(GrVertexColor(
Brian Salomon1d835422019-03-13 16:11:44 -0400681 color4f * (mode == CoverageMode::kWithColor ? coverage[i] : 1.f), wide));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500682 }
683
684 // save local position
685 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500686 vb->write(quad.fU[i], quad.fV[i],
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400687 If(spec.localQuadType() == GrQuad::Type::kPerspective, quad.fR[i]));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500688 }
689
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400690 // save the geometry domain
691 if (spec.requiresGeometryDomain()) {
692 vb->write(geomDomain);
693 }
694
695 // save the texture domain
Michael Ludwig93aeba02018-12-21 09:50:31 -0500696 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400697 vb->write(texDomain);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500698 }
699 }
700}
701
702GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
703
704static const int kVertsPerAAFillRect = 8;
705static const int kIndicesPerAAFillRect = 30;
706
Brian Salomondbf70722019-02-07 11:31:24 -0500707static sk_sp<const GrGpuBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500708 GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
709
710 // clang-format off
711 static const uint16_t gFillAARectIdx[] = {
712 0, 1, 2, 1, 3, 2,
713 0, 4, 1, 4, 5, 1,
714 0, 6, 4, 0, 2, 6,
715 2, 3, 6, 3, 7, 6,
716 1, 5, 3, 3, 5, 7,
717 };
718 // clang-format on
719
720 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
721 return resourceProvider->findOrCreatePatternedIndexBuffer(
722 gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
723 kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
Michael Ludwig553e9a92018-11-29 12:38:35 -0500724}
725
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400726} // anonymous namespace
727
Michael Ludwigc182b942018-11-16 10:27:51 -0500728namespace GrQuadPerEdgeAA {
729
Brian Osman8fa7ab42019-03-18 10:22:42 -0400730// This is a more elaborate version of SkPMColor4fNeedsWideColor that allows "no color" for white
731ColorType MinColorType(SkPMColor4f color, GrClampType clampType, const GrCaps& caps) {
Brian Salomon1d835422019-03-13 16:11:44 -0400732 if (color == SK_PMColor4fWHITE) {
733 return ColorType::kNone;
Brian Salomon1d835422019-03-13 16:11:44 -0400734 } else {
Brian Osman8fa7ab42019-03-18 10:22:42 -0400735 return SkPMColor4fNeedsWideColor(color, clampType, caps) ? ColorType::kHalf
736 : ColorType::kByte;
Brian Salomon1d835422019-03-13 16:11:44 -0400737 }
738}
739
Michael Ludwigc182b942018-11-16 10:27:51 -0500740////////////////// Tessellate Implementation
741
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400742void* Tessellate(void* vertices, const VertexSpec& spec, const GrQuad& deviceQuad,
743 const SkPMColor4f& color4f, const GrQuad& localQuad, const SkRect& domain,
Michael Ludwigc182b942018-11-16 10:27:51 -0500744 GrQuadAAFlags aaFlags) {
Michael Ludwig41f395d2019-05-23 13:59:45 -0400745 SkASSERT(deviceQuad.quadType() <= spec.deviceQuadType());
746 SkASSERT(!spec.hasLocalCoords() || localQuad.quadType() <= spec.localQuadType());
747
Michael Ludwig93aeba02018-12-21 09:50:31 -0500748 CoverageMode mode = get_mode_for_spec(spec);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400749
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400750 // Load position data into V4fs (always x, y, and load w to avoid branching down the road)
Michael Ludwige6266a22019-03-07 11:24:32 -0500751 Vertices outer;
752 outer.fX = deviceQuad.x4f();
753 outer.fY = deviceQuad.y4f();
754 outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400755
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400756 // Load local position data into V4fs (either none, just u,v or all three)
Michael Ludwige6266a22019-03-07 11:24:32 -0500757 outer.fUVRCount = spec.localDimensionality();
Michael Ludwigc182b942018-11-16 10:27:51 -0500758 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500759 outer.fU = localQuad.x4f();
760 outer.fV = localQuad.y4f();
761 outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400762 }
763
Michael Ludwigc182b942018-11-16 10:27:51 -0500764 GrVertexWriter vb{vertices};
Michael Ludwig93aeba02018-12-21 09:50:31 -0500765 if (spec.usesCoverageAA()) {
766 SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500767 // Must calculate two new quads, an outset and inset by .5 in projected device space, so
Michael Ludwige6266a22019-03-07 11:24:32 -0500768 // duplicate the original quad for the inner space
769 Vertices inner = outer;
Michael Ludwigc182b942018-11-16 10:27:51 -0500770
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400771 SkRect geomDomain;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400772 V4f maxCoverage = 1.f;
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400773 if (spec.deviceQuadType() == GrQuad::Type::kPerspective) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500774 // For perspective, send quads with all edges non-AA through the tessellation to ensure
775 // their corners are processed the same as adjacent quads. This approach relies on
776 // solving edge equations to reconstruct corners, which can create seams if an inner
777 // fully non-AA quad is not similarly processed.
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400778 maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer, &geomDomain);
Michael Ludwige6266a22019-03-07 11:24:32 -0500779 } else if (aaFlags != GrQuadAAFlags::kNone) {
780 // In 2D, the simpler corner math does not cause issues with seaming against non-AA
781 // inner quads.
782 maxCoverage = compute_nested_quad_vertices(
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400783 aaFlags, spec.deviceQuadType() <= GrQuad::Type::kRectilinear, &inner, &outer,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400784 &geomDomain);
Michael Ludwig25071cc2019-04-12 09:23:22 -0400785 } else if (spec.requiresGeometryDomain()) {
786 // The quad itself wouldn't need a geometric domain, but the batch does, so set the
787 // domain to the bounds of the X/Y coords. Since it's non-AA, this won't actually be
788 // evaluated by the shader, but make sure not to upload uninitialized data.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400789 geomDomain.fLeft = min(outer.fX);
790 geomDomain.fRight = max(outer.fX);
791 geomDomain.fTop = min(outer.fY);
792 geomDomain.fBottom = max(outer.fY);
Michael Ludwige6266a22019-03-07 11:24:32 -0500793 }
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400794
Michael Ludwig93aeba02018-12-21 09:50:31 -0500795 // Write two quads for inner and outer, inner will use the
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400796 write_quad(&vb, spec, mode, maxCoverage, color4f, geomDomain, domain, inner);
797 write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500798 } else {
799 // No outsetting needed, just write a single quad with full coverage
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400800 SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain());
801 write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain, outer);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400802 }
Michael Ludwigc182b942018-11-16 10:27:51 -0500803
804 return vb.fPtr;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400805}
Michael Ludwig20e909e2018-10-30 10:43:57 -0400806
Michael Ludwig93aeba02018-12-21 09:50:31 -0500807bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
808 int quadCount) {
809 if (spec.usesCoverageAA()) {
810 // AA quads use 8 vertices, basically nested rectangles
Brian Salomondbf70722019-02-07 11:31:24 -0500811 sk_sp<const GrGpuBuffer> ibuffer = get_index_buffer(target->resourceProvider());
Michael Ludwig93aeba02018-12-21 09:50:31 -0500812 if (!ibuffer) {
813 return false;
814 }
815
816 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500817 mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
818 quadCount, kNumAAQuadsInIndexBuffer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500819 } else {
820 // Non-AA quads use 4 vertices, and regular triangle strip layout
821 if (quadCount > 1) {
Brian Salomondbf70722019-02-07 11:31:24 -0500822 sk_sp<const GrGpuBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
Michael Ludwig93aeba02018-12-21 09:50:31 -0500823 if (!ibuffer) {
824 return false;
825 }
826
827 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500828 mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500829 GrResourceProvider::QuadCountOfQuadBuffer());
830 } else {
831 mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
832 mesh->setNonIndexedNonInstanced(4);
833 }
834 }
835
836 return true;
837}
838
Michael Ludwigc182b942018-11-16 10:27:51 -0500839////////////////// VertexSpec Implementation
Michael Ludwig20e909e2018-10-30 10:43:57 -0400840
Michael Ludwigc182b942018-11-16 10:27:51 -0500841int VertexSpec::deviceDimensionality() const {
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400842 return this->deviceQuadType() == GrQuad::Type::kPerspective ? 3 : 2;
Michael Ludwigc182b942018-11-16 10:27:51 -0500843}
844
845int VertexSpec::localDimensionality() const {
Michael Ludwigde4c58c2019-06-04 09:12:59 -0400846 return fHasLocalCoords ? (this->localQuadType() == GrQuad::Type::kPerspective ? 3 : 2) : 0;
Michael Ludwigc182b942018-11-16 10:27:51 -0500847}
848
Michael Ludwig467994d2018-12-03 14:58:31 +0000849////////////////// Geometry Processor Implementation
Michael Ludwigc182b942018-11-16 10:27:51 -0500850
Michael Ludwig467994d2018-12-03 14:58:31 +0000851class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
852public:
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400853 using Saturate = GrTextureOp::Saturate;
Michael Ludwig20e909e2018-10-30 10:43:57 -0400854
Michael Ludwig467994d2018-12-03 14:58:31 +0000855 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
856 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400857 }
858
Michael Ludwig467994d2018-12-03 14:58:31 +0000859 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
Brian Salomon67529b22019-08-13 15:31:04 -0400860 GrTextureType textureType,
Greg Daniel7a82edf2018-12-04 10:54:34 -0500861 const GrSamplerState& samplerState,
Greg Daniel2c3398d2019-06-19 11:58:01 -0400862 const GrSwizzle& swizzle, uint32_t extraSamplerKey,
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400863 sk_sp<GrColorSpaceXform> textureColorSpaceXform,
864 Saturate saturate) {
Michael Ludwig467994d2018-12-03 14:58:31 +0000865 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
Brian Salomon67529b22019-08-13 15:31:04 -0400866 vertexSpec, caps, textureType, samplerState, swizzle, extraSamplerKey,
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400867 std::move(textureColorSpaceXform), saturate));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400868 }
869
Michael Ludwig467994d2018-12-03 14:58:31 +0000870 const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
Michael Ludwig024e2622018-11-30 13:25:55 -0500871
Michael Ludwig467994d2018-12-03 14:58:31 +0000872 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400873 // texturing, device-dimensions are single bit flags
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400874 uint32_t x = (fTexDomain.isInitialized() ? 0 : 0x1)
875 | (fSampler.isInitialized() ? 0 : 0x2)
876 | (fNeedsPerspective ? 0 : 0x4)
877 | (fSaturate == Saturate::kNo ? 0 : 0x8);
Michael Ludwig467994d2018-12-03 14:58:31 +0000878 // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
879 if (fLocalCoord.isInitialized()) {
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400880 x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 0x10 : 0x20;
Brian Osman78dc72c2018-12-03 13:20:43 +0000881 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000882 // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
Michael Ludwig93aeba02018-12-21 09:50:31 -0500883 if (fColor.isInitialized()) {
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400884 x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 0x40 : 0x80;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500885 }
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400886 // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for
887 // position+geomdomain
888 SkASSERT(!fGeomDomain.isInitialized() || fCoverageMode == CoverageMode::kWithPosition);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500889 if (fCoverageMode != CoverageMode::kNone) {
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400890 x |= fGeomDomain.isInitialized()
891 ? 0x300
892 : (CoverageMode::kWithPosition == fCoverageMode ? 0x100 : 0x200);
Michael Ludwig467994d2018-12-03 14:58:31 +0000893 }
894
895 b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
896 b->add32(x);
Brian Osman78dc72c2018-12-03 13:20:43 +0000897 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000898
899 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
900 class GLSLProcessor : public GrGLSLGeometryProcessor {
901 public:
902 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
903 FPCoordTransformIter&& transformIter) override {
904 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
905 if (gp.fLocalCoord.isInitialized()) {
906 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
907 }
908 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
909 }
910
911 private:
912 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
913 using Interpolation = GrGLSLVaryingHandler::Interpolation;
914
915 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
916 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
917 gp.fTextureColorSpaceXform.get());
918
919 args.fVaryingHandler->emitAttributes(gp);
920
Michael Ludwig93aeba02018-12-21 09:50:31 -0500921 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
922 // Strip last channel from the vertex attribute to remove coverage and get the
923 // actual position
924 if (gp.fNeedsPerspective) {
925 args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
926 gp.fPosition.name());
927 } else {
928 args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
929 gp.fPosition.name());
930 }
931 gpArgs->fPositionVar = {"position",
932 gp.fNeedsPerspective ? kFloat3_GrSLType
933 : kFloat2_GrSLType,
934 GrShaderVar::kNone_TypeModifier};
Michael Ludwig467994d2018-12-03 14:58:31 +0000935 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500936 // No coverage to eliminate
937 gpArgs->fPositionVar = gp.fPosition.asShaderVar();
Michael Ludwig467994d2018-12-03 14:58:31 +0000938 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000939
940 // Handle local coordinates if they exist
941 if (gp.fLocalCoord.isInitialized()) {
942 // NOTE: If the only usage of local coordinates is for the inline texture fetch
943 // before FPs, then there are no registered FPCoordTransforms and this ends up
944 // emitting nothing, so there isn't a duplication of local coordinates
945 this->emitTransforms(args.fVertBuilder,
946 args.fVaryingHandler,
947 args.fUniformHandler,
948 gp.fLocalCoord.asShaderVar(),
949 args.fFPCoordTransformHandler);
950 }
951
952 // Solid color before any texturing gets modulated in
953 if (gp.fColor.isInitialized()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400954 SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500955 // The color cannot be flat if the varying coverage has been modulated into it
Michael Ludwig467994d2018-12-03 14:58:31 +0000956 args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500957 gp.fCoverageMode == CoverageMode::kWithColor ?
958 Interpolation::kInterpolated : Interpolation::kCanBeFlat);
959 } else {
960 // Output color must be initialized to something
961 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
Michael Ludwig467994d2018-12-03 14:58:31 +0000962 }
963
964 // If there is a texture, must also handle texture coordinates and reading from
965 // the texture in the fragment shader before continuing to fragment processors.
966 if (gp.fSampler.isInitialized()) {
967 // Texture coordinates clamped by the domain on the fragment shader; if the GP
968 // has a texture, it's guaranteed to have local coordinates
969 args.fFragBuilder->codeAppend("float2 texCoord;");
970 if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
971 // Can't do a pass through since we need to perform perspective division
972 GrGLSLVarying v(gp.fLocalCoord.gpuType());
973 args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
974 args.fVertBuilder->codeAppendf("%s = %s;",
975 v.vsOut(), gp.fLocalCoord.name());
976 args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
977 v.fsIn(), v.fsIn());
978 } else {
979 args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
980 }
981
982 // Clamp the now 2D localCoordName variable by the domain if it is provided
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400983 if (gp.fTexDomain.isInitialized()) {
Michael Ludwig467994d2018-12-03 14:58:31 +0000984 args.fFragBuilder->codeAppend("float4 domain;");
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400985 args.fVaryingHandler->addPassThroughAttribute(gp.fTexDomain, "domain",
Michael Ludwig467994d2018-12-03 14:58:31 +0000986 Interpolation::kCanBeFlat);
987 args.fFragBuilder->codeAppend(
988 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
989 }
990
991 // Now modulate the starting output color by the texture lookup
992 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
993 args.fFragBuilder->appendTextureLookupAndModulate(
994 args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
995 &fTextureColorSpaceXformHelper);
996 args.fFragBuilder->codeAppend(";");
Brian Salomonf19f9ca2019-09-18 15:54:26 -0400997 if (gp.fSaturate == Saturate::kYes) {
998 args.fFragBuilder->codeAppendf("%s = saturate(%s);",
999 args.fOutputColor, args.fOutputColor);
1000 }
1001 } else {
1002 // Saturate is only intended for use with a proxy to account for the fact
1003 // that GrTextureOp skips SkPaint conversion, which normally handles this.
1004 SkASSERT(gp.fSaturate == Saturate::kNo);
Michael Ludwig467994d2018-12-03 14:58:31 +00001005 }
1006
1007 // And lastly, output the coverage calculation code
Michael Ludwig93aeba02018-12-21 09:50:31 -05001008 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
1009 GrGLSLVarying coverage(kFloat_GrSLType);
1010 args.fVaryingHandler->addVarying("coverage", &coverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001011 if (gp.fNeedsPerspective) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -04001012 // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in
1013 // the fragment shader to get screen-space linear coverage.
1014 args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;",
1015 coverage.vsOut(), gp.fPosition.name(),
1016 gp.fPosition.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001017 args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;",
1018 coverage.fsIn());
Michael Ludwig93aeba02018-12-21 09:50:31 -05001019 } else {
Jim Van Verthd5d9c212019-05-02 10:22:10 -04001020 args.fVertBuilder->codeAppendf("%s = %s;",
1021 coverage.vsOut(), gp.fCoverage.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001022 args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn());
Michael Ludwig467994d2018-12-03 14:58:31 +00001023 }
Michael Ludwig93aeba02018-12-21 09:50:31 -05001024
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001025 if (gp.fGeomDomain.isInitialized()) {
1026 // Calculate distance from sk_FragCoord to the 4 edges of the domain
1027 // and clamp them to (0, 1). Use the minimum of these and the original
1028 // coverage. This only has to be done in the exterior triangles, the
1029 // interior of the quad geometry can never be clipped by the domain box.
1030 args.fFragBuilder->codeAppend("float4 geoDomain;");
1031 args.fVaryingHandler->addPassThroughAttribute(gp.fGeomDomain, "geoDomain",
1032 Interpolation::kCanBeFlat);
1033 args.fFragBuilder->codeAppend(
1034 "if (coverage < 0.5) {"
1035 " float4 dists4 = clamp(float4(1, 1, -1, -1) * "
1036 "(sk_FragCoord.xyxy - geoDomain), 0, 1);"
1037 " float2 dists2 = dists4.xy * dists4.zw;"
1038 " coverage = min(coverage, dists2.x * dists2.y);"
1039 "}");
1040 }
1041
1042 args.fFragBuilder->codeAppendf("%s = half4(half(coverage));",
1043 args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001044 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001045 // Set coverage to 1, since it's either non-AA or the coverage was already
1046 // folded into the output color
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001047 SkASSERT(!gp.fGeomDomain.isInitialized());
Ethan Nicholase1f55022019-02-05 17:17:40 -05001048 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001049 }
1050 }
1051 GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
1052 };
1053 return new GLSLProcessor;
1054 }
1055
1056private:
1057 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
1058 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
1059 , fTextureColorSpaceXform(nullptr) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001060 SkASSERT(!spec.hasDomain());
Michael Ludwig467994d2018-12-03 14:58:31 +00001061 this->initializeAttrs(spec);
1062 this->setTextureSamplerCnt(0);
1063 }
1064
Brian Salomon67529b22019-08-13 15:31:04 -04001065 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec,
1066 const GrShaderCaps& caps,
1067 GrTextureType textureType,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001068 const GrSamplerState& samplerState,
Greg Daniel2c3398d2019-06-19 11:58:01 -04001069 const GrSwizzle& swizzle,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001070 uint32_t extraSamplerKey,
Brian Salomonf19f9ca2019-09-18 15:54:26 -04001071 sk_sp<GrColorSpaceXform> textureColorSpaceXform,
1072 Saturate saturate)
Michael Ludwig467994d2018-12-03 14:58:31 +00001073 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
Brian Salomonf19f9ca2019-09-18 15:54:26 -04001074 , fSaturate(saturate)
Michael Ludwig467994d2018-12-03 14:58:31 +00001075 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
Brian Salomon67529b22019-08-13 15:31:04 -04001076 , fSampler(textureType, samplerState, swizzle, extraSamplerKey) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001077 SkASSERT(spec.hasLocalCoords());
Michael Ludwig467994d2018-12-03 14:58:31 +00001078 this->initializeAttrs(spec);
1079 this->setTextureSamplerCnt(1);
1080 }
1081
1082 void initializeAttrs(const VertexSpec& spec) {
1083 fNeedsPerspective = spec.deviceDimensionality() == 3;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001084 fCoverageMode = get_mode_for_spec(spec);
1085
1086 if (fCoverageMode == CoverageMode::kWithPosition) {
1087 if (fNeedsPerspective) {
1088 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1089 } else {
Jim Van Verthd5d9c212019-05-02 10:22:10 -04001090 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1091 fCoverage = {"coverage", kFloat_GrVertexAttribType, kFloat_GrSLType};
Michael Ludwig93aeba02018-12-21 09:50:31 -05001092 }
1093 } else {
1094 if (fNeedsPerspective) {
1095 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1096 } else {
1097 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1098 }
1099 }
Michael Ludwig467994d2018-12-03 14:58:31 +00001100
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001101 // Need a geometry domain when the quads are AA and not rectilinear, since their AA
1102 // outsetting can go beyond a half pixel.
1103 if (spec.requiresGeometryDomain()) {
1104 fGeomDomain = {"geomDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1105 }
1106
Michael Ludwig467994d2018-12-03 14:58:31 +00001107 int localDim = spec.localDimensionality();
1108 if (localDim == 3) {
1109 fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1110 } else if (localDim == 2) {
1111 fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1112 } // else localDim == 0 and attribute remains uninitialized
1113
1114 if (ColorType::kByte == spec.colorType()) {
1115 fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
1116 } else if (ColorType::kHalf == spec.colorType()) {
1117 fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
1118 }
1119
1120 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001121 fTexDomain = {"texDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
Michael Ludwig467994d2018-12-03 14:58:31 +00001122 }
1123
Jim Van Verthd5d9c212019-05-02 10:22:10 -04001124 this->setVertexAttributes(&fPosition, 6);
Michael Ludwig467994d2018-12-03 14:58:31 +00001125 }
1126
1127 const TextureSampler& onTextureSampler(int) const override { return fSampler; }
1128
Michael Ludwig93aeba02018-12-21 09:50:31 -05001129 Attribute fPosition; // May contain coverage as last channel
Jim Van Verthd5d9c212019-05-02 10:22:10 -04001130 Attribute fCoverage; // Used for non-perspective position to avoid Intel Metal issues
Michael Ludwig93aeba02018-12-21 09:50:31 -05001131 Attribute fColor; // May have coverage modulated in if the FPs support it
Michael Ludwig467994d2018-12-03 14:58:31 +00001132 Attribute fLocalCoord;
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001133 Attribute fGeomDomain; // Screen-space bounding box on geometry+aa outset
1134 Attribute fTexDomain; // Texture-space bounding box on local coords
Michael Ludwig467994d2018-12-03 14:58:31 +00001135
Michael Ludwig93aeba02018-12-21 09:50:31 -05001136 // The positions attribute may have coverage built into it, so float3 is an ambiguous type
1137 // and may mean 2d with coverage, or 3d with no coverage
Michael Ludwig467994d2018-12-03 14:58:31 +00001138 bool fNeedsPerspective;
Brian Salomonf19f9ca2019-09-18 15:54:26 -04001139 // Should saturate() be called on the color? Only relevant when created with a texture.
1140 Saturate fSaturate = Saturate::kNo;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001141 CoverageMode fCoverageMode;
Michael Ludwig467994d2018-12-03 14:58:31 +00001142
1143 // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
1144 // to skip texturing.
1145 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1146 TextureSampler fSampler;
1147
1148 typedef GrGeometryProcessor INHERITED;
1149};
1150
1151sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
1152 return QuadPerEdgeAAGeometryProcessor::Make(spec);
1153}
1154
1155sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
Brian Salomon67529b22019-08-13 15:31:04 -04001156 GrTextureType textureType,
1157 const GrSamplerState& samplerState,
1158 const GrSwizzle& swizzle, uint32_t extraSamplerKey,
Brian Salomonf19f9ca2019-09-18 15:54:26 -04001159 sk_sp<GrColorSpaceXform> textureColorSpaceXform,
1160 Saturate saturate) {
Brian Salomon67529b22019-08-13 15:31:04 -04001161 return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, samplerState, swizzle,
Brian Salomonf19f9ca2019-09-18 15:54:26 -04001162 extraSamplerKey, std::move(textureColorSpaceXform),
1163 saturate);
Michael Ludwig20e909e2018-10-30 10:43:57 -04001164}
Michael Ludwigc182b942018-11-16 10:27:51 -05001165
1166} // namespace GrQuadPerEdgeAA