blob: b9a7b45435d9a497997b6b8f1bdef0cc53a71bb1 [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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/private/SkNx.h"
9#include "src/gpu/GrQuad.h"
10#include "src/gpu/GrVertexWriter.h"
11#include "src/gpu/SkGr.h"
12#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h"
13#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
14#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
15#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h"
16#include "src/gpu/glsl/GrGLSLVarying.h"
17#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
18#include "src/gpu/ops/GrQuadPerEdgeAA.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
261 quad->fX += a * e1x + b * e2x;
262 quad->fY += a * e1y + b * e2y;
263 quad->fW += a * e1w + b * e2w;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400264 correct_bad_coords(abs(denom) < kTolerance, &quad->fX, &quad->fY, &quad->fW);
Michael Ludwige6266a22019-03-07 11:24:32 -0500265
266 if (quad->fUVRCount > 0) {
267 // 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 -0400268 V4f e1u = skvx::shuffle<2, 3, 2, 3>(quad->fU) - skvx::shuffle<0, 1, 0, 1>(quad->fU);
269 V4f e1v = skvx::shuffle<2, 3, 2, 3>(quad->fV) - skvx::shuffle<0, 1, 0, 1>(quad->fV);
270 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 -0500271 correct_bad_edges(fma(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r);
272
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400273 V4f e2u = skvx::shuffle<1, 1, 3, 3>(quad->fU) - skvx::shuffle<0, 0, 2, 2>(quad->fU);
274 V4f e2v = skvx::shuffle<1, 1, 3, 3>(quad->fV) - skvx::shuffle<0, 0, 2, 2>(quad->fV);
275 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 -0500276 correct_bad_edges(fma(e2u, e2u, e2v * e2v) < kTolerance * kTolerance, &e2u, &e2v, &e2r);
277
278 quad->fU += a * e1u + b * e2u;
279 quad->fV += a * e1v + b * e2v;
280 if (quad->fUVRCount == 3) {
281 quad->fR += a * e1r + b * e2r;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400282 correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, &quad->fR);
Michael Ludwige6266a22019-03-07 11:24:32 -0500283 } else {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400284 correct_bad_coords(abs(denom) < kTolerance, &quad->fU, &quad->fV, nullptr);
Michael Ludwigf995c052018-11-26 15:24:29 -0500285 }
286 }
287}
288
Michael Ludwige6266a22019-03-07 11:24:32 -0500289// Calculate area of intersection between quad (xs, ys) and a pixel at 'pixelCenter'.
290// a, b, c are edge equations of the quad, flipped is true if the line equations had their normals
291// reversed to correct for matrix transforms.
292static float get_exact_coverage(const SkPoint& pixelCenter, const Vertices& quad,
293 const Edges& edges) {
294 // Ordering of vertices given default tri-strip that produces CCW points
295 static const int kCCW[] = {0, 1, 3, 2};
296 // Ordering of vertices given inverted tri-strip that produces CCW
297 static const int kFlippedCCW[] = {0, 2, 3, 1};
298
299 // Edge boundaries of the pixel
300 float left = pixelCenter.fX - 0.5f;
301 float right = pixelCenter.fX + 0.5f;
302 float top = pixelCenter.fY - 0.5f;
303 float bot = pixelCenter.fY + 0.5f;
304
305 // Whether or not the 4 corners of the pixel are inside the quad geometry. Variable names are
306 // intentional to work easily with the helper macros.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400307 bool topleftInside = all((edges.fA * left + edges.fB * top + edges.fC) >= 0.f);
308 bool botleftInside = all((edges.fA * left + edges.fB * bot + edges.fC) >= 0.f);
309 bool botrightInside = all((edges.fA * right + edges.fB * bot + edges.fC) >= 0.f);
310 bool toprightInside = all((edges.fA * right + edges.fB * top + edges.fC) >= 0.f);
Michael Ludwige6266a22019-03-07 11:24:32 -0500311 if (topleftInside && botleftInside && botrightInside && toprightInside) {
312 // Quad fully contains the pixel, so we know the area will be 1.f
313 return 1.f;
314 }
315
316 // 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 -0400317 M4f leftValid = quad.fX >= left;
318 M4f rightValid = quad.fX <= right;
319 M4f topValid = quad.fY >= top;
320 M4f botValid = quad.fY <= bot;
Michael Ludwige6266a22019-03-07 11:24:32 -0500321
322 // Intercepts of quad lines with the 4 pixel edges
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400323 V4f leftCross = -(edges.fC + edges.fA * left) / edges.fB;
324 V4f rightCross = -(edges.fC + edges.fA * right) / edges.fB;
325 V4f topCross = -(edges.fC + edges.fB * top) / edges.fA;
326 V4f botCross = -(edges.fC + edges.fB * bot) / edges.fA;
Michael Ludwige6266a22019-03-07 11:24:32 -0500327
328 // State for implicitly tracking the intersection boundary and area
329 SkPoint firstPoint = {0.f, 0.f};
330 SkPoint lastPoint = {0.f, 0.f};
331 bool intersected = false;
332 float area = 0.f;
333
334 // Adds a point to the intersection hull, remembering first point (for closing) and the
335 // current point, and updates the running area total.
336 // See http://mathworld.wolfram.com/PolygonArea.html
337 auto accumulate = [&](const SkPoint& p) {
338 if (intersected) {
339 float da = lastPoint.fX * p.fY - p.fX * lastPoint.fY;
340 area += da;
341 } else {
342 firstPoint = p;
343 intersected = true;
344 }
345 lastPoint = p;
346 };
347
348 // Used during iteration over the quad points to check if edge intersections are valid and
349 // should be accumulated.
350#define ADD_EDGE_CROSSING_X(SIDE) \
351 do { \
352 if (SIDE##Cross[ei] >= top && SIDE##Cross[ei] <= bot) { \
353 accumulate({SIDE, SIDE##Cross[ei]}); \
354 addedIntersection = true; \
355 } \
356 } while(false)
357#define ADD_EDGE_CROSSING_Y(SIDE) \
358 do { \
359 if (SIDE##Cross[ei] >= left && SIDE##Cross[ei] <= right) { \
360 accumulate({SIDE##Cross[ei], SIDE}); \
361 addedIntersection = true; \
362 } \
363 } while(false)
364#define TEST_EDGES(SIDE, AXIS, I, NI) \
365 do { \
366 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
367 ADD_EDGE_CROSSING_##AXIS(SIDE); \
368 crossedEdges = true; \
369 } \
370 } while(false)
371 // Used during iteration over the quad points to check if a pixel corner should be included
372 // in the intersection boundary
373#define ADD_CORNER(CHECK, SIDE_LR, SIDE_TB) \
374 if (!CHECK##Valid[i] || !CHECK##Valid[ni]) { \
375 if (SIDE_TB##SIDE_LR##Inside) { \
376 accumulate({SIDE_LR, SIDE_TB}); \
377 } \
378 }
379#define TEST_CORNER_X(SIDE, I, NI) \
380 do { \
381 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
382 ADD_CORNER(top, SIDE, top) else ADD_CORNER(bot, SIDE, bot) \
383 } \
384 } while(false)
385#define TEST_CORNER_Y(SIDE, I, NI) \
386 do { \
387 if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
388 ADD_CORNER(left, left, SIDE) else ADD_CORNER(right, right, SIDE) \
389 } \
390 } while(false)
391
392 // Iterate over the 4 points of the quad, adding valid intersections with the pixel edges
393 // or adding interior pixel corners as it goes. This automatically keeps all accumulated points
394 // in CCW ordering so the area can be calculated on the fly and there's no need to store the
395 // list of hull points. This is somewhat inspired by the Sutherland-Hodgman algorithm but since
396 // there are only 4 points in each source polygon, there is no point list maintenance.
397 for (int j = 0; j < 4; ++j) {
398 // Current vertex
399 int i = edges.fFlipped ? kFlippedCCW[j] : kCCW[j];
400 // Moving to this vertex
401 int ni = edges.fFlipped ? kFlippedCCW[(j + 1) % 4] : kCCW[(j + 1) % 4];
402 // Index in edge vectors corresponding to move from i to ni
403 int ei = edges.fFlipped ? ni : i;
404
405 bool crossedEdges = false;
406 bool addedIntersection = false;
407
408 // First check if there are any outside -> inside edge crossings. There can be 0, 1, or 2.
409 // 2 can occur if one crossing is still outside the pixel, or if they both go through
410 // the corner (in which case a duplicate point is added, but that doesn't change area).
411
412 // Outside to inside crossing
413 TEST_EDGES(left, X, i, ni);
414 TEST_EDGES(right, X, i, ni);
415 TEST_EDGES(top, Y, i, ni);
416 TEST_EDGES(bot, Y, i, ni);
417 // Inside to outside crossing (swapping ni and i in the boolean test)
418 TEST_EDGES(left, X, ni, i);
419 TEST_EDGES(right, X, ni, i);
420 TEST_EDGES(top, Y, ni, i);
421 TEST_EDGES(bot, Y, ni, i);
422
423 // If we crossed edges but didn't add any intersections, check the corners of the pixel.
424 // If the pixel corners are inside the quad, include them in the boundary.
425 if (crossedEdges && !addedIntersection) {
426 // This can lead to repeated points, but those just accumulate zero area
427 TEST_CORNER_X(left, i, ni);
428 TEST_CORNER_X(right, i, ni);
429 TEST_CORNER_Y(top, i, ni);
430 TEST_CORNER_Y(bot, i, ni);
431
432 TEST_CORNER_X(left, ni, i);
433 TEST_CORNER_X(right, ni, i);
434 TEST_CORNER_Y(top, ni, i);
435 TEST_CORNER_Y(bot, ni, i);
436 }
437
438 // Lastly, if the next point is completely inside the pixel it gets included in the boundary
439 if (leftValid[ni] && rightValid[ni] && topValid[ni] && botValid[ni]) {
440 accumulate({quad.fX[ni], quad.fY[ni]});
441 }
442 }
443
444#undef TEST_CORNER_Y
445#undef TEST_CORNER_X
446#undef ADD_CORNER
447
448#undef TEST_EDGES
449#undef ADD_EDGE_CROSSING_Y
450#undef ADD_EDGE_CROSSING_X
451
452 // After all points have been considered, close the boundary to get final area. If we never
453 // added any points, it means the quad didn't intersect the pixel rectangle.
454 if (intersected) {
455 // Final equation for area of convex polygon is to multiply by -1/2 (minus since the points
456 // were in CCW order).
457 accumulate(firstPoint);
458 return -0.5f * area;
459 } else {
460 return 0.f;
461 }
462}
463
464// Outsets or insets xs/ys in place. To be used when the interior is very small, edges are near
465// parallel, or edges are very short/zero-length. Returns coverage for each vertex.
466// Requires (dx, dy) to already be fixed for empty edges.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400467static V4f compute_degenerate_quad(GrQuadAAFlags aaFlags, const V4f& mask, const Edges& edges,
Michael Ludwige6266a22019-03-07 11:24:32 -0500468 bool outset, Vertices* quad) {
469 // Move the edge 1/2 pixel in or out depending on 'outset'.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400470 V4f oc = edges.fC + mask * (outset ? 0.5f : -0.5f);
Michael Ludwige6266a22019-03-07 11:24:32 -0500471
472 // There are 6 points that we care about to determine the final shape of the polygon, which
473 // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the
474 // 4 corners), and (e1, e2), (e0, e3) (representing the intersections of opposite edges).
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400475 V4f denom = edges.fA * nextCW(edges.fB) - edges.fB * nextCW(edges.fA);
476 V4f px = (edges.fB * nextCW(oc) - oc * nextCW(edges.fB)) / denom;
477 V4f py = (oc * nextCW(edges.fA) - edges.fA * nextCW(oc)) / denom;
478 correct_bad_coords(abs(denom) < kTolerance, &px, &py, nullptr);
Michael Ludwige6266a22019-03-07 11:24:32 -0500479
480 // Calculate the signed distances from these 4 corners to the other two edges that did not
481 // define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and
482 // p(3) to e0,e2
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400483 V4f dists1 = px * skvx::shuffle<3, 3, 0, 0>(edges.fA) +
484 py * skvx::shuffle<3, 3, 0, 0>(edges.fB) +
485 skvx::shuffle<3, 3, 0, 0>(oc);
486 V4f dists2 = px * skvx::shuffle<1, 2, 1, 2>(edges.fA) +
487 py * skvx::shuffle<1, 2, 1, 2>(edges.fB) +
488 skvx::shuffle<1, 2, 1, 2>(oc);
Michael Ludwige6266a22019-03-07 11:24:32 -0500489
490 // If all the distances are >= 0, the 4 corners form a valid quadrilateral, so use them as
491 // the 4 points. If any point is on the wrong side of both edges, the interior has collapsed
492 // and we need to use a central point to represent it. If all four points are only on the
493 // wrong side of 1 edge, one edge has crossed over another and we use a line to represent it.
494 // Otherwise, use a triangle that replaces the bad points with the intersections of
495 // (e1, e2) or (e0, e3) as needed.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400496 M4f d1v0 = dists1 < kTolerance;
497 M4f d2v0 = dists2 < kTolerance;
498 M4f d1And2 = d1v0 & d2v0;
499 M4f d1Or2 = d1v0 | d2v0;
Michael Ludwige6266a22019-03-07 11:24:32 -0500500
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400501 V4f coverage;
502 if (!any(d1Or2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500503 // Every dists1 and dists2 >= kTolerance so it's not degenerate, use all 4 corners as-is
504 // and use full coverage
505 coverage = 1.f;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400506 } else if (any(d1And2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500507 // A point failed against two edges, so reduce the shape to a single point, which we take as
508 // the center of the original quad to ensure it is contained in the intended geometry. Since
509 // it has collapsed, we know the shape cannot cover a pixel so update the coverage.
510 SkPoint center = {0.25f * (quad->fX[0] + quad->fX[1] + quad->fX[2] + quad->fX[3]),
511 0.25f * (quad->fY[0] + quad->fY[1] + quad->fY[2] + quad->fY[3])};
512 coverage = get_exact_coverage(center, *quad, edges);
513 px = center.fX;
514 py = center.fY;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400515 } else if (all(d1Or2)) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500516 // Degenerates to a line. Compare p[2] and p[3] to edge 0. If they are on the wrong side,
517 // that means edge 0 and 3 crossed, and otherwise edge 1 and 2 crossed.
518 if (dists1[2] < kTolerance && dists1[3] < kTolerance) {
519 // 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 -0400520 px = 0.5f * (skvx::shuffle<0, 1, 0, 1>(px) + skvx::shuffle<2, 3, 2, 3>(px));
521 py = 0.5f * (skvx::shuffle<0, 1, 0, 1>(py) + skvx::shuffle<2, 3, 2, 3>(py));
Michael Ludwige6266a22019-03-07 11:24:32 -0500522 float mc02 = get_exact_coverage({px[0], py[0]}, *quad, edges);
523 float mc13 = get_exact_coverage({px[1], py[1]}, *quad, edges);
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400524 coverage = V4f{mc02, mc13, mc02, mc13};
Michael Ludwige6266a22019-03-07 11:24:32 -0500525 } else {
526 // 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 -0400527 px = 0.5f * (skvx::shuffle<0, 0, 2, 2>(px) + skvx::shuffle<1, 1, 3, 3>(px));
528 py = 0.5f * (skvx::shuffle<0, 0, 2, 2>(py) + skvx::shuffle<1, 1, 3, 3>(py));
Michael Ludwige6266a22019-03-07 11:24:32 -0500529 float mc01 = get_exact_coverage({px[0], py[0]}, *quad, edges);
530 float mc23 = get_exact_coverage({px[2], py[2]}, *quad, edges);
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400531 coverage = V4f{mc01, mc01, mc23, mc23};
Michael Ludwige6266a22019-03-07 11:24:32 -0500532 }
533 } else {
534 // This turns into a triangle. Replace corners as needed with the intersections between
535 // (e0,e3) and (e1,e2), which must now be calculated
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400536 using V2f = skvx::Vec<2, float>;
537 V2f eDenom = skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(edges.fB) -
538 skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(edges.fA);
539 V2f ex = (skvx::shuffle<0, 1>(edges.fB) * skvx::shuffle<3, 2>(oc) -
540 skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fB)) / eDenom;
541 V2f ey = (skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(edges.fA) -
542 skvx::shuffle<0, 1>(edges.fA) * skvx::shuffle<3, 2>(oc)) / eDenom;
Michael Ludwige6266a22019-03-07 11:24:32 -0500543
544 if (SkScalarAbs(eDenom[0]) > kTolerance) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400545 px = if_then_else(d1v0, V4f(ex[0]), px);
546 py = if_then_else(d1v0, V4f(ey[0]), py);
Michael Ludwige6266a22019-03-07 11:24:32 -0500547 }
548 if (SkScalarAbs(eDenom[1]) > kTolerance) {
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400549 px = if_then_else(d2v0, V4f(ex[1]), px);
550 py = if_then_else(d2v0, V4f(ey[1]), py);
Michael Ludwige6266a22019-03-07 11:24:32 -0500551 }
552
553 coverage = 1.f;
554 }
555
556 outset_projected_vertices(px, py, aaFlags, quad);
557 return coverage;
Michael Ludwigf995c052018-11-26 15:24:29 -0500558}
559
Michael Ludwig93aeba02018-12-21 09:50:31 -0500560// Computes the vertices for the two nested quads used to create AA edges. The original single quad
Michael Ludwige6266a22019-03-07 11:24:32 -0500561// should be duplicated as input in 'inner' and 'outer', and the resulting quad frame will be
562// stored in-place on return. Returns per-vertex coverage for the inner vertices.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400563static V4f compute_nested_quad_vertices(GrQuadAAFlags aaFlags, bool rectilinear,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400564 Vertices* inner, Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500565 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
566 SkASSERT(outer->fUVRCount == inner->fUVRCount);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400567
Michael Ludwige6266a22019-03-07 11:24:32 -0500568 QuadMetadata metadata = get_metadata(*inner, aaFlags);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400569
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400570 // Calculate domain first before updating vertices. It's only used when not rectilinear.
571 if (!rectilinear) {
572 SkASSERT(domain);
573 // The domain is the bounding box of the quad, outset by 0.5. Don't worry about edge masks
574 // since the FP only applies the domain on the exterior triangles, which are degenerate for
575 // non-AA edges.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400576 domain->fLeft = min(outer->fX) - 0.5f;
577 domain->fRight = max(outer->fX) + 0.5f;
578 domain->fTop = min(outer->fY) - 0.5f;
579 domain->fBottom = max(outer->fY) + 0.5f;
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400580 }
581
Michael Ludwig93aeba02018-12-21 09:50:31 -0500582 // 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 -0500583 // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment
584 // 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 -0400585 V4f outset = 0.5f;
Michael Ludwige6266a22019-03-07 11:24:32 -0500586 if (get_optimized_outset(metadata, rectilinear, &outset)) {
587 // Since it's not subpixel, outsetting and insetting are trivial vector additions.
588 outset_vertices(outset, metadata, outer);
589 outset_vertices(-outset, metadata, inner);
590 return 1.f;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400591 }
Michael Ludwig4921dc32018-12-03 14:57:29 +0000592
Michael Ludwige6266a22019-03-07 11:24:32 -0500593 // Only compute edge equations once since they are the same for inner and outer quads
594 Edges edges = get_edge_equations(metadata, *inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500595
Michael Ludwige6266a22019-03-07 11:24:32 -0500596 // Calculate both outset and inset, returning the coverage reported for the inset, since the
597 // outset will always have 0.0f.
598 compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer);
599 return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500600}
601
Michael Ludwige6266a22019-03-07 11:24:32 -0500602// Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective
603// division of the device coordinates, the original local coordinate value is at the original
604// un-outset device position.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400605static V4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400606 Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500607 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
608 SkASSERT(outer->fUVRCount == inner->fUVRCount);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500609
Michael Ludwige6266a22019-03-07 11:24:32 -0500610 // Calculate the projected 2D quad and use it to form projeccted inner/outer quads
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400611 V4f iw = 1.0f / inner->fW;
612 V4f x2d = inner->fX * iw;
613 V4f y2d = inner->fY * iw;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500614
Michael Ludwige6266a22019-03-07 11:24:32 -0500615 Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D
616 Vertices outer2D = inner2D;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400617
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400618 V4f coverage = compute_nested_quad_vertices(
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400619 aaFlags, /* rect */ false, &inner2D, &outer2D, domain);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400620
Michael Ludwige6266a22019-03-07 11:24:32 -0500621 // Now map from the 2D inset/outset back to 3D and update the local coordinates as well
622 outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner);
623 outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500624
Michael Ludwige6266a22019-03-07 11:24:32 -0500625 return coverage;
Eric Boren98cb1592018-11-26 18:38:05 +0000626}
627
Michael Ludwig93aeba02018-12-21 09:50:31 -0500628enum class CoverageMode {
629 kNone,
630 kWithPosition,
631 kWithColor
632};
633
634static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
635 if (spec.usesCoverageAA()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400636 if (spec.compatibleWithCoverageAsAlpha() && spec.hasVertexColors() &&
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400637 !spec.requiresGeometryDomain()) {
638 // Using a geometric domain acts as a second source of coverage and folding the original
639 // coverage into color makes it impossible to apply the color's alpha to the geometric
640 // domain's coverage when the original shape is clipped.
Michael Ludwig93aeba02018-12-21 09:50:31 -0500641 return CoverageMode::kWithColor;
642 } else {
643 return CoverageMode::kWithPosition;
644 }
Michael Ludwig553e9a92018-11-29 12:38:35 -0500645 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500646 return CoverageMode::kNone;
Michael Ludwig553e9a92018-11-29 12:38:35 -0500647 }
Michael Ludwig93aeba02018-12-21 09:50:31 -0500648}
Michael Ludwig553e9a92018-11-29 12:38:35 -0500649
Michael Ludwig93aeba02018-12-21 09:50:31 -0500650// Writes four vertices in triangle strip order, including the additional data for local
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400651// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec
Michael Ludwig93aeba02018-12-21 09:50:31 -0500652static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400653 CoverageMode mode, const V4f& coverage, SkPMColor4f color4f,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400654 const SkRect& geomDomain, const SkRect& texDomain, const Vertices& quad) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500655 static constexpr auto If = GrVertexWriter::If<float>;
656
Michael Ludwig553e9a92018-11-29 12:38:35 -0500657 for (int i = 0; i < 4; ++i) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500658 // save position, this is a float2 or float3 or float4 depending on the combination of
659 // perspective and coverage mode.
Michael Ludwige6266a22019-03-07 11:24:32 -0500660 vb->write(quad.fX[i], quad.fY[i],
661 If(spec.deviceQuadType() == GrQuadType::kPerspective, quad.fW[i]),
662 If(mode == CoverageMode::kWithPosition, coverage[i]));
Michael Ludwig4921dc32018-12-03 14:57:29 +0000663
Michael Ludwig93aeba02018-12-21 09:50:31 -0500664 // save color
665 if (spec.hasVertexColors()) {
Brian Salomon1d835422019-03-13 16:11:44 -0400666 bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
Michael Ludwige6266a22019-03-07 11:24:32 -0500667 vb->write(GrVertexColor(
Brian Salomon1d835422019-03-13 16:11:44 -0400668 color4f * (mode == CoverageMode::kWithColor ? coverage[i] : 1.f), wide));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500669 }
670
671 // save local position
672 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500673 vb->write(quad.fU[i], quad.fV[i],
674 If(spec.localQuadType() == GrQuadType::kPerspective, quad.fR[i]));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500675 }
676
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400677 // save the geometry domain
678 if (spec.requiresGeometryDomain()) {
679 vb->write(geomDomain);
680 }
681
682 // save the texture domain
Michael Ludwig93aeba02018-12-21 09:50:31 -0500683 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400684 vb->write(texDomain);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500685 }
686 }
687}
688
689GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
690
691static const int kVertsPerAAFillRect = 8;
692static const int kIndicesPerAAFillRect = 30;
693
Brian Salomondbf70722019-02-07 11:31:24 -0500694static sk_sp<const GrGpuBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500695 GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
696
697 // clang-format off
698 static const uint16_t gFillAARectIdx[] = {
699 0, 1, 2, 1, 3, 2,
700 0, 4, 1, 4, 5, 1,
701 0, 6, 4, 0, 2, 6,
702 2, 3, 6, 3, 7, 6,
703 1, 5, 3, 3, 5, 7,
704 };
705 // clang-format on
706
707 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
708 return resourceProvider->findOrCreatePatternedIndexBuffer(
709 gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
710 kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
Michael Ludwig553e9a92018-11-29 12:38:35 -0500711}
712
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400713} // anonymous namespace
714
Michael Ludwigc182b942018-11-16 10:27:51 -0500715namespace GrQuadPerEdgeAA {
716
Brian Osman8fa7ab42019-03-18 10:22:42 -0400717// This is a more elaborate version of SkPMColor4fNeedsWideColor that allows "no color" for white
718ColorType MinColorType(SkPMColor4f color, GrClampType clampType, const GrCaps& caps) {
Brian Salomon1d835422019-03-13 16:11:44 -0400719 if (color == SK_PMColor4fWHITE) {
720 return ColorType::kNone;
Brian Salomon1d835422019-03-13 16:11:44 -0400721 } else {
Brian Osman8fa7ab42019-03-18 10:22:42 -0400722 return SkPMColor4fNeedsWideColor(color, clampType, caps) ? ColorType::kHalf
723 : ColorType::kByte;
Brian Salomon1d835422019-03-13 16:11:44 -0400724 }
725}
726
Michael Ludwigc182b942018-11-16 10:27:51 -0500727////////////////// Tessellate Implementation
728
729void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
Brian Osman3d139a42018-11-19 10:42:10 -0500730 const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
Michael Ludwigc182b942018-11-16 10:27:51 -0500731 GrQuadAAFlags aaFlags) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500732 CoverageMode mode = get_mode_for_spec(spec);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400733
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400734 // Load position data into V4fs (always x, y, and load w to avoid branching down the road)
Michael Ludwige6266a22019-03-07 11:24:32 -0500735 Vertices outer;
736 outer.fX = deviceQuad.x4f();
737 outer.fY = deviceQuad.y4f();
738 outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400739
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400740 // Load local position data into V4fs (either none, just u,v or all three)
Michael Ludwige6266a22019-03-07 11:24:32 -0500741 outer.fUVRCount = spec.localDimensionality();
Michael Ludwigc182b942018-11-16 10:27:51 -0500742 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500743 outer.fU = localQuad.x4f();
744 outer.fV = localQuad.y4f();
745 outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400746 }
747
Michael Ludwigc182b942018-11-16 10:27:51 -0500748 GrVertexWriter vb{vertices};
Michael Ludwig93aeba02018-12-21 09:50:31 -0500749 if (spec.usesCoverageAA()) {
750 SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500751 // Must calculate two new quads, an outset and inset by .5 in projected device space, so
Michael Ludwige6266a22019-03-07 11:24:32 -0500752 // duplicate the original quad for the inner space
753 Vertices inner = outer;
Michael Ludwigc182b942018-11-16 10:27:51 -0500754
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400755 SkRect geomDomain;
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400756 V4f maxCoverage = 1.f;
Michael Ludwige6266a22019-03-07 11:24:32 -0500757 if (spec.deviceQuadType() == GrQuadType::kPerspective) {
758 // For perspective, send quads with all edges non-AA through the tessellation to ensure
759 // their corners are processed the same as adjacent quads. This approach relies on
760 // solving edge equations to reconstruct corners, which can create seams if an inner
761 // fully non-AA quad is not similarly processed.
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400762 maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer, &geomDomain);
Michael Ludwige6266a22019-03-07 11:24:32 -0500763 } else if (aaFlags != GrQuadAAFlags::kNone) {
764 // In 2D, the simpler corner math does not cause issues with seaming against non-AA
765 // inner quads.
766 maxCoverage = compute_nested_quad_vertices(
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400767 aaFlags, spec.deviceQuadType() <= GrQuadType::kRectilinear, &inner, &outer,
768 &geomDomain);
Michael Ludwig25071cc2019-04-12 09:23:22 -0400769 } else if (spec.requiresGeometryDomain()) {
770 // The quad itself wouldn't need a geometric domain, but the batch does, so set the
771 // domain to the bounds of the X/Y coords. Since it's non-AA, this won't actually be
772 // evaluated by the shader, but make sure not to upload uninitialized data.
Michael Ludwigb3461fa2019-04-30 11:50:55 -0400773 geomDomain.fLeft = min(outer.fX);
774 geomDomain.fRight = max(outer.fX);
775 geomDomain.fTop = min(outer.fY);
776 geomDomain.fBottom = max(outer.fY);
Michael Ludwige6266a22019-03-07 11:24:32 -0500777 }
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400778
Michael Ludwig93aeba02018-12-21 09:50:31 -0500779 // Write two quads for inner and outer, inner will use the
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400780 write_quad(&vb, spec, mode, maxCoverage, color4f, geomDomain, domain, inner);
781 write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500782 } else {
783 // No outsetting needed, just write a single quad with full coverage
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400784 SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain());
785 write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain, outer);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400786 }
Michael Ludwigc182b942018-11-16 10:27:51 -0500787
788 return vb.fPtr;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400789}
Michael Ludwig20e909e2018-10-30 10:43:57 -0400790
Michael Ludwig93aeba02018-12-21 09:50:31 -0500791bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
792 int quadCount) {
793 if (spec.usesCoverageAA()) {
794 // AA quads use 8 vertices, basically nested rectangles
Brian Salomondbf70722019-02-07 11:31:24 -0500795 sk_sp<const GrGpuBuffer> ibuffer = get_index_buffer(target->resourceProvider());
Michael Ludwig93aeba02018-12-21 09:50:31 -0500796 if (!ibuffer) {
797 return false;
798 }
799
800 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500801 mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
802 quadCount, kNumAAQuadsInIndexBuffer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500803 } else {
804 // Non-AA quads use 4 vertices, and regular triangle strip layout
805 if (quadCount > 1) {
Brian Salomondbf70722019-02-07 11:31:24 -0500806 sk_sp<const GrGpuBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
Michael Ludwig93aeba02018-12-21 09:50:31 -0500807 if (!ibuffer) {
808 return false;
809 }
810
811 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500812 mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500813 GrResourceProvider::QuadCountOfQuadBuffer());
814 } else {
815 mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
816 mesh->setNonIndexedNonInstanced(4);
817 }
818 }
819
820 return true;
821}
822
Michael Ludwigc182b942018-11-16 10:27:51 -0500823////////////////// VertexSpec Implementation
Michael Ludwig20e909e2018-10-30 10:43:57 -0400824
Michael Ludwigc182b942018-11-16 10:27:51 -0500825int VertexSpec::deviceDimensionality() const {
826 return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
827}
828
829int VertexSpec::localDimensionality() const {
830 return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
831}
832
Michael Ludwig467994d2018-12-03 14:58:31 +0000833////////////////// Geometry Processor Implementation
Michael Ludwigc182b942018-11-16 10:27:51 -0500834
Michael Ludwig467994d2018-12-03 14:58:31 +0000835class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
836public:
Michael Ludwig20e909e2018-10-30 10:43:57 -0400837
Michael Ludwig467994d2018-12-03 14:58:31 +0000838 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
839 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400840 }
841
Michael Ludwig467994d2018-12-03 14:58:31 +0000842 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
843 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -0500844 const GrSamplerState& samplerState,
845 uint32_t extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +0000846 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
847 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
Greg Daniel7a82edf2018-12-04 10:54:34 -0500848 vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +0000849 std::move(textureColorSpaceXform)));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400850 }
851
Michael Ludwig467994d2018-12-03 14:58:31 +0000852 const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
Michael Ludwig024e2622018-11-30 13:25:55 -0500853
Michael Ludwig467994d2018-12-03 14:58:31 +0000854 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400855 // texturing, device-dimensions are single bit flags
856 uint32_t x = fTexDomain.isInitialized() ? 0 : 1;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500857 x |= fSampler.isInitialized() ? 0 : 2;
858 x |= fNeedsPerspective ? 0 : 4;
Michael Ludwig467994d2018-12-03 14:58:31 +0000859 // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
860 if (fLocalCoord.isInitialized()) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500861 x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
Brian Osman78dc72c2018-12-03 13:20:43 +0000862 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000863 // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
Michael Ludwig93aeba02018-12-21 09:50:31 -0500864 if (fColor.isInitialized()) {
865 x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
866 }
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400867 // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for
868 // position+geomdomain
869 SkASSERT(!fGeomDomain.isInitialized() || fCoverageMode == CoverageMode::kWithPosition);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500870 if (fCoverageMode != CoverageMode::kNone) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400871 x |= fGeomDomain.isInitialized() ?
872 384 : (CoverageMode::kWithPosition == fCoverageMode ? 128 : 256);
Michael Ludwig467994d2018-12-03 14:58:31 +0000873 }
874
875 b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
876 b->add32(x);
Brian Osman78dc72c2018-12-03 13:20:43 +0000877 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000878
879 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
880 class GLSLProcessor : public GrGLSLGeometryProcessor {
881 public:
882 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
883 FPCoordTransformIter&& transformIter) override {
884 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
885 if (gp.fLocalCoord.isInitialized()) {
886 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
887 }
888 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
889 }
890
891 private:
892 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
893 using Interpolation = GrGLSLVaryingHandler::Interpolation;
894
895 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
896 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
897 gp.fTextureColorSpaceXform.get());
898
899 args.fVaryingHandler->emitAttributes(gp);
900
Michael Ludwig93aeba02018-12-21 09:50:31 -0500901 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
902 // Strip last channel from the vertex attribute to remove coverage and get the
903 // actual position
904 if (gp.fNeedsPerspective) {
905 args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
906 gp.fPosition.name());
907 } else {
908 args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
909 gp.fPosition.name());
910 }
911 gpArgs->fPositionVar = {"position",
912 gp.fNeedsPerspective ? kFloat3_GrSLType
913 : kFloat2_GrSLType,
914 GrShaderVar::kNone_TypeModifier};
Michael Ludwig467994d2018-12-03 14:58:31 +0000915 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500916 // No coverage to eliminate
917 gpArgs->fPositionVar = gp.fPosition.asShaderVar();
Michael Ludwig467994d2018-12-03 14:58:31 +0000918 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000919
920 // Handle local coordinates if they exist
921 if (gp.fLocalCoord.isInitialized()) {
922 // NOTE: If the only usage of local coordinates is for the inline texture fetch
923 // before FPs, then there are no registered FPCoordTransforms and this ends up
924 // emitting nothing, so there isn't a duplication of local coordinates
925 this->emitTransforms(args.fVertBuilder,
926 args.fVaryingHandler,
927 args.fUniformHandler,
928 gp.fLocalCoord.asShaderVar(),
929 args.fFPCoordTransformHandler);
930 }
931
932 // Solid color before any texturing gets modulated in
933 if (gp.fColor.isInitialized()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400934 SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500935 // The color cannot be flat if the varying coverage has been modulated into it
Michael Ludwig467994d2018-12-03 14:58:31 +0000936 args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500937 gp.fCoverageMode == CoverageMode::kWithColor ?
938 Interpolation::kInterpolated : Interpolation::kCanBeFlat);
939 } else {
940 // Output color must be initialized to something
941 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
Michael Ludwig467994d2018-12-03 14:58:31 +0000942 }
943
944 // If there is a texture, must also handle texture coordinates and reading from
945 // the texture in the fragment shader before continuing to fragment processors.
946 if (gp.fSampler.isInitialized()) {
947 // Texture coordinates clamped by the domain on the fragment shader; if the GP
948 // has a texture, it's guaranteed to have local coordinates
949 args.fFragBuilder->codeAppend("float2 texCoord;");
950 if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
951 // Can't do a pass through since we need to perform perspective division
952 GrGLSLVarying v(gp.fLocalCoord.gpuType());
953 args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
954 args.fVertBuilder->codeAppendf("%s = %s;",
955 v.vsOut(), gp.fLocalCoord.name());
956 args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
957 v.fsIn(), v.fsIn());
958 } else {
959 args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
960 }
961
962 // Clamp the now 2D localCoordName variable by the domain if it is provided
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400963 if (gp.fTexDomain.isInitialized()) {
Michael Ludwig467994d2018-12-03 14:58:31 +0000964 args.fFragBuilder->codeAppend("float4 domain;");
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400965 args.fVaryingHandler->addPassThroughAttribute(gp.fTexDomain, "domain",
Michael Ludwig467994d2018-12-03 14:58:31 +0000966 Interpolation::kCanBeFlat);
967 args.fFragBuilder->codeAppend(
968 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
969 }
970
971 // Now modulate the starting output color by the texture lookup
972 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
973 args.fFragBuilder->appendTextureLookupAndModulate(
974 args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
975 &fTextureColorSpaceXformHelper);
976 args.fFragBuilder->codeAppend(";");
977 }
978
979 // And lastly, output the coverage calculation code
Michael Ludwig93aeba02018-12-21 09:50:31 -0500980 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
981 GrGLSLVarying coverage(kFloat_GrSLType);
982 args.fVaryingHandler->addVarying("coverage", &coverage);
Michael Ludwig467994d2018-12-03 14:58:31 +0000983 if (gp.fNeedsPerspective) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400984 // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in
985 // the fragment shader to get screen-space linear coverage.
986 args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;",
987 coverage.vsOut(), gp.fPosition.name(),
988 gp.fPosition.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400989 args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;",
990 coverage.fsIn());
Michael Ludwig93aeba02018-12-21 09:50:31 -0500991 } else {
992 args.fVertBuilder->codeAppendf("%s = %s.z;",
993 coverage.vsOut(), gp.fPosition.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400994 args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn());
Michael Ludwig467994d2018-12-03 14:58:31 +0000995 }
Michael Ludwig93aeba02018-12-21 09:50:31 -0500996
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400997 if (gp.fGeomDomain.isInitialized()) {
998 // Calculate distance from sk_FragCoord to the 4 edges of the domain
999 // and clamp them to (0, 1). Use the minimum of these and the original
1000 // coverage. This only has to be done in the exterior triangles, the
1001 // interior of the quad geometry can never be clipped by the domain box.
1002 args.fFragBuilder->codeAppend("float4 geoDomain;");
1003 args.fVaryingHandler->addPassThroughAttribute(gp.fGeomDomain, "geoDomain",
1004 Interpolation::kCanBeFlat);
1005 args.fFragBuilder->codeAppend(
1006 "if (coverage < 0.5) {"
1007 " float4 dists4 = clamp(float4(1, 1, -1, -1) * "
1008 "(sk_FragCoord.xyxy - geoDomain), 0, 1);"
1009 " float2 dists2 = dists4.xy * dists4.zw;"
1010 " coverage = min(coverage, dists2.x * dists2.y);"
1011 "}");
1012 }
1013
1014 args.fFragBuilder->codeAppendf("%s = half4(half(coverage));",
1015 args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001016 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001017 // Set coverage to 1, since it's either non-AA or the coverage was already
1018 // folded into the output color
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001019 SkASSERT(!gp.fGeomDomain.isInitialized());
Ethan Nicholase1f55022019-02-05 17:17:40 -05001020 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001021 }
1022 }
1023 GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
1024 };
1025 return new GLSLProcessor;
1026 }
1027
1028private:
1029 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
1030 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
1031 , fTextureColorSpaceXform(nullptr) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001032 SkASSERT(!spec.hasDomain());
Michael Ludwig467994d2018-12-03 14:58:31 +00001033 this->initializeAttrs(spec);
1034 this->setTextureSamplerCnt(0);
1035 }
1036
1037 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
1038 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001039 const GrSamplerState& samplerState,
1040 uint32_t extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +00001041 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
1042 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
1043 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
Greg Daniel7a82edf2018-12-04 10:54:34 -05001044 , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001045 SkASSERT(spec.hasLocalCoords());
Michael Ludwig467994d2018-12-03 14:58:31 +00001046 this->initializeAttrs(spec);
1047 this->setTextureSamplerCnt(1);
1048 }
1049
1050 void initializeAttrs(const VertexSpec& spec) {
1051 fNeedsPerspective = spec.deviceDimensionality() == 3;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001052 fCoverageMode = get_mode_for_spec(spec);
1053
1054 if (fCoverageMode == CoverageMode::kWithPosition) {
1055 if (fNeedsPerspective) {
1056 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1057 } else {
1058 fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1059 }
1060 } else {
1061 if (fNeedsPerspective) {
1062 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1063 } else {
1064 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1065 }
1066 }
Michael Ludwig467994d2018-12-03 14:58:31 +00001067
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001068 // Need a geometry domain when the quads are AA and not rectilinear, since their AA
1069 // outsetting can go beyond a half pixel.
1070 if (spec.requiresGeometryDomain()) {
1071 fGeomDomain = {"geomDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1072 }
1073
Michael Ludwig467994d2018-12-03 14:58:31 +00001074 int localDim = spec.localDimensionality();
1075 if (localDim == 3) {
1076 fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1077 } else if (localDim == 2) {
1078 fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1079 } // else localDim == 0 and attribute remains uninitialized
1080
1081 if (ColorType::kByte == spec.colorType()) {
1082 fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
1083 } else if (ColorType::kHalf == spec.colorType()) {
1084 fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
1085 }
1086
1087 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001088 fTexDomain = {"texDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
Michael Ludwig467994d2018-12-03 14:58:31 +00001089 }
1090
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001091 this->setVertexAttributes(&fPosition, 5);
Michael Ludwig467994d2018-12-03 14:58:31 +00001092 }
1093
1094 const TextureSampler& onTextureSampler(int) const override { return fSampler; }
1095
Michael Ludwig93aeba02018-12-21 09:50:31 -05001096 Attribute fPosition; // May contain coverage as last channel
1097 Attribute fColor; // May have coverage modulated in if the FPs support it
Michael Ludwig467994d2018-12-03 14:58:31 +00001098 Attribute fLocalCoord;
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001099 Attribute fGeomDomain; // Screen-space bounding box on geometry+aa outset
1100 Attribute fTexDomain; // Texture-space bounding box on local coords
Michael Ludwig467994d2018-12-03 14:58:31 +00001101
Michael Ludwig93aeba02018-12-21 09:50:31 -05001102 // The positions attribute may have coverage built into it, so float3 is an ambiguous type
1103 // and may mean 2d with coverage, or 3d with no coverage
Michael Ludwig467994d2018-12-03 14:58:31 +00001104 bool fNeedsPerspective;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001105 CoverageMode fCoverageMode;
Michael Ludwig467994d2018-12-03 14:58:31 +00001106
1107 // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
1108 // to skip texturing.
1109 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1110 TextureSampler fSampler;
1111
1112 typedef GrGeometryProcessor INHERITED;
1113};
1114
1115sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
1116 return QuadPerEdgeAAGeometryProcessor::Make(spec);
1117}
1118
1119sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
1120 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001121 const GrSamplerState& samplerState, uint32_t extraSamplerKey,
1122 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
1123 return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
1124 samplerState, extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +00001125 std::move(textureColorSpaceXform));
Michael Ludwig20e909e2018-10-30 10:43:57 -04001126}
Michael Ludwigc182b942018-11-16 10:27:51 -05001127
1128} // namespace GrQuadPerEdgeAA