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" |
| 12 | #include "glsl/GrGLSLPrimitiveProcessor.h" |
| 13 | #include "glsl/GrGLSLFragmentShaderBuilder.h" |
| 14 | #include "glsl/GrGLSLVarying.h" |
| 15 | #include "glsl/GrGLSLVertexGeoBuilder.h" |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 16 | #include "SkNx.h" |
| 17 | |
| 18 | namespace { |
| 19 | |
| 20 | // This computes the four edge equations for a quad, then outsets them and optionally computes a new |
| 21 | // quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as |
| 22 | // input and the outset points as output. 'a', 'b', and 'c' are the edge equation coefficients on |
| 23 | // output. The values in x, y, u, v, and r are possibly updated if outsetting is needed. |
| 24 | // r is the local position's w component if it exists. |
| 25 | static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, Sk4f* a, |
| 26 | Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, |
| 27 | int uvrChannelCount, bool outsetCorners) { |
| 28 | SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| 29 | |
| 30 | static constexpr auto fma = SkNx_fma<4, float>; |
| 31 | // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip |
| 32 | // order. |
| 33 | auto nextCW = [](const Sk4f& v) { return SkNx_shuffle<2, 0, 3, 1>(v); }; |
| 34 | auto nextCCW = [](const Sk4f& v) { return SkNx_shuffle<1, 3, 0, 2>(v); }; |
| 35 | |
| 36 | // Compute edge equations for the quad. |
| 37 | auto xnext = nextCCW(*x); |
| 38 | auto ynext = nextCCW(*y); |
| 39 | // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. |
| 40 | auto xdiff = xnext - *x; |
| 41 | auto ydiff = ynext - *y; |
| 42 | auto invLengths = fma(xdiff, xdiff, ydiff * ydiff).rsqrt(); |
| 43 | xdiff *= invLengths; |
| 44 | ydiff *= invLengths; |
| 45 | |
| 46 | // Use above vectors to compute edge equations. |
| 47 | *c = fma(xnext, *y, -ynext * *x) * invLengths; |
| 48 | // Make sure the edge equations have their normals facing into the quad in device space. |
| 49 | auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c)); |
| 50 | if ((test < Sk4f(0)).anyTrue()) { |
| 51 | *a = -ydiff; |
| 52 | *b = xdiff; |
| 53 | *c = -*c; |
| 54 | } else { |
| 55 | *a = ydiff; |
| 56 | *b = -xdiff; |
| 57 | } |
| 58 | // Outset the edge equations so aa coverage evaluates to zero half a pixel away from the |
| 59 | // original quad edge. |
| 60 | *c += 0.5f; |
| 61 | |
| 62 | if (aaFlags != GrQuadAAFlags::kAll) { |
| 63 | // This order is the same order the edges appear in xdiff/ydiff and therefore as the |
| 64 | // edges in a/b/c. |
| 65 | auto mask = Sk4f(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| 66 | GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, |
| 67 | GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| 68 | GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); |
| 69 | // Outset edge equations for masked out edges another pixel so that they always evaluate |
| 70 | // >= 1. |
| 71 | *c += (1.f - mask); |
| 72 | if (outsetCorners) { |
| 73 | // Do the vertex outset. |
| 74 | mask *= 0.5f; |
| 75 | auto maskCW = nextCW(mask); |
| 76 | *x += maskCW * -xdiff + mask * nextCW(xdiff); |
| 77 | *y += maskCW * -ydiff + mask * nextCW(ydiff); |
| 78 | if (uvrChannelCount > 0) { |
| 79 | // We want to extend the texture coords by the same proportion as the positions. |
| 80 | maskCW *= invLengths; |
| 81 | mask *= nextCW(invLengths); |
| 82 | Sk4f udiff = nextCCW(*u) - *u; |
| 83 | Sk4f vdiff = nextCCW(*v) - *v; |
| 84 | *u += maskCW * -udiff + mask * nextCW(udiff); |
| 85 | *v += maskCW * -vdiff + mask * nextCW(vdiff); |
| 86 | if (uvrChannelCount == 3) { |
| 87 | Sk4f rdiff = nextCCW(*r) - *r; |
| 88 | *r += maskCW * -rdiff + mask * nextCW(rdiff); |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | } else if (outsetCorners) { |
| 93 | *x += 0.5f * (-xdiff + nextCW(xdiff)); |
| 94 | *y += 0.5f * (-ydiff + nextCW(ydiff)); |
| 95 | if (uvrChannelCount > 0) { |
| 96 | Sk4f t = 0.5f * invLengths; |
| 97 | Sk4f udiff = nextCCW(*u) - *u; |
| 98 | Sk4f vdiff = nextCCW(*v) - *v; |
| 99 | *u += t * -udiff + nextCW(t) * nextCW(udiff); |
| 100 | *v += t * -vdiff + nextCW(t) * nextCW(vdiff); |
| 101 | if (uvrChannelCount == 3) { |
| 102 | Sk4f rdiff = nextCCW(*r) - *r; |
| 103 | *r += t * -rdiff + nextCW(t) * nextCW(rdiff); |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | // Generalizes the above function to extrapolate local coords such that after perspective division |
| 110 | // of the device coordinate, the original local coordinate value is at the original un-outset |
| 111 | // device position. r is the local coordinate's w component. |
| 112 | static void compute_quad_edges_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, |
| 113 | Sk4f* w, Sk4f* a, Sk4f* b, Sk4f* c, |
| 114 | Sk4f* u, Sk4f* v, Sk4f* r, |
| 115 | int uvrChannelCount) { |
| 116 | SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| 117 | |
| 118 | auto iw = (*w).invert(); |
| 119 | auto x2d = (*x) * iw; |
| 120 | auto y2d = (*y) * iw; |
| 121 | // Don't compute outset corners in the normalized space, which means u, v, and r don't need |
| 122 | // to be provided here (outset separately below). |
| 123 | compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, a, b, c, nullptr, nullptr, nullptr, |
| 124 | /* uvr ct */ 0, /* outsetCorners */ false); |
| 125 | |
| 126 | static const float kOutset = 0.5f; |
| 127 | if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) { |
| 128 | // For each entry in x the equivalent entry in opX is the left/right opposite and so on. |
| 129 | Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x); |
| 130 | Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w); |
| 131 | Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y); |
| 132 | // vx/vy holds the device space left-to-right vectors along top and bottom of the quad. |
| 133 | Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d); |
| 134 | Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d); |
| 135 | Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| 136 | // For each device space corner, devP, label its left/right opposite device space point |
| 137 | // opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is |
| 138 | // (length(devPt - opDevPt) + 0.5) / length(devPt - opDevPt); |
| 139 | Sk4f s = SkNx_shuffle<0, 1, 0, 1>((len + kOutset) / len); |
| 140 | // Compute t in homogeneous space from s using similar triangles so that we can produce |
| 141 | // homogeneous outset vertices for perspective-correct interpolation. |
| 142 | Sk4f sOpW = s * opW; |
| 143 | Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| 144 | // mask is used to make the t values be 1 when the left/right side is not antialiased. |
| 145 | Sk4f mask(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| 146 | GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| 147 | GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f, |
| 148 | GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); |
| 149 | t = t * mask + (1.f - mask); |
| 150 | *x = opX + t * (*x - opX); |
| 151 | *y = opY + t * (*y - opY); |
| 152 | *w = opW + t * (*w - opW); |
| 153 | |
| 154 | if (uvrChannelCount > 0) { |
| 155 | Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u); |
| 156 | Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v); |
| 157 | *u = opU + t * (*u - opU); |
| 158 | *v = opV + t * (*v - opV); |
| 159 | if (uvrChannelCount == 3) { |
| 160 | Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r); |
| 161 | *r = opR + t * (*r - opR); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| 166 | // Update the 2D points for the top/bottom calculation. |
| 167 | iw = (*w).invert(); |
| 168 | x2d = (*x) * iw; |
| 169 | y2d = (*y) * iw; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| 174 | // This operates the same as above but for top/bottom rather than left/right. |
| 175 | Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x); |
| 176 | Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w); |
| 177 | Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y); |
| 178 | |
| 179 | Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d); |
| 180 | Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d); |
| 181 | Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| 182 | |
| 183 | Sk4f s = SkNx_shuffle<0, 0, 1, 1>((len + kOutset) / len); |
| 184 | |
| 185 | Sk4f sOpW = s * opW; |
| 186 | Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| 187 | |
| 188 | Sk4f mask(GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| 189 | GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, |
| 190 | GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| 191 | GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f); |
| 192 | t = t * mask + (1.f - mask); |
| 193 | *x = opX + t * (*x - opX); |
| 194 | *y = opY + t * (*y - opY); |
| 195 | *w = opW + t * (*w - opW); |
| 196 | |
| 197 | if (uvrChannelCount > 0) { |
| 198 | Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u); |
| 199 | Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v); |
| 200 | *u = opU + t * (*u - opU); |
| 201 | *v = opV + t * (*v - opV); |
| 202 | if (uvrChannelCount == 3) { |
| 203 | Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r); |
| 204 | *r = opR + t * (*r - opR); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | // Fast path for non-AA quads batched into an AA op. Since they are part of the AA op, the vertices |
| 211 | // need to have valid edge equations that ensure coverage is set to 1. To get perspective |
| 212 | // interpolation of the edge distance, the vertex shader outputs d*w and then multiplies by 1/w in |
| 213 | // the fragment shader. For non-AA edges, the edge equation can be simplified to 0*x/w + y/w + c >= |
| 214 | // 1, so the vertex shader outputs c*w. The quad is sent as two triangles, so a fragment is the |
| 215 | // interpolation between 3 of the 4 vertices. If iX are the weights for the 3 involved quad |
| 216 | // vertices, then the fragment shader's state is: |
| 217 | // f_cw = c * (iA*wA + iB*wB + iC*wC) and f_1/w = iA/wA + iB/wB + iC/wC |
| 218 | // (where A,B,C are chosen from {1,2,3, 4}) |
| 219 | // When there's no perspective, then f_cw*f_1/w = c and setting c = 1 guarantees a proper non-AA |
| 220 | // edge. Unfortunately when there is perspective, f_cw*f_1/w != c unless the fragment is at a |
| 221 | // vertex. We must pick a c such that f_cw*f_1/w >= 1 across the whole primitive. |
| 222 | // Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite |
| 223 | // f_1/w=(iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC) |
| 224 | // Since the iXs are weights for the interior of the primitive, then we have: |
| 225 | // n <= (iA*wA + iB*wB + iC*wC) <= m and |
| 226 | // n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and |
| 227 | // n^3 <= wA*wB*wC <= m^3 regardless of the choice of A,B, and C |
| 228 | // Thus if we set c = m^3/n^3, it guarantees f_cw*f_1/w >= 1 for any perspective. |
| 229 | static SkPoint3 compute_non_aa_persp_edge_coeffs(const Sk4f& w) { |
| 230 | float n = w.min(); |
| 231 | float m = w.max(); |
| 232 | return {0.f, 0.f, (m * m * m) / (n * n * n)}; |
| 233 | } |
| 234 | |
| 235 | // When there's guaranteed no perspective, the edge coefficients for non-AA quads is constant |
| 236 | static constexpr SkPoint3 kNonAANoPerspEdgeCoeffs = {0, 0, 1}; |
| 237 | |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 238 | } // anonymous namespace |
| 239 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 240 | namespace GrQuadPerEdgeAA { |
| 241 | |
| 242 | ////////////////// Tessellate Implementation |
| 243 | |
| 244 | void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad, |
| 245 | const GrColor& color, const GrPerspQuad& localQuad, const SkRect& domain, |
| 246 | GrQuadAAFlags aaFlags) { |
| 247 | bool deviceHasPerspective = spec.deviceQuadType() == GrQuadType::kPerspective; |
| 248 | bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 249 | |
| 250 | // Load position data into Sk4fs (always x, y and maybe w) |
| 251 | Sk4f x = deviceQuad.x4f(); |
| 252 | Sk4f y = deviceQuad.y4f(); |
| 253 | Sk4f w; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 254 | if (deviceHasPerspective) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 255 | w = deviceQuad.w4f(); |
| 256 | } |
| 257 | |
| 258 | // Load local position data into Sk4fs (either none, just u,v or all three) |
| 259 | Sk4f u, v, r; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 260 | if (spec.hasLocalCoords()) { |
| 261 | u = localQuad.x4f(); |
| 262 | v = localQuad.y4f(); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 263 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 264 | if (localHasPerspective) { |
| 265 | r = localQuad.w4f(); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 266 | } |
| 267 | } |
| 268 | |
| 269 | Sk4f a, b, c; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 270 | if (spec.usesCoverageAA()) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 271 | // Must calculate edges and possibly outside the positions |
| 272 | if (aaFlags == GrQuadAAFlags::kNone) { |
| 273 | // A non-AA quad that got batched into an AA group, so its edges will be the same for |
| 274 | // all four vertices and it does not need to be outset |
| 275 | SkPoint3 edgeCoeffs; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 276 | if (deviceHasPerspective) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 277 | edgeCoeffs = compute_non_aa_persp_edge_coeffs(w); |
| 278 | } else { |
| 279 | edgeCoeffs = kNonAANoPerspEdgeCoeffs; |
| 280 | } |
| 281 | |
| 282 | // Copy the coefficients into all four equations |
| 283 | a = edgeCoeffs.fX; |
| 284 | b = edgeCoeffs.fY; |
| 285 | c = edgeCoeffs.fZ; |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 286 | } else if (deviceHasPerspective) { |
| 287 | // 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] | 288 | // ensures that only loaded Sk4fs are modified in the compute functions. |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 289 | compute_quad_edges_and_outset_persp_vertices(aaFlags, &x, &y, &w, &a, &b, &c, |
| 290 | &u, &v, &r, spec.localDimensionality()); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 291 | } else { |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 292 | compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, &u, &v, &r, |
| 293 | spec.localDimensionality(), /* outset */ true); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 294 | } |
| 295 | } |
| 296 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 297 | // Now rearrange the Sk4fs into the interleaved vertex layout: |
| 298 | // i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y |
| 299 | GrVertexWriter vb{vertices}; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 300 | for (int i = 0; i < 4; ++i) { |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 301 | // save position |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 302 | if (deviceHasPerspective) { |
| 303 | vb.write<SkPoint3>({x[i], y[i], w[i]}); |
| 304 | } else { |
| 305 | vb.write<SkPoint>({x[i], y[i]}); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 306 | } |
| 307 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 308 | // save color |
| 309 | if (spec.hasVertexColors()) { |
| 310 | vb.write<GrColor>(color); |
| 311 | } |
| 312 | |
| 313 | // save local position |
| 314 | if (spec.hasLocalCoords()) { |
| 315 | if (localHasPerspective) { |
| 316 | vb.write<SkPoint3>({u[i], v[i], r[i]}); |
| 317 | } else { |
| 318 | vb.write<SkPoint>({u[i], v[i]}); |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 319 | } |
| 320 | } |
| 321 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 322 | // save the domain |
| 323 | if (spec.hasDomain()) { |
| 324 | vb.write<SkRect>(domain); |
| 325 | } |
| 326 | |
| 327 | // save the edges |
| 328 | if (spec.usesCoverageAA()) { |
| 329 | for (int j = 0; j < 4; j++) { |
| 330 | vb.write<SkPoint3>({a[j], b[j], c[j]}); |
| 331 | } |
| 332 | } |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 333 | } |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 334 | |
| 335 | return vb.fPtr; |
Michael Ludwig | 460eb5e | 2018-10-29 11:09:29 -0400 | [diff] [blame] | 336 | } |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 337 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 338 | ////////////////// VertexSpec Implementation |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 339 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 340 | int VertexSpec::deviceDimensionality() const { |
| 341 | return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2; |
| 342 | } |
| 343 | |
| 344 | int VertexSpec::localDimensionality() const { |
| 345 | return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0; |
| 346 | } |
| 347 | |
| 348 | ////////////////// GPAttributes Implementation |
| 349 | |
| 350 | GPAttributes::GPAttributes(const VertexSpec& spec) { |
| 351 | if (spec.deviceDimensionality() == 3) { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 352 | fPositions = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 353 | } else { |
| 354 | fPositions = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; |
| 355 | } |
| 356 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 357 | int localDim = spec.localDimensionality(); |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 358 | if (localDim == 3) { |
| 359 | fLocalCoords = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 360 | } else if (localDim == 2) { |
| 361 | fLocalCoords = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; |
| 362 | } // else localDim == 0 and attribute remains uninitialized |
| 363 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 364 | if (spec.hasVertexColors()) { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 365 | fColors = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; |
| 366 | } |
| 367 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 368 | if (spec.hasDomain()) { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 369 | fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; |
| 370 | } |
| 371 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 372 | if (spec.usesCoverageAA()) { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 373 | fAAEdges[0] = {"aaEdge0", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 374 | fAAEdges[1] = {"aaEdge1", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 375 | fAAEdges[2] = {"aaEdge2", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 376 | fAAEdges[3] = {"aaEdge3", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 377 | } |
| 378 | } |
| 379 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 380 | bool GPAttributes::needsPerspectiveInterpolation() const { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 381 | // This only needs to check the position; local position having perspective does not change |
| 382 | // how the varyings are interpolated |
| 383 | return fPositions.cpuType() == kFloat3_GrVertexAttribType; |
| 384 | } |
| 385 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 386 | uint32_t GPAttributes::getKey() const { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 387 | // aa, color, domain are single bit flags |
| 388 | uint32_t x = this->usesCoverageAA() ? 0 : 1; |
| 389 | x |= this->hasVertexColors() ? 0 : 2; |
| 390 | x |= this->hasDomain() ? 0 : 4; |
| 391 | // regular position has two options as well |
| 392 | x |= kFloat3_GrVertexAttribType == fPositions.cpuType() ? 0 : 8; |
| 393 | // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d |
| 394 | if (this->hasLocalCoords()) { |
| 395 | x |= kFloat3_GrVertexAttribType == fLocalCoords.cpuType() ? 16 : 32; |
| 396 | } |
| 397 | return x; |
| 398 | } |
| 399 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 400 | void GPAttributes::emitColor(GrGLSLPrimitiveProcessor::EmitArgs& args, |
| 401 | GrGLSLColorSpaceXformHelper* csXformHelper, |
| 402 | const char* colorVarName) const { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 403 | using Interpolation = GrGLSLVaryingHandler::Interpolation; |
| 404 | |
| 405 | if (!fColors.isInitialized()) { |
| 406 | return; |
| 407 | } |
| 408 | |
| 409 | if (csXformHelper == nullptr || csXformHelper->isNoop()) { |
| 410 | args.fVaryingHandler->addPassThroughAttribute( |
| 411 | fColors, args.fOutputColor, Interpolation::kCanBeFlat); |
| 412 | } else { |
| 413 | GrGLSLVarying varying(kHalf4_GrSLType); |
| 414 | args.fVaryingHandler->addVarying(colorVarName, &varying); |
| 415 | args.fVertBuilder->codeAppendf("half4 %s = ", colorVarName); |
| 416 | args.fVertBuilder->appendColorGamutXform(fColors.name(), csXformHelper); |
| 417 | args.fVertBuilder->codeAppend(";"); |
| 418 | args.fVertBuilder->codeAppendf("%s = half4(%s.rgb * %s.a, %s.a);", |
| 419 | varying.vsOut(), colorVarName, colorVarName, colorVarName); |
| 420 | args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn()); |
| 421 | } |
| 422 | } |
| 423 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 424 | void GPAttributes::emitExplicitLocalCoords( |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 425 | GrGLSLPrimitiveProcessor::EmitArgs& args, const char* localCoordName, |
| 426 | const char* domainName) const { |
| 427 | using Interpolation = GrGLSLVaryingHandler::Interpolation; |
| 428 | if (!this->hasLocalCoords()) { |
| 429 | return; |
| 430 | } |
| 431 | |
| 432 | args.fFragBuilder->codeAppendf("float2 %s;", localCoordName); |
| 433 | bool localHasPerspective = fLocalCoords.cpuType() == kFloat3_GrVertexAttribType; |
| 434 | if (localHasPerspective) { |
| 435 | // Can't do a pass through since we need to perform perspective division |
| 436 | GrGLSLVarying v(fLocalCoords.gpuType()); |
| 437 | args.fVaryingHandler->addVarying(fLocalCoords.name(), &v); |
| 438 | args.fVertBuilder->codeAppendf("%s = %s;", v.vsOut(), fLocalCoords.name()); |
| 439 | args.fFragBuilder->codeAppendf("%s = %s.xy / %s.z;", localCoordName, v.fsIn(), v.fsIn()); |
| 440 | } else { |
| 441 | args.fVaryingHandler->addPassThroughAttribute(fLocalCoords, localCoordName); |
| 442 | } |
| 443 | |
| 444 | // Clamp the now 2D localCoordName variable by the domain if it is provided |
| 445 | if (this->hasDomain()) { |
| 446 | args.fFragBuilder->codeAppendf("float4 %s;", domainName); |
| 447 | args.fVaryingHandler->addPassThroughAttribute(fDomain, domainName, |
| 448 | Interpolation::kCanBeFlat); |
| 449 | args.fFragBuilder->codeAppendf("%s = clamp(%s, %s.xy, %s.zw);", |
| 450 | localCoordName, localCoordName, domainName, domainName); |
| 451 | } |
| 452 | } |
| 453 | |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 454 | void GPAttributes::emitCoverage(GrGLSLPrimitiveProcessor::EmitArgs& args, |
| 455 | const char* edgeDistName) const { |
Michael Ludwig | 20e909e | 2018-10-30 10:43:57 -0400 | [diff] [blame] | 456 | if (this->usesCoverageAA()) { |
| 457 | bool mulByFragCoordW = false; |
| 458 | GrGLSLVarying aaDistVarying(kFloat4_GrSLType, |
| 459 | GrGLSLVarying::Scope::kVertToFrag); |
| 460 | args.fVaryingHandler->addVarying(edgeDistName, &aaDistVarying); |
| 461 | |
| 462 | if (kFloat3_GrVertexAttribType == fPositions.cpuType()) { |
| 463 | // The distance from edge equation e to homogeneous point p=sk_Position |
| 464 | // is e.x*p.x/p.w + e.y*p.y/p.w + e.z. However, we want screen space |
| 465 | // interpolation of this distance. We can do this by multiplying the |
| 466 | // varying in the VS by p.w and then multiplying by sk_FragCoord.w in |
| 467 | // the FS. So we output e.x*p.x + e.y*p.y + e.z * p.w |
| 468 | args.fVertBuilder->codeAppendf( |
| 469 | R"(%s = float4(dot(%s, %s), dot(%s, %s), |
| 470 | dot(%s, %s), dot(%s, %s));)", |
| 471 | aaDistVarying.vsOut(), fAAEdges[0].name(), fPositions.name(), |
| 472 | fAAEdges[1].name(), fPositions.name(), fAAEdges[2].name(), fPositions.name(), |
| 473 | fAAEdges[3].name(), fPositions.name()); |
| 474 | mulByFragCoordW = true; |
| 475 | } else { |
| 476 | args.fVertBuilder->codeAppendf( |
| 477 | R"(%s = float4(dot(%s.xy, %s.xy) + %s.z, |
| 478 | dot(%s.xy, %s.xy) + %s.z, |
| 479 | dot(%s.xy, %s.xy) + %s.z, |
| 480 | dot(%s.xy, %s.xy) + %s.z);)", |
| 481 | aaDistVarying.vsOut(), |
| 482 | fAAEdges[0].name(), fPositions.name(), fAAEdges[0].name(), |
| 483 | fAAEdges[1].name(), fPositions.name(), fAAEdges[1].name(), |
| 484 | fAAEdges[2].name(), fPositions.name(), fAAEdges[2].name(), |
| 485 | fAAEdges[3].name(), fPositions.name(), fAAEdges[3].name()); |
| 486 | } |
| 487 | |
| 488 | args.fFragBuilder->codeAppendf( |
| 489 | "float min%s = min(min(%s.x, %s.y), min(%s.z, %s.w));", |
| 490 | edgeDistName, aaDistVarying.fsIn(), aaDistVarying.fsIn(), aaDistVarying.fsIn(), |
| 491 | aaDistVarying.fsIn()); |
| 492 | if (mulByFragCoordW) { |
| 493 | args.fFragBuilder->codeAppendf("min%s *= sk_FragCoord.w;", edgeDistName); |
| 494 | } |
| 495 | args.fFragBuilder->codeAppendf("%s = float4(saturate(min%s));", |
| 496 | args.fOutputCoverage, edgeDistName); |
| 497 | } else { |
| 498 | // Set coverage to 1 |
| 499 | args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage); |
| 500 | } |
| 501 | } |
Michael Ludwig | c182b94 | 2018-11-16 10:27:51 -0500 | [diff] [blame] | 502 | |
| 503 | } // namespace GrQuadPerEdgeAA |