Revert "Change how vertex/instance attributes are handled in geometry processors."

This reverts commit 19c1233c447f625c2522e7ecd0a0adecc629bb2f.

Reason for revert: want to make sure Google3 can roll

Original change's description:
> Change how vertex/instance attributes are handled in geometry processors.
> 
> * No longer register vertex/instance attributes on base class, just counts
> 
> * Separate instance and vertex attributes and remove InputRate and offset
> 
> * Make attributes constexpr where possible
> 
> Change-Id: I1f1d5e772fa177a96d2aeb805aab7b69f35bfae6
> Reviewed-on: https://skia-review.googlesource.com/132405
> Commit-Queue: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Chris Dalton <csmartdalton@google.com>

TBR=egdaniel@google.com,bsalomon@google.com,csmartdalton@google.com

Change-Id: I4800632515e14fbf54af52826928ac915657b59f
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/135661
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index e2bf87b..defb214 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -45,13 +45,17 @@
 
     const char* name() const override { return "DefaultGeometryProcessor"; }
 
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inLocalCoords() const { return fInLocalCoords; }
+    const Attribute* inCoverage() const { return fInCoverage; }
     GrColor color() const { return fColor; }
-    bool hasVertexColor() const { return fInColor.isInitialized(); }
+    bool hasVertexColor() const { return SkToBool(fInColor); }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
     uint8_t coverage() const { return fCoverage; }
-    bool hasVertexCoverage() const { return fInCoverage.isInitialized(); }
+    bool hasVertexCoverage() const { return SkToBool(fInCoverage); }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
     public:
@@ -74,7 +78,7 @@
                 varyingHandler->addVarying("color", &varying);
 
                 // There are several optional steps to process the color. Start with the attribute:
-                vertBuilder->codeAppendf("half4 color = %s;", gp.fInColor.name());
+                vertBuilder->codeAppendf("half4 color = %s;", gp.inColor()->name());
 
                 // For SkColor, do a red/blue swap, possible color space conversion, and premul
                 if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
@@ -103,16 +107,16 @@
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      gp.fInPosition.name(),
+                                      gp.inPosition()->name(),
                                       gp.viewMatrix(),
                                       &fViewMatrixUniform);
 
-            if (gp.fInLocalCoords.isInitialized()) {
+            if (gp.inLocalCoords()) {
                 // emit transforms with explicit local coords
                 this->emitTransforms(vertBuilder,
                                      varyingHandler,
                                      uniformHandler,
-                                     gp.fInLocalCoords.asShaderVar(),
+                                     gp.inLocalCoords()->asShaderVar(),
                                      gp.localMatrix(),
                                      args.fFPCoordTransformHandler);
             } else {
@@ -120,7 +124,7 @@
                 this->emitTransforms(vertBuilder,
                                      varyingHandler,
                                      uniformHandler,
-                                     gp.fInPosition.asShaderVar(),
+                                     gp.inPosition()->asShaderVar(),
                                      gp.localMatrix(),
                                      args.fFPCoordTransformHandler);
             }
@@ -128,7 +132,7 @@
             // Setup coverage as pass through
             if (gp.hasVertexCoverage()) {
                 fragBuilder->codeAppendf("half alpha = 1.0;");
-                varyingHandler->addPassThroughAttribute(gp.fInCoverage, "alpha");
+                varyingHandler->addPassThroughAttribute(gp.inCoverage(), "alpha");
                 fragBuilder->codeAppendf("%s = half4(alpha);", args.fOutputCoverage);
             } else if (gp.coverage() == 0xff) {
                 fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
@@ -218,31 +222,22 @@
             , fFlags(gpTypeFlags)
             , fLocalCoordsWillBeRead(localCoordsWillBeRead)
             , fColorSpaceXform(std::move(colorSpaceXform)) {
-        fInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-        int cnt = 1;
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
         if (fFlags & kColorAttribute_GPFlag) {
-            fInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-            ++cnt;
+            fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
         }
         if (fFlags & kLocalCoordAttribute_GPFlag) {
-            fInLocalCoords = {"inLocalCoord", kFloat2_GrVertexAttribType};
-            ++cnt;
+            fInLocalCoords = &this->addVertexAttrib("inLocalCoord", kFloat2_GrVertexAttribType);
         }
         if (fFlags & kCoverageAttribute_GPFlag) {
-            fInCoverage = {"inCoverage", kHalf_GrVertexAttribType};
-            ++cnt;
+            fInCoverage = &this->addVertexAttrib("inCoverage", kHalf_GrVertexAttribType);
         }
-        this->setVertexAttributeCnt(cnt);
     }
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthInitializedAttribute(i, fInPosition, fInColor, fInLocalCoords, fInCoverage);
-    }
-
-    Attribute fInPosition;
-    Attribute fInColor;
-    Attribute fInLocalCoords;
-    Attribute fInCoverage;
+    const Attribute* fInPosition = nullptr;
+    const Attribute* fInColor = nullptr;
+    const Attribute* fInLocalCoords = nullptr;
+    const Attribute* fInCoverage = nullptr;
     GrColor fColor;
     SkMatrix fViewMatrix;
     SkMatrix fLocalMatrix;
diff --git a/src/gpu/GrGeometryProcessor.h b/src/gpu/GrGeometryProcessor.h
index 5035ad0..a12ab00 100644
--- a/src/gpu/GrGeometryProcessor.h
+++ b/src/gpu/GrGeometryProcessor.h
@@ -39,44 +39,7 @@
         fSampleShading = sampleShading;
     }
 
-    /**
-     * Recursive helpers for implementing onVertexAttribute or onInstanceAttribute.
-     */
-
-    template <typename... Args>
-    static const Attribute& IthAttribute(int i, const Attribute& attr0, const Args&... attrs) {
-        SkASSERT(attr0.isInitialized());
-        return (0 == i) ? attr0 : IthAttribute(i - 1, attrs...);
-    }
-
-    static const Attribute& IthAttribute(int i) {
-        SK_ABORT("Illegal attribute Index");
-        static constexpr Attribute kBogus;
-        return kBogus;
-    }
-
-    template <typename... Args>
-    static const Attribute& IthInitializedAttribute(int i, const Attribute& attr0,
-                                                    const Args&... attrs) {
-        if (attr0.isInitialized()) {
-            if (0 == i) {
-                return attr0;
-            }
-            i -= 1;
-        }
-        return IthInitializedAttribute(i, attrs...);
-    }
-
-    static const Attribute& IthInitializedAttribute(int i) { return IthAttribute(i); }
-
 private:
-    // Since most subclasses don't use instancing provide a default implementation for that case.
-    const Attribute& onInstanceAttribute(int i) const override {
-        SK_ABORT("No instanced attributes");
-        static constexpr Attribute kBogus;
-        return kBogus;
-    }
-
     bool fWillUseGeoShader;
     float fSampleShading;
 
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index fa2ce07..13ae6c9 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -33,12 +33,12 @@
                                 int meshCount,
                                 const SkRect& bounds) {
 #ifdef SK_DEBUG
-    SkASSERT(!primProc.hasInstanceAttributes() || this->gpu()->caps()->instanceAttribSupport());
+    SkASSERT(!primProc.hasInstanceAttribs() || this->gpu()->caps()->instanceAttribSupport());
     for (int i = 0; i < meshCount; ++i) {
         SkASSERT(!GrPrimTypeRequiresGeometryShaderSupport(meshes[i].primitiveType()) ||
                  this->gpu()->caps()->shaderCaps()->geometryShaderSupport());
-        SkASSERT(primProc.hasVertexAttributes() == meshes[i].hasVertexData());
-        SkASSERT(primProc.hasInstanceAttributes() == meshes[i].isInstanced());
+        SkASSERT(primProc.hasVertexAttribs() == meshes[i].hasVertexData());
+        SkASSERT(primProc.hasInstanceAttribs() == meshes[i].isInstanced());
     }
 #endif
     auto resourceProvider = this->gpu()->getContext()->contextPriv().resourceProvider();
@@ -47,7 +47,7 @@
         return false;
     }
 
-    if (primProc.numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
+    if (primProc.numAttribs() > this->gpu()->caps()->maxVertexAttributes()) {
         this->gpu()->stats()->incNumFailedDraws();
         return false;
     }
diff --git a/src/gpu/GrPathProcessor.h b/src/gpu/GrPathProcessor.h
index 6893e88..72e5168 100644
--- a/src/gpu/GrPathProcessor.h
+++ b/src/gpu/GrPathProcessor.h
@@ -38,18 +38,6 @@
     virtual bool isPathRendering() const override { return true; }
 
 private:
-    const Attribute& onVertexAttribute(int i) const final {
-        SK_ABORT("No vertex attributes");
-        static constexpr Attribute kBogus;
-        return kBogus;
-    }
-
-    const Attribute& onInstanceAttribute(int i) const final {
-        SK_ABORT("No instanced attributes");
-        static constexpr Attribute kBogus;
-        return kBogus;
-    }
-
     GrPathProcessor(GrColor, const SkMatrix& viewMatrix, const SkMatrix& localMatrix);
 
     GrColor fColor;
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index db79fb1..7d1e1a6 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -17,58 +17,6 @@
     kGeneral_MatrixType  = 1,
 };
 
-GrPrimitiveProcessor::GrPrimitiveProcessor(ClassID classID) : GrResourceIOProcessor(classID) {}
-
-const GrPrimitiveProcessor::Attribute& GrPrimitiveProcessor::vertexAttribute(int i) const {
-    SkASSERT(i >= 0 && i < this->numVertexAttributes());
-    const auto& result = this->onVertexAttribute(i);
-    SkASSERT(result.isInitialized());
-    return result;
-}
-
-const GrPrimitiveProcessor::Attribute& GrPrimitiveProcessor::instanceAttribute(int i) const {
-    SkASSERT(i >= 0 && i < this->numInstanceAttributes());
-    const auto& result = this->onInstanceAttribute(i);
-    SkASSERT(result.isInitialized());
-    return result;
-}
-
-#ifdef SK_DEBUG
-size_t GrPrimitiveProcessor::debugOnly_vertexStride() const {
-    size_t stride = 0;
-    for (int i = 0; i < fVertexAttributeCnt; ++i) {
-        stride += this->vertexAttribute(i).sizeAlign4();
-    }
-    return stride;
-}
-
-size_t GrPrimitiveProcessor::debugOnly_instanceStride() const {
-    size_t stride = 0;
-    for (int i = 0; i < fInstanceAttributeCnt; ++i) {
-        stride += this->instanceAttribute(i).sizeAlign4();
-    }
-    return stride;
-}
-
-size_t GrPrimitiveProcessor::debugOnly_vertexAttributeOffset(int i) const {
-    SkASSERT(i >= 0 && i < fVertexAttributeCnt);
-    size_t offset = 0;
-    for (int j = 0; j < i; ++j) {
-        offset += this->vertexAttribute(j).sizeAlign4();
-    }
-    return offset;
-}
-
-size_t GrPrimitiveProcessor::debugOnly_instanceAttributeOffset(int i) const {
-    SkASSERT(i >= 0 && i < fInstanceAttributeCnt);
-    size_t offset = 0;
-    for (int j = 0; j < i; ++j) {
-        offset += this->instanceAttribute(j).sizeAlign4();
-    }
-    return offset;
-}
-#endif
-
 uint32_t
 GrPrimitiveProcessor::getTransformKey(const SkTArray<const GrCoordTransform*, true>& coords,
                                       int numCoords) const {
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index bfc1e74..c75f13c 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -40,22 +40,23 @@
  */
 class GrPrimitiveProcessor : public GrResourceIOProcessor, public GrProgramElement {
 public:
-    /** Describes a vertex or instance attribute. */
     class Attribute {
     public:
+        enum class InputRate : bool {
+            kPerVertex,
+            kPerInstance
+        };
+
         constexpr Attribute() = default;
-        constexpr Attribute(const char* name, GrVertexAttribType type) : fName(name), fType(type) {}
-        constexpr Attribute(const Attribute&) = default;
+        constexpr Attribute(const char* name, GrVertexAttribType type, int offset, InputRate rate)
+                : fName(name), fType(type), fOffsetInRecord(offset), fInputRate(rate) {}
 
-        Attribute& operator=(const Attribute&) = default;
+        bool isInitialized() const { return SkToBool(fName); }
 
-        constexpr bool isInitialized() const { return SkToBool(fName); }
-
-        constexpr const char* name() const { return fName; }
-        constexpr GrVertexAttribType type() const { return fType; }
-
-        constexpr size_t size() const { return GrVertexAttribTypeSize(fType); }
-        constexpr size_t sizeAlign4() const { return SkAlign4(this->size()); }
+        const char* name() const { return fName; }
+        GrVertexAttribType type() const { return fType; }
+        int offsetInRecord() const { return fOffsetInRecord; }
+        InputRate inputRate() const { return fInputRate; }
 
         GrShaderVar asShaderVar() const {
             return {fName, GrVertexAttribTypeToSLType(fType), GrShaderVar::kIn_TypeModifier};
@@ -64,34 +65,35 @@
     private:
         const char* fName = nullptr;
         GrVertexAttribType fType = kFloat_GrVertexAttribType;
+        int fOffsetInRecord = 0;
+        InputRate fInputRate = InputRate::kPerVertex;
     };
 
-    GrPrimitiveProcessor(ClassID);
+    GrPrimitiveProcessor(ClassID classID)
+    : GrResourceIOProcessor(classID) {}
 
-    int numVertexAttributes() const { return fVertexAttributeCnt; }
-    const Attribute& vertexAttribute(int i) const;
-    int numInstanceAttributes() const { return fInstanceAttributeCnt; }
-    const Attribute& instanceAttribute(int i) const;
+    int numAttribs() const { return fAttribs.count(); }
+    const Attribute& getAttrib(int index) const { return fAttribs[index]; }
 
-    bool hasVertexAttributes() const { return SkToBool(fVertexAttributeCnt); }
-    bool hasInstanceAttributes() const { return SkToBool(fInstanceAttributeCnt); }
+    bool hasVertexAttribs() const { return SkToBool(fVertexStride); }
+    bool hasInstanceAttribs() const { return SkToBool(fInstanceStride); }
 
-#ifdef SK_DEBUG
     /**
-     * A common practice is to populate the the vertex/instance's memory using an implicit array of
-     * structs. In this case, it is best to assert that:
-     *     debugOnly_stride == sizeof(struct) and
-     *     offsetof(struct, field[i]) == debugOnly_AttributeOffset(i)
-     * In general having Op subclasses assert that attribute offsets and strides agree with their
-     * tessellation code's expectations is good practice.
-     * However, these functions walk the attributes to compute offsets and call virtual functions
-     * to access the attributes. Thus, they are only available in debug builds.
+     * These return the strides of the vertex and instance buffers. Attributes are expected to be
+     * laid out interleaved in their corresponding buffer (vertex or instance). fOffsetInRecord
+     * indicates an attribute's location in bytes relative to the first attribute. (These are padded
+     * to the nearest 4 bytes for performance reasons.)
+     *
+     * A common practice is to populate the buffer's memory using an implicit array of structs. In
+     * this case, it is best to assert:
+     *
+     *     stride == sizeof(struct) and
+     *     offsetof(struct, field[i]) == attrib[i].fOffsetInRecord
+     *
+     * NOTE: for instanced draws the vertex buffer has a single record that each instance reuses.
      */
-    size_t debugOnly_vertexStride() const;
-    size_t debugOnly_instanceStride() const;
-    size_t debugOnly_vertexAttributeOffset(int) const;
-    size_t debugOnly_instanceAttributeOffset(int) const;
-#endif
+    int getVertexStride() const { return fVertexStride; }
+    int getInstanceStride() const { return fInstanceStride; }
 
     // Only the GrGeometryProcessor subclass actually has a geo shader or vertex attributes, but
     // we put these calls on the base class to prevent having to cast
@@ -127,11 +129,24 @@
      */
     virtual const char* getDestColorOverride() const { return nullptr; }
 
-    virtual float getSampleShading() const { return 0.0; }
+    virtual float getSampleShading() const {
+        return 0.0;
+    }
 
 protected:
-    void setVertexAttributeCnt(int cnt) { fVertexAttributeCnt = cnt; }
-    void setInstanceAttributeCnt(int cnt) { fInstanceAttributeCnt = cnt; }
+    /**
+     * Subclasses call these from their constructor to register vertex and instance attributes.
+     */
+    const Attribute& addVertexAttrib(const char* name, GrVertexAttribType type) {
+        fAttribs.push_back() = {name, type, fVertexStride, Attribute::InputRate::kPerVertex};
+        fVertexStride += static_cast<int>(SkAlign4(GrVertexAttribTypeSize(type)));
+        return fAttribs.back();
+    }
+    const Attribute& addInstanceAttrib(const char* name, GrVertexAttribType type) {
+        fAttribs.push_back() = {name, type, fInstanceStride, Attribute::InputRate::kPerInstance};
+        fInstanceStride += static_cast<int>(SkAlign4(GrVertexAttribTypeSize(type)));
+        return fAttribs.back();
+    }
 
 private:
     void addPendingIOs() const override { GrResourceIOProcessor::addPendingIOs(); }
@@ -139,11 +154,10 @@
     void pendingIOComplete() const override { GrResourceIOProcessor::pendingIOComplete(); }
     void notifyRefCntIsZero() const final {}
 
-    virtual const Attribute& onVertexAttribute(int) const = 0;
-    virtual const Attribute& onInstanceAttribute(int) const = 0;
+    SkSTArray<8, Attribute> fAttribs;
+    int fVertexStride = 0;
+    int fInstanceStride = 0;
 
-    int fVertexAttributeCnt = 0;
-    int fInstanceAttributeCnt = 0;
     typedef GrProcessor INHERITED;
 };
 
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index 8989048..e3ea34f 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -246,13 +246,6 @@
     void initGS();
     void initVS(GrResourceProvider*);
 
-    const Attribute& onVertexAttribute(int i) const override { return fVertexAttribute; }
-
-    const Attribute& onInstanceAttribute(int i) const override {
-        SkASSERT(fImpl == Impl::kVertexShader);
-        return fInstanceAttributes[i];
-    }
-
     void appendGSMesh(GrBuffer* instanceBuffer, int instanceCount, int baseInstance,
                       SkTArray<GrMesh>* out) const;
     void appendVSMesh(GrBuffer* instanceBuffer, int instanceCount, int baseInstance,
@@ -260,8 +253,6 @@
 
     GrGLSLPrimitiveProcessor* createGSImpl(std::unique_ptr<Shader>) const;
     GrGLSLPrimitiveProcessor* createVSImpl(std::unique_ptr<Shader>) const;
-    // The type and meaning of this attribute depends on whether we're using VSImpl or GSImpl.
-    Attribute fVertexAttribute;
 
     const PrimitiveType fPrimitiveType;
     const Impl fImpl;
@@ -271,7 +262,6 @@
     const GSSubpass fGSSubpass = GSSubpass::kHulls;
 
     // Used by VSImpl.
-    Attribute fInstanceAttributes[2];
     sk_sp<const GrBuffer> fVSVertexBuffer;
     sk_sp<const GrBuffer> fVSIndexBuffer;
     int fVSNumIndicesPerInstance;
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
index 8134fbe..aa7a8db 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
@@ -31,8 +31,9 @@
         const GrCCCoverageProcessor& proc = args.fGP.cast<GrCCCoverageProcessor>();
 
         // The vertex shader simply forwards transposed x or y values to the geometry shader.
-        SkASSERT(1 == proc.numVertexAttributes());
-        gpArgs->fPositionVar = proc.fVertexAttribute.asShaderVar();
+        SkASSERT(1 == proc.numAttribs());
+        gpArgs->fPositionVar.set(GrVertexAttribTypeToSLType(proc.getAttrib(0).type()),
+                                 proc.getAttrib(0).name());
 
         // Geometry shader.
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
@@ -61,7 +62,7 @@
         Shader::CalcWind(proc, g, "pts", wind.c_str());
         if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) {
             SkASSERT(3 == numInputPoints);
-            SkASSERT(kFloat4_GrVertexAttribType == proc.fVertexAttribute.type());
+            SkASSERT(kFloat4_GrVertexAttribType == proc.getAttrib(0).type());
             g->codeAppendf("%s *= sk_in[0].sk_Position.w;", wind.c_str());
         }
 
@@ -381,19 +382,14 @@
 void GrCCCoverageProcessor::initGS() {
     SkASSERT(Impl::kGeometryShader == fImpl);
     if (4 == this->numInputPoints() || this->hasInputWeight()) {
-        fVertexAttribute = {"x_or_y_values", kFloat4_GrVertexAttribType};
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance) ==
-                         2 * GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fY) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
+        this->addVertexAttrib("x_or_y_values", kFloat4_GrVertexAttribType);
+        SkASSERT(sizeof(QuadPointInstance) == this->getVertexStride() * 2);
+        SkASSERT(offsetof(QuadPointInstance, fY) == this->getVertexStride());
     } else {
-        fVertexAttribute = {"x_or_y_values", kFloat3_GrVertexAttribType};
-        GR_STATIC_ASSERT(sizeof(TriPointInstance) ==
-                         2 * GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        GR_STATIC_ASSERT(offsetof(TriPointInstance, fY) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
+        this->addVertexAttrib("x_or_y_values", kFloat3_GrVertexAttribType);
+        SkASSERT(sizeof(TriPointInstance) == this->getVertexStride() * 2);
+        SkASSERT(offsetof(TriPointInstance, fY) == this->getVertexStride());
     }
-    this->setVertexAttributeCnt(1);
     this->setWillUseGeoShader();
 }
 
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
index 5c5120c..3c9fe89 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
@@ -28,8 +28,9 @@
     const int fNumSides;
 };
 
-static constexpr int kInstanceAttribIdx_X = 0;  // Transposed X values of all input points.
-static constexpr int kInstanceAttribIdx_Y = 1;  // Transposed Y values of all input points.
+static constexpr int kAttribIdx_X = 0; // Transposed X values of all input points.
+static constexpr int kAttribIdx_Y = 1; // Transposed Y values of all input points.
+static constexpr int kAttribIdx_VertexData = 2;
 
 // Vertex data tells the shader how to offset vertices for conservative raster, as well as how to
 // calculate coverage values for corners and edges.
@@ -259,16 +260,15 @@
     int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
     const char* swizzle = (4 == inputWidth) ? "xyzw" : "xyz";
     v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));", inputWidth, inputWidth,
-                   proc.fInstanceAttributes[kInstanceAttribIdx_X].name(), swizzle,
-                   proc.fInstanceAttributes[kInstanceAttribIdx_Y].name(), swizzle);
+                   proc.getAttrib(kAttribIdx_X).name(), swizzle,
+                   proc.getAttrib(kAttribIdx_Y).name(), swizzle);
 
     v->codeAppend ("half wind;");
     Shader::CalcWind(proc, v, "pts", "wind");
     if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) {
         SkASSERT(3 == numInputPoints);
-        SkASSERT(kFloat4_GrVertexAttribType ==
-                 proc.fInstanceAttributes[kInstanceAttribIdx_X].type());
-        v->codeAppendf("wind *= %s.w;", proc.fInstanceAttributes[kInstanceAttribIdx_X].name());
+        SkASSERT(kFloat4_GrVertexAttribType == proc.getAttrib(kAttribIdx_X).type());
+        v->codeAppendf("wind *= %s.w;", proc.getAttrib(kAttribIdx_X).name());
     }
 
     float bloat = kAABloatRadius;
@@ -284,12 +284,12 @@
 
     // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0].
     v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;",
-                   proc.fVertexAttribute.name(),
+                   proc.getAttrib(kAttribIdx_VertexData).name(),
                    ((fNumSides - 1) << kVertexData_LeftNeighborIdShift) |
                    ((fNumSides - 1) << kVertexData_RightNeighborIdShift) |
                    (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) |
                    (fNumSides - 1),
-                   proc.fVertexAttribute.name());
+                   proc.getAttrib(kAttribIdx_VertexData).name());
 
     // Here we generate conservative raster geometry for the input polygon. It is the convex
     // hull of N pixel-size boxes, one centered on each the input points. Each corner has three
@@ -322,7 +322,7 @@
     v->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);");
 
     v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
-                   proc.fVertexAttribute.name(), kVertexData_IsCornerBit);
+                   proc.getAttrib(kAttribIdx_VertexData).name(), kVertexData_IsCornerBit);
 
                        // In corner boxes, all 4 coverage values will not map linearly.
                        // Therefore it is important to align the box so its diagonal shared
@@ -341,7 +341,7 @@
     // continue rotating 90 degrees clockwise until we reach the desired raster vertex for this
     // invocation. Corners with less than 3 corresponding raster vertices will result in
     // redundant vertices and degenerate triangles.
-    v->codeAppendf("int bloatidx = (%s >> %i) & 3;", proc.fVertexAttribute.name(),
+    v->codeAppendf("int bloatidx = (%s >> %i) & 3;", proc.getAttrib(kAttribIdx_VertexData).name(),
                    kVertexData_BloatIdxShift);
     v->codeAppend ("switch (bloatidx) {");
     v->codeAppend (    "case 3:");
@@ -376,12 +376,12 @@
         v->codeAppend ("}");
 
         v->codeAppendf("if (0 != (%s & %i)) {",  // Are we an edge?
-                       proc.fVertexAttribute.name(), kVertexData_IsEdgeBit);
+                       proc.getAttrib(kAttribIdx_VertexData).name(), kVertexData_IsEdgeBit);
         v->codeAppend (    "coverage = left_coverage;");
         v->codeAppend ("}");
 
         v->codeAppendf("if (0 != (%s & %i)) {",  // Invert coverage?
-                       proc.fVertexAttribute.name(),
+                       proc.getAttrib(kAttribIdx_VertexData).name(),
                        kVertexData_InvertNegativeCoverageBit);
         v->codeAppend (    "coverage = -1 - coverage;");
         v->codeAppend ("}");
@@ -391,7 +391,7 @@
     v->codeAppend ("half2 corner_coverage = half2(0);");
 
     v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
-                   proc.fVertexAttribute.name(), kVertexData_IsCornerBit);
+                   proc.getAttrib(kAttribIdx_VertexData).name(), kVertexData_IsCornerBit);
                        // We use coverage=-1 to erase what the hull geometry wrote.
                        //
                        // In the context of curves, this effectively means "wind = -wind" and
@@ -495,27 +495,31 @@
         }
     }
 
-    GrVertexAttribType xyAttribType;
     if (4 == this->numInputPoints() || this->hasInputWeight()) {
-        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fX) == 0);
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fX) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fY) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        xyAttribType = kFloat4_GrVertexAttribType;
+        SkASSERT(kAttribIdx_X == this->numAttribs());
+        this->addInstanceAttrib("X", kFloat4_GrVertexAttribType);
+
+        SkASSERT(kAttribIdx_Y == this->numAttribs());
+        this->addInstanceAttrib("Y", kFloat4_GrVertexAttribType);
+
+        SkASSERT(offsetof(QuadPointInstance, fX) == this->getAttrib(kAttribIdx_X).offsetInRecord());
+        SkASSERT(offsetof(QuadPointInstance, fY) == this->getAttrib(kAttribIdx_Y).offsetInRecord());
+        SkASSERT(sizeof(QuadPointInstance) == this->getInstanceStride());
     } else {
-        GR_STATIC_ASSERT(offsetof(TriPointInstance, fX) == 0);
-        GR_STATIC_ASSERT(sizeof(TriPointInstance::fX) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        GR_STATIC_ASSERT(sizeof(TriPointInstance::fY) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        xyAttribType = kFloat3_GrVertexAttribType;
+        SkASSERT(kAttribIdx_X == this->numAttribs());
+        this->addInstanceAttrib("X", kFloat3_GrVertexAttribType);
+
+        SkASSERT(kAttribIdx_Y == this->numAttribs());
+        this->addInstanceAttrib("Y", kFloat3_GrVertexAttribType);
+
+        SkASSERT(offsetof(TriPointInstance, fX) == this->getAttrib(kAttribIdx_X).offsetInRecord());
+        SkASSERT(offsetof(TriPointInstance, fY) == this->getAttrib(kAttribIdx_Y).offsetInRecord());
+        SkASSERT(sizeof(TriPointInstance) == this->getInstanceStride());
     }
-    fInstanceAttributes[kInstanceAttribIdx_X] = {"X", xyAttribType};
-    fInstanceAttributes[kInstanceAttribIdx_Y] = {"Y", xyAttribType};
-    this->setInstanceAttributeCnt(2);
-    fVertexAttribute = {"vertexdata", kInt_GrVertexAttribType};
-    this->setVertexAttributeCnt(1);
+
+    SkASSERT(kAttribIdx_VertexData == this->numAttribs());
+    this->addVertexAttrib("vertexdata", kInt_GrVertexAttribType);
+    SkASSERT(sizeof(int32_t) == this->getVertexStride());
 
     if (caps.usePrimitiveRestart()) {
         fVSTriangleType = GrPrimitiveType::kTriangleStrip;
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index e180724..b8ebb44 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -65,9 +65,6 @@
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
 
-constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kInstanceAttribs[];
-constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kEdgeNormsAttrib;
-
 sk_sp<const GrBuffer> GrCCPathProcessor::FindIndexBuffer(GrOnFlushResourceProvider* onFlushRP) {
     GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
     if (onFlushRP->caps()->usePrimitiveRestart()) {
@@ -85,19 +82,24 @@
         : INHERITED(kGrCCPathProcessor_ClassID)
         , fAtlasAccess(std::move(atlas), GrSamplerState::Filter::kNearest,
                        GrSamplerState::WrapMode::kClamp, kFragment_GrShaderFlag) {
-    this->setInstanceAttributeCnt(kNumInstanceAttribs);
-    // Check that instance attributes exactly match Instance struct layout.
-    SkASSERT(!strcmp(this->instanceAttribute(0).name(), "devbounds"));
-    SkASSERT(!strcmp(this->instanceAttribute(1).name(), "devbounds45"));
-    SkASSERT(!strcmp(this->instanceAttribute(2).name(), "dev_to_atlas_offset"));
-    SkASSERT(!strcmp(this->instanceAttribute(3).name(), "color"));
-    SkASSERT(this->debugOnly_instanceAttributeOffset(0) == offsetof(Instance, fDevBounds));
-    SkASSERT(this->debugOnly_instanceAttributeOffset(1) == offsetof(Instance, fDevBounds45));
-    SkASSERT(this->debugOnly_instanceAttributeOffset(2) == offsetof(Instance, fDevToAtlasOffset));
-    SkASSERT(this->debugOnly_instanceAttributeOffset(3) == offsetof(Instance, fColor));
-    SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
+    this->addInstanceAttrib("devbounds", kFloat4_GrVertexAttribType);
+    this->addInstanceAttrib("devbounds45", kFloat4_GrVertexAttribType);
+    this->addInstanceAttrib("dev_to_atlas_offset", kInt2_GrVertexAttribType);
+    this->addInstanceAttrib("color", kUByte4_norm_GrVertexAttribType);
 
-    this->setVertexAttributeCnt(1);
+    SkASSERT(offsetof(Instance, fDevBounds) ==
+             this->getInstanceAttrib(InstanceAttribs::kDevBounds).offsetInRecord());
+    SkASSERT(offsetof(Instance, fDevBounds45) ==
+             this->getInstanceAttrib(InstanceAttribs::kDevBounds45).offsetInRecord());
+    SkASSERT(offsetof(Instance, fDevToAtlasOffset) ==
+             this->getInstanceAttrib(InstanceAttribs::kDevToAtlasOffset).offsetInRecord());
+    SkASSERT(offsetof(Instance, fColor) ==
+             this->getInstanceAttrib(InstanceAttribs::kColor).offsetInRecord());
+    SkASSERT(sizeof(Instance) == this->getInstanceStride());
+
+    GR_STATIC_ASSERT(4 == kNumInstanceAttribs);
+
+    this->addVertexAttrib("edge_norms", kFloat4_GrVertexAttribType);
 
     fAtlasAccess.instantiate(resourceProvider);
     this->addTextureSampler(&fAtlasAccess);
@@ -168,7 +170,7 @@
     GrGLSLVarying texcoord(kFloat3_GrSLType);
     GrGLSLVarying color(kHalf4_GrSLType);
     varyingHandler->addVarying("texcoord", &texcoord);
-    varyingHandler->addPassThroughAttribute(proc.getInstanceAttrib(InstanceAttribs::kColor),
+    varyingHandler->addPassThroughAttribute(&proc.getInstanceAttrib(InstanceAttribs::kColor),
                                             args.fOutputColor, Interpolation::kCanBeFlat);
 
     // The vertex shader bloats and intersects the devBounds and devBounds45 rectangles, in order to
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 54c1b7e..785dd2c 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -77,11 +77,16 @@
     const GrTexture* atlas() const { return fAtlasAccess.peekTexture(); }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     const Attribute& getInstanceAttrib(InstanceAttribs attribID) const {
-        int idx = static_cast<int>(attribID);
-        SkASSERT(idx >= 0 && idx < static_cast<int>(SK_ARRAY_COUNT(kInstanceAttribs)));
-        return kInstanceAttribs[idx];
+        const Attribute& attrib = this->getAttrib((int)attribID);
+        SkASSERT(Attribute::InputRate::kPerInstance == attrib.inputRate());
+        return attrib;
     }
-    const Attribute& getEdgeNormsAttrib() const { return kEdgeNormsAttrib; }
+    const Attribute& getEdgeNormsAttrib() const {
+        SkASSERT(1 + kNumInstanceAttribs == this->numAttribs());
+        const Attribute& attrib = this->getAttrib(kNumInstanceAttribs);
+        SkASSERT(Attribute::InputRate::kPerVertex == attrib.inputRate());
+        return attrib;
+    }
 
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
@@ -90,18 +95,8 @@
                    int baseInstance, int endInstance, const SkRect& bounds) const;
 
 private:
-    const Attribute& onVertexAttribute(int i) const override { return kEdgeNormsAttrib; }
-    const Attribute& onInstanceAttribute(int i) const override { return kInstanceAttribs[i]; }
-
     const TextureSampler fAtlasAccess;
     SkMatrix fLocalMatrix;
-    static constexpr Attribute kInstanceAttribs[kNumInstanceAttribs] = {
-            {"devbounds", kFloat4_GrVertexAttribType},
-            {"devbounds45", kFloat4_GrVertexAttribType},
-            {"dev_to_atlas_offset", kInt2_GrVertexAttribType},
-            {"color", kUByte4_norm_GrVertexAttribType}
-    };
-    static constexpr Attribute kEdgeNormsAttrib = {"edge_norms", kFloat4_GrVertexAttribType};
 
     typedef GrGeometryProcessor INHERITED;
 };
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index d68b1bf..3dee2f4 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -79,7 +79,7 @@
 
     GrGLSLVarying v(kFloat4_GrSLType);
     varyingHandler->addVarying("ConicCoeffs", &v);
-    vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inConicCoeffs().name());
+    vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inConicCoeffs()->name());
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
@@ -89,7 +89,7 @@
     this->writeOutputPosition(vertBuilder,
                               uniformHandler,
                               gpArgs,
-                              gp.inPosition().name(),
+                              gp.inPosition()->name(),
                               gp.viewMatrix(),
                               &fViewMatrixUniform);
 
@@ -97,7 +97,7 @@
     this->emitTransforms(vertBuilder,
                          varyingHandler,
                          uniformHandler,
-                         gp.inPosition().asShaderVar(),
+                         gp.inPosition()->asShaderVar(),
                          gp.localMatrix(),
                          args.fFPCoordTransformHandler);
 
@@ -219,8 +219,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-constexpr GrPrimitiveProcessor::Attribute GrConicEffect::kAttributes[];
-
 GrConicEffect::~GrConicEffect() {}
 
 void GrConicEffect::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -242,7 +240,8 @@
     , fUsesLocalCoords(usesLocalCoords)
     , fCoverageScale(coverage)
     , fEdgeType(edgeType) {
-    this->setVertexAttributeCnt(2);
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInConicCoeffs = &this->addVertexAttrib("inConicCoeffs", kHalf4_GrVertexAttribType);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -332,7 +331,7 @@
 
     GrGLSLVarying v(kHalf4_GrSLType);
     varyingHandler->addVarying("HairQuadEdge", &v);
-    vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inHairQuadEdge().name());
+    vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inHairQuadEdge()->name());
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
@@ -342,7 +341,7 @@
     this->writeOutputPosition(vertBuilder,
                               uniformHandler,
                               gpArgs,
-                              gp.inPosition().name(),
+                              gp.inPosition()->name(),
                               gp.viewMatrix(),
                               &fViewMatrixUniform);
 
@@ -350,7 +349,7 @@
     this->emitTransforms(vertBuilder,
                          varyingHandler,
                          uniformHandler,
-                         gp.inPosition().asShaderVar(),
+                         gp.inPosition()->asShaderVar(),
                          gp.localMatrix(),
                          args.fFPCoordTransformHandler);
 
@@ -421,8 +420,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-constexpr GrPrimitiveProcessor::Attribute GrQuadEffect::kAttributes[];
-
 GrQuadEffect::~GrQuadEffect() {}
 
 void GrQuadEffect::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -444,7 +441,8 @@
     , fUsesLocalCoords(usesLocalCoords)
     , fCoverageScale(coverage)
     , fEdgeType(edgeType) {
-    this->setVertexAttributeCnt(2);
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInHairQuadEdge = &this->addVertexAttrib("inHairQuadEdge", kHalf4_GrVertexAttribType);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -546,7 +544,7 @@
     this->writeOutputPosition(vertBuilder,
                               uniformHandler,
                               gpArgs,
-                              gp.inPosition().name(),
+                              gp.inPosition()->name(),
                               gp.viewMatrix(),
                               &fViewMatrixUniform);
 
@@ -578,7 +576,7 @@
     this->emitTransforms(vertBuilder,
                          varyingHandler,
                          uniformHandler,
-                         gp.inPosition().asShaderVar(),
+                         gp.inPosition()->asShaderVar(),
                          args.fFPCoordTransformHandler);
 
     GrShaderVar edgeAlpha("edgeAlpha", kFloat_GrSLType, 0);
@@ -649,8 +647,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-constexpr GrPrimitiveProcessor::Attribute GrCubicEffect::kInPosition;
-
 GrCubicEffect::~GrCubicEffect() {}
 
 void GrCubicEffect::getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const {
@@ -668,7 +664,7 @@
     , fViewMatrix(viewMatrix)
     , fDevKLMMatrix(devKLMMatrix)
     , fEdgeType(edgeType) {
-    this->setVertexAttributeCnt(1);
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/effects/GrBezierEffect.h b/src/gpu/effects/GrBezierEffect.h
index eebe42a..1744704 100644
--- a/src/gpu/effects/GrBezierEffect.h
+++ b/src/gpu/effects/GrBezierEffect.h
@@ -93,8 +93,8 @@
 
     const char* name() const override { return "Conic"; }
 
-    inline const Attribute& inPosition() const { return kAttributes[0]; }
-    inline const Attribute& inConicCoeffs() const { return kAttributes[1]; }
+    inline const Attribute* inPosition() const { return fInPosition; }
+    inline const Attribute* inConicCoeffs() const { return fInConicCoeffs; }
     inline bool isAntiAliased() const { return GrProcessorEdgeTypeIsAA(fEdgeType); }
     inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); }
     inline GrClipEdgeType getEdgeType() const { return fEdgeType; }
@@ -112,16 +112,14 @@
     GrConicEffect(GrColor, const SkMatrix& viewMatrix, uint8_t coverage, GrClipEdgeType,
                   const SkMatrix& localMatrix, bool usesLocalCoords);
 
-    const Attribute& onVertexAttribute(int i) const override { return kAttributes[i]; }
-
     GrColor             fColor;
     SkMatrix            fViewMatrix;
     SkMatrix            fLocalMatrix;
     bool                fUsesLocalCoords;
     uint8_t             fCoverageScale;
     GrClipEdgeType fEdgeType;
-    static constexpr Attribute kAttributes[] = {{"inPosition", kFloat2_GrVertexAttribType},
-                                                {"inConicCoeffs", kHalf4_GrVertexAttribType}};
+    const Attribute*    fInPosition;
+    const Attribute*    fInConicCoeffs;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -177,8 +175,8 @@
 
     const char* name() const override { return "Quad"; }
 
-    inline const Attribute& inPosition() const { return kAttributes[0]; }
-    inline const Attribute& inHairQuadEdge() const { return kAttributes[1]; }
+    inline const Attribute* inPosition() const { return fInPosition; }
+    inline const Attribute* inHairQuadEdge() const { return fInHairQuadEdge; }
     inline bool isAntiAliased() const { return GrProcessorEdgeTypeIsAA(fEdgeType); }
     inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); }
     inline GrClipEdgeType getEdgeType() const { return fEdgeType; }
@@ -196,17 +194,14 @@
     GrQuadEffect(GrColor, const SkMatrix& viewMatrix, uint8_t coverage, GrClipEdgeType,
                  const SkMatrix& localMatrix, bool usesLocalCoords);
 
-    const Attribute& onVertexAttribute(int i) const override { return kAttributes[i]; }
-
-    GrColor fColor;
-    SkMatrix fViewMatrix;
-    SkMatrix fLocalMatrix;
-    bool fUsesLocalCoords;
-    uint8_t fCoverageScale;
+    GrColor             fColor;
+    SkMatrix            fViewMatrix;
+    SkMatrix            fLocalMatrix;
+    bool                fUsesLocalCoords;
+    uint8_t             fCoverageScale;
     GrClipEdgeType fEdgeType;
-
-    static constexpr Attribute kAttributes[] = {{"inPosition", kFloat2_GrVertexAttribType},
-                                                {"inHairQuadEdge", kHalf4_GrVertexAttribType}};
+    const Attribute*    fInPosition;
+    const Attribute*    fInHairQuadEdge;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -268,7 +263,7 @@
 
     const char* name() const override { return "Cubic"; }
 
-    inline const Attribute& inPosition() const { return kInPosition; }
+    inline const Attribute* inPosition() const { return fInPosition; }
     inline bool isAntiAliased() const { return GrProcessorEdgeTypeIsAA(fEdgeType); }
     inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); }
     inline GrClipEdgeType getEdgeType() const { return fEdgeType; }
@@ -285,14 +280,11 @@
     GrCubicEffect(GrColor, const SkMatrix& viewMatrix, const SkMatrix& devKLMMatrix,
                   GrClipEdgeType);
 
-    const Attribute& onVertexAttribute(int) const override { return kInPosition; }
-
-    GrColor fColor;
-    SkMatrix fViewMatrix;
-    SkMatrix fDevKLMMatrix;
+    GrColor             fColor;
+    SkMatrix            fViewMatrix;
+    SkMatrix            fDevKLMMatrix;
     GrClipEdgeType fEdgeType;
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
+    const Attribute*    fInPosition;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 2594f40..4089555 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -40,7 +40,7 @@
         GrGLSLVarying uv(kFloat2_GrSLType);
         GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
         GrGLSLVarying texIdx(texIdxType);
-        append_index_uv_varyings(args, btgp.inTextureCoords().name(), atlasSizeInvName, &uv,
+        append_index_uv_varyings(args, btgp.inTextureCoords()->name(), atlasSizeInvName, &uv,
                                  &texIdx, nullptr);
 
         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
@@ -53,13 +53,13 @@
         }
 
         // Setup position
-        gpArgs->fPositionVar = btgp.inPosition().asShaderVar();
+        gpArgs->fPositionVar = btgp.inPosition()->asShaderVar();
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
-                             btgp.inPosition().asShaderVar(),
+                             btgp.inPosition()->asShaderVar(),
                              btgp.localMatrix(),
                              args.fFPCoordTransformHandler);
 
@@ -129,26 +129,23 @@
         , fColor(color)
         , fLocalMatrix(localMatrix)
         , fUsesW(usesW)
+        , fInColor(nullptr)
         , fMaskFormat(format) {
     SkASSERT(numActiveProxies <= kMaxTextures);
 
     if (usesW) {
-        fInPosition = {"inPosition", kFloat3_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat3_GrVertexAttribType);
     } else {
-        fInPosition = {"inPosition", kFloat2_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
     }
-    fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType};
-    int cnt = 2;
 
     bool hasVertexColor = kA8_GrMaskFormat == fMaskFormat ||
                           kA565_GrMaskFormat == fMaskFormat;
     if (hasVertexColor) {
-        fInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-        ++cnt;
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
     }
 
-    this->setVertexAttributeCnt(cnt);
-
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < numActiveProxies; ++i) {
         SkASSERT(proxies[i]);
 
@@ -157,10 +154,6 @@
     }
 }
 
-const GrPrimitiveProcessor::Attribute& GrBitmapTextGeoProc::onVertexAttribute(int i) const {
-    return IthInitializedAttribute(i, fInPosition, fInColor, fInTextureCoords);
-}
-
 void GrBitmapTextGeoProc::addNewProxies(const sk_sp<GrTextureProxy>* proxies,
                                         int numActiveProxies,
                                         const GrSamplerState& params) {
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.h b/src/gpu/effects/GrBitmapTextGeoProc.h
index 5e44497..40ba855 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.h
+++ b/src/gpu/effects/GrBitmapTextGeoProc.h
@@ -36,12 +36,12 @@
 
     const char* name() const override { return "Texture"; }
 
-    const Attribute& inPosition() const { return fInPosition; }
-    const Attribute& inColor() const { return fInColor; }
-    const Attribute& inTextureCoords() const { return fInTextureCoords; }
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inTextureCoords() const { return fInTextureCoords; }
     GrMaskFormat maskFormat() const { return fMaskFormat; }
     GrColor color() const { return fColor; }
-    bool hasVertexColor() const { return fInColor.isInitialized(); }
+    bool hasVertexColor() const { return SkToBool(fInColor); }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesW() const { return fUsesW; }
 
@@ -58,15 +58,13 @@
                         const GrSamplerState& params, GrMaskFormat format,
                         const SkMatrix& localMatrix, bool usesW);
 
-    const Attribute& onVertexAttribute(int i) const override;
-
     GrColor          fColor;
     SkMatrix         fLocalMatrix;
     bool             fUsesW;
     TextureSampler   fTextureSamplers[kMaxTextures];
-    Attribute        fInPosition;
-    Attribute        fInColor;
-    Attribute        fInTextureCoords;
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInTextureCoords;
     GrMaskFormat     fMaskFormat;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 4d5d50b..5ac7398 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -54,13 +54,13 @@
         varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
-        gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar();
+        gpArgs->fPositionVar = dfTexEffect.inPosition()->asShaderVar();
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
-                             dfTexEffect.inPosition().asShaderVar(),
+                             dfTexEffect.inPosition()->asShaderVar(),
                              dfTexEffect.localMatrix(),
                              args.fFPCoordTransformHandler);
 
@@ -69,7 +69,7 @@
         GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
         GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
-        append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
+        append_index_uv_varyings(args, dfTexEffect.inTextureCoords()->name(), atlasSizeInvName, &uv,
                                  &texIdx, &st);
 
         bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
@@ -206,34 +206,32 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldA8TextGeoProc::kInColor;
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldA8TextGeoProc::kInTextureCoords;
-
-GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const sk_sp<GrTextureProxy>* proxies,
-                                                           int numProxies,
-                                                           const GrSamplerState& params,
+GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(
+        const sk_sp<GrTextureProxy>* proxies,
+        int numProxies,
+        const GrSamplerState& params,
 #ifdef SK_GAMMA_APPLY_TO_A8
-                                                           float distanceAdjust,
+        float distanceAdjust,
 #endif
-                                                           uint32_t flags,
-                                                           const SkMatrix& localMatrix)
+        uint32_t flags,
+        const SkMatrix& localMatrix)
         : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
-        , fLocalMatrix(localMatrix)
-        , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
 #ifdef SK_GAMMA_APPLY_TO_A8
         , fDistanceAdjust(distanceAdjust)
 #endif
-{
+        , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
+        , fInColor(nullptr)
+        , fLocalMatrix(localMatrix) {
     SkASSERT(numProxies <= kMaxTextures);
+
     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
-
     if (flags & kPerspective_DistanceFieldEffectFlag) {
-        fInPosition = {"inPosition", kFloat3_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat3_GrVertexAttribType);
     } else {
-        fInPosition = {"inPosition", kFloat2_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
     }
-    this->setVertexAttributeCnt(3);
-
+    fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
 
@@ -339,8 +337,8 @@
         GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
         GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
-        append_index_uv_varyings(args, dfPathEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
-                                 &texIdx, &st);
+        append_index_uv_varyings(args, dfPathEffect.inTextureCoords()->name(), atlasSizeInvName,
+                                 &uv, &texIdx, &st);
 
         // setup pass through color
         varyingHandler->addPassThroughAttribute(dfPathEffect.inColor(), args.fOutputColor);
@@ -350,7 +348,7 @@
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      dfPathEffect.inPosition().name(),
+                                      dfPathEffect.inPosition()->name(),
                                       dfPathEffect.matrix(),
                                       &fMatrixUniform);
 
@@ -358,17 +356,17 @@
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 dfPathEffect.inPosition().asShaderVar(),
+                                 dfPathEffect.inPosition()->asShaderVar(),
                                  args.fFPCoordTransformHandler);
         } else {
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, dfPathEffect.inPosition().name());
+            this->writeOutputPosition(vertBuilder, gpArgs, dfPathEffect.inPosition()->name());
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 dfPathEffect.inPosition().asShaderVar(),
+                                 dfPathEffect.inPosition()->asShaderVar(),
                                  dfPathEffect.matrix(),
                                  args.fFPCoordTransformHandler);
         }
@@ -501,22 +499,22 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldPathGeoProc::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldPathGeoProc::kInColor;
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldPathGeoProc::kInTextureCoords;
-
-GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const SkMatrix& matrix,
-                                                       const sk_sp<GrTextureProxy>* proxies,
-                                                       int numProxies,
-                                                       const GrSamplerState& params,
-                                                       uint32_t flags)
+GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(
+                                                 const SkMatrix& matrix,
+                                                 const sk_sp<GrTextureProxy>* proxies,
+                                                 int numProxies,
+                                                 const GrSamplerState& params,
+                                                 uint32_t flags)
         : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
         , fMatrix(matrix)
-        , fFlags(flags & kNonLCD_DistanceFieldEffectMask) {
+        , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
+        , fInColor(nullptr) {
     SkASSERT(numProxies <= kMaxTextures);
-    SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
 
-    this->setVertexAttributeCnt(3);
+    SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
 
@@ -550,10 +548,6 @@
     return new GrGLDistanceFieldPathGeoProc();
 }
 
-const GrPrimitiveProcessor::Attribute& GrDistanceFieldPathGeoProc::onVertexAttribute(int i) const {
-    return IthAttribute(i, kInPosition, kInColor, kInTextureCoords);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc);
@@ -620,13 +614,13 @@
         varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
-        gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar();
+        gpArgs->fPositionVar = dfTexEffect.inPosition()->asShaderVar();
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
-                             dfTexEffect.inPosition().asShaderVar(),
+                             dfTexEffect.inPosition()->asShaderVar(),
                              dfTexEffect.localMatrix(),
                              args.fFPCoordTransformHandler);
 
@@ -635,7 +629,7 @@
         GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
         GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
-        append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
+        append_index_uv_varyings(args, dfTexEffect.inTextureCoords()->name(), atlasSizeInvName, &uv,
                                  &texIdx, &st);
 
         GrGLSLVarying delta(kFloat_GrSLType);
@@ -808,30 +802,27 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
-
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldLCDTextGeoProc::kInColor;
-constexpr GrPrimitiveProcessor::Attribute GrDistanceFieldLCDTextGeoProc::kInTextureCoords;
-
-GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const sk_sp<GrTextureProxy>* proxies,
-                                                             int numProxies,
-                                                             const GrSamplerState& params,
-                                                             DistanceAdjust distanceAdjust,
-                                                             uint32_t flags,
-                                                             const SkMatrix& localMatrix)
+GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(
+                                                 const sk_sp<GrTextureProxy>* proxies,
+                                                 int numProxies,
+                                                 const GrSamplerState& params,
+                                                 DistanceAdjust distanceAdjust,
+                                                 uint32_t flags,
+                                                 const SkMatrix& localMatrix)
         : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
-        , fLocalMatrix(localMatrix)
         , fDistanceAdjust(distanceAdjust)
-        , fFlags(flags & kLCD_DistanceFieldEffectMask) {
+        , fFlags(flags & kLCD_DistanceFieldEffectMask)
+        , fLocalMatrix(localMatrix) {
     SkASSERT(numProxies <= kMaxTextures);
+
     SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
-
     if (fFlags & kPerspective_DistanceFieldEffectFlag) {
-        fInPosition = {"inPosition", kFloat3_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat3_GrVertexAttribType);
     } else {
-        fInPosition = {"inPosition", kFloat2_GrVertexAttribType};
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
     }
-    this->setVertexAttributeCnt(3);
-
+    fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+    fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
 
@@ -864,11 +855,6 @@
     return new GrGLDistanceFieldLCDTextGeoProc();
 }
 
-const GrPrimitiveProcessor::Attribute& GrDistanceFieldLCDTextGeoProc::onVertexAttribute(
-        int i) const {
-    return IthAttribute(i, fInPosition, kInColor, kInTextureCoords);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc);
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index 71626fa..5ba19ed 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -76,9 +76,9 @@
 
     const char* name() const override { return "DistanceFieldA8Text"; }
 
-    const Attribute& inPosition() const { return fInPosition; }
-    const Attribute& inColor() const { return kInColor; }
-    const Attribute& inTextureCoords() const { return kInTextureCoords; }
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inTextureCoords() const { return fInTextureCoords; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
 #ifdef SK_GAMMA_APPLY_TO_A8
     float getDistanceAdjust() const { return fDistanceAdjust; }
@@ -100,22 +100,17 @@
 #endif
                                  uint32_t flags, const SkMatrix& localMatrix);
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, fInPosition, kInColor, kInTextureCoords);
-    }
-
     static constexpr int kMaxTextures = 4;
 
     TextureSampler   fTextureSamplers[kMaxTextures];
-    SkMatrix         fLocalMatrix;
-    Attribute        fInPosition;
-    uint32_t         fFlags;
 #ifdef SK_GAMMA_APPLY_TO_A8
     float            fDistanceAdjust;
 #endif
-
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType};
+    uint32_t         fFlags;
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInTextureCoords;
+    SkMatrix         fLocalMatrix;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -144,9 +139,9 @@
 
     const char* name() const override { return "DistanceFieldPath"; }
 
-    const Attribute& inPosition() const { return kInPosition; }
-    const Attribute& inColor() const { return kInColor; }
-    const Attribute& inTextureCoords() const { return kInTextureCoords; }
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inTextureCoords() const { return fInTextureCoords; }
     const SkMatrix& matrix() const { return fMatrix; }
     uint32_t getFlags() const { return fFlags; }
 
@@ -164,14 +159,12 @@
                                int numActiveProxies,
                                const GrSamplerState&, uint32_t flags);
 
-    const Attribute& onVertexAttribute(int i) const override;
-
     SkMatrix         fMatrix;      // view matrix if perspective, local matrix otherwise
     TextureSampler   fTextureSamplers[kMaxTextures];
     uint32_t         fFlags;
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType};
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInTextureCoords;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -216,9 +209,9 @@
 
     const char* name() const override { return "DistanceFieldLCDText"; }
 
-    const Attribute& inPosition() const { return fInPosition; }
-    const Attribute& inColor() const { return kInColor; }
-    const Attribute& inTextureCoords() const { return kInTextureCoords; }
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inTextureCoords() const { return fInTextureCoords; }
     DistanceAdjust getDistanceAdjust() const { return fDistanceAdjust; }
     uint32_t getFlags() const { return fFlags; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
@@ -234,18 +227,15 @@
                                   const GrSamplerState& params, DistanceAdjust wa, uint32_t flags,
                                   const SkMatrix& localMatrix);
 
-    const Attribute& onVertexAttribute(int) const override;
-
     static constexpr int kMaxTextures = 4;
 
     TextureSampler   fTextureSamplers[kMaxTextures];
-    const SkMatrix   fLocalMatrix;
     DistanceAdjust   fDistanceAdjust;
-    Attribute        fInPosition;
     uint32_t         fFlags;
-
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType};
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInTextureCoords;
+    const SkMatrix   fLocalMatrix;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
index bf78f41..52bf287 100644
--- a/src/gpu/effects/GrShadowGeoProc.cpp
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -33,13 +33,13 @@
         varyingHandler->addPassThroughAttribute(rsgp.inColor(), args.fOutputColor);
 
         // Setup position
-        this->writeOutputPosition(vertBuilder, gpArgs, rsgp.inPosition().name());
+        this->writeOutputPosition(vertBuilder, gpArgs, rsgp.inPosition()->name());
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
-                             rsgp.inPosition().asShaderVar(),
+                             rsgp.inPosition()->asShaderVar(),
                              args.fFPCoordTransformHandler);
 
         fragBuilder->codeAppend("half d = length(shadowParams.xy);");
@@ -62,8 +62,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrRRectShadowGeoProc::GrRRectShadowGeoProc() : INHERITED(kGrRRectShadowGeoProc_ClassID) {
-    this->setVertexAttributeCnt(3);
+GrRRectShadowGeoProc::GrRRectShadowGeoProc()
+: INHERITED(kGrRRectShadowGeoProc_ClassID) {
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+    fInShadowParams = &this->addVertexAttrib("inShadowParams", kHalf4_GrVertexAttribType);
 }
 
 GrGLSLPrimitiveProcessor* GrRRectShadowGeoProc::createGLSLInstance(const GrShaderCaps&) const {
@@ -72,10 +75,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-constexpr GrPrimitiveProcessor::Attribute GrRRectShadowGeoProc::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute GrRRectShadowGeoProc::kInColor;
-constexpr GrPrimitiveProcessor::Attribute GrRRectShadowGeoProc::kInShadowParams;
-
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRRectShadowGeoProc);
 
 #if GR_TEST_UTILS
diff --git a/src/gpu/effects/GrShadowGeoProc.h b/src/gpu/effects/GrShadowGeoProc.h
index df3e501..0b4baf4 100644
--- a/src/gpu/effects/GrShadowGeoProc.h
+++ b/src/gpu/effects/GrShadowGeoProc.h
@@ -25,9 +25,9 @@
 
     const char* name() const override { return "RRectShadow"; }
 
-    const Attribute& inPosition() const { return kInPosition; }
-    const Attribute& inColor() const { return kInColor; }
-    const Attribute& inShadowParams() const { return kInShadowParams; }
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inShadowParams() const { return fInShadowParams; }
     GrColor color() const { return fColor; }
 
     void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {}
@@ -37,15 +37,10 @@
 private:
     GrRRectShadowGeoProc();
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInColor, kInShadowParams);
-    }
-
     GrColor          fColor;
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInShadowParams = {"inShadowParams", kHalf4_GrVertexAttribType};
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInShadowParams;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index be53222..88f5abe 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1775,29 +1775,35 @@
         attribState = fHWVertexArrayState.bindInternalVertexArray(this);
     }
 
-    int numAttribs = fHWProgram->numVertexAttributes() + fHWProgram->numInstanceAttributes();
-    attribState->enableVertexArrays(this, numAttribs, enablePrimitiveRestart);
+    struct {
+        const GrBuffer*   fBuffer;
+        int               fStride;
+        size_t            fBufferOffset;
+    } bindings[2];
 
     if (int vertexStride = fHWProgram->vertexStride()) {
         SkASSERT(vertexBuffer && !vertexBuffer->isMapped());
-        size_t bufferOffset = vertexBuffer->baseOffset() + baseVertex * vertexStride;
-        for (int i = 0; i < fHWProgram->numVertexAttributes(); ++i) {
-            const auto& attrib = fHWProgram->vertexAttribute(i);
-            static constexpr int kDivisor = 0;
-            attribState->set(this, attrib.fLocation, vertexBuffer, attrib.fType, vertexStride,
-                             bufferOffset + attrib.fOffset, kDivisor);
-        }
+        bindings[0].fBuffer = vertexBuffer;
+        bindings[0].fStride = vertexStride;
+        bindings[0].fBufferOffset = vertexBuffer->baseOffset() + baseVertex * vertexStride;
     }
     if (int instanceStride = fHWProgram->instanceStride()) {
         SkASSERT(instanceBuffer && !instanceBuffer->isMapped());
-        size_t bufferOffset = instanceBuffer->baseOffset() + baseInstance * instanceStride;
-        int attribIdx = fHWProgram->numVertexAttributes();
-        for (int i = 0; i < fHWProgram->numInstanceAttributes(); ++i, ++attribIdx) {
-            const auto& attrib = fHWProgram->instanceAttribute(i);
-            static constexpr int kDivisor = 1;
-            attribState->set(this, attrib.fLocation, instanceBuffer, attrib.fType, instanceStride,
-                             bufferOffset + attrib.fOffset, kDivisor);
-        }
+        bindings[1].fBuffer = instanceBuffer;
+        bindings[1].fStride = instanceStride;
+        bindings[1].fBufferOffset = instanceBuffer->baseOffset() + baseInstance * instanceStride;
+    }
+
+    auto numAttributes = fHWProgram->numAttributes();
+    attribState->enableVertexArrays(this, numAttributes, enablePrimitiveRestart);
+
+    for (int i = 0; i < numAttributes; ++i) {
+        using InputRate = GrPrimitiveProcessor::Attribute::InputRate;
+        const GrGLProgram::Attribute& attribute = fHWProgram->attribute(i);
+        const int divisor = InputRate::kPerInstance == attribute.fInputRate ? 1 : 0;
+        const auto& binding = bindings[divisor];
+        attribState->set(this, attribute.fLocation, binding.fBuffer, attribute.fType,
+                         binding.fStride, binding.fBufferOffset + attribute.fOffset, divisor);
     }
 }
 
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 3d4bd38..262f0b2 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -38,8 +38,7 @@
         std::unique_ptr<std::unique_ptr<GrGLSLFragmentProcessor>[]> fragmentProcessors,
         int fragmentProcessorCnt,
         std::unique_ptr<Attribute[]> attributes,
-        int vertexAttributeCnt,
-        int instanceAttributeCnt,
+        int attributeCnt,
         int vertexStride,
         int instanceStride)
         : fBuiltinUniformHandles(builtinUniforms)
@@ -49,8 +48,7 @@
         , fFragmentProcessors(std::move(fragmentProcessors))
         , fFragmentProcessorCnt(fragmentProcessorCnt)
         , fAttributes(std::move(attributes))
-        , fVertexAttributeCnt(vertexAttributeCnt)
-        , fInstanceAttributeCnt(instanceAttributeCnt)
+        , fAttributeCnt(attributeCnt)
         , fVertexStride(vertexStride)
         , fInstanceStride(instanceStride)
         , fGpu(gpu)
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index ca4eddc..6ea1f2c 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -10,6 +10,7 @@
 #define GrGLProgram_DEFINED
 
 #include "GrGLProgramDataManager.h"
+#include "GrPrimitiveProcessor.h"
 #include "glsl/GrGLSLProgramDataManager.h"
 #include "glsl/GrGLSLUniformHandler.h"
 
@@ -34,18 +35,15 @@
      */
     struct Attribute {
         GrVertexAttribType fType;
-        size_t fOffset;
+        int fOffset;
         GrGLint fLocation;
+        GrPrimitiveProcessor::Attribute::InputRate fInputRate;
     };
 
     using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
     using UniformInfoArray = GrGLProgramDataManager::UniformInfoArray;
     using VaryingInfoArray = GrGLProgramDataManager::VaryingInfoArray;
 
-    /**
-     * The attribute array consists of vertexAttributeCnt + instanceAttributeCnt elements with
-     * the vertex attributes preceding the instance attributes.
-     */
     GrGLProgram(GrGLGpu*,
                 const GrGLSLBuiltinUniformHandles&,
                 GrGLuint programID,
@@ -58,8 +56,7 @@
                 std::unique_ptr<std::unique_ptr<GrGLSLFragmentProcessor>[]> fragmentProcessors,
                 int fragmentProcessorCnt,
                 std::unique_ptr<Attribute[]>,
-                int vertexAttributeCnt,
-                int instanceAttributeCnt,
+                int attributeCnt,
                 int vertexStride,
                 int instanceStride);
 
@@ -127,17 +124,8 @@
     int vertexStride() const { return fVertexStride; }
     int instanceStride() const { return fInstanceStride; }
 
-    int numVertexAttributes() const { return fVertexAttributeCnt; }
-    const Attribute& vertexAttribute(int i) const {
-        SkASSERT(i >= 0 && i < fVertexAttributeCnt);
-        return fAttributes[i];
-    }
-
-    int numInstanceAttributes() const { return fInstanceAttributeCnt; }
-    const Attribute& instanceAttribute(int i) const {
-        SkASSERT(i >= 0 && i < fInstanceAttributeCnt);
-        return fAttributes[i + fVertexAttributeCnt];
-    }
+    int numAttributes() const { return fAttributeCnt; }
+    const Attribute& attribute(int i) const { return fAttributes[i]; }
 
 private:
     // A helper to loop over effects, set the transforms (via subclass) and bind textures
@@ -165,8 +153,7 @@
     int fFragmentProcessorCnt;
 
     std::unique_ptr<Attribute[]> fAttributes;
-    int fVertexAttributeCnt;
-    int fInstanceAttributeCnt;
+    int fAttributeCnt;
     int fVertexStride;
     int fInstanceStride;
 
diff --git a/src/gpu/gl/GrGLVaryingHandler.cpp b/src/gpu/gl/GrGLVaryingHandler.cpp
index e426d46..eecc63e 100644
--- a/src/gpu/gl/GrGLVaryingHandler.cpp
+++ b/src/gpu/gl/GrGLVaryingHandler.cpp
@@ -20,8 +20,7 @@
     SkASSERT(glPB->gpu()->glCaps().shaderCaps()->pathRenderingSupport() &&
              glPB->fPrimProc.isPathRendering() &&
              !glPB->fPrimProc.willUseGeoShader() &&
-             !glPB->fPrimProc.numVertexAttributes() &&
-             !glPB->fPrimProc.numInstanceAttributes());
+             glPB->fPrimProc.numAttribs() == 0);
 #endif
     this->addVarying(name, v);
     auto varyingInfo = fPathProcVaryingInfos.push_back();
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 79e13d3..0cdc3b5 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -70,8 +70,7 @@
         , fGpu(gpu)
         , fVaryingHandler(this)
         , fUniformHandler(this)
-        , fVertexAttributeCnt(0)
-        , fInstanceAttributeCnt(0)
+        , fAttributeCnt(0)
         , fVertexStride(0)
         , fInstanceStride(0) {}
 
@@ -228,30 +227,18 @@
         // NVPR actually requires a vertex shader to compile
         bool useNvpr = primProc.isPathRendering();
         if (!useNvpr) {
-            fVertexAttributeCnt = primProc.numVertexAttributes();
-            fInstanceAttributeCnt = primProc.numInstanceAttributes();
-            fAttributes.reset(
-                    new GrGLProgram::Attribute[fVertexAttributeCnt + fInstanceAttributeCnt]);
-            auto addAttr = [&](int i, const auto& a, size_t* stride) {
-                fAttributes[i].fType = a.type();
-                fAttributes[i].fOffset = *stride;
-                *stride += a.sizeAlign4();
+            fAttributeCnt = primProc.numAttribs();
+            fAttributes.reset(new GrGLProgram::Attribute[fAttributeCnt]);
+            fVertexStride = primProc.getVertexStride();
+            fInstanceStride = primProc.getInstanceStride();
+            for (int i = 0; i < fAttributeCnt; i++) {
+                const auto& attr = primProc.getAttrib(i);
+                fAttributes[i].fInputRate = attr.inputRate();
+                fAttributes[i].fType = attr.type();
+                fAttributes[i].fOffset = attr.offsetInRecord();
                 fAttributes[i].fLocation = i;
-                GL_CALL(BindAttribLocation(programID, i, a.name()));
-            };
-            fVertexStride = 0;
-            int i = 0;
-            for (; i < fVertexAttributeCnt; i++) {
-                addAttr(i, primProc.vertexAttribute(i), &fVertexStride);
-                SkASSERT(fAttributes[i].fOffset == primProc.debugOnly_vertexAttributeOffset(i));
+                GL_CALL(BindAttribLocation(programID, i, attr.name()));
             }
-            SkASSERT(fVertexStride == primProc.debugOnly_vertexStride());
-            fInstanceStride = 0;
-            for (int j = 0; j < fInstanceAttributeCnt; j++, ++i) {
-                addAttr(i, primProc.instanceAttribute(j), &fInstanceStride);
-                SkASSERT(fAttributes[i].fOffset == primProc.debugOnly_instanceAttributeOffset(j));
-            }
-            SkASSERT(fInstanceStride == primProc.debugOnly_instanceStride());
         }
 
         if (primProc.willUseGeoShader()) {
@@ -419,8 +406,7 @@
                            std::move(fFragmentProcessors),
                            fFragmentProcessorCnt,
                            std::move(fAttributes),
-                           fVertexAttributeCnt,
-                           fInstanceAttributeCnt,
+                           fAttributeCnt,
                            fVertexStride,
                            fInstanceStride);
 }
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.h b/src/gpu/gl/builders/GrGLProgramBuilder.h
index 30d5179..ea3432b 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.h
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.h
@@ -81,10 +81,9 @@
     GrGLUniformHandler    fUniformHandler;
 
     std::unique_ptr<GrGLProgram::Attribute[]> fAttributes;
-    int fVertexAttributeCnt;
-    int fInstanceAttributeCnt;
-    size_t fVertexStride;
-    size_t fInstanceStride;
+    int fAttributeCnt;
+    int fVertexStride;
+    int fInstanceStride;
 
     // shader pulled from cache. Data is organized as:
     // SkSL::Program::Inputs inputs
diff --git a/src/gpu/glsl/GrGLSLVarying.cpp b/src/gpu/glsl/GrGLSLVarying.cpp
index 0563406..f8ec1c2 100644
--- a/src/gpu/glsl/GrGLSLVarying.cpp
+++ b/src/gpu/glsl/GrGLSLVarying.cpp
@@ -9,15 +9,14 @@
 #include "glsl/GrGLSLVarying.h"
 #include "glsl/GrGLSLProgramBuilder.h"
 
-void GrGLSLVaryingHandler::addPassThroughAttribute(const GrGeometryProcessor::Attribute& input,
+void GrGLSLVaryingHandler::addPassThroughAttribute(const GrGeometryProcessor::Attribute* input,
                                                    const char* output,
                                                    Interpolation interpolation) {
-    SkASSERT(input.isInitialized());
     SkASSERT(!fProgramBuilder->primitiveProcessor().willUseGeoShader());
-    GrSLType type = GrVertexAttribTypeToSLType(input.type());
+    GrSLType type = GrVertexAttribTypeToSLType(input->type());
     GrGLSLVarying v(type);
-    this->addVarying(input.name(), &v, interpolation);
-    fProgramBuilder->fVS.codeAppendf("%s = %s;", v.vsOut(), input.name());
+    this->addVarying(input->name(), &v, interpolation);
+    fProgramBuilder->fVS.codeAppendf("%s = %s;", v.vsOut(), input->name());
     fProgramBuilder->fFS.codeAppendf("%s = %s;", output, v.fsIn());
 }
 
@@ -68,13 +67,10 @@
 }
 
 void GrGLSLVaryingHandler::emitAttributes(const GrGeometryProcessor& gp) {
-    int vaCount = gp.numVertexAttributes();
+    int vaCount = gp.numAttribs();
     for (int i = 0; i < vaCount; i++) {
-        this->addAttribute(gp.vertexAttribute(i).asShaderVar());
-    }
-    int iaCount = gp.numInstanceAttributes();
-    for (int i = 0; i < iaCount; i++) {
-        this->addAttribute(gp.instanceAttribute(i).asShaderVar());
+        const GrGeometryProcessor::Attribute& attr = gp.getAttrib(i);
+        this->addAttribute(attr.asShaderVar());
     }
 }
 
diff --git a/src/gpu/glsl/GrGLSLVarying.h b/src/gpu/glsl/GrGLSLVarying.h
index 0da88a0..57704ad 100644
--- a/src/gpu/glsl/GrGLSLVarying.h
+++ b/src/gpu/glsl/GrGLSLVarying.h
@@ -104,7 +104,7 @@
      * that will be set as the output varying for all emitted vertices.
      * TODO it might be nicer behavior to have a flag to declare output inside these calls
      */
-    void addPassThroughAttribute(const GrGeometryProcessor::Attribute&, const char* output,
+    void addPassThroughAttribute(const GrGeometryProcessor::Attribute*, const char* output,
                                  Interpolation = Interpolation::kInterpolated);
 
     void emitAttributes(const GrGeometryProcessor& gp);
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 566eebd..3237afe 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -576,21 +576,21 @@
 
             GrGLSLVarying v(kHalf4_GrSLType);
             varyingHandler->addVarying("QuadEdge", &v);
-            vertBuilder->codeAppendf("%s = %s;", v.vsOut(), qe.kInQuadEdge.name());
+            vertBuilder->codeAppendf("%s = %s;", v.vsOut(), qe.fInQuadEdge->name());
 
             // Setup pass through color
-            varyingHandler->addPassThroughAttribute(qe.kInColor, args.fOutputColor);
+            varyingHandler->addPassThroughAttribute(qe.fInColor, args.fOutputColor);
 
             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, qe.kInPosition.name());
+            this->writeOutputPosition(vertBuilder, gpArgs, qe.fInPosition->name());
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 qe.kInPosition.asShaderVar(),
+                                 qe.fInPosition->asShaderVar(),
                                  qe.fLocalMatrix,
                                  args.fFPCoordTransformHandler);
 
@@ -646,25 +646,21 @@
             : INHERITED(kQuadEdgeEffect_ClassID)
             , fLocalMatrix(localMatrix)
             , fUsesLocalCoords(usesLocalCoords) {
-        this->setVertexAttributeCnt(3);
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+        fInQuadEdge = &this->addVertexAttrib("inQuadEdge", kHalf4_GrVertexAttribType);
     }
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInColor, kInQuadEdge);
-    }
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInQuadEdge = {"inQuadEdge", kHalf4_GrVertexAttribType};
-    SkMatrix fLocalMatrix;
-    bool fUsesLocalCoords;
+    const Attribute* fInPosition;
+    const Attribute* fInQuadEdge;
+    const Attribute* fInColor;
+    SkMatrix         fLocalMatrix;
+    bool             fUsesLocalCoords;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute QuadEdgeEffect::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute QuadEdgeEffect::kInColor;
-constexpr GrPrimitiveProcessor::Attribute QuadEdgeEffect::kInQuadEdge;
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(QuadEdgeEffect);
 
@@ -799,10 +795,12 @@
             return;
         }
 
-        size_t vertexStride = fHelper.compatibleWithAlphaAsCoverage()
-                                      ? sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
-                                      : sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr);
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+
+        SkASSERT(fHelper.compatibleWithAlphaAsCoverage()
+                         ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
+                         : vertexStride ==
+                                   sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr));
 
         GrAAConvexTessellator tess;
 
@@ -903,9 +901,9 @@
             const GrBuffer* vertexBuffer;
             int firstVertex;
 
-            SkASSERT(sizeof(QuadVertex) == quadProcessor->debugOnly_vertexStride());
+            size_t vertexStride = quadProcessor->getVertexStride();
             QuadVertex* verts = reinterpret_cast<QuadVertex*>(target->makeVertexSpace(
-                    sizeof(QuadVertex), vertexCount, &vertexBuffer, &firstVertex));
+                vertexStride, vertexCount, &vertexBuffer, &firstVertex));
 
             if (!verts) {
                 SkDebugf("Could not allocate vertices\n");
diff --git a/src/gpu/ops/GrAAFillRectOp.cpp b/src/gpu/ops/GrAAFillRectOp.cpp
index 76bc812..5472f11 100644
--- a/src/gpu/ops/GrAAFillRectOp.cpp
+++ b/src/gpu/ops/GrAAFillRectOp.cpp
@@ -242,19 +242,12 @@
     void onPrepareDraws(Target* target) override {
         using namespace GrDefaultGeoProcFactory;
 
-        size_t vertexStride = sizeof(SkPoint) + sizeof(GrColor);
         Color color(Color::kPremulGrColorAttribute_Type);
-        Coverage::Type coverageType = Coverage::kSolid_Type;
-        if (!fHelper.compatibleWithAlphaAsCoverage()) {
-            coverageType = Coverage::kAttribute_Type;
-            vertexStride += sizeof(float);
-        }
-        LocalCoords lc = LocalCoords::kUnused_Type;
-        if (fHelper.usesLocalCoords()) {
-            lc = LocalCoords::kHasExplicit_Type;
-            vertexStride += sizeof(SkPoint);
-        }
-
+        Coverage::Type coverageType = fHelper.compatibleWithAlphaAsCoverage()
+                                              ? Coverage::kSolid_Type
+                                              : Coverage::kAttribute_Type;
+        LocalCoords lc = fHelper.usesLocalCoords() ? LocalCoords::kHasExplicit_Type
+                                                   : LocalCoords::kUnused_Type;
         sk_sp<GrGeometryProcessor> gp =
                 GrDefaultGeoProcFactory::Make(color, coverageType, lc, SkMatrix::I());
         if (!gp) {
@@ -262,7 +255,7 @@
             return;
         }
 
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
 
         sk_sp<const GrBuffer> indexBuffer = get_index_buffer(target->resourceProvider());
         PatternHelper helper(GrPrimitiveType::kTriangles);
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index 89dedc4..b62c14b 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -959,16 +959,18 @@
         const GrBuffer* vertexBuffer;
         int firstVertex;
 
-        SkASSERT(sizeof(LineVertex) == lineGP->debugOnly_vertexStride());
+        size_t vertexStride = lineGP->getVertexStride();
         int vertexCount = kLineSegNumVertices * lineCount;
-        LineVertex* verts = reinterpret_cast<LineVertex*>(target->makeVertexSpace(
-                sizeof(LineVertex), vertexCount, &vertexBuffer, &firstVertex));
+        LineVertex* verts = reinterpret_cast<LineVertex*>(
+            target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex));
 
         if (!verts|| !linesIndexBuffer) {
             SkDebugf("Could not allocate vertices\n");
             return;
         }
 
+        SkASSERT(lineGP->getVertexStride() == sizeof(LineVertex));
+
         for (int i = 0; i < lineCount; ++i) {
             add_line(&lines[2*i], toSrc, this->coverage(), &verts);
         }
@@ -1002,11 +1004,10 @@
 
         sk_sp<const GrBuffer> quadsIndexBuffer = get_quads_index_buffer(target->resourceProvider());
 
-        SkASSERT(sizeof(BezierVertex) == quadGP->debugOnly_vertexStride());
-        SkASSERT(sizeof(BezierVertex) == conicGP->debugOnly_vertexStride());
+        size_t vertexStride = sizeof(BezierVertex);
         int vertexCount = kQuadNumVertices * quadAndConicCount;
-        void* vertices = target->makeVertexSpace(sizeof(BezierVertex), vertexCount, &vertexBuffer,
-                                                 &firstVertex);
+        void *vertices = target->makeVertexSpace(vertexStride, vertexCount,
+                                                 &vertexBuffer, &firstVertex);
 
         if (!vertices || !quadsIndexBuffer) {
             SkDebugf("Could not allocate vertices\n");
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index b6b7077..5659099 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -249,10 +249,12 @@
             return;
         }
 
-        size_t vertexStride = fHelper.compatibleWithAlphaAsCoverage()
-                                      ? sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
-                                      : sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr);
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+
+        SkASSERT(fHelper.compatibleWithAlphaAsCoverage()
+                         ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
+                         : vertexStride ==
+                                   sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr));
 
         int instanceCount = fPaths.count();
 
diff --git a/src/gpu/ops/GrAAStrokeRectOp.cpp b/src/gpu/ops/GrAAStrokeRectOp.cpp
index 3347227..b9a6d88 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrAAStrokeRectOp.cpp
@@ -268,11 +268,11 @@
         return;
     }
 
-    size_t vertexStride = fHelper.compatibleWithAlphaAsCoverage()
-                                  ? sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
-                                  : sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr);
+    size_t vertexStride = gp->getVertexStride();
 
-    SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+    SkASSERT(fHelper.compatibleWithAlphaAsCoverage()
+                     ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr)
+                     : vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr));
     int innerVertexNum = 4;
     int outerVertexNum = this->miterStroke() ? 4 : 8;
     int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2;
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 2d98d2d..eb39079 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -307,8 +307,8 @@
     }
 
     flushInfo.fGlyphsToFlush = 0;
-    size_t vertexStride = GrTextBlob::GetVertexStride(maskFormat, vmPerspective);
-    SkASSERT(vertexStride == flushInfo.fGeometryProcessor->debugOnly_vertexStride());
+    size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
+    SkASSERT(vertexStride == GrTextBlob::GetVertexStride(maskFormat, vmPerspective));
 
     int glyphCount = this->numGlyphs();
     const GrBuffer* vertexBuffer;
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 5023f38..b784b05 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -624,15 +624,7 @@
         }
 
         QuadHelper helper;
-        size_t vertexStride;
-        if (fullDash) {
-            vertexStride =
-                    SkPaint::kRound_Cap == fCap ? sizeof(DashCircleVertex) : sizeof(DashLineVertex);
-        } else {
-            vertexStride = sizeof(SkPoint);
-        }
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
-        void* vertices = helper.init(target, vertexStride, totalRectCount);
+        void* vertices = helper.init(target, gp->getVertexStride(), totalRectCount);
         if (!vertices) {
             return;
         }
@@ -644,13 +636,15 @@
 
             if (!draws[i].fLineDone) {
                 if (fullDash) {
-                    setup_dashed_rect(
-                            rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
-                            draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY,
-                            draws[i].fLineLength, draws[i].fHalfDevStroke, draws[i].fIntervals[0],
-                            draws[i].fIntervals[1], draws[i].fStrokeWidth, capType, vertexStride);
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloatX,
+                                      draws[i].fDevBloatY, draws[i].fLineLength,
+                                      draws[i].fHalfDevStroke, draws[i].fIntervals[0],
+                                      draws[i].fIntervals[1], draws[i].fStrokeWidth,
+                                      capType, gp->getVertexStride());
                 } else {
                     SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
                     setup_dashed_rect_pos(rects[rectIndex], curVIdx, geom.fSrcRotInv, verts);
                 }
                 curVIdx += 4;
@@ -659,13 +653,15 @@
 
             if (draws[i].fHasStartRect) {
                 if (fullDash) {
-                    setup_dashed_rect(
-                            rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
-                            draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY,
-                            draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0],
-                            draws[i].fIntervals[1], draws[i].fStrokeWidth, capType, vertexStride);
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloatX,
+                                      draws[i].fDevBloatY, draws[i].fIntervals[0],
+                                      draws[i].fHalfDevStroke, draws[i].fIntervals[0],
+                                      draws[i].fIntervals[1], draws[i].fStrokeWidth, capType,
+                                      gp->getVertexStride());
                 } else {
                     SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
                     setup_dashed_rect_pos(rects[rectIndex], curVIdx, geom.fSrcRotInv, verts);
                 }
                 curVIdx += 4;
@@ -674,13 +670,15 @@
 
             if (draws[i].fHasEndRect) {
                 if (fullDash) {
-                    setup_dashed_rect(
-                            rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
-                            draws[i].fStartOffset, draws[i].fDevBloatX, draws[i].fDevBloatY,
-                            draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0],
-                            draws[i].fIntervals[1], draws[i].fStrokeWidth, capType, vertexStride);
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, geom.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloatX,
+                                      draws[i].fDevBloatY, draws[i].fIntervals[0],
+                                      draws[i].fHalfDevStroke, draws[i].fIntervals[0],
+                                      draws[i].fIntervals[1], draws[i].fStrokeWidth, capType,
+                                      gp->getVertexStride());
                 } else {
                     SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
                     setup_dashed_rect_pos(rects[rectIndex], curVIdx, geom.fSrcRotInv, verts);
                 }
                 curVIdx += 4;
@@ -840,6 +838,12 @@
 
     const char* name() const override { return "DashingCircleEffect"; }
 
+    const Attribute* inPosition() const { return fInPosition; }
+
+    const Attribute* inDashParams() const { return fInDashParams; }
+
+    const Attribute* inCircleParams() const { return fInCircleParams; }
+
     AAMode aaMode() const { return fAAMode; }
 
     GrColor color() const { return fColor; }
@@ -856,27 +860,18 @@
     DashingCircleEffect(GrColor, AAMode aaMode, const SkMatrix& localMatrix,
                         bool usesLocalCoords);
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInDashParams, kInCircleParams);
-    }
-
     GrColor             fColor;
     SkMatrix            fLocalMatrix;
     bool                fUsesLocalCoords;
     AAMode              fAAMode;
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInDashParams = {"inDashParams", kHalf3_GrVertexAttribType};
-    static constexpr Attribute kInCircleParams = {"inCircleParams", kHalf2_GrVertexAttribType};
+    const Attribute*    fInPosition;
+    const Attribute*    fInDashParams;
+    const Attribute*    fInCircleParams;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
-    friend class GLDashingCircleEffect;
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute DashingCircleEffect::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute DashingCircleEffect::kInDashParams;
-constexpr GrPrimitiveProcessor::Attribute DashingCircleEffect::kInCircleParams;
 
 //////////////////////////////////////////////////////////////////////////////
 
@@ -921,25 +916,25 @@
     // XY are dashPos, Z is dashInterval
     GrGLSLVarying dashParams(kHalf3_GrSLType);
     varyingHandler->addVarying("DashParam", &dashParams);
-    vertBuilder->codeAppendf("%s = %s;", dashParams.vsOut(), dce.kInDashParams.name());
+    vertBuilder->codeAppendf("%s = %s;", dashParams.vsOut(), dce.inDashParams()->name());
 
     // x refers to circle radius - 0.5, y refers to cicle's center x coord
     GrGLSLVarying circleParams(kHalf2_GrSLType);
     varyingHandler->addVarying("CircleParams", &circleParams);
-    vertBuilder->codeAppendf("%s = %s;", circleParams.vsOut(), dce.kInCircleParams.name());
+    vertBuilder->codeAppendf("%s = %s;", circleParams.vsOut(), dce.inCircleParams()->name());
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
-    this->writeOutputPosition(vertBuilder, gpArgs, dce.kInPosition.name());
+    this->writeOutputPosition(vertBuilder, gpArgs, dce.inPosition()->name());
 
     // emit transforms
     this->emitTransforms(vertBuilder,
                          varyingHandler,
                          uniformHandler,
-                         dce.kInPosition.asShaderVar(),
+                         dce.inPosition()->asShaderVar(),
                          dce.localMatrix(),
                          args.fFPCoordTransformHandler);
 
@@ -1012,7 +1007,9 @@
     , fLocalMatrix(localMatrix)
     , fUsesLocalCoords(usesLocalCoords)
     , fAAMode(aaMode) {
-    this->setVertexAttributeCnt(3);
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInDashParams = &this->addVertexAttrib("inDashParams", kHalf3_GrVertexAttribType);
+    fInCircleParams = &this->addVertexAttrib("inCircleParams", kHalf2_GrVertexAttribType);
 }
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect);
@@ -1050,6 +1047,12 @@
 
     const char* name() const override { return "DashingEffect"; }
 
+    const Attribute* inPosition() const { return fInPosition; }
+
+    const Attribute* inDashParams() const { return fInDashParams; }
+
+    const Attribute* inRectParams() const { return fInRectParams; }
+
     AAMode aaMode() const { return fAAMode; }
 
     GrColor color() const { return fColor; }
@@ -1066,28 +1069,18 @@
     DashingLineEffect(GrColor, AAMode aaMode, const SkMatrix& localMatrix,
                       bool usesLocalCoords);
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInDashParams, kInRectParams);
-    }
-
     GrColor             fColor;
     SkMatrix            fLocalMatrix;
     bool                fUsesLocalCoords;
     AAMode              fAAMode;
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInDashParams = {"inDashParams", kHalf3_GrVertexAttribType};
-    static constexpr Attribute kInRectParams = {"inRect", kHalf4_GrVertexAttribType};
+    const Attribute*    fInPosition;
+    const Attribute*    fInDashParams;
+    const Attribute*    fInRectParams;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
-    friend class GLDashingLineEffect;
-
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute DashingLineEffect::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute DashingLineEffect::kInDashParams;
-constexpr GrPrimitiveProcessor::Attribute DashingLineEffect::kInRectParams;
 
 //////////////////////////////////////////////////////////////////////////////
 
@@ -1125,26 +1118,26 @@
     // XY refers to dashPos, Z is the dash interval length
     GrGLSLVarying inDashParams(kFloat3_GrSLType);
     varyingHandler->addVarying("DashParams", &inDashParams);
-    vertBuilder->codeAppendf("%s = %s;", inDashParams.vsOut(), de.kInDashParams.name());
+    vertBuilder->codeAppendf("%s = %s;", inDashParams.vsOut(), de.inDashParams()->name());
 
     // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bottom - 0.5),
     // respectively.
     GrGLSLVarying inRectParams(kFloat4_GrSLType);
     varyingHandler->addVarying("RectParams", &inRectParams);
-    vertBuilder->codeAppendf("%s = %s;", inRectParams.vsOut(), de.kInRectParams.name());
+    vertBuilder->codeAppendf("%s = %s;", inRectParams.vsOut(), de.inRectParams()->name());
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
-    this->writeOutputPosition(vertBuilder, gpArgs, de.kInPosition.name());
+    this->writeOutputPosition(vertBuilder, gpArgs, de.inPosition()->name());
 
     // emit transforms
     this->emitTransforms(vertBuilder,
                          varyingHandler,
                          uniformHandler,
-                         de.kInPosition.asShaderVar(),
+                         de.inPosition()->asShaderVar(),
                          de.localMatrix(),
                          args.fFPCoordTransformHandler);
 
@@ -1235,7 +1228,9 @@
     , fLocalMatrix(localMatrix)
     , fUsesLocalCoords(usesLocalCoords)
     , fAAMode(aaMode) {
-    this->setVertexAttributeCnt(3);
+    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    fInDashParams = &this->addVertexAttrib("inDashParams", kHalf3_GrVertexAttribType);
+    fInRectParams = &this->addVertexAttrib("inRect", kHalf4_GrVertexAttribType);
 }
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect);
@@ -1247,8 +1242,8 @@
                                    aaMode, GrTest::TestMatrix(d->fRandom),
                                    d->fRandom->nextBool());
 }
-
 #endif
+
 //////////////////////////////////////////////////////////////////////////////
 
 static sk_sp<GrGeometryProcessor> make_dash_gp(GrColor color,
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index aa0963b..df6f81f 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -404,7 +404,7 @@
             gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
         }
 
-        SkASSERT(gp->debugOnly_vertexStride() == sizeof(SkPoint));
+        SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
 
         int instanceCount = fPaths.count();
 
diff --git a/src/gpu/ops/GrDrawAtlasOp.cpp b/src/gpu/ops/GrDrawAtlasOp.cpp
index 47bad8b..fa08e55 100644
--- a/src/gpu/ops/GrDrawAtlasOp.cpp
+++ b/src/gpu/ops/GrDrawAtlasOp.cpp
@@ -122,9 +122,9 @@
     sk_sp<GrGeometryProcessor> gp(make_gp(this->hasColors(), this->color(), this->viewMatrix()));
 
     int instanceCount = fGeoData.count();
-    size_t vertexStride =
-            sizeof(SkPoint) + sizeof(SkPoint) + (this->hasColors() ? sizeof(GrColor) : 0);
-    SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+    size_t vertexStride = gp->getVertexStride();
+    SkASSERT(vertexStride ==
+             sizeof(SkPoint) + sizeof(SkPoint) + (this->hasColors() ? sizeof(GrColor) : 0));
 
     QuadHelper helper;
     int numQuads = this->quadCount();
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 8f6ad97..c968611 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -140,10 +140,10 @@
     bool hasColorAttribute;
     bool hasLocalCoordsAttribute;
     sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute, &hasLocalCoordsAttribute);
+    size_t vertexStride = gp->getVertexStride();
 
-    size_t vertexStride = sizeof(SkPoint) + (hasColorAttribute ? sizeof(uint32_t) : 0) +
-                          (hasLocalCoordsAttribute ? sizeof(SkPoint) : 0);
-    SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+    SkASSERT(vertexStride == sizeof(SkPoint) + (hasColorAttribute ? sizeof(uint32_t) : 0) +
+                                     (hasLocalCoordsAttribute ? sizeof(SkPoint) : 0));
 
     int instanceCount = fMeshes.count();
 
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 7bfa72e..5903231 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -62,19 +62,19 @@
                                                 latticeGP.fColorSpaceXform.get());
 
                 args.fVaryingHandler->emitAttributes(latticeGP);
-                this->writeOutputPosition(args.fVertBuilder, gpArgs, latticeGP.kPositions.name());
+                this->writeOutputPosition(args.fVertBuilder, gpArgs, latticeGP.fPositions.name());
                 this->emitTransforms(args.fVertBuilder,
                                      args.fVaryingHandler,
                                      args.fUniformHandler,
-                                     latticeGP.kTextureCoords.asShaderVar(),
+                                     latticeGP.fTextureCoords.asShaderVar(),
                                      args.fFPCoordTransformHandler);
                 args.fFragBuilder->codeAppend("float2 textureCoords;");
-                args.fVaryingHandler->addPassThroughAttribute(latticeGP.kTextureCoords,
+                args.fVaryingHandler->addPassThroughAttribute(&latticeGP.fTextureCoords,
                                                               "textureCoords");
                 args.fFragBuilder->codeAppend("float4 textureDomain;");
                 args.fVaryingHandler->addPassThroughAttribute(
-                        latticeGP.kTextureDomain, "textureDomain", Interpolation::kCanBeFlat);
-                args.fVaryingHandler->addPassThroughAttribute(latticeGP.kColors, args.fOutputColor,
+                        &latticeGP.fTextureDomain, "textureDomain", Interpolation::kCanBeFlat);
+                args.fVaryingHandler->addPassThroughAttribute(&latticeGP.fColors, args.fOutputColor,
                                                               Interpolation::kCanBeFlat);
                 args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
                 args.fFragBuilder->appendTextureLookupAndModulate(
@@ -95,31 +95,24 @@
     LatticeGP(sk_sp<GrTextureProxy> proxy, sk_sp<GrColorSpaceXform> csxf,
               GrSamplerState::Filter filter)
             : INHERITED(kLatticeGP_ClassID), fColorSpaceXform(std::move(csxf)) {
+        fPositions = this->addVertexAttrib("position", kFloat2_GrVertexAttribType);
         fSampler.reset(std::move(proxy), filter);
         this->addTextureSampler(&fSampler);
-        this->setVertexAttributeCnt(4);
+        fTextureCoords = this->addVertexAttrib("textureCoords", kFloat2_GrVertexAttribType);
+        fTextureDomain = this->addVertexAttrib("textureDomain", kFloat4_GrVertexAttribType);
+        fColors = this->addVertexAttrib("color", kUByte4_norm_GrVertexAttribType);
     }
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kPositions, kTextureCoords, kTextureDomain, kColors);
-    }
-
-    static constexpr Attribute kPositions = {"position", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kTextureDomain = {"textureDomain", kFloat4_GrVertexAttribType};
-    static constexpr Attribute kColors = {"color", kUByte4_norm_GrVertexAttribType};
-
+    Attribute fPositions;
+    Attribute fTextureCoords;
+    Attribute fTextureDomain;
+    Attribute fColors;
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
     TextureSampler fSampler;
 
     typedef GrGeometryProcessor INHERITED;
 };
 
-constexpr GrPrimitiveProcessor::Attribute LatticeGP::kPositions;
-constexpr GrPrimitiveProcessor::Attribute LatticeGP::kTextureCoords;
-constexpr GrPrimitiveProcessor::Attribute LatticeGP::kTextureDomain;
-constexpr GrPrimitiveProcessor::Attribute LatticeGP::kColors;
-
 class NonAALatticeOp final : public GrMeshDrawOp {
 private:
     using Helper = GrSimpleMeshDrawOpHelper;
@@ -207,10 +200,7 @@
             return;
         }
 
-        static constexpr size_t kVertexStide =
-                sizeof(SkPoint) + sizeof(SkPoint) + sizeof(SkRect) + sizeof(uint32_t);
-        SkASSERT(kVertexStide == gp->debugOnly_vertexStride());
-
+        size_t vertexStride = gp->getVertexStride();
         int patchCnt = fPatches.count();
         int numRects = 0;
         for (int i = 0; i < patchCnt; i++) {
@@ -223,7 +213,7 @@
 
         sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
         PatternHelper helper(GrPrimitiveType::kTriangles);
-        void* vertices = helper.init(target, kVertexStide, indexBuffer.get(), kVertsPerRect,
+        void* vertices = helper.init(target, vertexStride, indexBuffer.get(), kVertsPerRect,
                                      kIndicesPerRect, numRects);
         if (!vertices || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
@@ -251,7 +241,7 @@
             static const Sk4f kFlipMuls(1.f, -1.f, 1.f, -1.f);
             while (patch.fIter->next(&srcR, &dstR)) {
                 auto vertices = reinterpret_cast<LatticeGP::Vertex*>(verts);
-                SkPointPriv::SetRectTriStrip(&vertices->fPosition, dstR, kVertexStide);
+                SkPointPriv::SetRectTriStrip(&vertices->fPosition, dstR, vertexStride);
                 Sk4f coords(SkIntToScalar(srcR.fLeft), SkIntToScalar(srcR.fTop),
                             SkIntToScalar(srcR.fRight), SkIntToScalar(srcR.fBottom));
                 Sk4f domain = coords + kDomainOffsets;
@@ -262,7 +252,7 @@
                     domain = SkNx_shuffle<0, 3, 2, 1>(kFlipMuls * domain + kFlipOffsets);
                 }
                 SkPointPriv::SetRectTriStrip(&vertices->fTextureCoords, coords[0], coords[1],
-                                             coords[2], coords[3], kVertexStide);
+                                             coords[2], coords[3], vertexStride);
                 for (int j = 0; j < kVertsPerRect; ++j) {
                     vertices[j].fTextureDomain = {domain[0], domain[1], domain[2], domain[3]};
                 }
@@ -270,13 +260,13 @@
                 for (int j = 0; j < kVertsPerRect; ++j) {
                     vertices[j].fColor = patch.fColor;
                 }
-                verts += kVertsPerRect * kVertexStide;
+                verts += kVertsPerRect * vertexStride;
             }
 
             // If we didn't handle it above, apply the matrix here.
             if (!isScaleTranslate) {
                 SkPoint* positions = reinterpret_cast<SkPoint*>(patchVerts);
-                SkMatrixPriv::MapPointsWithStride(patch.fViewMatrix, positions, kVertexStide,
+                SkMatrixPriv::MapPointsWithStride(patch.fViewMatrix, positions, vertexStride,
                                                   kVertsPerRect * patch.fIter->numRectsToDraw());
             }
         }
diff --git a/src/gpu/ops/GrNonAAFillRectOp.cpp b/src/gpu/ops/GrNonAAFillRectOp.cpp
index d4ebedc..20bd599 100644
--- a/src/gpu/ops/GrNonAAFillRectOp.cpp
+++ b/src/gpu/ops/GrNonAAFillRectOp.cpp
@@ -181,16 +181,15 @@
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
         }
+        SkASSERT(gp->getVertexStride() ==
+                 sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr));
 
-        static constexpr size_t kVertexStride =
-                sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr);
-        SkASSERT(kVertexStride == gp->debugOnly_vertexStride());
-
+        size_t vertexStride = gp->getVertexStride();
         int rectCount = fRects.count();
 
         sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
         PatternHelper helper(GrPrimitiveType::kTriangles);
-        void* vertices = helper.init(target, kVertexStride, indexBuffer.get(), kVertsPerRect,
+        void* vertices = helper.init(target, vertexStride, indexBuffer.get(), kVertsPerRect,
                                      kIndicesPerRect, rectCount);
         if (!vertices || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
@@ -199,8 +198,8 @@
 
         for (int i = 0; i < rectCount; i++) {
             intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * kVertexStride;
-            tesselate(verts, kVertexStride, fRects[i].fColor, &fRects[i].fViewMatrix,
+                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * vertexStride;
+            tesselate(verts, vertexStride, fRects[i].fColor, &fRects[i].fViewMatrix,
                       fRects[i].fRect, &fRects[i].fLocalQuad);
         }
         helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
@@ -312,11 +311,13 @@
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
         }
-        size_t vertexStride = fHasLocalRect
-                                      ? sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr)
-                                      : sizeof(GrDefaultGeoProcFactory::PositionColorAttr);
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+        SkASSERT(fHasLocalRect
+                         ? gp->getVertexStride() ==
+                                   sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr)
+                         : gp->getVertexStride() ==
+                                   sizeof(GrDefaultGeoProcFactory::PositionColorAttr));
 
+        size_t vertexStride = gp->getVertexStride();
         int rectCount = fRects.count();
 
         sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
index 11c2f8c..5e00e55 100644
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
@@ -152,9 +152,9 @@
                                                fViewMatrix);
         }
 
-        static constexpr size_t kVertexStride = sizeof(GrDefaultGeoProcFactory::PositionAttr);
+        size_t vertexStride = gp->getVertexStride();
 
-        SkASSERT(kVertexStride == gp->debugOnly_vertexStride());
+        SkASSERT(vertexStride == sizeof(GrDefaultGeoProcFactory::PositionAttr));
 
         int vertexCount = kVertsPerHairlineRect;
         if (fStrokeWidth > 0) {
@@ -165,7 +165,7 @@
         int firstVertex;
 
         void* verts =
-                target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
+                target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex);
 
         if (!verts) {
             SkDebugf("Could not allocate vertices\n");
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 75ffb17..747fa5f 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -73,26 +73,32 @@
             : INHERITED(kCircleGeometryProcessor_ClassID)
             , fLocalMatrix(localMatrix)
             , fStroke(stroke) {
-        int cnt = 3;
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+        fInCircleEdge = &this->addVertexAttrib("inCircleEdge", kFloat4_GrVertexAttribType);
         if (clipPlane) {
-            fInClipPlane = {"inClipPlane", kHalf3_GrVertexAttribType};
-            ++cnt;
+            fInClipPlane = &this->addVertexAttrib("inClipPlane", kHalf3_GrVertexAttribType);
+        } else {
+            fInClipPlane = nullptr;
         }
         if (isectPlane) {
-            fInIsectPlane = {"inIsectPlane", kHalf3_GrVertexAttribType};
-            ++cnt;
+            fInIsectPlane = &this->addVertexAttrib("inIsectPlane", kHalf3_GrVertexAttribType);
+        } else {
+            fInIsectPlane = nullptr;
         }
         if (unionPlane) {
-            fInUnionPlane = {"inUnionPlane", kHalf3_GrVertexAttribType};
-            ++cnt;
+            fInUnionPlane = &this->addVertexAttrib("inUnionPlane", kHalf3_GrVertexAttribType);
+        } else {
+            fInUnionPlane = nullptr;
         }
         if (roundCaps) {
             SkASSERT(stroke);
             SkASSERT(clipPlane);
-            fInRoundCapCenters = {"inRoundCapCenters", kFloat4_GrVertexAttribType};
-            ++cnt;
+            fInRoundCapCenters =
+                    &this->addVertexAttrib("inRoundCapCenters", kFloat4_GrVertexAttribType);
+        } else {
+            fInRoundCapCenters = nullptr;
         }
-        this->setVertexAttributeCnt(cnt);
     }
 
     ~CircleGeometryProcessor() override {}
@@ -122,22 +128,23 @@
             // emit attributes
             varyingHandler->emitAttributes(cgp);
             fragBuilder->codeAppend("float4 circleEdge;");
-            varyingHandler->addPassThroughAttribute(cgp.kInCircleEdge, "circleEdge");
-            if (cgp.fInClipPlane.isInitialized()) {
+            varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge, "circleEdge");
+            if (cgp.fInClipPlane) {
                 fragBuilder->codeAppend("half3 clipPlane;");
                 varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane");
             }
-            if (cgp.fInIsectPlane.isInitialized()) {
+            if (cgp.fInIsectPlane) {
+                SkASSERT(cgp.fInClipPlane);
                 fragBuilder->codeAppend("half3 isectPlane;");
                 varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane");
             }
-            if (cgp.fInUnionPlane.isInitialized()) {
-                SkASSERT(cgp.fInClipPlane.isInitialized());
+            if (cgp.fInUnionPlane) {
+                SkASSERT(cgp.fInClipPlane);
                 fragBuilder->codeAppend("half3 unionPlane;");
                 varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane");
             }
             GrGLSLVarying capRadius(kFloat_GrSLType);
-            if (cgp.fInRoundCapCenters.isInitialized()) {
+            if (cgp.fInRoundCapCenters) {
                 fragBuilder->codeAppend("float4 roundCapCenters;");
                 varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters, "roundCapCenters");
                 varyingHandler->addVarying("capRadius", &capRadius,
@@ -145,20 +152,20 @@
                 // This is the cap radius in normalized space where the outer radius is 1 and
                 // circledEdge.w is the normalized inner radius.
                 vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(),
-                                         cgp.kInCircleEdge.name());
+                                         cgp.fInCircleEdge->name());
             }
 
             // setup pass through color
-            varyingHandler->addPassThroughAttribute(cgp.kInColor, args.fOutputColor);
+            varyingHandler->addPassThroughAttribute(cgp.fInColor, args.fOutputColor);
 
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, cgp.kInPosition.name());
+            this->writeOutputPosition(vertBuilder, gpArgs, cgp.fInPosition->name());
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 cgp.kInPosition.asShaderVar(),
+                                 cgp.fInPosition->asShaderVar(),
                                  cgp.fLocalMatrix,
                                  args.fFPCoordTransformHandler);
 
@@ -172,22 +179,22 @@
                 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
             }
 
-            if (cgp.fInClipPlane.isInitialized()) {
+            if (cgp.fInClipPlane) {
                 fragBuilder->codeAppend(
                         "half clip = clamp(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + "
                         "clipPlane.z, 0.0, 1.0);");
-                if (cgp.fInIsectPlane.isInitialized()) {
+                if (cgp.fInIsectPlane) {
                     fragBuilder->codeAppend(
                             "clip *= clamp(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + "
                             "isectPlane.z, 0.0, 1.0);");
                 }
-                if (cgp.fInUnionPlane.isInitialized()) {
+                if (cgp.fInUnionPlane) {
                     fragBuilder->codeAppend(
                             "clip += (1.0 - clip)*clamp(circleEdge.z * dot(circleEdge.xy, "
                             "unionPlane.xy) + unionPlane.z, 0.0, 1.0);");
                 }
                 fragBuilder->codeAppend("edgeAlpha *= clip;");
-                if (cgp.fInRoundCapCenters.isInitialized()) {
+                if (cgp.fInRoundCapCenters) {
                     // We compute coverage of the round caps as circles at the butt caps produced
                     // by the clip planes. The inverse of the clip planes is applied so that there
                     // is no double counting.
@@ -211,10 +218,10 @@
             uint16_t key;
             key = cgp.fStroke ? 0x01 : 0x0;
             key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0;
-            key |= cgp.fInClipPlane.isInitialized() ? 0x04 : 0x0;
-            key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0;
-            key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0;
-            key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0;
+            key |= cgp.fInClipPlane ? 0x04 : 0x0;
+            key |= cgp.fInIsectPlane ? 0x08 : 0x0;
+            key |= cgp.fInUnionPlane ? 0x10 : 0x0;
+            key |= cgp.fInRoundCapCenters ? 0x20 : 0x0;
             b->add32(key);
         }
 
@@ -228,31 +235,19 @@
         typedef GrGLSLGeometryProcessor INHERITED;
     };
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthInitializedAttribute(i, kInPosition, kInColor, kInCircleEdge, fInClipPlane,
-                                       fInIsectPlane, fInUnionPlane, fInRoundCapCenters);
-    }
-
     SkMatrix fLocalMatrix;
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType};
-
-    // Optional attributes.
-    Attribute fInClipPlane;
-    Attribute fInIsectPlane;
-    Attribute fInUnionPlane;
-    Attribute fInRoundCapCenters;
-
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInCircleEdge;
+    const Attribute* fInClipPlane;
+    const Attribute* fInIsectPlane;
+    const Attribute* fInUnionPlane;
+    const Attribute* fInRoundCapCenters;
     bool fStroke;
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInColor;
-constexpr GrPrimitiveProcessor::Attribute CircleGeometryProcessor::kInCircleEdge;
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor);
 
@@ -273,7 +268,10 @@
 public:
     ButtCapDashedCircleGeometryProcessor(const SkMatrix& localMatrix)
             : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) {
-        this->setVertexAttributeCnt(4);
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+        fInCircleEdge = &this->addVertexAttrib("inCircleEdge", kFloat4_GrVertexAttribType);
+        fInDashParams = &this->addVertexAttrib("inDashParams", kFloat4_GrVertexAttribType);
     }
 
     ~ButtCapDashedCircleGeometryProcessor() override {}
@@ -304,11 +302,11 @@
             // emit attributes
             varyingHandler->emitAttributes(bcscgp);
             fragBuilder->codeAppend("float4 circleEdge;");
-            varyingHandler->addPassThroughAttribute(bcscgp.kInCircleEdge, "circleEdge");
+            varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge, "circleEdge");
 
             fragBuilder->codeAppend("float4 dashParams;");
             varyingHandler->addPassThroughAttribute(
-                    bcscgp.kInDashParams, "dashParams",
+                    bcscgp.fInDashParams, "dashParams",
                     GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
             GrGLSLVarying wrapDashes(kHalf4_GrSLType);
             varyingHandler->addVarying("wrapDashes", &wrapDashes,
@@ -316,7 +314,7 @@
             GrGLSLVarying lastIntervalLength(kHalf_GrSLType);
             varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength,
                                        GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
-            vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.kInDashParams.name());
+            vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams->name());
             // Our fragment shader works in on/off intervals as specified by dashParams.xy:
             //     x = length of on interval, y = length of on + off.
             // There are two other parameters in dashParams.zw:
@@ -378,17 +376,17 @@
 
             // setup pass through color
             varyingHandler->addPassThroughAttribute(
-                    bcscgp.kInColor, args.fOutputColor,
+                    bcscgp.fInColor, args.fOutputColor,
                     GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
 
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.kInPosition.name());
+            this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition->name());
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 bcscgp.kInPosition.asShaderVar(),
+                                 bcscgp.fInPosition->asShaderVar(),
                                  bcscgp.fLocalMatrix,
                                  args.fFPCoordTransformHandler);
             GrShaderVar fnArgs[] = {
@@ -482,24 +480,16 @@
         typedef GrGLSLGeometryProcessor INHERITED;
     };
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInColor, kInCircleEdge, kInDashParams);
-    }
-
     SkMatrix fLocalMatrix;
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType};
-    static constexpr Attribute kInDashParams = {"inDashParams", kFloat4_GrVertexAttribType};
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInCircleEdge;
+    const Attribute* fInDashParams;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInColor;
-constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInCircleEdge;
-constexpr GrPrimitiveProcessor::Attribute ButtCapDashedCircleGeometryProcessor::kInDashParams;
 
 #if GR_TEST_UTILS
 sk_sp<GrGeometryProcessor> ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
@@ -523,7 +513,10 @@
     EllipseGeometryProcessor(bool stroke, const SkMatrix& localMatrix)
     : INHERITED(kEllipseGeometryProcessor_ClassID)
     , fLocalMatrix(localMatrix) {
-        this->setVertexAttributeCnt(4);
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+        fInEllipseOffset = &this->addVertexAttrib("inEllipseOffset", kHalf2_GrVertexAttribType);
+        fInEllipseRadii = &this->addVertexAttrib("inEllipseRadii", kHalf4_GrVertexAttribType);
         fStroke = stroke;
     }
 
@@ -556,24 +549,24 @@
             GrGLSLVarying ellipseOffsets(kHalf2_GrSLType);
             varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets);
             vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(),
-                                     egp.kInEllipseOffset.name());
+                                     egp.fInEllipseOffset->name());
 
             GrGLSLVarying ellipseRadii(kHalf4_GrSLType);
             varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
-            vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.kInEllipseRadii.name());
+            vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii->name());
 
             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
             // setup pass through color
-            varyingHandler->addPassThroughAttribute(egp.kInColor, args.fOutputColor);
+            varyingHandler->addPassThroughAttribute(egp.fInColor, args.fOutputColor);
 
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, egp.kInPosition.name());
+            this->writeOutputPosition(vertBuilder, gpArgs, egp.fInPosition->name());
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 egp.kInPosition.asShaderVar(),
+                                 egp.fInPosition->asShaderVar(),
                                  egp.fLocalMatrix,
                                  args.fFPCoordTransformHandler);
 
@@ -621,15 +614,10 @@
         typedef GrGLSLGeometryProcessor INHERITED;
     };
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInColor, kInEllipseOffset, kInEllipseRadii);
-    }
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInEllipseOffset = {"inEllipseOffset", kHalf2_GrVertexAttribType};
-    static constexpr Attribute kInEllipseRadii = {"inEllipseRadii", kHalf4_GrVertexAttribType};
-
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInEllipseOffset;
+    const Attribute* fInEllipseRadii;
     SkMatrix fLocalMatrix;
     bool fStroke;
 
@@ -637,10 +625,6 @@
 
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInColor;
-constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInEllipseOffset;
-constexpr GrPrimitiveProcessor::Attribute EllipseGeometryProcessor::kInEllipseRadii;
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor);
 
@@ -669,8 +653,11 @@
     DIEllipseGeometryProcessor(const SkMatrix& viewMatrix, DIEllipseStyle style)
             : INHERITED(kDIEllipseGeometryProcessor_ClassID)
             , fViewMatrix(viewMatrix) {
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+        fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
+        fInEllipseOffsets0 = &this->addVertexAttrib("inEllipseOffsets0", kHalf2_GrVertexAttribType);
+        fInEllipseOffsets1 = &this->addVertexAttrib("inEllipseOffsets1", kHalf2_GrVertexAttribType);
         fStyle = style;
-        this->setVertexAttributeCnt(4);
     }
 
     ~DIEllipseGeometryProcessor() override {}
@@ -701,20 +688,22 @@
 
             GrGLSLVarying offsets0(kHalf2_GrSLType);
             varyingHandler->addVarying("EllipseOffsets0", &offsets0);
-            vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.kInEllipseOffsets0.name());
+            vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(),
+                                     diegp.fInEllipseOffsets0->name());
 
             GrGLSLVarying offsets1(kHalf2_GrSLType);
             varyingHandler->addVarying("EllipseOffsets1", &offsets1);
-            vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.kInEllipseOffsets1.name());
+            vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(),
+                                     diegp.fInEllipseOffsets1->name());
 
             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-            varyingHandler->addPassThroughAttribute(diegp.kInColor, args.fOutputColor);
+            varyingHandler->addPassThroughAttribute(diegp.fInColor, args.fOutputColor);
 
             // Setup position
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      diegp.kInPosition.name(),
+                                      diegp.fInPosition->name(),
                                       diegp.fViewMatrix,
                                       &fViewMatrixUniform);
 
@@ -722,7 +711,7 @@
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 diegp.kInPosition.asShaderVar(),
+                                 diegp.fInPosition->asShaderVar(),
                                  args.fFPCoordTransformHandler);
 
             // for outer curve
@@ -793,17 +782,10 @@
         typedef GrGLSLGeometryProcessor INHERITED;
     };
 
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthAttribute(i, kInPosition, kInColor, kInEllipseOffsets0, kInEllipseOffsets1);
-    }
-
-    static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
-    static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
-    static constexpr Attribute kInEllipseOffsets0 = {"inEllipseOffsets0",
-                                                     kHalf2_GrVertexAttribType};
-    static constexpr Attribute kInEllipseOffsets1 = {"inEllipseOffsets1",
-                                                     kHalf2_GrVertexAttribType};
-
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInEllipseOffsets0;
+    const Attribute* fInEllipseOffsets1;
     SkMatrix fViewMatrix;
     DIEllipseStyle fStyle;
 
@@ -811,10 +793,6 @@
 
     typedef GrGeometryProcessor INHERITED;
 };
-constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInPosition;
-constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInColor;
-constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInEllipseOffsets0;
-constexpr GrPrimitiveProcessor::Attribute DIEllipseGeometryProcessor::kInEllipseOffsets1;
 
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor);
 
@@ -1163,11 +1141,11 @@
         auto vertexCapCenters = [numPlanes](CircleVertex* v) {
             return (void*)(v->fHalfPlanes + numPlanes);
         };
-        size_t vertexStride = sizeof(CircleVertex) - (fClipPlane ? 0 : 3 * sizeof(SkScalar)) -
-                              (fClipPlaneIsect ? 0 : 3 * sizeof(SkScalar)) -
-                              (fClipPlaneUnion ? 0 : 3 * sizeof(SkScalar)) +
-                              (fRoundCaps ? 2 * sizeof(SkPoint) : 0);
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex) - (fClipPlane ? 0 : 3 * sizeof(SkScalar)) -
+                                         (fClipPlaneIsect ? 0 : 3 * sizeof(SkScalar)) -
+                                         (fClipPlaneUnion ? 0 : 3 * sizeof(SkScalar)) +
+                                         (fRoundCaps ? 2 * sizeof(SkPoint) : 0));
 
         const GrBuffer* vertexBuffer;
         int firstVertex;
@@ -1651,12 +1629,12 @@
             SkScalar fPhaseAngle;
         };
 
-        static constexpr size_t kVertexStride = sizeof(CircleVertex);
-        SkASSERT(kVertexStride == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
 
         const GrBuffer* vertexBuffer;
         int firstVertex;
-        char* vertices = (char*)target->makeVertexSpace(kVertexStride, fVertCount, &vertexBuffer,
+        char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount, &vertexBuffer,
                                                         &firstVertex);
         if (!vertices) {
             SkDebugf("Could not allocate vertices\n");
@@ -1707,7 +1685,7 @@
             SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
             SkScalar halfWidth = 0.5f * bounds.width();
             auto init_outer_vertex = [&](int idx, SkScalar x, SkScalar y) {
-                CircleVertex* v = reinterpret_cast<CircleVertex*>(vertices + idx * kVertexStride);
+                CircleVertex* v = reinterpret_cast<CircleVertex*>(vertices + idx * vertexStride);
                 v->fPos = center + SkPoint{x * halfWidth, y * halfWidth};
                 v->fOffset = {x, y};
                 init_const_attrs_and_reflect(v);
@@ -1725,7 +1703,7 @@
             // Compute the vertices of the inner octagon.
             auto init_inner_vertex = [&](int idx, SkScalar x, SkScalar y) {
                 CircleVertex* v =
-                        reinterpret_cast<CircleVertex*>(vertices + (idx + 8) * kVertexStride);
+                        reinterpret_cast<CircleVertex*>(vertices + (idx + 8) * vertexStride);
                 v->fPos = center + SkPoint{x * circle.fInnerRadius, y * circle.fInnerRadius};
                 v->fOffset = {x * normInnerRadius, y * normInnerRadius};
                 init_const_attrs_and_reflect(v);
@@ -1751,7 +1729,7 @@
             }
 
             currStartVertex += circle_type_to_vert_count(true);
-            vertices += circle_type_to_vert_count(true) * kVertexStride;
+            vertices += circle_type_to_vert_count(true) * vertexStride;
         }
 
         GrMesh mesh(GrPrimitiveType::kTriangles);
@@ -1953,9 +1931,10 @@
         sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix));
 
         QuadHelper helper;
-        SkASSERT(sizeof(EllipseVertex) == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(EllipseVertex));
         EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(
-                helper.init(target, sizeof(EllipseVertex), fEllipses.count()));
+                helper.init(target, vertexStride, fEllipses.count()));
         if (!verts) {
             return;
         }
@@ -2181,10 +2160,11 @@
         sk_sp<GrGeometryProcessor> gp(
                 new DIEllipseGeometryProcessor(this->viewMatrix(), this->style()));
 
-        SkASSERT(sizeof(DIEllipseVertex) == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(DIEllipseVertex));
         QuadHelper helper;
         DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>(
-                helper.init(target, sizeof(DIEllipseVertex), fEllipses.count()));
+                helper.init(target, vertexStride, fEllipses.count()));
         if (!verts) {
             return;
         }
@@ -2595,13 +2575,14 @@
         sk_sp<GrGeometryProcessor> gp(
                 new CircleGeometryProcessor(!fAllFill, false, false, false, false, localMatrix));
 
-        SkASSERT(sizeof(CircleVertex) == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(sizeof(CircleVertex) == vertexStride);
 
         const GrBuffer* vertexBuffer;
         int firstVertex;
 
-        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
-                sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
+        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount,
+                                                                     &vertexBuffer, &firstVertex);
         if (!verts) {
             SkDebugf("Could not allocate vertices\n");
             return;
@@ -2883,7 +2864,8 @@
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix));
 
-        SkASSERT(sizeof(EllipseVertex) == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(EllipseVertex));
 
         // drop out the middle quad if we're stroked
         int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
@@ -2892,8 +2874,8 @@
 
         PatternHelper helper(GrPrimitiveType::kTriangles);
         EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(
-                helper.init(target, sizeof(EllipseVertex), indexBuffer.get(),
-                            kVertsPerStandardRRect, indicesPerInstance, fRRects.count()));
+                helper.init(target, vertexStride, indexBuffer.get(), kVertsPerStandardRRect,
+                            indicesPerInstance, fRRects.count()));
         if (!verts || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
             return;
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 9a9814e..9e81d66 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -115,8 +115,7 @@
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
         }
-        static constexpr size_t kVertexStride = sizeof(GrDefaultGeoProcFactory::PositionColorAttr);
-        SkASSERT(kVertexStride == gp->debugOnly_vertexStride());
+        SkASSERT(gp->getVertexStride() == sizeof(GrDefaultGeoProcFactory::PositionColorAttr));
 
         int numRegions = fRegions.count();
         int numRects = 0;
@@ -127,10 +126,12 @@
         if (!numRects) {
             return;
         }
+        size_t vertexStride = gp->getVertexStride();
         sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
         PatternHelper helper(GrPrimitiveType::kTriangles);
-        void* vertices = helper.init(target, kVertexStride, indexBuffer.get(), kVertsPerInstance,
-                                     kIndicesPerInstance, numRects);
+        void* vertices =
+                helper.init(target, vertexStride, indexBuffer.get(), kVertsPerInstance,
+                            kIndicesPerInstance, numRects);
         if (!vertices || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
             return;
@@ -138,9 +139,9 @@
 
         intptr_t verts = reinterpret_cast<intptr_t>(vertices);
         for (int i = 0; i < numRegions; i++) {
-            tesselate_region(verts, kVertexStride, fRegions[i].fColor, fRegions[i].fRegion);
+            tesselate_region(verts, vertexStride, fRegions[i].fColor, fRegions[i].fRegion);
             int numRectsInRegion = fRegions[i].fRegion.computeRegionComplexity();
-            verts += numRectsInRegion * kVertsPerInstance * kVertexStride;
+            verts += numRectsInRegion * kVertsPerInstance * vertexStride;
         }
         helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
     }
diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp
index f6b2003..a2ec230 100644
--- a/src/gpu/ops/GrShadowRRectOp.cpp
+++ b/src/gpu/ops/GrShadowRRectOp.cpp
@@ -576,12 +576,13 @@
         sk_sp<GrGeometryProcessor> gp = GrRRectShadowGeoProc::Make();
 
         int instanceCount = fGeoData.count();
-        SkASSERT(sizeof(CircleVertex) == gp->debugOnly_vertexStride());
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(sizeof(CircleVertex) == vertexStride);
 
         const GrBuffer* vertexBuffer;
         int firstVertex;
-        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
-                sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
+        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount,
+                                                                     &vertexBuffer, &firstVertex);
         if (!verts) {
             SkDebugf("Could not allocate vertices\n");
             return;
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index a0c9f9c..4ffe478 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -351,9 +351,8 @@
         }
 
         // allocate vertices
-        static constexpr size_t kVertexStride =
-                sizeof(SkPoint) + sizeof(GrColor) + 2 * sizeof(uint16_t);
-        SkASSERT(kVertexStride == flushInfo.fGeometryProcessor->debugOnly_vertexStride());
+        size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
+        SkASSERT(vertexStride == sizeof(SkPoint) + sizeof(GrColor) + 2*sizeof(uint16_t));
 
         const GrBuffer* vertexBuffer;
 
@@ -362,7 +361,7 @@
         if (instanceCount > SK_MaxS32 / kVerticesPerQuad) {
             return;
         }
-        void* vertices = target->makeVertexSpace(kVertexStride,
+        void* vertices = target->makeVertexSpace(vertexStride,
                                                  kVerticesPerQuad * instanceCount,
                                                  &vertexBuffer,
                                                  &flushInfo.fVertexOffset);
@@ -477,9 +476,13 @@
             auto uploadTarget = target->deferredUploadTarget();
             fAtlas->setLastUseToken(shapeData->fID, uploadTarget->tokenTracker()->nextDrawToken());
 
-            this->writePathVertices(
-                    fAtlas, offset, args.fColor, kVertexStride, args.fViewMatrix, shapeData);
-            offset += kVerticesPerQuad * kVertexStride;
+            this->writePathVertices(fAtlas,
+                                    offset,
+                                    args.fColor,
+                                    vertexStride,
+                                    args.fViewMatrix,
+                                    shapeData);
+            offset += kVerticesPerQuad * vertexStride;
             flushInfo.fInstancesToFlush++;
         }
 
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index 4c436c2..51dac5a 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -237,7 +237,7 @@
         return path;
     }
 
-    void draw(Target* target, const GrGeometryProcessor* gp, size_t vertexStride) {
+    void draw(Target* target, const GrGeometryProcessor* gp) {
         SkASSERT(!fAntiAlias);
         GrResourceProvider* rp = target->resourceProvider();
         bool inverseFill = fShape.inverseFilled();
@@ -274,7 +274,7 @@
         vmi.mapRect(&clipBounds);
         bool isLinear;
         bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
-        StaticVertexAllocator allocator(vertexStride, rp, canMapVB);
+        StaticVertexAllocator allocator(gp->getVertexStride(), rp, canMapVB);
         int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator,
                                                    false, GrColor(), false, &isLinear);
         if (count == 0) {
@@ -289,7 +289,7 @@
         fShape.addGenIDChangeListener(sk_make_sp<PathInvalidator>(key));
     }
 
-    void drawAA(Target* target, const GrGeometryProcessor* gp, size_t vertexStride) {
+    void drawAA(Target* target, const GrGeometryProcessor* gp) {
         SkASSERT(fAntiAlias);
         SkPath path = getPath();
         if (path.isEmpty()) {
@@ -299,7 +299,7 @@
         path.transform(fViewMatrix);
         SkScalar tol = GrPathUtils::kDefaultTolerance;
         bool isLinear;
-        DynamicVertexAllocator allocator(vertexStride, target);
+        DynamicVertexAllocator allocator(gp->getVertexStride(), target);
         int count =
                 GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator, true, fColor,
                                                fHelper.compatibleWithAlphaAsCoverage(), &isLinear);
@@ -311,12 +311,9 @@
 
     void onPrepareDraws(Target* target) override {
         sk_sp<GrGeometryProcessor> gp;
-        size_t vertexStride;
         {
             using namespace GrDefaultGeoProcFactory;
 
-            vertexStride = sizeof(SkPoint);  // position
-
             Color color(fColor);
             LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
                                                         ? LocalCoords::kUsePosition_Type
@@ -324,12 +321,10 @@
             Coverage::Type coverageType;
             if (fAntiAlias) {
                 color = Color(Color::kPremulGrColorAttribute_Type);
-                vertexStride += sizeof(uint32_t);
                 if (fHelper.compatibleWithAlphaAsCoverage()) {
                     coverageType = Coverage::kSolid_Type;
                 } else {
                     coverageType = Coverage::kAttribute_Type;
-                    vertexStride += 4;
                 }
             } else {
                 coverageType = Coverage::kSolid_Type;
@@ -345,11 +340,10 @@
         if (!gp.get()) {
             return;
         }
-        SkASSERT(vertexStride == gp->debugOnly_vertexStride());
         if (fAntiAlias) {
-            this->drawAA(target, gp.get(), vertexStride);
+            this->drawAA(target, gp.get());
         } else {
-            this->draw(target, gp.get(), vertexStride);
+            this->draw(target, gp.get());
         }
     }
 
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 95378ac..80174bc 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -163,14 +163,16 @@
                                      args.fUniformHandler,
                                      textureGP.fTextureCoords.asShaderVar(),
                                      args.fFPCoordTransformHandler);
-                args.fVaryingHandler->addPassThroughAttribute(
-                        textureGP.fColors, args.fOutputColor, Interpolation::kCanBeFlat);
+                args.fVaryingHandler->addPassThroughAttribute(&textureGP.fColors,
+                                                              args.fOutputColor,
+                                                              Interpolation::kCanBeFlat);
                 args.fFragBuilder->codeAppend("float2 texCoord;");
-                args.fVaryingHandler->addPassThroughAttribute(textureGP.fTextureCoords, "texCoord");
+                args.fVaryingHandler->addPassThroughAttribute(&textureGP.fTextureCoords,
+                                                              "texCoord");
                 if (textureGP.fDomain.isInitialized()) {
                     args.fFragBuilder->codeAppend("float4 domain;");
                     args.fVaryingHandler->addPassThroughAttribute(
-                            textureGP.fDomain, "domain",
+                            &textureGP.fDomain, "domain",
                             GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
                     args.fFragBuilder->codeAppend(
                             "texCoord = clamp(texCoord, domain.xy, domain.zw);");
@@ -180,7 +182,7 @@
                     SkASSERT(kInt_GrVertexAttribType == textureGP.fTextureIdx.type());
                     SkASSERT(args.fShaderCaps->integerSupport());
                     args.fFragBuilder->codeAppend("int texIdx;");
-                    args.fVaryingHandler->addPassThroughAttribute(textureGP.fTextureIdx, "texIdx",
+                    args.fVaryingHandler->addPassThroughAttribute(&textureGP.fTextureIdx, "texIdx",
                                                                   Interpolation::kMustBeFlat);
                     args.fFragBuilder->codeAppend("switch (texIdx) {");
                     for (int i = 0; i < textureGP.numTextureSamplers(); ++i) {
@@ -314,13 +316,12 @@
         }
 
         if (perspective) {
-            fPositions = {"position", kFloat3_GrVertexAttribType};
+            fPositions = this->addVertexAttrib("position", kFloat3_GrVertexAttribType);
         } else {
-            fPositions = {"position", kFloat2_GrVertexAttribType};
+            fPositions = this->addVertexAttrib("position", kFloat2_GrVertexAttribType);
         }
-        fColors = {"color", kUByte4_norm_GrVertexAttribType};
-        fTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType};
-        int vertexAttributeCnt = 3;
+        fColors = this->addVertexAttrib("color", kUByte4_norm_GrVertexAttribType);
+        fTextureCoords = this->addVertexAttrib("textureCoords", kFloat2_GrVertexAttribType);
 
         if (samplerCnt > 1) {
             // Here we initialize any extra samplers by repeating the last one samplerCnt - proxyCnt
@@ -331,26 +332,17 @@
                 this->addTextureSampler(&fSamplers[i]);
             }
             SkASSERT(caps.integerSupport());
-            fTextureIdx = {"textureIdx", kInt_GrVertexAttribType};
-            ++vertexAttributeCnt;
+            fTextureIdx = this->addVertexAttrib("textureIdx", kInt_GrVertexAttribType);
         }
         if (domain == Domain::kYes) {
-            fDomain = {"domain", kFloat4_GrVertexAttribType};
-            ++vertexAttributeCnt;
+            fDomain = this->addVertexAttrib("domain", kFloat4_GrVertexAttribType);
         }
         if (coverageAA) {
-            fAAEdges[0] = {"aaEdge0", kFloat3_GrVertexAttribType};
-            fAAEdges[1] = {"aaEdge1", kFloat3_GrVertexAttribType};
-            fAAEdges[2] = {"aaEdge2", kFloat3_GrVertexAttribType};
-            fAAEdges[3] = {"aaEdge3", kFloat3_GrVertexAttribType};
-            vertexAttributeCnt += 4;
+            fAAEdges[0] = this->addVertexAttrib("aaEdge0", kFloat3_GrVertexAttribType);
+            fAAEdges[1] = this->addVertexAttrib("aaEdge1", kFloat3_GrVertexAttribType);
+            fAAEdges[2] = this->addVertexAttrib("aaEdge2", kFloat3_GrVertexAttribType);
+            fAAEdges[3] = this->addVertexAttrib("aaEdge3", kFloat3_GrVertexAttribType);
         }
-        this->setVertexAttributeCnt(vertexAttributeCnt);
-    }
-
-    const Attribute& onVertexAttribute(int i) const override {
-        return IthInitializedAttribute(i, fPositions, fColors, fTextureCoords, fTextureIdx, fDomain,
-                                       fAAEdges[0], fAAEdges[1], fAAEdges[2], fAAEdges[3]);
     }
 
     Attribute fPositions;
@@ -752,7 +744,7 @@
     template <typename Pos, MultiTexture MT, Domain D, GrAA AA>
     void tess(void* v, const float iw[], const float ih[], const GrGeometryProcessor* gp) {
         using Vertex = TextureGeometryProcessor::Vertex<Pos, MT, D, AA>;
-        SkASSERT(gp->debugOnly_vertexStride() == sizeof(Vertex));
+        SkASSERT(gp->getVertexStride() == sizeof(Vertex));
         auto vertices = static_cast<Vertex*>(v);
         auto proxies = this->proxies();
         auto filters = this->filters();
@@ -793,47 +785,10 @@
 
         const GrPipeline* pipeline = target->allocPipeline(args, GrProcessorSet::MakeEmptySet(),
                                                            target->detachAppliedClip());
-        using TessFn =
-                decltype(&TextureOp::tess<SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kNo>);
-#define TESS_FN_AND_VERTEX_SIZE(Point, MT, Domain, AA)                          \
-    {                                                                           \
-        &TextureOp::tess<Point, MT, Domain, AA>,                                \
-                sizeof(TextureGeometryProcessor::Vertex<Point, MT, Domain, AA>) \
-    }
-        static constexpr struct {
-            TessFn fTessFn;
-            size_t fVertexSize;
-        } kTessFnsAndVertexSizes[] = {
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kNo, Domain::kYes, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kNo, Domain::kYes, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kYes, Domain::kNo, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kYes, Domain::kNo, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kYes, Domain::kYes, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint, MultiTexture::kYes, Domain::kYes, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kNo, Domain::kNo, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kNo, Domain::kNo, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kNo, Domain::kYes, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kNo, Domain::kYes, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kYes, Domain::kNo, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kYes, Domain::kNo, GrAA::kYes),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kNo),
-                TESS_FN_AND_VERTEX_SIZE(SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kYes),
-        };
-#undef TESS_FN_AND_VERTEX_SIZE
-        int tessFnIdx = 0;
-        tessFnIdx |= coverageAA      ? 0x1 : 0x0;
-        tessFnIdx |= fDomain         ? 0x2 : 0x0;
-        tessFnIdx |= (fProxyCnt > 1) ? 0x4 : 0x0;
-        tessFnIdx |= fPerspective    ? 0x8 : 0x0;
-
-        SkASSERT(kTessFnsAndVertexSizes[tessFnIdx].fVertexSize == gp->debugOnly_vertexStride());
-
         int vstart;
         const GrBuffer* vbuffer;
-        void* vdata = target->makeVertexSpace(kTessFnsAndVertexSizes[tessFnIdx].fVertexSize,
-                                              4 * fDraws.count(), &vbuffer, &vstart);
+        void* vdata = target->makeVertexSpace(gp->getVertexStride(), 4 * fDraws.count(), &vbuffer,
+                                              &vstart);
         if (!vdata) {
             SkDebugf("Could not allocate vertices\n");
             return;
@@ -847,7 +802,32 @@
             ih[t] = 1.f / texture->height();
         }
 
-        (this->*(kTessFnsAndVertexSizes[tessFnIdx].fTessFn))(vdata, iw, ih, gp.get());
+        using TessFn =
+                decltype(&TextureOp::tess<SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kNo>);
+        static constexpr TessFn kTessFns[] = {
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kYes>,
+        };
+        int tessFnIdx = 0;
+        tessFnIdx |= coverageAA      ? 0x1 : 0x0;
+        tessFnIdx |= fDomain         ? 0x2 : 0x0;
+        tessFnIdx |= (fProxyCnt > 1) ? 0x4 : 0x0;
+        tessFnIdx |= fPerspective    ? 0x8 : 0x0;
+        (this->*(kTessFns[tessFnIdx]))(vdata, iw, ih, gp.get());
 
         GrPrimitiveType primitiveType =
                 fDraws.count() > 1 ? GrPrimitiveType::kTriangles : GrPrimitiveType::kTriangleStrip;
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index c5b4592..500ab96 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -59,58 +59,37 @@
                                   VkVertexInputAttributeDescription* attributeDesc) {
     uint32_t vertexBinding = 0, instanceBinding = 0;
 
-    int nextBinding = bindingDescs->count();
-    if (primProc.hasVertexAttributes()) {
-        vertexBinding = nextBinding++;
+    if (primProc.hasVertexAttribs()) {
+        vertexBinding = bindingDescs->count();
+        bindingDescs->push_back() = {
+            vertexBinding,
+            (uint32_t) primProc.getVertexStride(),
+            VK_VERTEX_INPUT_RATE_VERTEX
+        };
     }
 
-    if (primProc.hasInstanceAttributes()) {
-        instanceBinding = nextBinding;
+    if (primProc.hasInstanceAttribs()) {
+        instanceBinding = bindingDescs->count();
+        bindingDescs->push_back() = {
+            instanceBinding,
+            (uint32_t) primProc.getInstanceStride(),
+            VK_VERTEX_INPUT_RATE_INSTANCE
+        };
     }
 
     // setup attribute descriptions
-    int vaCount = primProc.numVertexAttributes();
-    int attribIndex = 0;
-    size_t vertexAttributeOffset = 0;
-    for (; attribIndex < vaCount; attribIndex++) {
-        const GrGeometryProcessor::Attribute& attrib = primProc.vertexAttribute(attribIndex);
-        VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex];
-        vkAttrib.location = attribIndex;  // for now assume location = attribIndex
-        vkAttrib.binding = vertexBinding;
-        vkAttrib.format = attrib_type_to_vkformat(attrib.type());
-        vkAttrib.offset = vertexAttributeOffset;
-        SkASSERT(vkAttrib.offset == primProc.debugOnly_vertexAttributeOffset(attribIndex));
-        vertexAttributeOffset += attrib.sizeAlign4();
-    }
-    SkASSERT(vertexAttributeOffset == primProc.debugOnly_vertexStride());
-
-    int iaCount = primProc.numInstanceAttributes();
-    size_t instanceAttributeOffset = 0;
-    for (int iaIndex = 0; iaIndex < iaCount; ++iaIndex, ++attribIndex) {
-        const GrGeometryProcessor::Attribute& attrib = primProc.instanceAttribute(iaIndex);
-        VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex];
-        vkAttrib.location = attribIndex;  // for now assume location = attribIndex
-        vkAttrib.binding = instanceBinding;
-        vkAttrib.format = attrib_type_to_vkformat(attrib.type());
-        vkAttrib.offset = instanceAttributeOffset;
-        SkASSERT(vkAttrib.offset == primProc.debugOnly_instanceAttributeOffset(iaIndex));
-        instanceAttributeOffset += attrib.sizeAlign4();
-    }
-    SkASSERT(instanceAttributeOffset == primProc.debugOnly_instanceStride());
-
-    if (primProc.hasVertexAttributes()) {
-        bindingDescs->push_back() = {
-                vertexBinding,
-                (uint32_t) vertexAttributeOffset,
-                VK_VERTEX_INPUT_RATE_VERTEX
-        };
-    }
-    if (primProc.hasInstanceAttributes()) {
-        bindingDescs->push_back() = {
-                instanceBinding,
-                (uint32_t) instanceAttributeOffset,
-                VK_VERTEX_INPUT_RATE_INSTANCE
-        };
+    int vaCount = primProc.numAttribs();
+    if (vaCount > 0) {
+        for (int attribIndex = 0; attribIndex < vaCount; attribIndex++) {
+            using InputRate = GrPrimitiveProcessor::Attribute::InputRate;
+            const GrGeometryProcessor::Attribute& attrib = primProc.getAttrib(attribIndex);
+            VkVertexInputAttributeDescription& vkAttrib = attributeDesc[attribIndex];
+            vkAttrib.location = attribIndex; // for now assume location = attribIndex
+            vkAttrib.binding =
+                    InputRate::kPerInstance == attrib.inputRate() ? instanceBinding : vertexBinding;
+            vkAttrib.format = attrib_type_to_vkformat(attrib.type());
+            vkAttrib.offset = attrib.offsetInRecord();
+        }
     }
 
     memset(vertexInputInfo, 0, sizeof(VkPipelineVertexInputStateCreateInfo));
@@ -119,7 +98,7 @@
     vertexInputInfo->flags = 0;
     vertexInputInfo->vertexBindingDescriptionCount = bindingDescs->count();
     vertexInputInfo->pVertexBindingDescriptions = bindingDescs->begin();
-    vertexInputInfo->vertexAttributeDescriptionCount = vaCount + iaCount;
+    vertexInputInfo->vertexAttributeDescriptionCount = vaCount;
     vertexInputInfo->pVertexAttributeDescriptions = attributeDesc;
 }
 
@@ -453,9 +432,8 @@
     VkPipelineVertexInputStateCreateInfo vertexInputInfo;
     SkSTArray<2, VkVertexInputBindingDescription, true> bindingDescs;
     SkSTArray<16, VkVertexInputAttributeDescription> attributeDesc;
-    int totalAttributeCnt = primProc.numVertexAttributes() + primProc.numInstanceAttributes();
-    SkASSERT(totalAttributeCnt <= gpu->vkCaps().maxVertexAttributes());
-    VkVertexInputAttributeDescription* pAttribs = attributeDesc.push_back_n(totalAttributeCnt);
+    SkASSERT(primProc.numAttribs() <= gpu->vkCaps().maxVertexAttributes());
+    VkVertexInputAttributeDescription* pAttribs = attributeDesc.push_back_n(primProc.numAttribs());
     setup_vertex_input_state(primProc, &vertexInputInfo, &bindingDescs, pAttribs);
 
     VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;