1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "GrQuadPerEdgeAA.h"
9 #include "GrQuad.h"
10 #include "GrVertexWriter.h"
11 #include "glsl/GrGLSLColorSpaceXformHelper.h"
12 #include "glsl/GrGLSLGeometryProcessor.h"
13 #include "glsl/GrGLSLPrimitiveProcessor.h"
14 #include "glsl/GrGLSLFragmentShaderBuilder.h"
15 #include "glsl/GrGLSLVarying.h"
16 #include "glsl/GrGLSLVertexGeoBuilder.h"
17 #include "SkNx.h"
18
19 #define AI SK_ALWAYS_INLINE
20
21 namespace {
22
fma(const Sk4f & f,const Sk4f & m,const Sk4f & a)23 static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) {
24 return SkNx_fma<4, float>(f, m, a);
25 }
26
27 // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
28 // order.
nextCW(const Sk4f & v)29 static AI Sk4f nextCW(const Sk4f& v) {
30 return SkNx_shuffle<2, 0, 3, 1>(v);
31 }
32
nextCCW(const Sk4f & v)33 static AI Sk4f nextCCW(const Sk4f& v) {
34 return SkNx_shuffle<1, 3, 0, 2>(v);
35 }
36
37 // Fills Sk4f with 1f if edge bit is set, 0f otherwise. Edges are ordered LBTR to match CCW ordering
38 // of vertices in the quad.
compute_edge_mask(GrQuadAAFlags aaFlags)39 static AI Sk4f compute_edge_mask(GrQuadAAFlags aaFlags) {
40 return Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
41 (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
42 (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
43 (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
44 }
45
46 // Outputs normalized edge vectors in xdiff and ydiff, as well as the reciprocal of the original
47 // edge lengths in invLengths
compute_edge_vectors(const Sk4f & x,const Sk4f & y,const Sk4f & xnext,const Sk4f & ynext,Sk4f * xdiff,Sk4f * ydiff,Sk4f * invLengths)48 static AI void compute_edge_vectors(const Sk4f& x, const Sk4f& y, const Sk4f& xnext,
49 const Sk4f& ynext, Sk4f* xdiff, Sk4f* ydiff, Sk4f* invLengths) {
50 *xdiff = xnext - x;
51 *ydiff = ynext - y;
52 *invLengths = fma(*xdiff, *xdiff, *ydiff * *ydiff).rsqrt();
53 *xdiff *= *invLengths;
54 *ydiff *= *invLengths;
55 }
56
57 // outset and outsetCW are provided separately to allow for different magnitude outsets for
58 // with-edge and "perpendicular" edge shifts. This is needed when one axis cannot be inset the full
59 // half pixel without crossing over the other side.
outset_masked_vertices(const Sk4f & outset,const Sk4f & outsetCW,const Sk4f & xdiff,const Sk4f & ydiff,const Sk4f & invLengths,const Sk4f & mask,Sk4f * x,Sk4f * y,Sk4f * u,Sk4f * v,Sk4f * r,int uvrCount)60 static AI void outset_masked_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
61 const Sk4f& ydiff, const Sk4f& invLengths, const Sk4f& mask,
62 Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
63 // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
64 // both its points need to be moved along their other edge vectors.
65 auto maskedOutset = -outset * nextCW(mask);
66 auto maskedOutsetCW = outsetCW * mask;
67 // x = x + outsetCW * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
68 *x += fma(maskedOutsetCW, nextCW(xdiff), maskedOutset * xdiff);
69 *y += fma(maskedOutsetCW, nextCW(ydiff), maskedOutset * ydiff);
70 if (uvrCount > 0) {
71 // We want to extend the texture coords by the same proportion as the positions.
72 maskedOutset *= invLengths;
73 maskedOutsetCW *= nextCW(invLengths);
74 Sk4f udiff = nextCCW(*u) - *u;
75 Sk4f vdiff = nextCCW(*v) - *v;
76 *u += fma(maskedOutsetCW, nextCW(udiff), maskedOutset * udiff);
77 *v += fma(maskedOutsetCW, nextCW(vdiff), maskedOutset * vdiff);
78 if (uvrCount == 3) {
79 Sk4f rdiff = nextCCW(*r) - *r;
80 *r += fma(maskedOutsetCW, nextCW(rdiff), maskedOutset * rdiff);
81 }
82 }
83 }
84
outset_vertices(const Sk4f & outset,const Sk4f & outsetCW,const Sk4f & xdiff,const Sk4f & ydiff,const Sk4f & invLengths,Sk4f * x,Sk4f * y,Sk4f * u,Sk4f * v,Sk4f * r,int uvrCount)85 static AI void outset_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
86 const Sk4f& ydiff, const Sk4f& invLengths,
87 Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
88 // x = x + outsetCW * nextCW(xdiff) - outset * xdiff (as above, but where mask = (1,1,1,1))
89 *x += fma(outsetCW, nextCW(xdiff), -outset * xdiff);
90 *y += fma(outsetCW, nextCW(ydiff), -outset * ydiff);
91 if (uvrCount > 0) {
92 Sk4f t = -outset * invLengths; // Bake minus sign in here
93 Sk4f tCW = outsetCW * nextCW(invLengths);
94 Sk4f udiff = nextCCW(*u) - *u;
95 Sk4f vdiff = nextCCW(*v) - *v;
96 *u += fma(tCW, nextCW(udiff), t * udiff);
97 *v += fma(tCW, nextCW(vdiff), t * vdiff);
98 if (uvrCount == 3) {
99 Sk4f rdiff = nextCCW(*r) - *r;
100 *r += fma(tCW, nextCW(rdiff), t * rdiff);
101 }
102 }
103 }
104
105 // Updates outset in place to account for non-90 degree angles of the quad edges stored in
106 // xdiff, ydiff (which are assumed to be normalized).
adjust_non_rectilinear_outset(const Sk4f & xdiff,const Sk4f & ydiff,Sk4f * outset)107 static void adjust_non_rectilinear_outset(const Sk4f& xdiff, const Sk4f& ydiff, Sk4f* outset) {
108 // The distance the point needs to move is outset/sqrt(1-cos^2(theta)), where theta is the angle
109 // between the two edges at that point. cos(theta) is equal to dot(xydiff, nextCW(xydiff)),
110 Sk4f cosTheta = fma(xdiff, nextCW(xdiff), ydiff * nextCW(ydiff));
111 *outset *= (1.f - cosTheta * cosTheta).rsqrt();
112 // But clamp to make sure we don't expand by a giant amount if the sheer is really high
113 *outset = Sk4f::Max(-3.f, Sk4f::Min(*outset, 3.f));
114 }
115
116 // Computes the vertices for the two nested quads used to create AA edges. The original single quad
117 // should be duplicated as input in x1 and x2, y1 and y2, and possibly u1|u2, v1|v2, [r1|r2]
118 // (controlled by uvrChannelCount). While the values should be duplicated, they should be separate
119 // pointers. The outset quad is written in-place back to x1, y1, etc. and the inset inner quad is
120 // written to x2, y2, etc.
compute_nested_quad_vertices(GrQuadAAFlags aaFlags,Sk4f * x1,Sk4f * y1,Sk4f * u1,Sk4f * v1,Sk4f * r1,Sk4f * x2,Sk4f * y2,Sk4f * u2,Sk4f * v2,Sk4f * r2,int uvrCount,bool rectilinear)121 static float compute_nested_quad_vertices(GrQuadAAFlags aaFlags, Sk4f* x1, Sk4f* y1,
122 Sk4f* u1, Sk4f* v1, Sk4f* r1, Sk4f* x2, Sk4f* y2, Sk4f* u2, Sk4f* v2, Sk4f* r2,
123 int uvrCount, bool rectilinear) {
124 SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
125
126 // Compute edge vectors for the quad.
127 auto xnext = nextCCW(*x1);
128 auto ynext = nextCCW(*y1);
129 // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
130 Sk4f xdiff, ydiff, invLengths;
131 compute_edge_vectors(*x1, *y1, xnext, ynext, &xdiff, &ydiff, &invLengths);
132
133 // When outsetting, we want the new edge to be .5px away from the old line, which means the
134 // corners may need to be adjusted by more than .5px if the matrix had sheer.
135 Sk4f outset = 0.5f;
136 if (!rectilinear) {
137 adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
138 }
139
140 // When insetting, cap the inset amount to be half of the edge length, except that each edge
141 // has to remain parallel, so we separately limit LR and TB to half of the smallest of the
142 // opposing edges.
143 Sk4f lengths = invLengths.invert();
144 Sk2f sides(SkMinScalar(lengths[0], lengths[3]), SkMinScalar(lengths[1], lengths[2]));
145 Sk4f edgeLimits = 0.5f * SkNx_shuffle<0, 1, 1, 0>(sides);
146
147 if ((edgeLimits < 0.5f).anyTrue()) {
148 // Dealing with a subpixel rectangle, so must calculate clamped insets and padded outsets.
149 // The outsets are padded to ensure that the quad spans 2 pixels for improved interpolation.
150 Sk4f inset = -Sk4f::Min(outset, edgeLimits);
151 Sk4f insetCW = -Sk4f::Min(outset, nextCW(edgeLimits));
152
153 // The parallel distance shift caused by outset is currently 0.5, but need to scale it up to
154 // 0.5*(2 - side) so that (side + 2*shift) = 2px. Thus scale outsets for thin edges by
155 // (2 - side) since it already has the 1/2.
156 Sk4f outsetScale = 2.f - 2.f * Sk4f::Min(edgeLimits, 0.5f); // == 1 for non-thin edges
157 Sk4f outsetCW = outset * nextCW(outsetScale);
158 outset *= outsetScale;
159
160 if (aaFlags != GrQuadAAFlags::kAll) {
161 Sk4f mask = compute_edge_mask(aaFlags);
162 outset_masked_vertices(outset, outsetCW, xdiff, ydiff, invLengths, mask, x1, y1,
163 u1, v1, r1, uvrCount);
164 outset_masked_vertices(inset, insetCW, xdiff, ydiff, invLengths, mask, x2, y2,
165 u2, v2, r2, uvrCount);
166 } else {
167 outset_vertices(outset, outsetCW, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
168 outset_vertices(inset, insetCW, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
169 }
170 } else {
171 // Since it's not subpixel, the inset is just the opposite of the outset and there's no
172 // difference between CCW and CW behavior.
173 Sk4f inset = -outset;
174 if (aaFlags != GrQuadAAFlags::kAll) {
175 Sk4f mask = compute_edge_mask(aaFlags);
176 outset_masked_vertices(outset, outset, xdiff, ydiff, invLengths, mask, x1, y1,
177 u1, v1, r1, uvrCount);
178 outset_masked_vertices(inset, inset, xdiff, ydiff, invLengths, mask, x2, y2,
179 u2, v2, r2, uvrCount);
180 } else {
181 outset_vertices(outset, outset, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
182 outset_vertices(inset, inset, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
183 }
184 }
185
186 // An approximation of the pixel area covered by the quad
187 sides = Sk2f::Min(1.f, sides);
188 return sides[0] * sides[1];
189 }
190
191 // For each device space corner, devP, label its left/right or top/bottom opposite device space
192 // point opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
193 // (length(devPt - opDevPt) + outset) / length(devPt - opDevPt); This returns the interpolant s,
194 // adjusted for any subpixel corrections. If subpixel, it also updates the max coverage.
get_projected_interpolant(const Sk4f & len,const Sk4f & outsets,float * maxCoverage)195 static Sk4f get_projected_interpolant(const Sk4f& len, const Sk4f& outsets, float* maxCoverage) {
196 if ((len < 1.f).anyTrue()) {
197 *maxCoverage *= len.min();
198
199 // When insetting, the amount is clamped to be half the minimum edge length to prevent
200 // overlap. When outsetting, the amount is padded to cover 2 pixels.
201 if ((outsets < 0.f).anyTrue()) {
202 return (len - 0.5f * len.min()) / len;
203 } else {
204 return (len + outsets * (2.f - len.min())) / len;
205 }
206 } else {
207 return (len + outsets) / len;
208 }
209 }
210
211 // Generalizes compute_nested_quad_vertices to extrapolate local coords such that
212 // after perspective division of the device coordinate, the original local coordinate value is at
213 // the original un-outset device position. r is the local coordinate's w component. However, since
214 // the projected edges will be different for inner and outer quads, there isn't much reuse between
215 // the calculations, so it's easier to just have this operate on one quad a time.
compute_quad_persp_vertices(GrQuadAAFlags aaFlags,Sk4f * x,Sk4f * y,Sk4f * w,Sk4f * u,Sk4f * v,Sk4f * r,int uvrCount,bool inset)216 static float compute_quad_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
217 Sk4f* w, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount, bool inset) {
218 SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
219
220 auto iw = (*w).invert();
221 auto x2d = (*x) * iw;
222 auto y2d = (*y) * iw;
223
224 // Must compute non-rectilinear outset quantity using the projected 2d edge vectors
225 Sk4f xdiff, ydiff, invLengths;
226 compute_edge_vectors(x2d, y2d, nextCCW(x2d), nextCCW(y2d), &xdiff, &ydiff, &invLengths);
227 Sk4f outset = inset ? -0.5f : 0.5f;
228 adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
229
230 float maxProjectedCoverage = 1.f;
231
232 if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
233 // For each entry in x the equivalent entry in opX is the left/right opposite and so on.
234 Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x);
235 Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w);
236 Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y);
237 // vx/vy holds the device space left-to-right vectors along top and bottom of the quad.
238 Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d);
239 Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d);
240 Sk4f len = SkNx_shuffle<0, 1, 0, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
241
242 // Compute t in homogeneous space from s using similar triangles so that we can produce
243 // homogeneous outset vertices for perspective-correct interpolation.
244 Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
245 Sk4f sOpW = s * opW;
246 Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
247 // mask is used to make the t values be 1 when the left/right side is not antialiased.
248 Sk4f mask(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f,
249 GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f,
250 GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f,
251 GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f);
252 t = t * mask + (1.f - mask);
253 *x = opX + t * (*x - opX);
254 *y = opY + t * (*y - opY);
255 *w = opW + t * (*w - opW);
256
257 if (uvrCount > 0) {
258 Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u);
259 Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v);
260 *u = opU + t * (*u - opU);
261 *v = opV + t * (*v - opV);
262 if (uvrCount == 3) {
263 Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r);
264 *r = opR + t * (*r - opR);
265 }
266 }
267
268 if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
269 // Update the 2D points for the top/bottom calculation.
270 iw = (*w).invert();
271 x2d = (*x) * iw;
272 y2d = (*y) * iw;
273 }
274 }
275
276 if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
277 // This operates the same as above but for top/bottom rather than left/right.
278 Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x);
279 Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w);
280 Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y);
281
282 Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d);
283 Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d);
284 Sk4f len = SkNx_shuffle<0, 0, 1, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
285
286 Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
287 Sk4f sOpW = s * opW;
288 Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
289
290 Sk4f mask(GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f,
291 GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f,
292 GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f,
293 GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f);
294 t = t * mask + (1.f - mask);
295 *x = opX + t * (*x - opX);
296 *y = opY + t * (*y - opY);
297 *w = opW + t * (*w - opW);
298
299 if (uvrCount > 0) {
300 Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u);
301 Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v);
302 *u = opU + t * (*u - opU);
303 *v = opV + t * (*v - opV);
304 if (uvrCount == 3) {
305 Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r);
306 *r = opR + t * (*r - opR);
307 }
308 }
309 }
310
311 return maxProjectedCoverage;
312 }
313
314 enum class CoverageMode {
315 kNone,
316 kWithPosition,
317 kWithColor
318 };
319
get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec & spec)320 static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
321 if (spec.usesCoverageAA()) {
322 if (spec.compatibleWithAlphaAsCoverage() && spec.hasVertexColors()) {
323 return CoverageMode::kWithColor;
324 } else {
325 return CoverageMode::kWithPosition;
326 }
327 } else {
328 return CoverageMode::kNone;
329 }
330 }
331
332 // Writes four vertices in triangle strip order, including the additional data for local
333 // coordinates, domain, color, and coverage as needed to satisfy the vertex spec.
write_quad(GrVertexWriter * vb,const GrQuadPerEdgeAA::VertexSpec & spec,CoverageMode mode,float coverage,SkPMColor4f color4f,bool wideColor,const SkRect & domain,const Sk4f & x,const Sk4f & y,const Sk4f & w,const Sk4f & u,const Sk4f & v,const Sk4f & r)334 static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
335 CoverageMode mode, float coverage,
336 SkPMColor4f color4f, bool wideColor,
337 const SkRect& domain,
338 const Sk4f& x, const Sk4f& y, const Sk4f& w,
339 const Sk4f& u, const Sk4f& v, const Sk4f& r) {
340 static constexpr auto If = GrVertexWriter::If<float>;
341
342 if (mode == CoverageMode::kWithColor) {
343 // Multiply the color by the coverage up front
344 SkASSERT(spec.hasVertexColors());
345 color4f = color4f * coverage;
346 }
347 GrVertexColor color(color4f, wideColor);
348
349 for (int i = 0; i < 4; ++i) {
350 // save position, this is a float2 or float3 or float4 depending on the combination of
351 // perspective and coverage mode.
352 vb->write(x[i], y[i], If(spec.deviceQuadType() == GrQuadType::kPerspective, w[i]),
353 If(mode == CoverageMode::kWithPosition, coverage));
354
355 // save color
356 if (spec.hasVertexColors()) {
357 vb->write(color);
358 }
359
360 // save local position
361 if (spec.hasLocalCoords()) {
362 vb->write(u[i], v[i], If(spec.localQuadType() == GrQuadType::kPerspective, r[i]));
363 }
364
365 // save the domain
366 if (spec.hasDomain()) {
367 vb->write(domain);
368 }
369 }
370 }
371
372 GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
373
374 static const int kVertsPerAAFillRect = 8;
375 static const int kIndicesPerAAFillRect = 30;
376
get_index_buffer(GrResourceProvider * resourceProvider)377 static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
378 GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
379
380 // clang-format off
381 static const uint16_t gFillAARectIdx[] = {
382 0, 1, 2, 1, 3, 2,
383 0, 4, 1, 4, 5, 1,
384 0, 6, 4, 0, 2, 6,
385 2, 3, 6, 3, 7, 6,
386 1, 5, 3, 3, 5, 7,
387 };
388 // clang-format on
389
390 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
391 return resourceProvider->findOrCreatePatternedIndexBuffer(
392 gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
393 kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
394 }
395
396 } // anonymous namespace
397
398 namespace GrQuadPerEdgeAA {
399
400 ////////////////// Tessellate Implementation
401
Tessellate(void * vertices,const VertexSpec & spec,const GrPerspQuad & deviceQuad,const SkPMColor4f & color4f,const GrPerspQuad & localQuad,const SkRect & domain,GrQuadAAFlags aaFlags)402 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
403 const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
404 GrQuadAAFlags aaFlags) {
405 bool wideColor = GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType();
406 CoverageMode mode = get_mode_for_spec(spec);
407
408 // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
409 Sk4f oX = deviceQuad.x4f();
410 Sk4f oY = deviceQuad.y4f();
411 Sk4f oW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
412
413 // Load local position data into Sk4fs (either none, just u,v or all three)
414 Sk4f oU, oV, oR;
415 if (spec.hasLocalCoords()) {
416 oU = localQuad.x4f();
417 oV = localQuad.y4f();
418 oR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
419 }
420
421 GrVertexWriter vb{vertices};
422 if (spec.usesCoverageAA()) {
423 SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
424
425 // Must calculate two new quads, an outset and inset by .5 in projected device space, so
426 // duplicate the original quad into new Sk4fs for the inset.
427 Sk4f iX = oX, iY = oY, iW = oW;
428 Sk4f iU = oU, iV = oV, iR = oR;
429
430 float maxCoverage = 1.f;
431 if (aaFlags != GrQuadAAFlags::kNone) {
432 if (spec.deviceQuadType() == GrQuadType::kPerspective) {
433 // Outset and inset the quads independently because perspective makes each shift
434 // unique. Since iX copied pre-outset oX, this will compute the proper inset too.
435 compute_quad_persp_vertices(aaFlags, &oX, &oY, &oW, &oU, &oV, &oW,
436 spec.localDimensionality(), /* inset */ false);
437 // Save coverage limit when computing inset quad
438 maxCoverage = compute_quad_persp_vertices(aaFlags, &iX, &iY, &iW, &iU, &iV, &iW,
439 spec.localDimensionality(), true);
440 } else {
441 // In the 2D case, insetting and outsetting can reuse the edge vectors, so the
442 // nested quads are computed together
443 maxCoverage = compute_nested_quad_vertices(aaFlags, &oX, &oY, &oU, &oV, &oR,
444 &iX, &iY, &iU, &iV, &iR, spec.localDimensionality(),
445 spec.deviceQuadType() <= GrQuadType::kRectilinear);
446 }
447 // NOTE: could provide an even more optimized tessellation function for axis-aligned
448 // rects since the positions can be outset by constants without doing vector math,
449 // except it must handle identifying the winding of the quad vertices if the transform
450 // applied a mirror, etc. The current 2D case is already adequately fast.
451 } // else don't adjust any positions, let the outer quad form degenerate triangles
452
453 // Write two quads for inner and outer, inner will use the
454 write_quad(&vb, spec, mode, maxCoverage, color4f, wideColor, domain,
455 iX, iY, iW, iU, iV, iR);
456 write_quad(&vb, spec, mode, 0.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
457 } else {
458 // No outsetting needed, just write a single quad with full coverage
459 SkASSERT(mode == CoverageMode::kNone);
460 write_quad(&vb, spec, mode, 1.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
461 }
462
463 return vb.fPtr;
464 }
465
ConfigureMeshIndices(GrMeshDrawOp::Target * target,GrMesh * mesh,const VertexSpec & spec,int quadCount)466 bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
467 int quadCount) {
468 if (spec.usesCoverageAA()) {
469 // AA quads use 8 vertices, basically nested rectangles
470 sk_sp<const GrBuffer> ibuffer = get_index_buffer(target->resourceProvider());
471 if (!ibuffer) {
472 return false;
473 }
474
475 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
476 mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
477 quadCount, kNumAAQuadsInIndexBuffer);
478 } else {
479 // Non-AA quads use 4 vertices, and regular triangle strip layout
480 if (quadCount > 1) {
481 sk_sp<const GrBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
482 if (!ibuffer) {
483 return false;
484 }
485
486 mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
487 mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
488 GrResourceProvider::QuadCountOfQuadBuffer());
489 } else {
490 mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
491 mesh->setNonIndexedNonInstanced(4);
492 }
493 }
494
495 return true;
496 }
497
498 ////////////////// VertexSpec Implementation
499
deviceDimensionality() const500 int VertexSpec::deviceDimensionality() const {
501 return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
502 }
503
localDimensionality() const504 int VertexSpec::localDimensionality() const {
505 return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
506 }
507
508 ////////////////// Geometry Processor Implementation
509
510 class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
511 public:
512
Make(const VertexSpec & spec)513 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
514 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
515 }
516
Make(const VertexSpec & vertexSpec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)517 static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
518 GrTextureType textureType, GrPixelConfig textureConfig,
519 const GrSamplerState& samplerState,
520 uint32_t extraSamplerKey,
521 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
522 return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
523 vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
524 std::move(textureColorSpaceXform)));
525 }
526
name() const527 const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
528
getGLSLProcessorKey(const GrShaderCaps &,GrProcessorKeyBuilder * b) const529 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
530 // domain, texturing, device-dimensions are single bit flags
531 uint32_t x = fDomain.isInitialized() ? 0 : 1;
532 x |= fSampler.isInitialized() ? 0 : 2;
533 x |= fNeedsPerspective ? 0 : 4;
534 // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
535 if (fLocalCoord.isInitialized()) {
536 x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
537 }
538 // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
539 if (fColor.isInitialized()) {
540 x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
541 }
542 // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor
543 if (fCoverageMode != CoverageMode::kNone) {
544 x |= CoverageMode::kWithPosition == fCoverageMode ? 128 : 256;
545 }
546
547 b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
548 b->add32(x);
549 }
550
createGLSLInstance(const GrShaderCaps & caps) const551 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
552 class GLSLProcessor : public GrGLSLGeometryProcessor {
553 public:
554 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
555 FPCoordTransformIter&& transformIter) override {
556 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
557 if (gp.fLocalCoord.isInitialized()) {
558 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
559 }
560 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
561 }
562
563 private:
564 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
565 using Interpolation = GrGLSLVaryingHandler::Interpolation;
566
567 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
568 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
569 gp.fTextureColorSpaceXform.get());
570
571 args.fVaryingHandler->emitAttributes(gp);
572
573 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
574 // Strip last channel from the vertex attribute to remove coverage and get the
575 // actual position
576 if (gp.fNeedsPerspective) {
577 args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
578 gp.fPosition.name());
579 } else {
580 args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
581 gp.fPosition.name());
582 }
583 gpArgs->fPositionVar = {"position",
584 gp.fNeedsPerspective ? kFloat3_GrSLType
585 : kFloat2_GrSLType,
586 GrShaderVar::kNone_TypeModifier};
587 } else {
588 // No coverage to eliminate
589 gpArgs->fPositionVar = gp.fPosition.asShaderVar();
590 }
591
592 // Handle local coordinates if they exist
593 if (gp.fLocalCoord.isInitialized()) {
594 // NOTE: If the only usage of local coordinates is for the inline texture fetch
595 // before FPs, then there are no registered FPCoordTransforms and this ends up
596 // emitting nothing, so there isn't a duplication of local coordinates
597 this->emitTransforms(args.fVertBuilder,
598 args.fVaryingHandler,
599 args.fUniformHandler,
600 gp.fLocalCoord.asShaderVar(),
601 args.fFPCoordTransformHandler);
602 }
603
604 // Solid color before any texturing gets modulated in
605 if (gp.fColor.isInitialized()) {
606 // The color cannot be flat if the varying coverage has been modulated into it
607 args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
608 gp.fCoverageMode == CoverageMode::kWithColor ?
609 Interpolation::kInterpolated : Interpolation::kCanBeFlat);
610 } else {
611 // Output color must be initialized to something
612 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
613 }
614
615 // If there is a texture, must also handle texture coordinates and reading from
616 // the texture in the fragment shader before continuing to fragment processors.
617 if (gp.fSampler.isInitialized()) {
618 // Texture coordinates clamped by the domain on the fragment shader; if the GP
619 // has a texture, it's guaranteed to have local coordinates
620 args.fFragBuilder->codeAppend("float2 texCoord;");
621 if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
622 // Can't do a pass through since we need to perform perspective division
623 GrGLSLVarying v(gp.fLocalCoord.gpuType());
624 args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
625 args.fVertBuilder->codeAppendf("%s = %s;",
626 v.vsOut(), gp.fLocalCoord.name());
627 args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
628 v.fsIn(), v.fsIn());
629 } else {
630 args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
631 }
632
633 // Clamp the now 2D localCoordName variable by the domain if it is provided
634 if (gp.fDomain.isInitialized()) {
635 args.fFragBuilder->codeAppend("float4 domain;");
636 args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
637 Interpolation::kCanBeFlat);
638 args.fFragBuilder->codeAppend(
639 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
640 }
641
642 // Now modulate the starting output color by the texture lookup
643 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
644 args.fFragBuilder->appendTextureLookupAndModulate(
645 args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
646 &fTextureColorSpaceXformHelper);
647 args.fFragBuilder->codeAppend(";");
648 }
649
650 // And lastly, output the coverage calculation code
651 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
652 GrGLSLVarying coverage(kFloat_GrSLType);
653 args.fVaryingHandler->addVarying("coverage", &coverage);
654 if (gp.fNeedsPerspective) {
655 args.fVertBuilder->codeAppendf("%s = %s.w;",
656 coverage.vsOut(), gp.fPosition.name());
657 } else {
658 args.fVertBuilder->codeAppendf("%s = %s.z;",
659 coverage.vsOut(), gp.fPosition.name());
660 }
661
662 args.fFragBuilder->codeAppendf("%s = float4(%s);",
663 args.fOutputCoverage, coverage.fsIn());
664 } else {
665 // Set coverage to 1, since it's either non-AA or the coverage was already
666 // folded into the output color
667 args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
668 }
669 }
670 GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
671 };
672 return new GLSLProcessor;
673 }
674
675 private:
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec)676 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
677 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
678 , fTextureColorSpaceXform(nullptr) {
679 SkASSERT(!spec.hasDomain());
680 this->initializeAttrs(spec);
681 this->setTextureSamplerCnt(0);
682 }
683
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)684 QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
685 GrTextureType textureType, GrPixelConfig textureConfig,
686 const GrSamplerState& samplerState,
687 uint32_t extraSamplerKey,
688 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
689 : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
690 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
691 , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
692 SkASSERT(spec.hasLocalCoords());
693 this->initializeAttrs(spec);
694 this->setTextureSamplerCnt(1);
695 }
696
initializeAttrs(const VertexSpec & spec)697 void initializeAttrs(const VertexSpec& spec) {
698 fNeedsPerspective = spec.deviceDimensionality() == 3;
699 fCoverageMode = get_mode_for_spec(spec);
700
701 if (fCoverageMode == CoverageMode::kWithPosition) {
702 if (fNeedsPerspective) {
703 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
704 } else {
705 fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
706 }
707 } else {
708 if (fNeedsPerspective) {
709 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
710 } else {
711 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
712 }
713 }
714
715 int localDim = spec.localDimensionality();
716 if (localDim == 3) {
717 fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
718 } else if (localDim == 2) {
719 fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
720 } // else localDim == 0 and attribute remains uninitialized
721
722 if (ColorType::kByte == spec.colorType()) {
723 fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
724 } else if (ColorType::kHalf == spec.colorType()) {
725 fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
726 }
727
728 if (spec.hasDomain()) {
729 fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
730 }
731
732 this->setVertexAttributes(&fPosition, 4);
733 }
734
onTextureSampler(int) const735 const TextureSampler& onTextureSampler(int) const override { return fSampler; }
736
737 Attribute fPosition; // May contain coverage as last channel
738 Attribute fColor; // May have coverage modulated in if the FPs support it
739 Attribute fLocalCoord;
740 Attribute fDomain;
741
742 // The positions attribute may have coverage built into it, so float3 is an ambiguous type
743 // and may mean 2d with coverage, or 3d with no coverage
744 bool fNeedsPerspective;
745 CoverageMode fCoverageMode;
746
747 // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
748 // to skip texturing.
749 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
750 TextureSampler fSampler;
751
752 typedef GrGeometryProcessor INHERITED;
753 };
754
MakeProcessor(const VertexSpec & spec)755 sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
756 return QuadPerEdgeAAGeometryProcessor::Make(spec);
757 }
758
MakeTexturedProcessor(const VertexSpec & spec,const GrShaderCaps & caps,GrTextureType textureType,GrPixelConfig textureConfig,const GrSamplerState & samplerState,uint32_t extraSamplerKey,sk_sp<GrColorSpaceXform> textureColorSpaceXform)759 sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
760 GrTextureType textureType, GrPixelConfig textureConfig,
761 const GrSamplerState& samplerState, uint32_t extraSamplerKey,
762 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
763 return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
764 samplerState, extraSamplerKey,
765 std::move(textureColorSpaceXform));
766 }
767
768 } // namespace GrQuadPerEdgeAA
769