Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 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" |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 10 | #include "GrVertexWriter.h" |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 11 | #include "glsl/GrGLSLColorSpaceXformHelper.h" |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 12 | #include "glsl/GrGLSLGeometryProcessor.h" |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 13 | #include "glsl/GrGLSLPrimitiveProcessor.h" |
| 14 | #include "glsl/GrGLSLFragmentShaderBuilder.h" |
| 15 | #include "glsl/GrGLSLVarying.h" |
| 16 | #include "glsl/GrGLSLVertexGeoBuilder.h" |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 17 | #include "SkNx.h" |
| 18 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 19 | #define AI SK_ALWAYS_INLINE |
| 20 | |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 21 | namespace { |
| 22 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 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. |
| 29 | static AI Sk4f nextCW(const Sk4f& v) { |
| 30 | return SkNx_shuffle<2, 0, 3, 1>(v); |
| 31 | } |
| 32 | |
| 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. |
| 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 | |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 46 | // Outputs normalized edge vectors in xdiff and ydiff, as well as the reciprocal of the original |
| 47 | // edge lengths in 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 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 57 | static AI void outset_masked_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths, |
| 58 | const Sk4f& mask, Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, |
| 59 | int uvrCount) { |
| 60 | auto halfMask = 0.5f * mask; |
| 61 | auto maskCW = nextCW(halfMask); |
| 62 | *x += maskCW * -xdiff + halfMask * nextCW(xdiff); |
| 63 | *y += maskCW * -ydiff + halfMask * nextCW(ydiff); |
| 64 | if (uvrCount > 0) { |
| 65 | // We want to extend the texture coords by the same proportion as the positions. |
| 66 | maskCW *= invLengths; |
| 67 | halfMask *= nextCW(invLengths); |
| 68 | Sk4f udiff = nextCCW(*u) - *u; |
| 69 | Sk4f vdiff = nextCCW(*v) - *v; |
| 70 | *u += maskCW * -udiff + halfMask * nextCW(udiff); |
| 71 | *v += maskCW * -vdiff + halfMask * nextCW(vdiff); |
| 72 | if (uvrCount == 3) { |
| 73 | Sk4f rdiff = nextCCW(*r) - *r; |
| 74 | *r += maskCW * -rdiff + halfMask * nextCW(rdiff); |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | static AI void outset_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths, |
| 80 | Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) { |
| 81 | *x += 0.5f * (-xdiff + nextCW(xdiff)); |
| 82 | *y += 0.5f * (-ydiff + nextCW(ydiff)); |
| 83 | if (uvrCount > 0) { |
| 84 | Sk4f t = 0.5f * invLengths; |
| 85 | Sk4f udiff = nextCCW(*u) - *u; |
| 86 | Sk4f vdiff = nextCCW(*v) - *v; |
| 87 | *u += t * -udiff + nextCW(t) * nextCW(udiff); |
| 88 | *v += t * -vdiff + nextCW(t) * nextCW(vdiff); |
| 89 | if (uvrCount == 3) { |
| 90 | Sk4f rdiff = nextCCW(*r) - *r; |
| 91 | *r += t * -rdiff + nextCW(t) * nextCW(rdiff); |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | static AI void compute_edge_distances(const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& x, |
| 97 | const Sk4f& y, const Sk4f& w, Sk4f edgeDistances[]) { |
| 98 | for (int i = 0; i < 4; ++i) { |
| 99 | edgeDistances[i] = a * x[i] + b * y[i] + c * w[i]; |
| 100 | } |
| 101 | } |
| 102 | |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 103 | static AI float get_max_coverage(const Sk4f& lengths) { |
| 104 | float minWidth = SkMinScalar(lengths[0], lengths[3]); |
| 105 | float minHeight = SkMinScalar(lengths[1], lengths[2]); |
| 106 | // Calculate approximate area of the quad, pinning dimensions to 1 in case the quad is larger |
| 107 | // than a pixel. Sub-pixel quads that are rotated may in fact have a different true maximum |
| 108 | // coverage than this calculation, but this will be close and is stable. |
| 109 | return SkMinScalar(minWidth, 1.f) * SkMinScalar(minHeight, 1.f); |
| 110 | } |
| 111 | |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 112 | // This computes the four edge equations for a quad, then outsets them and optionally computes a new |
| 113 | // quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 114 | // input and the outset points as output. In order to be used as a component of perspective edge |
| 115 | // distance calculation, this exports edge equations in 'a', 'b', and 'c'. Use |
| 116 | // compute_edge_distances to turn these equations into the distances needed by the shader. The |
| 117 | // values in x, y, u, v, and r are possibly updated if outsetting is needed. r is the local |
| 118 | // position's w component if it exists. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 119 | // |
| 120 | // Returns maximum coverage allowed for any given pixel. |
| 121 | static float compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 122 | Sk4f* a, Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount, bool outset) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 123 | SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| 124 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 125 | // Compute edge vectors for the quad. |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 126 | auto xnext = nextCCW(*x); |
| 127 | auto ynext = nextCCW(*y); |
| 128 | // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 129 | Sk4f xdiff, ydiff, invLengths; |
| 130 | compute_edge_vectors(*x, *y, xnext, ynext, &xdiff, &ydiff, &invLengths); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 131 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 132 | // Use above vectors to compute edge equations (importantly before we outset positions). |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 133 | *c = fma(xnext, *y, -ynext * *x) * invLengths; |
| 134 | // Make sure the edge equations have their normals facing into the quad in device space. |
| 135 | auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c)); |
| 136 | if ((test < Sk4f(0)).anyTrue()) { |
| 137 | *a = -ydiff; |
| 138 | *b = xdiff; |
| 139 | *c = -*c; |
| 140 | } else { |
| 141 | *a = ydiff; |
| 142 | *b = -xdiff; |
| 143 | } |
| 144 | // Outset the edge equations so aa coverage evaluates to zero half a pixel away from the |
| 145 | // original quad edge. |
| 146 | *c += 0.5f; |
| 147 | |
| 148 | if (aaFlags != GrQuadAAFlags::kAll) { |
| 149 | // This order is the same order the edges appear in xdiff/ydiff and therefore as the |
| 150 | // edges in a/b/c. |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 151 | Sk4f mask = compute_edge_mask(aaFlags); |
| 152 | |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 153 | // Outset edge equations for masked out edges another pixel so that they always evaluate |
| 154 | // >= 1. |
| 155 | *c += (1.f - mask); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 156 | if (outset) { |
| 157 | outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 158 | } |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 159 | } else if (outset) { |
| 160 | outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 161 | } |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 162 | |
| 163 | return get_max_coverage(invLengths.invert()); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 164 | } |
| 165 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 166 | // A specialization of the above function that can compute edge distances very quickly when it knows |
| 167 | // that the edges intersect at right angles, i.e. any transform other than skew and perspective |
| 168 | // (GrQuadType::kRectilinear). Unlike the above function, this always outsets the corners since it |
| 169 | // cannot be reused in the perspective case. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 170 | static float compute_rectilinear_dists_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 171 | Sk4f* y, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) { |
| 172 | SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 173 | // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 174 | Sk4f xdiff, ydiff, invLengths; |
| 175 | compute_edge_vectors(*x, *y, nextCCW(*x), nextCCW(*y), &xdiff, &ydiff, &invLengths); |
| 176 | Sk4f lengths = invLengths.invert(); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 177 | |
| 178 | // Since the quad is rectilinear, the edge distances are predictable and independent of the |
| 179 | // actual orientation of the quad. The lengths vector stores |p1-p0|, |p3-p1|, |p0-p2|, |p2-p3|, |
| 180 | // matching the CCW order. For instance, edge distances for p0 are 0 for e0 and e2 since they |
| 181 | // intersect at p0. Distance to e1 is the same as p0 to p1. Distance to e3 is p0 to p2 since |
| 182 | // e3 goes through p2 and since the quad is rectilinear, we know that's the shortest distance. |
| 183 | edgeDistances[0] = Sk4f(0.f, lengths[0], 0.f, lengths[2]); |
| 184 | edgeDistances[1] = Sk4f(0.f, 0.f, lengths[0], lengths[1]); |
| 185 | edgeDistances[2] = Sk4f(lengths[2], lengths[3], 0.f, 0.f); |
| 186 | edgeDistances[3] = Sk4f(lengths[1], 0.f, lengths[3], 0.f); |
| 187 | |
| 188 | if (aaFlags != GrQuadAAFlags::kAll) { |
| 189 | // This order is the same order the edges appear in xdiff/ydiff and therefore as the |
| 190 | // edges in a/b/c. |
| 191 | Sk4f mask = compute_edge_mask(aaFlags); |
| 192 | |
| 193 | // Update opposite corner distances by 1 (when enabled by the mask). The distance |
| 194 | // calculations used in compute_quad_edges_... calculates the edge equations from original |
Michael Ludwig | 553e9a9 | 2018-11-29 12:38:35 -0500 | [diff] [blame] | 195 | // positions and then shifts the coefficient by 0.5. If the opposite edges are also outset |
| 196 | // then must add an additional 0.5 to account for its shift away from that edge. |
| 197 | Sk4f maskWithOpposites = mask + SkNx_shuffle<3, 2, 1, 0>(mask); |
| 198 | edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f) * maskWithOpposites; |
| 199 | edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f) * maskWithOpposites; |
| 200 | edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f) * maskWithOpposites; |
| 201 | edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f) * maskWithOpposites; |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 202 | |
| 203 | // Outset edge equations for masked out edges another pixel so that they always evaluate |
| 204 | // So add 1-mask to each point's edge distances vector so that coverage >= 1 on non-aa |
| 205 | for (int i = 0; i < 4; ++i) { |
| 206 | edgeDistances[i] += (1.f - mask); |
| 207 | } |
| 208 | outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount); |
| 209 | } else { |
| 210 | // Update opposite corner distances by 0.5 pixel and 0.5 edge shift, skipping the need for |
| 211 | // mask since that's 1s |
| 212 | edgeDistances[0] += Sk4f(0.f, 1.f, 0.f, 1.f); |
| 213 | edgeDistances[1] += Sk4f(0.f, 0.f, 1.f, 1.f); |
| 214 | edgeDistances[2] += Sk4f(1.f, 1.f, 0.f, 0.f); |
| 215 | edgeDistances[3] += Sk4f(1.f, 0.f, 1.f, 0.f); |
| 216 | |
| 217 | outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount); |
| 218 | } |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 219 | |
| 220 | return get_max_coverage(lengths); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | // Generalizes compute_quad_edge_distances_and_outset_vertices to extrapolate local coords such that |
| 224 | // after perspective division of the device coordinate, the original local coordinate value is at |
| 225 | // the original un-outset device position. r is the local coordinate's w component. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 226 | static float compute_quad_dists_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 227 | Sk4f* y, Sk4f* w, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 228 | SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| 229 | |
| 230 | auto iw = (*w).invert(); |
| 231 | auto x2d = (*x) * iw; |
| 232 | auto y2d = (*y) * iw; |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 233 | Sk4f a, b, c; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 234 | // Don't compute outset corners in the normalized space, which means u, v, and r don't need |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 235 | // to be provided here (outset separately below). Since this is computing distances for a |
| 236 | // projected quad, there is a very good chance it's not rectilinear so use the general 2D path. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 237 | float maxProjectedCoverage = compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, |
| 238 | &a, &b, &c, nullptr, nullptr, nullptr, /* uvr ct */ 0, /* outsetCorners */ false); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 239 | |
| 240 | static const float kOutset = 0.5f; |
| 241 | if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) { |
| 242 | // For each entry in x the equivalent entry in opX is the left/right opposite and so on. |
| 243 | Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x); |
| 244 | Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w); |
| 245 | Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y); |
| 246 | // vx/vy holds the device space left-to-right vectors along top and bottom of the quad. |
| 247 | Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d); |
| 248 | Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d); |
| 249 | Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| 250 | // For each device space corner, devP, label its left/right opposite device space point |
| 251 | // opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is |
| 252 | // (length(devPt - opDevPt) + 0.5) / length(devPt - opDevPt); |
| 253 | Sk4f s = SkNx_shuffle<0, 1, 0, 1>((len + kOutset) / len); |
| 254 | // Compute t in homogeneous space from s using similar triangles so that we can produce |
| 255 | // homogeneous outset vertices for perspective-correct interpolation. |
| 256 | Sk4f sOpW = s * opW; |
| 257 | Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| 258 | // mask is used to make the t values be 1 when the left/right side is not antialiased. |
| 259 | Sk4f mask(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| 260 | GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| 261 | GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f, |
| 262 | GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); |
| 263 | t = t * mask + (1.f - mask); |
| 264 | *x = opX + t * (*x - opX); |
| 265 | *y = opY + t * (*y - opY); |
| 266 | *w = opW + t * (*w - opW); |
| 267 | |
| 268 | if (uvrChannelCount > 0) { |
| 269 | Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u); |
| 270 | Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v); |
| 271 | *u = opU + t * (*u - opU); |
| 272 | *v = opV + t * (*v - opV); |
| 273 | if (uvrChannelCount == 3) { |
| 274 | Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r); |
| 275 | *r = opR + t * (*r - opR); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| 280 | // Update the 2D points for the top/bottom calculation. |
| 281 | iw = (*w).invert(); |
| 282 | x2d = (*x) * iw; |
| 283 | y2d = (*y) * iw; |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| 288 | // This operates the same as above but for top/bottom rather than left/right. |
| 289 | Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x); |
| 290 | Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w); |
| 291 | Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y); |
| 292 | |
| 293 | Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d); |
| 294 | Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d); |
| 295 | Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| 296 | |
| 297 | Sk4f s = SkNx_shuffle<0, 0, 1, 1>((len + kOutset) / len); |
| 298 | |
| 299 | Sk4f sOpW = s * opW; |
| 300 | Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| 301 | |
| 302 | Sk4f mask(GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| 303 | GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, |
| 304 | GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| 305 | GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f); |
| 306 | t = t * mask + (1.f - mask); |
| 307 | *x = opX + t * (*x - opX); |
| 308 | *y = opY + t * (*y - opY); |
| 309 | *w = opW + t * (*w - opW); |
| 310 | |
| 311 | if (uvrChannelCount > 0) { |
| 312 | Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u); |
| 313 | Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v); |
| 314 | *u = opU + t * (*u - opU); |
| 315 | *v = opV + t * (*v - opV); |
| 316 | if (uvrChannelCount == 3) { |
| 317 | Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r); |
| 318 | *r = opR + t * (*r - opR); |
| 319 | } |
| 320 | } |
| 321 | } |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 322 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 323 | // Use the original edge equations with the outset homogeneous coordinates to get the edge |
| 324 | // distance (technically multiplied by w, so that the fragment shader can do perspective |
| 325 | // interpolation when it multiplies by 1/w later). |
| 326 | compute_edge_distances(a, b, c, *x, *y, *w, edgeDistances); |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 327 | |
| 328 | return maxProjectedCoverage; |
Eric Boren | 98cb159 | 2018-11-26 18:38:05 +0000 | [diff] [blame] | 329 | } |
| 330 | |
Michael Ludwig | 553e9a9 | 2018-11-29 12:38:35 -0500 | [diff] [blame] | 331 | // Calculate safe edge distances for non-aa quads that have been batched with aa quads. Since the |
| 332 | // fragment shader multiples by 1/w, so the edge distance cannot just be set to 1. It cannot just |
| 333 | // be set to w either due to interpolation across the triangle. If iA, iB, and iC are the |
| 334 | // barycentric weights of the triangle, and we set the edge distance to w, the fragment shader |
| 335 | // actually sees d = (iA*wA + iB*wB + iC*wC) * (iA/wA + iB/wB + iC/wC). Without perspective this |
| 336 | // simplifies to 1 as necessary, but we must choose something other than w when there is perspective |
| 337 | // to ensure that d >= 1 and the edge shows as non-aa. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 338 | static float compute_nonaa_edge_distances(const Sk4f& w, bool hasPersp, Sk4f edgeDistances[4]) { |
Michael Ludwig | 553e9a9 | 2018-11-29 12:38:35 -0500 | [diff] [blame] | 339 | // Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite |
| 340 | // d = (iA*wA + iB*wB + iC*wC) * (iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC) |
| 341 | // | e=attr from VS | | fragCoord.w = 1/w | |
| 342 | // Since the weights are the interior of the primitive then we have: |
| 343 | // n <= (iA*wA + iB*wB + iC*wC) <= m and |
| 344 | // n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and |
| 345 | // n^3 <= wA*wB*wC <= m^3 regardless of the choice of A, B, and C verts in the quad |
| 346 | // Thus if we set e = m^3/n^3, it guarantees d >= 1 for any perspective. |
| 347 | float e; |
| 348 | if (hasPersp) { |
| 349 | float m = w.max(); |
| 350 | float n = w.min(); |
| 351 | e = (m * m * m) / (n * n * n); |
| 352 | } else { |
| 353 | e = 1.f; |
| 354 | } |
| 355 | |
| 356 | // All edge distances set to the same |
| 357 | for (int i = 0; i < 4; ++i) { |
| 358 | edgeDistances[i] = e; |
| 359 | } |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 360 | |
| 361 | // Non-aa, so always use full coverage |
| 362 | return 1.f; |
Michael Ludwig | 553e9a9 | 2018-11-29 12:38:35 -0500 | [diff] [blame] | 363 | } |
| 364 | |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 365 | } // anonymous namespace |
| 366 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 367 | namespace GrQuadPerEdgeAA { |
| 368 | |
| 369 | ////////////////// Tessellate Implementation |
| 370 | |
| 371 | void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad, |
Brian Osman | 3d139a4 | 2018-11-19 10:42:10 -0500 | [diff] [blame] | 372 | const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain, |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 373 | GrQuadAAFlags aaFlags) { |
| 374 | bool deviceHasPerspective = spec.deviceQuadType() == GrQuadType::kPerspective; |
| 375 | bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective; |
Brian Osman | 3d139a4 | 2018-11-19 10:42:10 -0500 | [diff] [blame] | 376 | GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType()); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 377 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 378 | // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road) |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 379 | Sk4f x = deviceQuad.x4f(); |
| 380 | Sk4f y = deviceQuad.y4f(); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 381 | Sk4f w = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 382 | |
| 383 | // Load local position data into Sk4fs (either none, just u,v or all three) |
| 384 | Sk4f u, v, r; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 385 | if (spec.hasLocalCoords()) { |
| 386 | u = localQuad.x4f(); |
| 387 | v = localQuad.y4f(); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 388 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 389 | if (localHasPerspective) { |
| 390 | r = localQuad.w4f(); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 391 | } |
| 392 | } |
| 393 | |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 394 | // Index into array refers to vertex. Index into particular Sk4f refers to edge. |
| 395 | Sk4f edgeDistances[4]; |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 396 | float maxCoverage = 1.f; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 397 | if (spec.usesCoverageAA()) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 398 | // Must calculate edges and possibly outside the positions |
| 399 | if (aaFlags == GrQuadAAFlags::kNone) { |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 400 | // A non-AA quad that got batched into an AA group, so it should have full coverage |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 401 | maxCoverage = compute_nonaa_edge_distances(w, deviceHasPerspective, edgeDistances); |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 402 | } else if (deviceHasPerspective) { |
| 403 | // For simplicity, pointers to u, v, and r are always provided, but the local dim param |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 404 | // ensures that only loaded Sk4fs are modified in the compute functions. |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 405 | maxCoverage = compute_quad_dists_and_outset_persp_vertices(aaFlags, &x, &y, &w, |
| 406 | edgeDistances, &u, &v, &r, spec.localDimensionality()); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 407 | } else if (spec.deviceQuadType() <= GrQuadType::kRectilinear) { |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 408 | maxCoverage = compute_rectilinear_dists_and_outset_vertices(aaFlags, &x, &y, |
| 409 | edgeDistances, &u, &v, &r, spec.localDimensionality()); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 410 | } else { |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 411 | Sk4f a, b, c; |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 412 | maxCoverage = compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, |
| 413 | &u, &v, &r, spec.localDimensionality(), /*outset*/ true); |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 414 | compute_edge_distances(a, b, c, x, y, w, edgeDistances); // w holds 1.f as desired |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 415 | } |
| 416 | } |
| 417 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 418 | // Now rearrange the Sk4fs into the interleaved vertex layout: |
| 419 | // i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y |
| 420 | GrVertexWriter vb{vertices}; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 421 | for (int i = 0; i < 4; ++i) { |
Michael Ludwig | 4921dc3 | 2018-12-03 14:57:29 +0000 | [diff] [blame] | 422 | // save position, always send a vec4 because we embed max coverage in the last component. |
| 423 | // For 2D quads, we know w holds the correct 1.f, so just write it out without branching |
| 424 | vb.write(x[i], y[i], w[i], maxCoverage); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 425 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 426 | // save color |
| 427 | if (spec.hasVertexColors()) { |
Brian Osman | 3d139a4 | 2018-11-19 10:42:10 -0500 | [diff] [blame] | 428 | vb.write(color); |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 429 | } |
| 430 | |
| 431 | // save local position |
| 432 | if (spec.hasLocalCoords()) { |
| 433 | if (localHasPerspective) { |
| 434 | vb.write<SkPoint3>({u[i], v[i], r[i]}); |
| 435 | } else { |
| 436 | vb.write<SkPoint>({u[i], v[i]}); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 437 | } |
| 438 | } |
| 439 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 440 | // save the domain |
| 441 | if (spec.hasDomain()) { |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 442 | vb.write(domain); |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 443 | } |
| 444 | |
| 445 | // save the edges |
| 446 | if (spec.usesCoverageAA()) { |
Michael Ludwig | f995c05 | 2018-11-26 15:24:29 -0500 | [diff] [blame] | 447 | vb.write(edgeDistances[i]); |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 448 | } |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 449 | } |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 450 | |
| 451 | return vb.fPtr; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 452 | } |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 453 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 454 | ////////////////// VertexSpec Implementation |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 455 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 456 | int VertexSpec::deviceDimensionality() const { |
| 457 | return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2; |
| 458 | } |
| 459 | |
| 460 | int VertexSpec::localDimensionality() const { |
| 461 | return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0; |
| 462 | } |
| 463 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 464 | ////////////////// Geometry Processor Implementation |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 465 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 466 | class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor { |
| 467 | public: |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 468 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 469 | static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) { |
| 470 | return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec)); |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 471 | } |
| 472 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 473 | static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps, |
| 474 | GrTextureType textureType, GrPixelConfig textureConfig, |
Greg Daniel | 7a82edf | 2018-12-04 10:54:34 -0500 | [diff] [blame] | 475 | const GrSamplerState& samplerState, |
| 476 | uint32_t extraSamplerKey, |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 477 | sk_sp<GrColorSpaceXform> textureColorSpaceXform) { |
| 478 | return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor( |
Greg Daniel | 7a82edf | 2018-12-04 10:54:34 -0500 | [diff] [blame] | 479 | vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey, |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 480 | std::move(textureColorSpaceXform))); |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 481 | } |
| 482 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 483 | const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; } |
Michael Ludwig | 024e262 | 2018-11-30 13:25:55 -0500 | [diff] [blame] | 484 | |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 485 | void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { |
| 486 | // aa, domain, texturing are single bit flags |
| 487 | uint32_t x = fAAEdgeDistances.isInitialized() ? 0 : 1; |
| 488 | x |= fDomain.isInitialized() ? 0 : 2; |
| 489 | x |= fSampler.isInitialized() ? 0 : 4; |
| 490 | // regular position has two options as well |
| 491 | x |= fNeedsPerspective ? 0 : 8; |
| 492 | // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d |
| 493 | if (fLocalCoord.isInitialized()) { |
| 494 | x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 16 : 32; |
Brian Osman | 78dc72c | 2018-12-03 13:20:43 +0000 | [diff] [blame] | 495 | } |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 496 | // similar for colors, 00 for none, 01 for bytes, 10 for half-floats |
| 497 | if (this->fColor.isInitialized()) { |
| 498 | x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 64 : 128; |
| 499 | } |
| 500 | |
| 501 | b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get())); |
| 502 | b->add32(x); |
Brian Osman | 78dc72c | 2018-12-03 13:20:43 +0000 | [diff] [blame] | 503 | } |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 504 | |
| 505 | GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override { |
| 506 | class GLSLProcessor : public GrGLSLGeometryProcessor { |
| 507 | public: |
| 508 | void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, |
| 509 | FPCoordTransformIter&& transformIter) override { |
| 510 | const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>(); |
| 511 | if (gp.fLocalCoord.isInitialized()) { |
| 512 | this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); |
| 513 | } |
| 514 | fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get()); |
| 515 | } |
| 516 | |
| 517 | private: |
| 518 | void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { |
| 519 | using Interpolation = GrGLSLVaryingHandler::Interpolation; |
| 520 | |
| 521 | const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>(); |
| 522 | fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler, |
| 523 | gp.fTextureColorSpaceXform.get()); |
| 524 | |
| 525 | args.fVaryingHandler->emitAttributes(gp); |
| 526 | |
| 527 | // Extract effective position out of vec4 as a local variable in the vertex shader |
| 528 | if (gp.fNeedsPerspective) { |
| 529 | args.fVertBuilder->codeAppendf("float3 position = %s.xyz;", |
| 530 | gp.fPositionWithCoverage.name()); |
| 531 | } else { |
| 532 | args.fVertBuilder->codeAppendf("float2 position = %s.xy;", |
| 533 | gp.fPositionWithCoverage.name()); |
| 534 | } |
| 535 | gpArgs->fPositionVar = {"position", |
| 536 | gp.fNeedsPerspective ? kFloat3_GrSLType : kFloat2_GrSLType, |
| 537 | GrShaderVar::kNone_TypeModifier}; |
| 538 | |
| 539 | // Handle local coordinates if they exist |
| 540 | if (gp.fLocalCoord.isInitialized()) { |
| 541 | // NOTE: If the only usage of local coordinates is for the inline texture fetch |
| 542 | // before FPs, then there are no registered FPCoordTransforms and this ends up |
| 543 | // emitting nothing, so there isn't a duplication of local coordinates |
| 544 | this->emitTransforms(args.fVertBuilder, |
| 545 | args.fVaryingHandler, |
| 546 | args.fUniformHandler, |
| 547 | gp.fLocalCoord.asShaderVar(), |
| 548 | args.fFPCoordTransformHandler); |
| 549 | } |
| 550 | |
| 551 | // Solid color before any texturing gets modulated in |
| 552 | if (gp.fColor.isInitialized()) { |
| 553 | args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor, |
| 554 | Interpolation::kCanBeFlat); |
| 555 | } |
| 556 | |
| 557 | // If there is a texture, must also handle texture coordinates and reading from |
| 558 | // the texture in the fragment shader before continuing to fragment processors. |
| 559 | if (gp.fSampler.isInitialized()) { |
| 560 | // Texture coordinates clamped by the domain on the fragment shader; if the GP |
| 561 | // has a texture, it's guaranteed to have local coordinates |
| 562 | args.fFragBuilder->codeAppend("float2 texCoord;"); |
| 563 | if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) { |
| 564 | // Can't do a pass through since we need to perform perspective division |
| 565 | GrGLSLVarying v(gp.fLocalCoord.gpuType()); |
| 566 | args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v); |
| 567 | args.fVertBuilder->codeAppendf("%s = %s;", |
| 568 | v.vsOut(), gp.fLocalCoord.name()); |
| 569 | args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;", |
| 570 | v.fsIn(), v.fsIn()); |
| 571 | } else { |
| 572 | args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord"); |
| 573 | } |
| 574 | |
| 575 | // Clamp the now 2D localCoordName variable by the domain if it is provided |
| 576 | if (gp.fDomain.isInitialized()) { |
| 577 | args.fFragBuilder->codeAppend("float4 domain;"); |
| 578 | args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain", |
| 579 | Interpolation::kCanBeFlat); |
| 580 | args.fFragBuilder->codeAppend( |
| 581 | "texCoord = clamp(texCoord, domain.xy, domain.zw);"); |
| 582 | } |
| 583 | |
| 584 | // Now modulate the starting output color by the texture lookup |
| 585 | args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor); |
| 586 | args.fFragBuilder->appendTextureLookupAndModulate( |
| 587 | args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType, |
| 588 | &fTextureColorSpaceXformHelper); |
| 589 | args.fFragBuilder->codeAppend(";"); |
| 590 | } |
| 591 | |
| 592 | // And lastly, output the coverage calculation code |
| 593 | if (gp.fAAEdgeDistances.isInitialized()) { |
| 594 | GrGLSLVarying maxCoverage(kFloat_GrSLType); |
| 595 | args.fVaryingHandler->addVarying("maxCoverage", &maxCoverage); |
| 596 | args.fVertBuilder->codeAppendf("%s = %s.w;", |
| 597 | maxCoverage.vsOut(), gp.fPositionWithCoverage.name()); |
| 598 | |
| 599 | args.fFragBuilder->codeAppend("float4 edgeDists;"); |
| 600 | args.fVaryingHandler->addPassThroughAttribute(gp.fAAEdgeDistances, "edgeDists"); |
| 601 | |
| 602 | args.fFragBuilder->codeAppend( |
| 603 | "float minDist = min(min(edgeDists.x, edgeDists.y)," |
| 604 | " min(edgeDists.z, edgeDists.w));"); |
| 605 | if (gp.fNeedsPerspective) { |
| 606 | // The distance from edge equation e to homogeneous point p=sk_Position is |
| 607 | // e.x*p.x/p.w + e.y*p.y/p.w + e.z. However, we want screen space |
| 608 | // interpolation of this distance. We can do this by multiplying the vertex |
| 609 | // attribute by p.w and then multiplying by sk_FragCoord.w in the FS. So we |
| 610 | // output e.x*p.x + e.y*p.y + e.z * p.w |
| 611 | args.fFragBuilder->codeAppend("minDist *= sk_FragCoord.w;"); |
| 612 | } |
| 613 | // Clamp to max coverage after the perspective divide since perspective quads |
| 614 | // calculated the max coverage in projected space. |
| 615 | args.fFragBuilder->codeAppendf("%s = float4(clamp(minDist, 0.0, %s));", |
| 616 | args.fOutputCoverage, maxCoverage.fsIn()); |
| 617 | } else { |
| 618 | // Set coverage to 1 |
| 619 | args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage); |
| 620 | } |
| 621 | } |
| 622 | GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper; |
| 623 | }; |
| 624 | return new GLSLProcessor; |
| 625 | } |
| 626 | |
| 627 | private: |
| 628 | QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec) |
| 629 | : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) |
| 630 | , fTextureColorSpaceXform(nullptr) { |
| 631 | SkASSERT(spec.hasVertexColors() && !spec.hasDomain()); |
| 632 | this->initializeAttrs(spec); |
| 633 | this->setTextureSamplerCnt(0); |
| 634 | } |
| 635 | |
| 636 | QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps, |
| 637 | GrTextureType textureType, GrPixelConfig textureConfig, |
Greg Daniel | 7a82edf | 2018-12-04 10:54:34 -0500 | [diff] [blame] | 638 | const GrSamplerState& samplerState, |
| 639 | uint32_t extraSamplerKey, |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 640 | sk_sp<GrColorSpaceXform> textureColorSpaceXform) |
| 641 | : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) |
| 642 | , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) |
Greg Daniel | 7a82edf | 2018-12-04 10:54:34 -0500 | [diff] [blame] | 643 | , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) { |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 644 | SkASSERT(spec.hasVertexColors() && spec.hasLocalCoords()); |
| 645 | this->initializeAttrs(spec); |
| 646 | this->setTextureSamplerCnt(1); |
| 647 | } |
| 648 | |
| 649 | void initializeAttrs(const VertexSpec& spec) { |
| 650 | fNeedsPerspective = spec.deviceDimensionality() == 3; |
| 651 | fPositionWithCoverage = {"posAndCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; |
| 652 | |
| 653 | int localDim = spec.localDimensionality(); |
| 654 | if (localDim == 3) { |
| 655 | fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 656 | } else if (localDim == 2) { |
| 657 | fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; |
| 658 | } // else localDim == 0 and attribute remains uninitialized |
| 659 | |
| 660 | if (ColorType::kByte == spec.colorType()) { |
| 661 | fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; |
| 662 | } else if (ColorType::kHalf == spec.colorType()) { |
| 663 | fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType}; |
| 664 | } |
| 665 | |
| 666 | if (spec.hasDomain()) { |
| 667 | fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; |
| 668 | } |
| 669 | |
| 670 | if (spec.usesCoverageAA()) { |
| 671 | fAAEdgeDistances = {"aaEdgeDist", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; |
| 672 | } |
| 673 | this->setVertexAttributes(&fPositionWithCoverage, 5); |
| 674 | } |
| 675 | |
| 676 | const TextureSampler& onTextureSampler(int) const override { return fSampler; } |
| 677 | |
| 678 | Attribute fPositionWithCoverage; |
| 679 | Attribute fColor; |
| 680 | Attribute fLocalCoord; |
| 681 | Attribute fDomain; |
| 682 | Attribute fAAEdgeDistances; |
| 683 | |
| 684 | // The positions attribute is always a vec4 and can't be used to encode perspectiveness |
| 685 | bool fNeedsPerspective; |
| 686 | |
| 687 | // Color space will be null and fSampler.isInitialized() returns false when the GP is configured |
| 688 | // to skip texturing. |
| 689 | sk_sp<GrColorSpaceXform> fTextureColorSpaceXform; |
| 690 | TextureSampler fSampler; |
| 691 | |
| 692 | typedef GrGeometryProcessor INHERITED; |
| 693 | }; |
| 694 | |
| 695 | sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) { |
| 696 | return QuadPerEdgeAAGeometryProcessor::Make(spec); |
| 697 | } |
| 698 | |
| 699 | sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps, |
| 700 | GrTextureType textureType, GrPixelConfig textureConfig, |
Greg Daniel | 7a82edf | 2018-12-04 10:54:34 -0500 | [diff] [blame] | 701 | const GrSamplerState& samplerState, uint32_t extraSamplerKey, |
| 702 | sk_sp<GrColorSpaceXform> textureColorSpaceXform) { |
| 703 | return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig, |
| 704 | samplerState, extraSamplerKey, |
Michael Ludwig | 467994d | 2018-12-03 14:58:31 +0000 | [diff] [blame] | 705 | std::move(textureColorSpaceXform)); |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 706 | } |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 707 | |
| 708 | } // namespace GrQuadPerEdgeAA |