Pull out sksl emission logic for per-edge aa from GrTextureOp

Bug: skia:
Change-Id: Iba068276e618d7d962b0b4ab059541f3b431539b
Reviewed-on: https://skia-review.googlesource.com/c/165528
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 0eca1e6..ecacd60 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -7,6 +7,11 @@
 
 #include "GrQuadPerEdgeAA.h"
 #include "GrQuad.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 {
@@ -367,3 +372,165 @@
         vb += vertexSize;
     }
 }
+
+GrQuadPerEdgeAA::GPAttributes::GPAttributes(int posDim, int localDim, bool hasColor, GrAAType aa,
+                                            Domain domain) {
+    SkASSERT(posDim == 2 || posDim == 3);
+    SkASSERT(localDim == 0 || localDim == 2 || localDim == 3);
+
+    if (posDim == 3) {
+        fPositions = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+    } else {
+        fPositions = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
+    }
+
+    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 (hasColor) {
+        fColors = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+    }
+
+    if (domain == Domain::kYes) {
+        fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+    }
+
+    if (aa == GrAAType::kCoverage) {
+        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 GrQuadPerEdgeAA::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;
+}
+
+int GrQuadPerEdgeAA::GPAttributes::vertexAttributeCount() const {
+    // Always has position, hence 1+
+    return (1 + this->hasLocalCoords() + this->hasVertexColors() + this->hasDomain() +
+            4 * this->usesCoverageAA());
+}
+
+uint32_t GrQuadPerEdgeAA::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 GrQuadPerEdgeAA::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 GrQuadPerEdgeAA::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 GrQuadPerEdgeAA::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);
+    }
+}
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index 643a757..9a70850 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -9,11 +9,14 @@
 #define GrQuadPerEdgeAA_DEFINED
 
 #include "GrColor.h"
+#include "GrPrimitiveProcessor.h"
 #include "GrSamplerState.h"
 #include "GrTypesPriv.h"
+#include "glsl/GrGLSLPrimitiveProcessor.h"
 #include "SkPoint.h"
 #include "SkPoint3.h"
 
+class GrGLSLColorSpaceXformHelper;
 class GrPerspQuad;
 
 class GrQuadPerEdgeAA {
@@ -57,6 +60,76 @@
         char fData[kVertexSize];
     };
 
+    // Utility class that manages the attribute state necessary to render a particular batch of
+    // quads. It is similar to a geometry processor but is meant to be included in a has-a
+    // relationship by specialized GP's that provide further functionality on top of the per-edge AA
+    // coverage.
+    //
+    // For performance reasons, this uses fixed names for the attribute variables; since it defines
+    // the majority of attributes a GP will likely need, this shouldn't be too limiting.
+    //
+    // In terms of responsibilities, the actual geometry processor must still call emitTransforms(),
+    // using the localCoords() attribute as the 4th argument; it must set the transform data helper
+    // to use the identity matrix; it must manage the color space transform for the quad's paint
+    // color; it should include getKey() in the geometry processor's key builder; and it should
+    // return these managed attributes from its onVertexAttribute() function.
+    class GPAttributes {
+    public:
+        using Attribute = GrPrimitiveProcessor::Attribute;
+
+        GPAttributes(int posDim, int localDim, bool hasColor, GrAAType aa, Domain domain);
+
+        const Attribute& positions() const { return fPositions; }
+        const Attribute& colors() const { return fColors; }
+        const Attribute& localCoords() const { return fLocalCoords; }
+        const Attribute& domain() const { return fDomain; }
+        const Attribute& edges(int i) const { return fAAEdges[i]; }
+
+        bool hasVertexColors() const { return fColors.isInitialized(); }
+
+        bool usesCoverageAA() const { return fAAEdges[0].isInitialized(); }
+
+        bool hasLocalCoords() const { return fLocalCoords.isInitialized(); }
+
+        bool hasDomain() const { return fDomain.isInitialized(); }
+
+        bool needsPerspectiveInterpolation() const;
+
+        int vertexAttributeCount() const;
+
+        uint32_t getKey() const;
+
+        // Functions to be called at appropriate times in a processor's onEmitCode() block. These
+        // are separated into discrete pieces so that they can be interleaved with the rest of the
+        // processor's shader code as needed. The functions take char* arguments for the names of
+        // variables the emitted code must declare, so that the calling GP can ensure there's no
+        // naming conflicts with their own code.
+
+        void emitColor(GrGLSLPrimitiveProcessor::EmitArgs& args,
+                       GrGLSLColorSpaceXformHelper* colorSpaceXformHelper,
+                       const char* colorVarName) const;
+
+        // localCoordName will be declared as a float2, with any domain applied after any
+        // perspective division is performed.
+        //
+        // Note: this should only be used if the local coordinates need to be passed separately
+        // from the standard coord transform process that is used by FPs.
+        // FIXME: This can go in two directions from here, if GrTextureOp stops needing per-quad
+        // domains it can be removed and GrTextureOp rewritten to use coord transforms. Or
+        // emitTransform() in the primitive builder can be updated to have a notion of domain for
+        // local coords, and all domain-needing code (blurs, filters, etc.) can switch to that magic
+        void emitExplicitLocalCoords(GrGLSLPrimitiveProcessor::EmitArgs& args,
+                                     const char* localCoordName, const char* domainVarName) const;
+
+        void emitCoverage(GrGLSLPrimitiveProcessor::EmitArgs& args, const char* edgeDistName) const;
+    private:
+        Attribute fPositions;   // named "position" in SkSL
+        Attribute fColors;      // named "color" in SkSL
+        Attribute fLocalCoords; // named "localCoord" in SkSL
+        Attribute fDomain;      // named "domain" in SkSL
+        Attribute fAAEdges[4];  // named "aaEdgeX" for X = 0,1,2,3
+    };
+
     // Tessellate the given quad specification into the vertices buffer. If the specific vertex
     // type does not use color, local positions, domain, etc. then the passed in values used for
     // that field will be ignored.
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index dbe7de8..0bf4d27 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -31,10 +31,8 @@
 #include "SkRectPriv.h"
 #include "SkTo.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVarying.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
 
 namespace {
 
@@ -52,11 +50,11 @@
                                            const GrSamplerState::Filter filter,
                                            sk_sp<GrColorSpaceXform> textureColorSpaceXform,
                                            sk_sp<GrColorSpaceXform> paintColorSpaceXform,
-                                           bool coverageAA, bool perspective,
-                                           Domain domain, const GrShaderCaps& caps) {
+                                           int positionDim, GrAAType aa,  Domain domain,
+                                           const GrShaderCaps& caps) {
         return sk_sp<TextureGeometryProcessor>(new TextureGeometryProcessor(
                 textureType, textureConfig, filter, std::move(textureColorSpaceXform),
-                std::move(paintColorSpaceXform), coverageAA, perspective, domain, caps));
+                std::move(paintColorSpaceXform), positionDim, aa, domain, caps));
     }
 
     const char* name() const override { return "TextureGeometryProcessor"; }
@@ -64,10 +62,7 @@
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
         b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
         b->add32(GrColorSpaceXform::XformKey(fPaintColorSpaceXform.get()));
-        uint32_t x = this->usesCoverageEdgeAA() ? 0 : 1;
-        x |= kFloat3_GrVertexAttribType == fPositions.cpuType() ? 0 : 2;
-        x |= fDomain.isInitialized() ? 4 : 0;
-        b->add32(x);
+        b->add32(fAttrs.getKey());
     }
 
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
@@ -84,94 +79,33 @@
 
         private:
             void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
-                using Interpolation = GrGLSLVaryingHandler::Interpolation;
                 const auto& textureGP = args.fGP.cast<TextureGeometryProcessor>();
                 fTextureColorSpaceXformHelper.emitCode(
                         args.fUniformHandler, textureGP.fTextureColorSpaceXform.get());
                 fPaintColorSpaceXformHelper.emitCode(
                         args.fUniformHandler, textureGP.fPaintColorSpaceXform.get(),
                         kVertex_GrShaderFlag);
-                if (kFloat2_GrVertexAttribType == textureGP.fPositions.cpuType()) {
+                if (!textureGP.fAttrs.needsPerspectiveInterpolation()) {
                     args.fVaryingHandler->setNoPerspective();
                 }
                 args.fVaryingHandler->emitAttributes(textureGP);
-                gpArgs->fPositionVar = textureGP.fPositions.asShaderVar();
+                gpArgs->fPositionVar = textureGP.fAttrs.positions().asShaderVar();
 
                 this->emitTransforms(args.fVertBuilder,
                                      args.fVaryingHandler,
                                      args.fUniformHandler,
-                                     textureGP.fTextureCoords.asShaderVar(),
+                                     textureGP.fAttrs.localCoords().asShaderVar(),
                                      args.fFPCoordTransformHandler);
-                if (fPaintColorSpaceXformHelper.isNoop()) {
-                    args.fVaryingHandler->addPassThroughAttribute(
-                            textureGP.fColors, args.fOutputColor, Interpolation::kCanBeFlat);
-                } else {
-                    GrGLSLVarying varying(kHalf4_GrSLType);
-                    args.fVaryingHandler->addVarying("color", &varying);
-                    args.fVertBuilder->codeAppend("half4 color = ");
-                    args.fVertBuilder->appendColorGamutXform(textureGP.fColors.name(),
-                                                             &fPaintColorSpaceXformHelper);
-                    args.fVertBuilder->codeAppend(";");
-                    args.fVertBuilder->codeAppendf("%s = half4(color.rgb * color.a, color.a);",
-                                                   varying.vsOut());
-                    args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn());
-                }
-                args.fFragBuilder->codeAppend("float2 texCoord;");
-                args.fVaryingHandler->addPassThroughAttribute(textureGP.fTextureCoords, "texCoord");
-                if (textureGP.fDomain.isInitialized()) {
-                    args.fFragBuilder->codeAppend("float4 domain;");
-                    args.fVaryingHandler->addPassThroughAttribute(
-                            textureGP.fDomain, "domain",
-                            GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
-                    args.fFragBuilder->codeAppend(
-                            "texCoord = clamp(texCoord, domain.xy, domain.zw);");
-                }
+                textureGP.fAttrs.emitColor(args, &fPaintColorSpaceXformHelper, "paintColor");
+                textureGP.fAttrs.emitExplicitLocalCoords(args, "texCoord", "domain");
+
                 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
                 args.fFragBuilder->appendTextureLookupAndModulate(
                         args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
                         &fTextureColorSpaceXformHelper);
                 args.fFragBuilder->codeAppend(";");
-                if (textureGP.usesCoverageEdgeAA()) {
-                    bool mulByFragCoordW = false;
-                    GrGLSLVarying aaDistVarying(kFloat4_GrSLType,
-                                                GrGLSLVarying::Scope::kVertToFrag);
-                    if (kFloat3_GrVertexAttribType == textureGP.fPositions.cpuType()) {
-                        args.fVaryingHandler->addVarying("aaDists", &aaDistVarying);
-                        // 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(aaEdge0, %s), dot(aaEdge1, %s),
-                                               dot(aaEdge2, %s), dot(aaEdge3, %s));)",
-                                aaDistVarying.vsOut(), textureGP.fPositions.name(),
-                                textureGP.fPositions.name(), textureGP.fPositions.name(),
-                                textureGP.fPositions.name());
-                        mulByFragCoordW = true;
-                    } else {
-                        args.fVaryingHandler->addVarying("aaDists", &aaDistVarying);
-                        args.fVertBuilder->codeAppendf(
-                                R"(%s = float4(dot(aaEdge0.xy, %s.xy) + aaEdge0.z,
-                                               dot(aaEdge1.xy, %s.xy) + aaEdge1.z,
-                                               dot(aaEdge2.xy, %s.xy) + aaEdge2.z,
-                                               dot(aaEdge3.xy, %s.xy) + aaEdge3.z);)",
-                                aaDistVarying.vsOut(), textureGP.fPositions.name(),
-                                textureGP.fPositions.name(), textureGP.fPositions.name(),
-                                textureGP.fPositions.name());
-                    }
-                    args.fFragBuilder->codeAppendf(
-                            "float mindist = min(min(%s.x, %s.y), min(%s.z, %s.w));",
-                            aaDistVarying.fsIn(), aaDistVarying.fsIn(), aaDistVarying.fsIn(),
-                            aaDistVarying.fsIn());
-                    if (mulByFragCoordW) {
-                        args.fFragBuilder->codeAppend("mindist *= sk_FragCoord.w;");
-                    }
-                    args.fFragBuilder->codeAppendf("%s = float4(saturate(mindist));",
-                                                   args.fOutputCoverage);
-                } else {
-                    args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
-                }
+
+                textureGP.fAttrs.emitCoverage(args, "aaDist");
             }
             GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
             GrGLSLColorSpaceXformHelper fPaintColorSpaceXformHelper;
@@ -179,55 +113,30 @@
         return new GLSLProcessor;
     }
 
-    bool usesCoverageEdgeAA() const { return SkToBool(fAAEdges[0].isInitialized()); }
-
 private:
     TextureGeometryProcessor(GrTextureType textureType, GrPixelConfig textureConfig,
                              GrSamplerState::Filter filter,
                              sk_sp<GrColorSpaceXform> textureColorSpaceXform,
-                             sk_sp<GrColorSpaceXform> paintColorSpaceXform, bool coverageAA,
-                             bool perspective, Domain domain, const GrShaderCaps& caps)
+                             sk_sp<GrColorSpaceXform> paintColorSpaceXform, int positionDim,
+                             GrAAType aa, Domain domain, const GrShaderCaps& caps)
             : INHERITED(kTextureGeometryProcessor_ClassID)
+            , fAttrs(positionDim, /* src dim */ 2, /* vertex color */ true, aa, domain)
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fPaintColorSpaceXform(std::move(paintColorSpaceXform))
             , fSampler(textureType, textureConfig, filter) {
         this->setTextureSamplerCnt(1);
-
-        if (perspective) {
-            fPositions = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
-        } else {
-            fPositions = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        }
-        fColors = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
-        fTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        int vertexAttributeCnt = 3;
-
-        if (domain == Domain::kYes) {
-            fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-            ++vertexAttributeCnt;
-        }
-        if (coverageAA) {
-            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};
-            vertexAttributeCnt += 4;
-        }
-        this->setVertexAttributeCnt(vertexAttributeCnt);
+        this->setVertexAttributeCnt(fAttrs.vertexAttributeCount());
     }
 
     const Attribute& onVertexAttribute(int i) const override {
-        return IthInitializedAttribute(i, fPositions, fColors, fTextureCoords, fDomain, fAAEdges[0],
-                                       fAAEdges[1], fAAEdges[2], fAAEdges[3]);
+        return IthInitializedAttribute(i, fAttrs.positions(), fAttrs.colors(), fAttrs.localCoords(),
+                                       fAttrs.domain(), fAttrs.edges(0), fAttrs.edges(1),
+                                       fAttrs.edges(2), fAttrs.edges(3));
     }
 
     const TextureSampler& onTextureSampler(int) const override { return fSampler; }
 
-    Attribute fPositions;
-    Attribute fColors;
-    Attribute fTextureCoords;
-    Attribute fDomain;
-    Attribute fAAEdges[4];
+    GrQuadPerEdgeAA::GPAttributes fAttrs;
     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
     sk_sp<GrColorSpaceXform> fPaintColorSpaceXform;
     TextureSampler fSampler;
@@ -548,10 +457,9 @@
             }
         }
 
-        bool coverageAA = GrAAType::kCoverage == aaType;
         sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
                 textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
-                std::move(fPaintColorSpaceXform), coverageAA, hasPerspective, domain,
+                std::move(fPaintColorSpaceXform), hasPerspective ? 3 : 2, aaType, domain,
                 *target->caps().shaderCaps());
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
@@ -597,9 +505,9 @@
         };
 #undef TESS_FN_AND_VERTEX_SIZE
         int tessFnIdx = 0;
-        tessFnIdx |= coverageAA               ? 0x1 : 0x0;
-        tessFnIdx |= (domain == Domain::kYes) ? 0x2 : 0x0;
-        tessFnIdx |= hasPerspective           ? 0x4 : 0x0;
+        tessFnIdx |= (GrAAType::kCoverage == aaType) ? 0x1 : 0x0;
+        tessFnIdx |= (domain == Domain::kYes)        ? 0x2 : 0x0;
+        tessFnIdx |= hasPerspective                  ? 0x4 : 0x0;
 
         size_t vertexSize = kTessFnsAndVertexSizes[tessFnIdx].fVertexSize;
         SkASSERT(vertexSize == gp->debugOnly_vertexStride());