Share GP implementation between GrFillRectOp and GrTextureOp

Bug: skia:
Change-Id: I6c71dac4cf88f274a39f121f0b5315e6c5b914c8
Reviewed-on: https://skia-review.googlesource.com/c/173700
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index 4dfc834..337d476 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -73,69 +73,6 @@
     GrQuadAAFlags fAAFlags;
 };
 
-// A GeometryProcessor for rendering TransformedQuads using the vertex attributes from
-// GrQuadPerEdgeAA. This is similar to the TextureGeometryProcessor of GrTextureOp except that it
-// handles full GrPaints.
-class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
-public:
-
-    static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
-        return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
-    }
-
-    const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
-
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        // The attributes' key includes the device and local quad types implicitly since those
-        // types decide the vertex attribute size
-        b->add32(fAttrs.getKey());
-    }
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
-        class GLSLProcessor : public GrGLSLGeometryProcessor {
-        public:
-            void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                         FPCoordTransformIter&& transformIter) override {
-                const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
-                if (gp.fAttrs.hasLocalCoords()) {
-                    this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-                }
-            }
-
-        private:
-            void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
-                const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
-                args.fVaryingHandler->emitAttributes(gp);
-                gpArgs->fPositionVar = gp.fAttrs.emitPosition(args, "pos");
-
-                if (gp.fAttrs.hasLocalCoords()) {
-                    this->emitTransforms(args.fVertBuilder,
-                                         args.fVaryingHandler,
-                                         args.fUniformHandler,
-                                         gp.fAttrs.localCoords().asShaderVar(),
-                                         args.fFPCoordTransformHandler);
-                }
-
-                gp.fAttrs.emitColor(args, "paintColor");
-                gp.fAttrs.emitCoverage(args, "aaDist");
-            }
-        };
-        return new GLSLProcessor;
-    }
-
-private:
-    QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
-            : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
-            , fAttrs(spec) {
-        SkASSERT(spec.hasVertexColors());
-        this->setVertexAttributes(fAttrs.attributes(), fAttrs.attributeCount());
-    }
-
-    GrQuadPerEdgeAA::GPAttributes fAttrs;
-
-    typedef GrGeometryProcessor INHERITED;
-};
-
 class FillRectOp final : public GrMeshDrawOp {
 private:
     using Helper = GrSimpleMeshDrawOpHelperWithStencil;
@@ -273,7 +210,7 @@
                               this->localQuadType(), fHelper.usesLocalCoords(), Domain::kNo,
                               fHelper.aaType());
 
-        sk_sp<GrGeometryProcessor> gp = QuadPerEdgeAAGeometryProcessor::Make(vertexSpec);
+        sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeProcessor(vertexSpec);
         size_t vertexSize = gp->vertexStride();
 
         const GrBuffer* vbuffer;
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 3f69d4a..2849e29 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -9,6 +9,7 @@
 #include "GrQuad.h"
 #include "GrVertexWriter.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLPrimitiveProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLVarying.h"
@@ -460,135 +461,244 @@
     return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
 }
 
-////////////////// GPAttributes Implementation
+////////////////// Geometry Processor Implementation
 
-GPAttributes::GPAttributes(const VertexSpec& spec) {
-    fNeedsPerspective = spec.deviceDimensionality() == 3;
-    fPositions = {"position", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
+public:
 
-    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 (ColorType::kByte == spec.colorType()) {
-        fColors = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
-    } else if (ColorType::kHalf == spec.colorType()) {
-        fColors = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
+    static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
+        return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
     }
 
-    if (spec.hasDomain()) {
-        fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+    static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
+                                           GrTextureType textureType, GrPixelConfig textureConfig,
+                                           const GrSamplerState::Filter filter,
+                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
+        return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
+                vertexSpec, caps, textureType, textureConfig, filter,
+                std::move(textureColorSpaceXform)));
     }
 
-    if (spec.usesCoverageAA()) {
-        fAAEdgeDistances = {"aaEdgeDist", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-    }
-}
+    const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
 
-bool GPAttributes::needsPerspectiveInterpolation() const {
-    // This only needs to check the position; local position having perspective does not change
-    // how the varyings are interpolated
-    return fNeedsPerspective;
-}
-
-uint32_t GPAttributes::getKey() const {
-    // aa, domain are single bit flags
-    uint32_t x = this->usesCoverageAA() ? 0 : 1;
-    x |= this->hasDomain() ? 0 : 2;
-    // regular position has two options as well
-    x |= fNeedsPerspective ? 0 : 4;
-    // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
-    if (this->hasLocalCoords()) {
-        x |= kFloat3_GrVertexAttribType == fLocalCoords.cpuType() ? 8 : 16;
-    }
-    // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
-    if (this->hasVertexColors()) {
-        x |= kUByte4_norm_GrVertexAttribType == fColors.cpuType() ? 32 : 64;
-    }
-    return x;
-}
-
-GrShaderVar GPAttributes::emitPosition(GrGLSLPrimitiveProcessor::EmitArgs& args,
-                                       const char* posVarName) const {
-    if (fNeedsPerspective) {
-        args.fVertBuilder->codeAppendf("float3 %s = %s.xyz;", posVarName, fPositions.name());
-        return {posVarName, kFloat3_GrSLType, GrShaderVar::kNone_TypeModifier};
-    } else {
-        args.fVertBuilder->codeAppendf("float2 %s = %s.xy;", posVarName, fPositions.name());
-        return {posVarName, kFloat2_GrSLType, GrShaderVar::kNone_TypeModifier};
-    }
-}
-
-void GPAttributes::emitColor(GrGLSLPrimitiveProcessor::EmitArgs& args,
-                             const char* colorVarName) const {
-    using Interpolation = GrGLSLVaryingHandler::Interpolation;
-    if (fColors.isInitialized()) {
-        args.fVaryingHandler->addPassThroughAttribute(fColors, args.fOutputColor,
-                                                      Interpolation::kCanBeFlat);
-    }
-}
-
-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()) {
-        GrGLSLVarying maxCoverage(kFloat_GrSLType);
-        args.fVaryingHandler->addVarying("maxCoverage", &maxCoverage);
-        args.fVertBuilder->codeAppendf("%s = %s.w;", maxCoverage.vsOut(), fPositions.name());
-
-        args.fFragBuilder->codeAppendf("float4 %s;", edgeDistName);
-        args.fVaryingHandler->addPassThroughAttribute(fAAEdgeDistances, edgeDistName);
-
-        args.fFragBuilder->codeAppendf(
-                "float min%s = min(min(%s.x, %s.y), min(%s.z, %s.w));",
-                edgeDistName, edgeDistName, edgeDistName, edgeDistName, edgeDistName);
-        if (fNeedsPerspective) {
-            // 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 vertex attribute 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.fFragBuilder->codeAppendf("min%s *= sk_FragCoord.w;", edgeDistName);
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
+        // aa, domain, texturing are single bit flags
+        uint32_t x = fAAEdgeDistances.isInitialized() ? 0 : 1;
+        x |= fDomain.isInitialized() ? 0 : 2;
+        x |= fSampler.isInitialized() ? 0 : 4;
+        // regular position has two options as well
+        x |= fNeedsPerspective ? 0 : 8;
+        // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
+        if (fLocalCoord.isInitialized()) {
+            x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 16 : 32;
         }
-        // Clamp to max coverage after the perspective divide since perspective quads calculated
-        // the max coverage in projected space.
-        args.fFragBuilder->codeAppendf("%s = float4(clamp(min%s, 0.0, %s));",
-                                       args.fOutputCoverage, edgeDistName, maxCoverage.fsIn());
-    } else {
-        // Set coverage to 1
-        args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
+        // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
+        if (this->fColor.isInitialized()) {
+            x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 64 : 128;
+        }
+
+        b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
+        b->add32(x);
     }
+
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
+        class GLSLProcessor : public GrGLSLGeometryProcessor {
+        public:
+            void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
+                         FPCoordTransformIter&& transformIter) override {
+                const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
+                if (gp.fLocalCoord.isInitialized()) {
+                    this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                }
+                fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
+            }
+
+        private:
+            void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+                using Interpolation = GrGLSLVaryingHandler::Interpolation;
+
+                const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
+                fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
+                                                       gp.fTextureColorSpaceXform.get());
+
+                args.fVaryingHandler->emitAttributes(gp);
+
+                // Extract effective position out of vec4 as a local variable in the vertex shader
+                if (gp.fNeedsPerspective) {
+                    args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
+                                                   gp.fPositionWithCoverage.name());
+                } else {
+                    args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
+                                                   gp.fPositionWithCoverage.name());
+                }
+                gpArgs->fPositionVar = {"position",
+                                        gp.fNeedsPerspective ? kFloat3_GrSLType : kFloat2_GrSLType,
+                                        GrShaderVar::kNone_TypeModifier};
+
+                // Handle local coordinates if they exist
+                if (gp.fLocalCoord.isInitialized()) {
+                    // NOTE: If the only usage of local coordinates is for the inline texture fetch
+                    // before FPs, then there are no registered FPCoordTransforms and this ends up
+                    // emitting nothing, so there isn't a duplication of local coordinates
+                    this->emitTransforms(args.fVertBuilder,
+                                         args.fVaryingHandler,
+                                         args.fUniformHandler,
+                                         gp.fLocalCoord.asShaderVar(),
+                                         args.fFPCoordTransformHandler);
+                }
+
+                // Solid color before any texturing gets modulated in
+                if (gp.fColor.isInitialized()) {
+                    args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
+                                                                  Interpolation::kCanBeFlat);
+                }
+
+                // If there is a texture, must also handle texture coordinates and reading from
+                // the texture in the fragment shader before continuing to fragment processors.
+                if (gp.fSampler.isInitialized()) {
+                    // Texture coordinates clamped by the domain on the fragment shader; if the GP
+                    // has a texture, it's guaranteed to have local coordinates
+                    args.fFragBuilder->codeAppend("float2 texCoord;");
+                    if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
+                        // Can't do a pass through since we need to perform perspective division
+                        GrGLSLVarying v(gp.fLocalCoord.gpuType());
+                        args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
+                        args.fVertBuilder->codeAppendf("%s = %s;",
+                                                       v.vsOut(), gp.fLocalCoord.name());
+                        args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
+                                                       v.fsIn(), v.fsIn());
+                    } else {
+                        args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
+                    }
+
+                    // Clamp the now 2D localCoordName variable by the domain if it is provided
+                    if (gp.fDomain.isInitialized()) {
+                        args.fFragBuilder->codeAppend("float4 domain;");
+                        args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
+                                                                      Interpolation::kCanBeFlat);
+                        args.fFragBuilder->codeAppend(
+                                "texCoord = clamp(texCoord, domain.xy, domain.zw);");
+                    }
+
+                    // Now modulate the starting output color by the texture lookup
+                    args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
+                    args.fFragBuilder->appendTextureLookupAndModulate(
+                        args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
+                        &fTextureColorSpaceXformHelper);
+                    args.fFragBuilder->codeAppend(";");
+                }
+
+                // And lastly, output the coverage calculation code
+                if (gp.fAAEdgeDistances.isInitialized()) {
+                    GrGLSLVarying maxCoverage(kFloat_GrSLType);
+                    args.fVaryingHandler->addVarying("maxCoverage", &maxCoverage);
+                    args.fVertBuilder->codeAppendf("%s = %s.w;",
+                                                   maxCoverage.vsOut(), gp.fPositionWithCoverage.name());
+
+                    args.fFragBuilder->codeAppend("float4 edgeDists;");
+                    args.fVaryingHandler->addPassThroughAttribute(gp.fAAEdgeDistances, "edgeDists");
+
+                    args.fFragBuilder->codeAppend(
+                            "float minDist = min(min(edgeDists.x, edgeDists.y),"
+                            " min(edgeDists.z, edgeDists.w));");
+                    if (gp.fNeedsPerspective) {
+                        // 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 vertex
+                        // attribute 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.fFragBuilder->codeAppend("minDist *= sk_FragCoord.w;");
+                    }
+                    // Clamp to max coverage after the perspective divide since perspective quads
+                    // calculated the max coverage in projected space.
+                    args.fFragBuilder->codeAppendf("%s = float4(clamp(minDist, 0.0, %s));",
+                                                   args.fOutputCoverage, maxCoverage.fsIn());
+                } else {
+                    // Set coverage to 1
+                    args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
+                }
+            }
+            GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
+        };
+        return new GLSLProcessor;
+    }
+
+private:
+    QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
+            : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
+            , fTextureColorSpaceXform(nullptr) {
+        SkASSERT(spec.hasVertexColors() && !spec.hasDomain());
+        this->initializeAttrs(spec);
+        this->setTextureSamplerCnt(0);
+    }
+
+    QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
+                                   GrTextureType textureType, GrPixelConfig textureConfig,
+                                   GrSamplerState::Filter filter,
+                                   sk_sp<GrColorSpaceXform> textureColorSpaceXform)
+            : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
+            , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
+            , fSampler(textureType, textureConfig, filter) {
+        SkASSERT(spec.hasVertexColors() && spec.hasLocalCoords());
+        this->initializeAttrs(spec);
+        this->setTextureSamplerCnt(1);
+    }
+
+    void initializeAttrs(const VertexSpec& spec) {
+        fNeedsPerspective = spec.deviceDimensionality() == 3;
+        fPositionWithCoverage = {"posAndCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+
+        int localDim = spec.localDimensionality();
+        if (localDim == 3) {
+            fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+        } else if (localDim == 2) {
+            fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
+        } // else localDim == 0 and attribute remains uninitialized
+
+        if (ColorType::kByte == spec.colorType()) {
+            fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+        } else if (ColorType::kHalf == spec.colorType()) {
+            fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
+        }
+
+        if (spec.hasDomain()) {
+            fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+        }
+
+        if (spec.usesCoverageAA()) {
+            fAAEdgeDistances = {"aaEdgeDist", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+        }
+        this->setVertexAttributes(&fPositionWithCoverage, 5);
+    }
+
+    const TextureSampler& onTextureSampler(int) const override { return fSampler; }
+
+    Attribute fPositionWithCoverage;
+    Attribute fColor;
+    Attribute fLocalCoord;
+    Attribute fDomain;
+    Attribute fAAEdgeDistances;
+
+    // The positions attribute is always a vec4 and can't be used to encode perspectiveness
+    bool fNeedsPerspective;
+
+    // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
+    // to skip texturing.
+    sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
+    TextureSampler fSampler;
+
+    typedef GrGeometryProcessor INHERITED;
+};
+
+sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
+    return QuadPerEdgeAAGeometryProcessor::Make(spec);
+}
+
+sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
+        GrTextureType textureType, GrPixelConfig textureConfig,
+        const GrSamplerState::Filter filter, sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
+    return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig, filter,
+                                                std::move(textureColorSpaceXform));
 }
 
 } // namespace GrQuadPerEdgeAA
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index c02be56..9d1b650 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -9,15 +9,15 @@
 #define GrQuadPerEdgeAA_DEFINED
 
 #include "GrColor.h"
-#include "GrPrimitiveProcessor.h"
+#include "GrGeometryProcessor.h"
 #include "GrQuad.h"
 #include "GrSamplerState.h"
 #include "GrTypesPriv.h"
-#include "glsl/GrGLSLPrimitiveProcessor.h"
 #include "SkPoint.h"
 #include "SkPoint3.h"
 
-class GrGLSLColorSpaceXformHelper;
+class GrColorSpaceXform;
+class GrShaderCaps;
 
 namespace GrQuadPerEdgeAA {
 
@@ -65,81 +65,11 @@
         unsigned fUsesCoverageAA: 1;
     };
 
-    // 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
-    // add these attributes at construction time.
-    class GPAttributes {
-    public:
-        using Attribute = GrPrimitiveProcessor::Attribute;
+    sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec);
 
-        GPAttributes(const VertexSpec& vertexSpec);
-
-        const Attribute& positionsAndMaxCoverage() const { return fPositions; }
-        const Attribute& colors() const { return fColors; }
-        const Attribute& localCoords() const { return fLocalCoords; }
-        const Attribute& domain() const { return fDomain; }
-        const Attribute& edgeDistances() const { return fAAEdgeDistances; }
-
-        bool hasVertexColors() const { return fColors.isInitialized(); }
-
-        bool usesCoverageAA() const { return fAAEdgeDistances.isInitialized(); }
-
-        bool hasLocalCoords() const { return fLocalCoords.isInitialized(); }
-
-        bool hasDomain() const { return fDomain.isInitialized(); }
-
-        bool needsPerspectiveInterpolation() const;
-
-        const Attribute* attributes() const { return &fPositions; }
-        int attributeCount() const {  return 5; }
-
-        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.
-
-        // Returns the GrShaderVar to store in GrGPArgs
-        GrShaderVar emitPosition(GrGLSLPrimitiveProcessor::EmitArgs& args,
-                                 const char* posVarName) const;
-
-        void emitColor(GrGLSLPrimitiveProcessor::EmitArgs& args, 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 fAAEdgeDistances; // named "aaEdgeDist" in SkSL
-
-        // The positions attribute is always a vec4 and can't be used to encode perspectiveness
-        bool fNeedsPerspective;
-    };
+    sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec,
+            const GrShaderCaps& caps, GrTextureType textureType, GrPixelConfig textureConfig,
+            const GrSamplerState::Filter filter, sk_sp<GrColorSpaceXform> textureColorSpaceXform);
 
     // Fill vertices with the vertex data needed to represent the given quad. The device position,
     // local coords, vertex color, domain, and edge coefficients will be written and/or computed
@@ -147,7 +77,6 @@
     // then its corresponding function argument is ignored.
     //
     // Returns the advanced pointer in vertices.
-    // TODO4F: Switch GrColor to GrVertexColor
     void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
                      const SkPMColor4f& color, const GrPerspQuad& localQuad, const SkRect& domain,
                      GrQuadAAFlags aa);
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 1a1cf1c..96174b2 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -30,8 +30,6 @@
 #include "SkPoint3.h"
 #include "SkRectPriv.h"
 #include "SkTo.h"
-#include "glsl/GrGLSLColorSpaceXformHelper.h"
-#include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVarying.h"
 
 namespace {
@@ -40,96 +38,6 @@
 using VertexSpec = GrQuadPerEdgeAA::VertexSpec;
 using ColorType = GrQuadPerEdgeAA::ColorType;
 
-/**
- * Geometry Processor that draws a texture modulated by a vertex color (though, this is meant to be
- * the same value across all vertices of a quad and uses flat interpolation when available). This is
- * used by TextureOp below.
- */
-class TextureGeometryProcessor : public GrGeometryProcessor {
-public:
-
-    static sk_sp<GrGeometryProcessor> Make(GrTextureType textureType, GrPixelConfig textureConfig,
-                                           const GrSamplerState::Filter filter,
-                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform,
-                                           const VertexSpec& vertexSpec, const GrShaderCaps& caps) {
-        return sk_sp<TextureGeometryProcessor>(new TextureGeometryProcessor(
-                textureType, textureConfig, filter, std::move(textureColorSpaceXform), vertexSpec,
-                caps));
-    }
-
-    const char* name() const override { return "TextureGeometryProcessor"; }
-
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
-        b->add32(fAttrs.getKey());
-    }
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
-        class GLSLProcessor : public GrGLSLGeometryProcessor {
-        public:
-            void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                         FPCoordTransformIter&& transformIter) override {
-                const auto& textureGP = proc.cast<TextureGeometryProcessor>();
-                this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-                fTextureColorSpaceXformHelper.setData(
-                        pdman, textureGP.fTextureColorSpaceXform.get());
-            }
-
-        private:
-            void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
-                const auto& textureGP = args.fGP.cast<TextureGeometryProcessor>();
-                fTextureColorSpaceXformHelper.emitCode(
-                        args.fUniformHandler, textureGP.fTextureColorSpaceXform.get());
-                if (!textureGP.fAttrs.needsPerspectiveInterpolation()) {
-                    args.fVaryingHandler->setNoPerspective();
-                }
-                args.fVaryingHandler->emitAttributes(textureGP);
-                gpArgs->fPositionVar = textureGP.fAttrs.emitPosition(args, "pos");
-
-                this->emitTransforms(args.fVertBuilder,
-                                     args.fVaryingHandler,
-                                     args.fUniformHandler,
-                                     textureGP.fAttrs.localCoords().asShaderVar(),
-                                     args.fFPCoordTransformHandler);
-                textureGP.fAttrs.emitColor(args, "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(";");
-
-                textureGP.fAttrs.emitCoverage(args, "aaDist");
-            }
-            GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
-        };
-        return new GLSLProcessor;
-    }
-
-private:
-    TextureGeometryProcessor(GrTextureType textureType, GrPixelConfig textureConfig,
-                             GrSamplerState::Filter filter,
-                             sk_sp<GrColorSpaceXform> textureColorSpaceXform,
-                             const VertexSpec& vertexSpec, const GrShaderCaps& caps)
-            : INHERITED(kTextureGeometryProcessor_ClassID)
-            , fAttrs(vertexSpec)
-            , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
-            , fSampler(textureType, textureConfig, filter) {
-        SkASSERT(vertexSpec.hasVertexColors() && vertexSpec.localDimensionality() == 2);
-        this->setTextureSamplerCnt(1);
-        this->setVertexAttributes(fAttrs.attributes(), fAttrs.attributeCount());
-    }
-
-    const TextureSampler& onTextureSampler(int) const override { return fSampler; }
-
-    GrQuadPerEdgeAA::GPAttributes fAttrs;
-    sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
-    TextureSampler fSampler;
-
-    typedef GrGeometryProcessor INHERITED;
-};
-
 static bool filter_has_effect_for_rect_stays_rect(const GrPerspQuad& quad, const SkRect& srcRect) {
     SkASSERT(quad.quadType() == GrQuadType::kRect);
     float ql = quad.x(0);
@@ -444,9 +352,9 @@
         VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte,
                               GrQuadType::kRect, /* hasLocal */ true, domain, aaType);
 
-        sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
-                textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
-                vertexSpec, *target->caps().shaderCaps());
+        sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeTexturedProcessor(
+                vertexSpec, *target->caps().shaderCaps(),
+                textureType, config, this->filter(), std::move(fTextureColorSpaceXform));
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
         args.fCaps = &target->caps();