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