| /* |
| * Copyright 2018 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "GrQuadPerEdgeAA.h" |
| #include "GrQuad.h" |
| #include "GrVertexWriter.h" |
| #include "glsl/GrGLSLColorSpaceXformHelper.h" |
| #include "glsl/GrGLSLPrimitiveProcessor.h" |
| #include "glsl/GrGLSLFragmentShaderBuilder.h" |
| #include "glsl/GrGLSLVarying.h" |
| #include "glsl/GrGLSLVertexGeoBuilder.h" |
| #include "SkNx.h" |
| |
| namespace { |
| |
| // This computes the four edge equations for a quad, then outsets them and optionally computes a new |
| // quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as |
| // input and the outset points as output. 'a', 'b', and 'c' are the edge equation coefficients on |
| // output. The values in x, y, u, v, and r are possibly updated if outsetting is needed. |
| // r is the local position's w component if it exists. |
| static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, Sk4f* a, |
| Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, |
| int uvrChannelCount, bool outsetCorners) { |
| SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| |
| static constexpr auto fma = SkNx_fma<4, float>; |
| // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip |
| // order. |
| auto nextCW = [](const Sk4f& v) { return SkNx_shuffle<2, 0, 3, 1>(v); }; |
| auto nextCCW = [](const Sk4f& v) { return SkNx_shuffle<1, 3, 0, 2>(v); }; |
| |
| // Compute edge equations for the quad. |
| auto xnext = nextCCW(*x); |
| auto ynext = nextCCW(*y); |
| // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. |
| auto xdiff = xnext - *x; |
| auto ydiff = ynext - *y; |
| auto invLengths = fma(xdiff, xdiff, ydiff * ydiff).rsqrt(); |
| xdiff *= invLengths; |
| ydiff *= invLengths; |
| |
| // Use above vectors to compute edge equations. |
| *c = fma(xnext, *y, -ynext * *x) * invLengths; |
| // Make sure the edge equations have their normals facing into the quad in device space. |
| auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c)); |
| if ((test < Sk4f(0)).anyTrue()) { |
| *a = -ydiff; |
| *b = xdiff; |
| *c = -*c; |
| } else { |
| *a = ydiff; |
| *b = -xdiff; |
| } |
| // Outset the edge equations so aa coverage evaluates to zero half a pixel away from the |
| // original quad edge. |
| *c += 0.5f; |
| |
| if (aaFlags != GrQuadAAFlags::kAll) { |
| // This order is the same order the edges appear in xdiff/ydiff and therefore as the |
| // edges in a/b/c. |
| auto mask = Sk4f(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); |
| // Outset edge equations for masked out edges another pixel so that they always evaluate |
| // >= 1. |
| *c += (1.f - mask); |
| if (outsetCorners) { |
| // Do the vertex outset. |
| mask *= 0.5f; |
| auto maskCW = nextCW(mask); |
| *x += maskCW * -xdiff + mask * nextCW(xdiff); |
| *y += maskCW * -ydiff + mask * nextCW(ydiff); |
| if (uvrChannelCount > 0) { |
| // We want to extend the texture coords by the same proportion as the positions. |
| maskCW *= invLengths; |
| mask *= nextCW(invLengths); |
| Sk4f udiff = nextCCW(*u) - *u; |
| Sk4f vdiff = nextCCW(*v) - *v; |
| *u += maskCW * -udiff + mask * nextCW(udiff); |
| *v += maskCW * -vdiff + mask * nextCW(vdiff); |
| if (uvrChannelCount == 3) { |
| Sk4f rdiff = nextCCW(*r) - *r; |
| *r += maskCW * -rdiff + mask * nextCW(rdiff); |
| } |
| } |
| } |
| } else if (outsetCorners) { |
| *x += 0.5f * (-xdiff + nextCW(xdiff)); |
| *y += 0.5f * (-ydiff + nextCW(ydiff)); |
| if (uvrChannelCount > 0) { |
| Sk4f t = 0.5f * invLengths; |
| Sk4f udiff = nextCCW(*u) - *u; |
| Sk4f vdiff = nextCCW(*v) - *v; |
| *u += t * -udiff + nextCW(t) * nextCW(udiff); |
| *v += t * -vdiff + nextCW(t) * nextCW(vdiff); |
| if (uvrChannelCount == 3) { |
| Sk4f rdiff = nextCCW(*r) - *r; |
| *r += t * -rdiff + nextCW(t) * nextCW(rdiff); |
| } |
| } |
| } |
| } |
| |
| // Generalizes the above function to extrapolate local coords such that after perspective division |
| // of the device coordinate, the original local coordinate value is at the original un-outset |
| // device position. r is the local coordinate's w component. |
| static void compute_quad_edges_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, |
| Sk4f* w, Sk4f* a, Sk4f* b, Sk4f* c, |
| Sk4f* u, Sk4f* v, Sk4f* r, |
| int uvrChannelCount) { |
| SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); |
| |
| auto iw = (*w).invert(); |
| auto x2d = (*x) * iw; |
| auto y2d = (*y) * iw; |
| // Don't compute outset corners in the normalized space, which means u, v, and r don't need |
| // to be provided here (outset separately below). |
| compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, a, b, c, nullptr, nullptr, nullptr, |
| /* uvr ct */ 0, /* outsetCorners */ false); |
| |
| static const float kOutset = 0.5f; |
| if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) { |
| // For each entry in x the equivalent entry in opX is the left/right opposite and so on. |
| Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x); |
| Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w); |
| Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y); |
| // vx/vy holds the device space left-to-right vectors along top and bottom of the quad. |
| Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d); |
| Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d); |
| Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| // For each device space corner, devP, label its left/right opposite device space point |
| // opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is |
| // (length(devPt - opDevPt) + 0.5) / length(devPt - opDevPt); |
| Sk4f s = SkNx_shuffle<0, 1, 0, 1>((len + kOutset) / len); |
| // Compute t in homogeneous space from s using similar triangles so that we can produce |
| // homogeneous outset vertices for perspective-correct interpolation. |
| Sk4f sOpW = s * opW; |
| Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| // mask is used to make the t values be 1 when the left/right side is not antialiased. |
| Sk4f mask(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); |
| t = t * mask + (1.f - mask); |
| *x = opX + t * (*x - opX); |
| *y = opY + t * (*y - opY); |
| *w = opW + t * (*w - opW); |
| |
| if (uvrChannelCount > 0) { |
| Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u); |
| Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v); |
| *u = opU + t * (*u - opU); |
| *v = opV + t * (*v - opV); |
| if (uvrChannelCount == 3) { |
| Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r); |
| *r = opR + t * (*r - opR); |
| } |
| } |
| |
| if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| // Update the 2D points for the top/bottom calculation. |
| iw = (*w).invert(); |
| x2d = (*x) * iw; |
| y2d = (*y) * iw; |
| } |
| } |
| |
| if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) { |
| // This operates the same as above but for top/bottom rather than left/right. |
| Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x); |
| Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w); |
| Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y); |
| |
| Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d); |
| Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d); |
| Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt(); |
| |
| Sk4f s = SkNx_shuffle<0, 0, 1, 1>((len + kOutset) / len); |
| |
| Sk4f sOpW = s * opW; |
| Sk4f t = sOpW / (sOpW + (1.f - s) * (*w)); |
| |
| Sk4f mask(GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, |
| GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f); |
| t = t * mask + (1.f - mask); |
| *x = opX + t * (*x - opX); |
| *y = opY + t * (*y - opY); |
| *w = opW + t * (*w - opW); |
| |
| if (uvrChannelCount > 0) { |
| Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u); |
| Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v); |
| *u = opU + t * (*u - opU); |
| *v = opV + t * (*v - opV); |
| if (uvrChannelCount == 3) { |
| Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r); |
| *r = opR + t * (*r - opR); |
| } |
| } |
| } |
| } |
| |
| // Fast path for non-AA quads batched into an AA op. Since they are part of the AA op, the vertices |
| // need to have valid edge equations that ensure coverage is set to 1. To get perspective |
| // interpolation of the edge distance, the vertex shader outputs d*w and then multiplies by 1/w in |
| // the fragment shader. For non-AA edges, the edge equation can be simplified to 0*x/w + y/w + c >= |
| // 1, so the vertex shader outputs c*w. The quad is sent as two triangles, so a fragment is the |
| // interpolation between 3 of the 4 vertices. If iX are the weights for the 3 involved quad |
| // vertices, then the fragment shader's state is: |
| // f_cw = c * (iA*wA + iB*wB + iC*wC) and f_1/w = iA/wA + iB/wB + iC/wC |
| // (where A,B,C are chosen from {1,2,3, 4}) |
| // When there's no perspective, then f_cw*f_1/w = c and setting c = 1 guarantees a proper non-AA |
| // edge. Unfortunately when there is perspective, f_cw*f_1/w != c unless the fragment is at a |
| // vertex. We must pick a c such that f_cw*f_1/w >= 1 across the whole primitive. |
| // Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite |
| // f_1/w=(iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC) |
| // Since the iXs are weights for the interior of the primitive, then we have: |
| // n <= (iA*wA + iB*wB + iC*wC) <= m and |
| // n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and |
| // n^3 <= wA*wB*wC <= m^3 regardless of the choice of A,B, and C |
| // Thus if we set c = m^3/n^3, it guarantees f_cw*f_1/w >= 1 for any perspective. |
| static SkPoint3 compute_non_aa_persp_edge_coeffs(const Sk4f& w) { |
| float n = w.min(); |
| float m = w.max(); |
| return {0.f, 0.f, (m * m * m) / (n * n * n)}; |
| } |
| |
| // When there's guaranteed no perspective, the edge coefficients for non-AA quads is constant |
| static constexpr SkPoint3 kNonAANoPerspEdgeCoeffs = {0, 0, 1}; |
| |
| } // anonymous namespace |
| |
| namespace GrQuadPerEdgeAA { |
| |
| ////////////////// Tessellate Implementation |
| |
| void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad, |
| const GrColor& color, const GrPerspQuad& localQuad, const SkRect& domain, |
| GrQuadAAFlags aaFlags) { |
| bool deviceHasPerspective = spec.deviceQuadType() == GrQuadType::kPerspective; |
| bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective; |
| |
| // Load position data into Sk4fs (always x, y and maybe w) |
| Sk4f x = deviceQuad.x4f(); |
| Sk4f y = deviceQuad.y4f(); |
| Sk4f w; |
| if (deviceHasPerspective) { |
| w = deviceQuad.w4f(); |
| } |
| |
| // Load local position data into Sk4fs (either none, just u,v or all three) |
| Sk4f u, v, r; |
| if (spec.hasLocalCoords()) { |
| u = localQuad.x4f(); |
| v = localQuad.y4f(); |
| |
| if (localHasPerspective) { |
| r = localQuad.w4f(); |
| } |
| } |
| |
| Sk4f a, b, c; |
| if (spec.usesCoverageAA()) { |
| // Must calculate edges and possibly outside the positions |
| if (aaFlags == GrQuadAAFlags::kNone) { |
| // A non-AA quad that got batched into an AA group, so its edges will be the same for |
| // all four vertices and it does not need to be outset |
| SkPoint3 edgeCoeffs; |
| if (deviceHasPerspective) { |
| edgeCoeffs = compute_non_aa_persp_edge_coeffs(w); |
| } else { |
| edgeCoeffs = kNonAANoPerspEdgeCoeffs; |
| } |
| |
| // Copy the coefficients into all four equations |
| a = edgeCoeffs.fX; |
| b = edgeCoeffs.fY; |
| c = edgeCoeffs.fZ; |
| } else if (deviceHasPerspective) { |
| // For simplicity, pointers to u, v, and r are always provided, but the local dim param |
| // ensures that only loaded Sk4fs are modified in the compute functions. |
| compute_quad_edges_and_outset_persp_vertices(aaFlags, &x, &y, &w, &a, &b, &c, |
| &u, &v, &r, spec.localDimensionality()); |
| } else { |
| compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, &u, &v, &r, |
| spec.localDimensionality(), /* outset */ true); |
| } |
| } |
| |
| // Now rearrange the Sk4fs into the interleaved vertex layout: |
| // i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y |
| GrVertexWriter vb{vertices}; |
| for (int i = 0; i < 4; ++i) { |
| // save position |
| if (deviceHasPerspective) { |
| vb.write<SkPoint3>({x[i], y[i], w[i]}); |
| } else { |
| vb.write<SkPoint>({x[i], y[i]}); |
| } |
| |
| // save color |
| if (spec.hasVertexColors()) { |
| vb.write<GrColor>(color); |
| } |
| |
| // save local position |
| if (spec.hasLocalCoords()) { |
| if (localHasPerspective) { |
| vb.write<SkPoint3>({u[i], v[i], r[i]}); |
| } else { |
| vb.write<SkPoint>({u[i], v[i]}); |
| } |
| } |
| |
| // save the domain |
| if (spec.hasDomain()) { |
| vb.write<SkRect>(domain); |
| } |
| |
| // save the edges |
| if (spec.usesCoverageAA()) { |
| for (int j = 0; j < 4; j++) { |
| vb.write<SkPoint3>({a[j], b[j], c[j]}); |
| } |
| } |
| } |
| |
| return vb.fPtr; |
| } |
| |
| ////////////////// VertexSpec Implementation |
| |
| int VertexSpec::deviceDimensionality() const { |
| return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2; |
| } |
| |
| int VertexSpec::localDimensionality() const { |
| return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0; |
| } |
| |
| ////////////////// GPAttributes Implementation |
| |
| GPAttributes::GPAttributes(const VertexSpec& spec) { |
| if (spec.deviceDimensionality() == 3) { |
| fPositions = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| } else { |
| fPositions = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; |
| } |
| |
| int localDim = spec.localDimensionality(); |
| if (localDim == 3) { |
| fLocalCoords = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| } else if (localDim == 2) { |
| fLocalCoords = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; |
| } // else localDim == 0 and attribute remains uninitialized |
| |
| if (spec.hasVertexColors()) { |
| fColors = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType}; |
| } |
| |
| if (spec.hasDomain()) { |
| fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; |
| } |
| |
| if (spec.usesCoverageAA()) { |
| fAAEdges[0] = {"aaEdge0", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| fAAEdges[1] = {"aaEdge1", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| fAAEdges[2] = {"aaEdge2", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| fAAEdges[3] = {"aaEdge3", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| } |
| } |
| |
| bool GPAttributes::needsPerspectiveInterpolation() const { |
| // This only needs to check the position; local position having perspective does not change |
| // how the varyings are interpolated |
| return fPositions.cpuType() == kFloat3_GrVertexAttribType; |
| } |
| |
| uint32_t GPAttributes::getKey() const { |
| // aa, color, domain are single bit flags |
| uint32_t x = this->usesCoverageAA() ? 0 : 1; |
| x |= this->hasVertexColors() ? 0 : 2; |
| x |= this->hasDomain() ? 0 : 4; |
| // regular position has two options as well |
| x |= kFloat3_GrVertexAttribType == fPositions.cpuType() ? 0 : 8; |
| // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d |
| if (this->hasLocalCoords()) { |
| x |= kFloat3_GrVertexAttribType == fLocalCoords.cpuType() ? 16 : 32; |
| } |
| return x; |
| } |
| |
| void GPAttributes::emitColor(GrGLSLPrimitiveProcessor::EmitArgs& args, |
| GrGLSLColorSpaceXformHelper* csXformHelper, |
| const char* colorVarName) const { |
| using Interpolation = GrGLSLVaryingHandler::Interpolation; |
| |
| if (!fColors.isInitialized()) { |
| return; |
| } |
| |
| if (csXformHelper == nullptr || csXformHelper->isNoop()) { |
| args.fVaryingHandler->addPassThroughAttribute( |
| fColors, args.fOutputColor, Interpolation::kCanBeFlat); |
| } else { |
| GrGLSLVarying varying(kHalf4_GrSLType); |
| args.fVaryingHandler->addVarying(colorVarName, &varying); |
| args.fVertBuilder->codeAppendf("half4 %s = ", colorVarName); |
| args.fVertBuilder->appendColorGamutXform(fColors.name(), csXformHelper); |
| args.fVertBuilder->codeAppend(";"); |
| args.fVertBuilder->codeAppendf("%s = half4(%s.rgb * %s.a, %s.a);", |
| varying.vsOut(), colorVarName, colorVarName, colorVarName); |
| args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn()); |
| } |
| } |
| |
| void GPAttributes::emitExplicitLocalCoords( |
| GrGLSLPrimitiveProcessor::EmitArgs& args, const char* localCoordName, |
| const char* domainName) const { |
| using Interpolation = GrGLSLVaryingHandler::Interpolation; |
| if (!this->hasLocalCoords()) { |
| return; |
| } |
| |
| args.fFragBuilder->codeAppendf("float2 %s;", localCoordName); |
| bool localHasPerspective = fLocalCoords.cpuType() == kFloat3_GrVertexAttribType; |
| if (localHasPerspective) { |
| // Can't do a pass through since we need to perform perspective division |
| GrGLSLVarying v(fLocalCoords.gpuType()); |
| args.fVaryingHandler->addVarying(fLocalCoords.name(), &v); |
| args.fVertBuilder->codeAppendf("%s = %s;", v.vsOut(), fLocalCoords.name()); |
| args.fFragBuilder->codeAppendf("%s = %s.xy / %s.z;", localCoordName, v.fsIn(), v.fsIn()); |
| } else { |
| args.fVaryingHandler->addPassThroughAttribute(fLocalCoords, localCoordName); |
| } |
| |
| // Clamp the now 2D localCoordName variable by the domain if it is provided |
| if (this->hasDomain()) { |
| args.fFragBuilder->codeAppendf("float4 %s;", domainName); |
| args.fVaryingHandler->addPassThroughAttribute(fDomain, domainName, |
| Interpolation::kCanBeFlat); |
| args.fFragBuilder->codeAppendf("%s = clamp(%s, %s.xy, %s.zw);", |
| localCoordName, localCoordName, domainName, domainName); |
| } |
| } |
| |
| void GPAttributes::emitCoverage(GrGLSLPrimitiveProcessor::EmitArgs& args, |
| const char* edgeDistName) const { |
| if (this->usesCoverageAA()) { |
| bool mulByFragCoordW = false; |
| GrGLSLVarying aaDistVarying(kFloat4_GrSLType, |
| GrGLSLVarying::Scope::kVertToFrag); |
| args.fVaryingHandler->addVarying(edgeDistName, &aaDistVarying); |
| |
| if (kFloat3_GrVertexAttribType == fPositions.cpuType()) { |
| // The distance from edge equation e to homogeneous point p=sk_Position |
| // is e.x*p.x/p.w + e.y*p.y/p.w + e.z. However, we want screen space |
| // interpolation of this distance. We can do this by multiplying the |
| // varying in the VS by p.w and then multiplying by sk_FragCoord.w in |
| // the FS. So we output e.x*p.x + e.y*p.y + e.z * p.w |
| args.fVertBuilder->codeAppendf( |
| R"(%s = float4(dot(%s, %s), dot(%s, %s), |
| dot(%s, %s), dot(%s, %s));)", |
| aaDistVarying.vsOut(), fAAEdges[0].name(), fPositions.name(), |
| fAAEdges[1].name(), fPositions.name(), fAAEdges[2].name(), fPositions.name(), |
| fAAEdges[3].name(), fPositions.name()); |
| mulByFragCoordW = true; |
| } else { |
| args.fVertBuilder->codeAppendf( |
| R"(%s = float4(dot(%s.xy, %s.xy) + %s.z, |
| dot(%s.xy, %s.xy) + %s.z, |
| dot(%s.xy, %s.xy) + %s.z, |
| dot(%s.xy, %s.xy) + %s.z);)", |
| aaDistVarying.vsOut(), |
| fAAEdges[0].name(), fPositions.name(), fAAEdges[0].name(), |
| fAAEdges[1].name(), fPositions.name(), fAAEdges[1].name(), |
| fAAEdges[2].name(), fPositions.name(), fAAEdges[2].name(), |
| fAAEdges[3].name(), fPositions.name(), fAAEdges[3].name()); |
| } |
| |
| args.fFragBuilder->codeAppendf( |
| "float min%s = min(min(%s.x, %s.y), min(%s.z, %s.w));", |
| edgeDistName, aaDistVarying.fsIn(), aaDistVarying.fsIn(), aaDistVarying.fsIn(), |
| aaDistVarying.fsIn()); |
| if (mulByFragCoordW) { |
| args.fFragBuilder->codeAppendf("min%s *= sk_FragCoord.w;", edgeDistName); |
| } |
| args.fFragBuilder->codeAppendf("%s = float4(saturate(min%s));", |
| args.fOutputCoverage, edgeDistName); |
| } else { |
| // Set coverage to 1 |
| args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage); |
| } |
| } |
| |
| } // namespace GrQuadPerEdgeAA |