blob: 2c70fd88f453f19b833b470bf9646b7ffd7e4b8b [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,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400559 Vertices* inner, Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500560 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 Ludwigdcfbe322019-04-01 14:55:54 -0400565 // Calculate domain first before updating vertices. It's only used when not rectilinear.
566 if (!rectilinear) {
567 SkASSERT(domain);
568 // The domain is the bounding box of the quad, outset by 0.5. Don't worry about edge masks
569 // since the FP only applies the domain on the exterior triangles, which are degenerate for
570 // non-AA edges.
571 domain->fLeft = outer->fX.min() - 0.5f;
572 domain->fRight = outer->fX.max() + 0.5f;
573 domain->fTop = outer->fY.min() - 0.5f;
574 domain->fBottom = outer->fY.max() + 0.5f;
575 }
576
Michael Ludwig93aeba02018-12-21 09:50:31 -0500577 // 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 -0500578 // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment
579 // 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 -0500580 Sk4f outset = 0.5f;
Michael Ludwige6266a22019-03-07 11:24:32 -0500581 if (get_optimized_outset(metadata, rectilinear, &outset)) {
582 // Since it's not subpixel, outsetting and insetting are trivial vector additions.
583 outset_vertices(outset, metadata, outer);
584 outset_vertices(-outset, metadata, inner);
585 return 1.f;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400586 }
Michael Ludwig4921dc32018-12-03 14:57:29 +0000587
Michael Ludwige6266a22019-03-07 11:24:32 -0500588 // Only compute edge equations once since they are the same for inner and outer quads
589 Edges edges = get_edge_equations(metadata, *inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500590
Michael Ludwige6266a22019-03-07 11:24:32 -0500591 // Calculate both outset and inset, returning the coverage reported for the inset, since the
592 // outset will always have 0.0f.
593 compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer);
594 return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner);
Michael Ludwigf995c052018-11-26 15:24:29 -0500595}
596
Michael Ludwige6266a22019-03-07 11:24:32 -0500597// Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective
598// division of the device coordinates, the original local coordinate value is at the original
599// un-outset device position.
600static Sk4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400601 Vertices* outer, SkRect* domain) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500602 SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
603 SkASSERT(outer->fUVRCount == inner->fUVRCount);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500604
Michael Ludwige6266a22019-03-07 11:24:32 -0500605 // Calculate the projected 2D quad and use it to form projeccted inner/outer quads
606 // Don't use Sk4f.invert() here because it does not preserve 1/1 == 1, which creates rendering
607 // mismatches for 2D content that was batched into a 3D op, vs. 2D on its own.
608 Sk4f iw = 1.0f / inner->fW;
609 Sk4f x2d = inner->fX * iw;
610 Sk4f y2d = inner->fY * iw;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500611
Michael Ludwige6266a22019-03-07 11:24:32 -0500612 Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D
613 Vertices outer2D = inner2D;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400614
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400615 Sk4f coverage = compute_nested_quad_vertices(
616 aaFlags, /* rect */ false, &inner2D, &outer2D, domain);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400617
Michael Ludwige6266a22019-03-07 11:24:32 -0500618 // Now map from the 2D inset/outset back to 3D and update the local coordinates as well
619 outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner);
620 outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500621
Michael Ludwige6266a22019-03-07 11:24:32 -0500622 return coverage;
Eric Boren98cb1592018-11-26 18:38:05 +0000623}
624
Michael Ludwig93aeba02018-12-21 09:50:31 -0500625enum class CoverageMode {
626 kNone,
627 kWithPosition,
628 kWithColor
629};
630
631static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
632 if (spec.usesCoverageAA()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400633 if (spec.compatibleWithCoverageAsAlpha() && spec.hasVertexColors() &&
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400634 !spec.requiresGeometryDomain()) {
635 // Using a geometric domain acts as a second source of coverage and folding the original
636 // coverage into color makes it impossible to apply the color's alpha to the geometric
637 // domain's coverage when the original shape is clipped.
Michael Ludwig93aeba02018-12-21 09:50:31 -0500638 return CoverageMode::kWithColor;
639 } else {
640 return CoverageMode::kWithPosition;
641 }
Michael Ludwig553e9a92018-11-29 12:38:35 -0500642 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500643 return CoverageMode::kNone;
Michael Ludwig553e9a92018-11-29 12:38:35 -0500644 }
Michael Ludwig93aeba02018-12-21 09:50:31 -0500645}
Michael Ludwig553e9a92018-11-29 12:38:35 -0500646
Michael Ludwig93aeba02018-12-21 09:50:31 -0500647// Writes four vertices in triangle strip order, including the additional data for local
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400648// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec
Michael Ludwig93aeba02018-12-21 09:50:31 -0500649static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400650 CoverageMode mode, Sk4f coverage, SkPMColor4f color4f,
651 const SkRect& geomDomain, const SkRect& texDomain, const Vertices& quad) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500652 static constexpr auto If = GrVertexWriter::If<float>;
653
Michael Ludwig553e9a92018-11-29 12:38:35 -0500654 for (int i = 0; i < 4; ++i) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500655 // save position, this is a float2 or float3 or float4 depending on the combination of
656 // perspective and coverage mode.
Michael Ludwige6266a22019-03-07 11:24:32 -0500657 vb->write(quad.fX[i], quad.fY[i],
658 If(spec.deviceQuadType() == GrQuadType::kPerspective, quad.fW[i]),
659 If(mode == CoverageMode::kWithPosition, coverage[i]));
Michael Ludwig4921dc32018-12-03 14:57:29 +0000660
Michael Ludwig93aeba02018-12-21 09:50:31 -0500661 // save color
662 if (spec.hasVertexColors()) {
Brian Salomon1d835422019-03-13 16:11:44 -0400663 bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
Michael Ludwige6266a22019-03-07 11:24:32 -0500664 vb->write(GrVertexColor(
Brian Salomon1d835422019-03-13 16:11:44 -0400665 color4f * (mode == CoverageMode::kWithColor ? coverage[i] : 1.f), wide));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500666 }
667
668 // save local position
669 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500670 vb->write(quad.fU[i], quad.fV[i],
671 If(spec.localQuadType() == GrQuadType::kPerspective, quad.fR[i]));
Michael Ludwig93aeba02018-12-21 09:50:31 -0500672 }
673
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400674 // save the geometry domain
675 if (spec.requiresGeometryDomain()) {
676 vb->write(geomDomain);
677 }
678
679 // save the texture domain
Michael Ludwig93aeba02018-12-21 09:50:31 -0500680 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400681 vb->write(texDomain);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500682 }
683 }
684}
685
686GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
687
688static const int kVertsPerAAFillRect = 8;
689static const int kIndicesPerAAFillRect = 30;
690
Brian Salomondbf70722019-02-07 11:31:24 -0500691static sk_sp<const GrGpuBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500692 GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
693
694 // clang-format off
695 static const uint16_t gFillAARectIdx[] = {
696 0, 1, 2, 1, 3, 2,
697 0, 4, 1, 4, 5, 1,
698 0, 6, 4, 0, 2, 6,
699 2, 3, 6, 3, 7, 6,
700 1, 5, 3, 3, 5, 7,
701 };
702 // clang-format on
703
704 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
705 return resourceProvider->findOrCreatePatternedIndexBuffer(
706 gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
707 kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
Michael Ludwig553e9a92018-11-29 12:38:35 -0500708}
709
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400710} // anonymous namespace
711
Michael Ludwigc182b942018-11-16 10:27:51 -0500712namespace GrQuadPerEdgeAA {
713
Brian Osman8fa7ab42019-03-18 10:22:42 -0400714// This is a more elaborate version of SkPMColor4fNeedsWideColor that allows "no color" for white
715ColorType MinColorType(SkPMColor4f color, GrClampType clampType, const GrCaps& caps) {
Brian Salomon1d835422019-03-13 16:11:44 -0400716 if (color == SK_PMColor4fWHITE) {
717 return ColorType::kNone;
Brian Salomon1d835422019-03-13 16:11:44 -0400718 } else {
Brian Osman8fa7ab42019-03-18 10:22:42 -0400719 return SkPMColor4fNeedsWideColor(color, clampType, caps) ? ColorType::kHalf
720 : ColorType::kByte;
Brian Salomon1d835422019-03-13 16:11:44 -0400721 }
722}
723
Michael Ludwigc182b942018-11-16 10:27:51 -0500724////////////////// Tessellate Implementation
725
726void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
Brian Osman3d139a42018-11-19 10:42:10 -0500727 const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
Michael Ludwigc182b942018-11-16 10:27:51 -0500728 GrQuadAAFlags aaFlags) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500729 CoverageMode mode = get_mode_for_spec(spec);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400730
Michael Ludwigf995c052018-11-26 15:24:29 -0500731 // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
Michael Ludwige6266a22019-03-07 11:24:32 -0500732 Vertices outer;
733 outer.fX = deviceQuad.x4f();
734 outer.fY = deviceQuad.y4f();
735 outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400736
737 // Load local position data into Sk4fs (either none, just u,v or all three)
Michael Ludwige6266a22019-03-07 11:24:32 -0500738 outer.fUVRCount = spec.localDimensionality();
Michael Ludwigc182b942018-11-16 10:27:51 -0500739 if (spec.hasLocalCoords()) {
Michael Ludwige6266a22019-03-07 11:24:32 -0500740 outer.fU = localQuad.x4f();
741 outer.fV = localQuad.y4f();
742 outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400743 }
744
Michael Ludwigc182b942018-11-16 10:27:51 -0500745 GrVertexWriter vb{vertices};
Michael Ludwig93aeba02018-12-21 09:50:31 -0500746 if (spec.usesCoverageAA()) {
747 SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500748 // Must calculate two new quads, an outset and inset by .5 in projected device space, so
Michael Ludwige6266a22019-03-07 11:24:32 -0500749 // duplicate the original quad for the inner space
750 Vertices inner = outer;
Michael Ludwigc182b942018-11-16 10:27:51 -0500751
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400752 SkRect geomDomain;
Michael Ludwige6266a22019-03-07 11:24:32 -0500753 Sk4f maxCoverage = 1.f;
754 if (spec.deviceQuadType() == GrQuadType::kPerspective) {
755 // For perspective, send quads with all edges non-AA through the tessellation to ensure
756 // their corners are processed the same as adjacent quads. This approach relies on
757 // solving edge equations to reconstruct corners, which can create seams if an inner
758 // fully non-AA quad is not similarly processed.
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400759 maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer, &geomDomain);
Michael Ludwige6266a22019-03-07 11:24:32 -0500760 } else if (aaFlags != GrQuadAAFlags::kNone) {
761 // In 2D, the simpler corner math does not cause issues with seaming against non-AA
762 // inner quads.
763 maxCoverage = compute_nested_quad_vertices(
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400764 aaFlags, spec.deviceQuadType() <= GrQuadType::kRectilinear, &inner, &outer,
765 &geomDomain);
Michael Ludwige6266a22019-03-07 11:24:32 -0500766 }
767 // NOTE: could provide an even more optimized tessellation function for axis-aligned
768 // rects since the positions can be outset by constants without doing vector math,
769 // except it must handle identifying the winding of the quad vertices if the transform
770 // applied a mirror, etc. The current 2D case is already adequately fast.
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400771
Michael Ludwig93aeba02018-12-21 09:50:31 -0500772 // Write two quads for inner and outer, inner will use the
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400773 write_quad(&vb, spec, mode, maxCoverage, color4f, geomDomain, domain, inner);
774 write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, outer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500775 } else {
776 // No outsetting needed, just write a single quad with full coverage
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400777 SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain());
778 write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain, outer);
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400779 }
Michael Ludwigc182b942018-11-16 10:27:51 -0500780
781 return vb.fPtr;
Michael Ludwig460eb5e2018-10-29 11:09:29 -0400782}
Michael Ludwig20e909e2018-10-30 10:43:57 -0400783
Michael Ludwig93aeba02018-12-21 09:50:31 -0500784bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
785 int quadCount) {
786 if (spec.usesCoverageAA()) {
787 // AA quads use 8 vertices, basically nested rectangles
Brian Salomondbf70722019-02-07 11:31:24 -0500788 sk_sp<const GrGpuBuffer> ibuffer = get_index_buffer(target->resourceProvider());
Michael Ludwig93aeba02018-12-21 09:50:31 -0500789 if (!ibuffer) {
790 return false;
791 }
792
793 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500794 mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
795 quadCount, kNumAAQuadsInIndexBuffer);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500796 } else {
797 // Non-AA quads use 4 vertices, and regular triangle strip layout
798 if (quadCount > 1) {
Brian Salomondbf70722019-02-07 11:31:24 -0500799 sk_sp<const GrGpuBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
Michael Ludwig93aeba02018-12-21 09:50:31 -0500800 if (!ibuffer) {
801 return false;
802 }
803
804 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
Brian Salomon12d22642019-01-29 14:38:50 -0500805 mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500806 GrResourceProvider::QuadCountOfQuadBuffer());
807 } else {
808 mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
809 mesh->setNonIndexedNonInstanced(4);
810 }
811 }
812
813 return true;
814}
815
Michael Ludwigc182b942018-11-16 10:27:51 -0500816////////////////// VertexSpec Implementation
Michael Ludwig20e909e2018-10-30 10:43:57 -0400817
Michael Ludwigc182b942018-11-16 10:27:51 -0500818int VertexSpec::deviceDimensionality() const {
819 return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
820}
821
822int VertexSpec::localDimensionality() const {
823 return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
824}
825
Michael Ludwig467994d2018-12-03 14:58:31 +0000826////////////////// Geometry Processor Implementation
Michael Ludwigc182b942018-11-16 10:27:51 -0500827
Michael Ludwig467994d2018-12-03 14:58:31 +0000828class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
829public:
Michael Ludwig20e909e2018-10-30 10:43:57 -0400830
Michael Ludwig467994d2018-12-03 14:58:31 +0000831 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
832 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400833 }
834
Michael Ludwig467994d2018-12-03 14:58:31 +0000835 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
836 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -0500837 const GrSamplerState& samplerState,
838 uint32_t extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +0000839 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
840 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
Greg Daniel7a82edf2018-12-04 10:54:34 -0500841 vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +0000842 std::move(textureColorSpaceXform)));
Michael Ludwig20e909e2018-10-30 10:43:57 -0400843 }
844
Michael Ludwig467994d2018-12-03 14:58:31 +0000845 const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
Michael Ludwig024e2622018-11-30 13:25:55 -0500846
Michael Ludwig467994d2018-12-03 14:58:31 +0000847 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400848 // texturing, device-dimensions are single bit flags
849 uint32_t x = fTexDomain.isInitialized() ? 0 : 1;
Michael Ludwig93aeba02018-12-21 09:50:31 -0500850 x |= fSampler.isInitialized() ? 0 : 2;
851 x |= fNeedsPerspective ? 0 : 4;
Michael Ludwig467994d2018-12-03 14:58:31 +0000852 // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
853 if (fLocalCoord.isInitialized()) {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500854 x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
Brian Osman78dc72c2018-12-03 13:20:43 +0000855 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000856 // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
Michael Ludwig93aeba02018-12-21 09:50:31 -0500857 if (fColor.isInitialized()) {
858 x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
859 }
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400860 // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for
861 // position+geomdomain
862 SkASSERT(!fGeomDomain.isInitialized() || fCoverageMode == CoverageMode::kWithPosition);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500863 if (fCoverageMode != CoverageMode::kNone) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400864 x |= fGeomDomain.isInitialized() ?
865 384 : (CoverageMode::kWithPosition == fCoverageMode ? 128 : 256);
Michael Ludwig467994d2018-12-03 14:58:31 +0000866 }
867
868 b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
869 b->add32(x);
Brian Osman78dc72c2018-12-03 13:20:43 +0000870 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000871
872 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
873 class GLSLProcessor : public GrGLSLGeometryProcessor {
874 public:
875 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
876 FPCoordTransformIter&& transformIter) override {
877 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
878 if (gp.fLocalCoord.isInitialized()) {
879 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
880 }
881 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
882 }
883
884 private:
885 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
886 using Interpolation = GrGLSLVaryingHandler::Interpolation;
887
888 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
889 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
890 gp.fTextureColorSpaceXform.get());
891
892 args.fVaryingHandler->emitAttributes(gp);
893
Michael Ludwig93aeba02018-12-21 09:50:31 -0500894 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
895 // Strip last channel from the vertex attribute to remove coverage and get the
896 // actual position
897 if (gp.fNeedsPerspective) {
898 args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
899 gp.fPosition.name());
900 } else {
901 args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
902 gp.fPosition.name());
903 }
904 gpArgs->fPositionVar = {"position",
905 gp.fNeedsPerspective ? kFloat3_GrSLType
906 : kFloat2_GrSLType,
907 GrShaderVar::kNone_TypeModifier};
Michael Ludwig467994d2018-12-03 14:58:31 +0000908 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -0500909 // No coverage to eliminate
910 gpArgs->fPositionVar = gp.fPosition.asShaderVar();
Michael Ludwig467994d2018-12-03 14:58:31 +0000911 }
Michael Ludwig467994d2018-12-03 14:58:31 +0000912
913 // Handle local coordinates if they exist
914 if (gp.fLocalCoord.isInitialized()) {
915 // NOTE: If the only usage of local coordinates is for the inline texture fetch
916 // before FPs, then there are no registered FPCoordTransforms and this ends up
917 // emitting nothing, so there isn't a duplication of local coordinates
918 this->emitTransforms(args.fVertBuilder,
919 args.fVaryingHandler,
920 args.fUniformHandler,
921 gp.fLocalCoord.asShaderVar(),
922 args.fFPCoordTransformHandler);
923 }
924
925 // Solid color before any texturing gets modulated in
926 if (gp.fColor.isInitialized()) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400927 SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective);
Michael Ludwig93aeba02018-12-21 09:50:31 -0500928 // The color cannot be flat if the varying coverage has been modulated into it
Michael Ludwig467994d2018-12-03 14:58:31 +0000929 args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
Michael Ludwig93aeba02018-12-21 09:50:31 -0500930 gp.fCoverageMode == CoverageMode::kWithColor ?
931 Interpolation::kInterpolated : Interpolation::kCanBeFlat);
932 } else {
933 // Output color must be initialized to something
934 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
Michael Ludwig467994d2018-12-03 14:58:31 +0000935 }
936
937 // If there is a texture, must also handle texture coordinates and reading from
938 // the texture in the fragment shader before continuing to fragment processors.
939 if (gp.fSampler.isInitialized()) {
940 // Texture coordinates clamped by the domain on the fragment shader; if the GP
941 // has a texture, it's guaranteed to have local coordinates
942 args.fFragBuilder->codeAppend("float2 texCoord;");
943 if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
944 // Can't do a pass through since we need to perform perspective division
945 GrGLSLVarying v(gp.fLocalCoord.gpuType());
946 args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
947 args.fVertBuilder->codeAppendf("%s = %s;",
948 v.vsOut(), gp.fLocalCoord.name());
949 args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
950 v.fsIn(), v.fsIn());
951 } else {
952 args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
953 }
954
955 // Clamp the now 2D localCoordName variable by the domain if it is provided
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400956 if (gp.fTexDomain.isInitialized()) {
Michael Ludwig467994d2018-12-03 14:58:31 +0000957 args.fFragBuilder->codeAppend("float4 domain;");
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400958 args.fVaryingHandler->addPassThroughAttribute(gp.fTexDomain, "domain",
Michael Ludwig467994d2018-12-03 14:58:31 +0000959 Interpolation::kCanBeFlat);
960 args.fFragBuilder->codeAppend(
961 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
962 }
963
964 // Now modulate the starting output color by the texture lookup
965 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
966 args.fFragBuilder->appendTextureLookupAndModulate(
967 args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
968 &fTextureColorSpaceXformHelper);
969 args.fFragBuilder->codeAppend(";");
970 }
971
972 // And lastly, output the coverage calculation code
Michael Ludwig93aeba02018-12-21 09:50:31 -0500973 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
974 GrGLSLVarying coverage(kFloat_GrSLType);
975 args.fVaryingHandler->addVarying("coverage", &coverage);
Michael Ludwig467994d2018-12-03 14:58:31 +0000976 if (gp.fNeedsPerspective) {
Michael Ludwig3d2753e2019-03-29 14:36:32 -0400977 // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in
978 // the fragment shader to get screen-space linear coverage.
979 args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;",
980 coverage.vsOut(), gp.fPosition.name(),
981 gp.fPosition.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400982 args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;",
983 coverage.fsIn());
Michael Ludwig93aeba02018-12-21 09:50:31 -0500984 } else {
985 args.fVertBuilder->codeAppendf("%s = %s.z;",
986 coverage.vsOut(), gp.fPosition.name());
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400987 args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn());
Michael Ludwig467994d2018-12-03 14:58:31 +0000988 }
Michael Ludwig93aeba02018-12-21 09:50:31 -0500989
Michael Ludwigdcfbe322019-04-01 14:55:54 -0400990 if (gp.fGeomDomain.isInitialized()) {
991 // Calculate distance from sk_FragCoord to the 4 edges of the domain
992 // and clamp them to (0, 1). Use the minimum of these and the original
993 // coverage. This only has to be done in the exterior triangles, the
994 // interior of the quad geometry can never be clipped by the domain box.
995 args.fFragBuilder->codeAppend("float4 geoDomain;");
996 args.fVaryingHandler->addPassThroughAttribute(gp.fGeomDomain, "geoDomain",
997 Interpolation::kCanBeFlat);
998 args.fFragBuilder->codeAppend(
999 "if (coverage < 0.5) {"
1000 " float4 dists4 = clamp(float4(1, 1, -1, -1) * "
1001 "(sk_FragCoord.xyxy - geoDomain), 0, 1);"
1002 " float2 dists2 = dists4.xy * dists4.zw;"
1003 " coverage = min(coverage, dists2.x * dists2.y);"
1004 "}");
1005 }
1006
1007 args.fFragBuilder->codeAppendf("%s = half4(half(coverage));",
1008 args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001009 } else {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001010 // Set coverage to 1, since it's either non-AA or the coverage was already
1011 // folded into the output color
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001012 SkASSERT(!gp.fGeomDomain.isInitialized());
Ethan Nicholase1f55022019-02-05 17:17:40 -05001013 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
Michael Ludwig467994d2018-12-03 14:58:31 +00001014 }
1015 }
1016 GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
1017 };
1018 return new GLSLProcessor;
1019 }
1020
1021private:
1022 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
1023 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
1024 , fTextureColorSpaceXform(nullptr) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001025 SkASSERT(!spec.hasDomain());
Michael Ludwig467994d2018-12-03 14:58:31 +00001026 this->initializeAttrs(spec);
1027 this->setTextureSamplerCnt(0);
1028 }
1029
1030 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
1031 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001032 const GrSamplerState& samplerState,
1033 uint32_t extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +00001034 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
1035 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
1036 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
Greg Daniel7a82edf2018-12-04 10:54:34 -05001037 , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
Michael Ludwig93aeba02018-12-21 09:50:31 -05001038 SkASSERT(spec.hasLocalCoords());
Michael Ludwig467994d2018-12-03 14:58:31 +00001039 this->initializeAttrs(spec);
1040 this->setTextureSamplerCnt(1);
1041 }
1042
1043 void initializeAttrs(const VertexSpec& spec) {
1044 fNeedsPerspective = spec.deviceDimensionality() == 3;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001045 fCoverageMode = get_mode_for_spec(spec);
1046
1047 if (fCoverageMode == CoverageMode::kWithPosition) {
1048 if (fNeedsPerspective) {
1049 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1050 } else {
1051 fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1052 }
1053 } else {
1054 if (fNeedsPerspective) {
1055 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1056 } else {
1057 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1058 }
1059 }
Michael Ludwig467994d2018-12-03 14:58:31 +00001060
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001061 // Need a geometry domain when the quads are AA and not rectilinear, since their AA
1062 // outsetting can go beyond a half pixel.
1063 if (spec.requiresGeometryDomain()) {
1064 fGeomDomain = {"geomDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
1065 }
1066
Michael Ludwig467994d2018-12-03 14:58:31 +00001067 int localDim = spec.localDimensionality();
1068 if (localDim == 3) {
1069 fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
1070 } else if (localDim == 2) {
1071 fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
1072 } // else localDim == 0 and attribute remains uninitialized
1073
1074 if (ColorType::kByte == spec.colorType()) {
1075 fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
1076 } else if (ColorType::kHalf == spec.colorType()) {
1077 fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
1078 }
1079
1080 if (spec.hasDomain()) {
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001081 fTexDomain = {"texDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
Michael Ludwig467994d2018-12-03 14:58:31 +00001082 }
1083
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001084 this->setVertexAttributes(&fPosition, 5);
Michael Ludwig467994d2018-12-03 14:58:31 +00001085 }
1086
1087 const TextureSampler& onTextureSampler(int) const override { return fSampler; }
1088
Michael Ludwig93aeba02018-12-21 09:50:31 -05001089 Attribute fPosition; // May contain coverage as last channel
1090 Attribute fColor; // May have coverage modulated in if the FPs support it
Michael Ludwig467994d2018-12-03 14:58:31 +00001091 Attribute fLocalCoord;
Michael Ludwigdcfbe322019-04-01 14:55:54 -04001092 Attribute fGeomDomain; // Screen-space bounding box on geometry+aa outset
1093 Attribute fTexDomain; // Texture-space bounding box on local coords
Michael Ludwig467994d2018-12-03 14:58:31 +00001094
Michael Ludwig93aeba02018-12-21 09:50:31 -05001095 // The positions attribute may have coverage built into it, so float3 is an ambiguous type
1096 // and may mean 2d with coverage, or 3d with no coverage
Michael Ludwig467994d2018-12-03 14:58:31 +00001097 bool fNeedsPerspective;
Michael Ludwig93aeba02018-12-21 09:50:31 -05001098 CoverageMode fCoverageMode;
Michael Ludwig467994d2018-12-03 14:58:31 +00001099
1100 // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
1101 // to skip texturing.
1102 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1103 TextureSampler fSampler;
1104
1105 typedef GrGeometryProcessor INHERITED;
1106};
1107
1108sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
1109 return QuadPerEdgeAAGeometryProcessor::Make(spec);
1110}
1111
1112sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
1113 GrTextureType textureType, GrPixelConfig textureConfig,
Greg Daniel7a82edf2018-12-04 10:54:34 -05001114 const GrSamplerState& samplerState, uint32_t extraSamplerKey,
1115 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
1116 return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
1117 samplerState, extraSamplerKey,
Michael Ludwig467994d2018-12-03 14:58:31 +00001118 std::move(textureColorSpaceXform));
Michael Ludwig20e909e2018-10-30 10:43:57 -04001119}
Michael Ludwigc182b942018-11-16 10:27:51 -05001120
1121} // namespace GrQuadPerEdgeAA