Fix glDrawRangeElements

Adds explicit min/max index value fields to GrMesh. This eliminates the
previous assumption that the index values were within the range
[0..vertexCount-1]. In the pattern case we still maintain this
assumption.

Updates GrMesh to hide its fields and handle its new complexity using a
"helper" interface instead.

Adds a unit test for GrMesh.

Bug: skia:
Change-Id: Ia23de72d510f8827cee56072b727fb70a6e46b8d
Reviewed-on: https://skia-review.googlesource.com/17964
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index 043c54f..0c7bb97 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -43,7 +43,7 @@
                               const SkRect& bounds) {
 #ifdef SK_DEBUG
     for (int i = 0; i < meshCount; ++i) {
-        SkASSERT(SkToBool(primProc.numAttribs()) == SkToBool(meshes[i].vertexBuffer()));
+        SkASSERT(SkToBool(primProc.numAttribs()) == meshes[i].hasVertexData());
     }
 #endif
 
diff --git a/src/gpu/GrMesh.h b/src/gpu/GrMesh.h
index 6aed8d3..98f9911 100644
--- a/src/gpu/GrMesh.h
+++ b/src/gpu/GrMesh.h
@@ -11,6 +11,8 @@
 #include "GrBuffer.h"
 #include "GrGpuResourceRef.h"
 
+class GrPrimitiveProcessor;
+
 /**
  * Used to communicate index and vertex buffers, counts, and offsets for a draw from GrOp to
  * GrGpu. It also holds the primitive type for the draw. TODO: Consider moving ownership of this
@@ -20,28 +22,39 @@
 class GrMesh {
 public:
     GrMesh(GrPrimitiveType primitiveType)
-        : fPrimitiveType(primitiveType) {
-        SkDEBUGCODE(fVertexCount = 0;)
-        SkDEBUGCODE(fBaseVertex = -1;)
+        : fPrimitiveType(primitiveType)
+        , fBaseVertex(0) {
+        SkDEBUGCODE(fNonIndexData.fVertexCount = -1);
     }
 
-    void setNonIndexed();
-    void setIndexed(const GrBuffer* indexBuffer, int indexCount, int baseIndex = 0);
-    void setIndexedPatterned(const GrBuffer* indexBuffer, int indexCount,
+    GrPrimitiveType primitiveType() const { return fPrimitiveType; }
+    bool isIndexed() const { return SkToBool(fIndexBuffer.get()); }
+    bool hasVertexData() const { return SkToBool(fVertexBuffer.get()); }
+
+    void setNonIndexed(int vertexCount);
+    void setIndexed(const GrBuffer* indexBuffer, int indexCount, int baseIndex,
+                    uint16_t minIndexValue, uint16_t maxIndexValue);
+    void setIndexedPatterned(const GrBuffer* indexBuffer, int indexCount, int vertexCount,
                              int patternRepeatCount, int maxPatternRepetitionsInIndexBuffer);
 
-    void setVertices(const GrBuffer* vertexBuffer, int vertexCount, int baseVertex = 0);
+    void setVertexData(const GrBuffer* vertexBuffer, int baseVertex = 0);
 
-    GrPrimitiveType primitiveType() const { return fPrimitiveType; }
+    class SendToGpuImpl {
+    public:
+        virtual void sendMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                                   const GrBuffer* vertexBuffer, int vertexCount,
+                                   int baseVertex) = 0;
 
-    bool isIndexed() const { return SkToBool(fIndexBuffer.get()); }
-    const GrBuffer* indexBuffer() const { return fIndexBuffer.get(); }
-    int indexCount() const { SkASSERT(this->isIndexed()); return fIndexCount; }
-    int baseIndex() const { SkASSERT(this->isIndexed()); return fBaseIndex; }
+        virtual void sendIndexedMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                                          const GrBuffer* indexBuffer, int indexCount,
+                                          int baseIndex, uint16_t minIndexValue,
+                                          uint16_t maxIndexValue, const GrBuffer* vertexBuffer,
+                                          int baseVertex) = 0;
 
-    const GrBuffer* vertexBuffer() const { return fVertexBuffer.get(); }
-    int vertexCount() const { SkASSERT(fVertexCount >= 1); return fVertexCount; }
-    int baseVertex() const { SkASSERT(fBaseVertex >= 0); return fBaseVertex; }
+        virtual ~SendToGpuImpl() {}
+    };
+
+    void sendToGpu(const GrPrimitiveProcessor&, SendToGpuImpl*) const;
 
     struct PatternBatch;
 
@@ -49,98 +62,108 @@
     using PendingBuffer = GrPendingIOResource<const GrBuffer, kRead_GrIOType>;
 
     GrPrimitiveType   fPrimitiveType;
-
     PendingBuffer     fIndexBuffer;
-    int               fIndexCount;
-    int               fBaseIndex;
-    int               fPatternRepeatCount;
-    int               fMaxPatternRepetitionsInIndexBuffer;
-
     PendingBuffer     fVertexBuffer;
-    int               fVertexCount;
     int               fBaseVertex;
 
-    class PatternIterator;
-    friend GrMesh::PatternIterator begin(const GrMesh&);
-    friend GrMesh::PatternIterator end(const GrMesh&);
+    union {
+        struct { // When fIndexBuffer == nullptr.
+            int   fVertexCount;
+        } fNonIndexData;
+
+        struct { // When fIndexBuffer != nullptr.
+            struct {
+                int   fIndexCount;
+                int   fPatternRepeatCount;
+            } fIndexData;
+
+            union {
+                struct { // When fPatternRepeatCount == 0.
+                    int        fBaseIndex;
+                    uint16_t   fMinIndexValue;
+                    uint16_t   fMaxIndexValue;
+                } fNonPatternIndexData;
+
+                struct { // When fPatternRepeatCount != 0.
+                    int   fVertexCount;
+                    int   fMaxPatternRepetitionsInIndexBuffer;
+                } fPatternData;
+            };
+        };
+    };
 };
 
-inline void GrMesh::setNonIndexed() {
+inline void GrMesh::setNonIndexed(int vertexCount) {
     fIndexBuffer.reset(nullptr);
+    fNonIndexData.fVertexCount = vertexCount;
 }
 
-inline void GrMesh::setIndexed(const GrBuffer* indexBuffer, int indexCount, int baseIndex) {
+inline void GrMesh::setIndexed(const GrBuffer* indexBuffer, int indexCount, int baseIndex,
+                               uint16_t minIndexValue, uint16_t maxIndexValue) {
     SkASSERT(indexBuffer);
     SkASSERT(indexCount >= 1);
     SkASSERT(baseIndex >= 0);
+    SkASSERT(maxIndexValue > minIndexValue);
     fIndexBuffer.reset(indexBuffer);
-    fIndexCount = indexCount;
-    fBaseIndex = baseIndex;
-    fPatternRepeatCount = fMaxPatternRepetitionsInIndexBuffer = 1;
+    fIndexData.fIndexCount = indexCount;
+    fIndexData.fPatternRepeatCount = 0;
+    fNonPatternIndexData.fBaseIndex = baseIndex;
+    fNonPatternIndexData.fMinIndexValue = minIndexValue;
+    fNonPatternIndexData.fMaxIndexValue = maxIndexValue;
 }
 
 inline void GrMesh::setIndexedPatterned(const GrBuffer* indexBuffer, int indexCount,
-                                        int patternRepeatCount,
+                                        int vertexCount, int patternRepeatCount,
                                         int maxPatternRepetitionsInIndexBuffer) {
     SkASSERT(indexBuffer);
     SkASSERT(indexCount >= 1);
+    SkASSERT(vertexCount >= 1);
     SkASSERT(patternRepeatCount >= 1);
     SkASSERT(maxPatternRepetitionsInIndexBuffer >= 1);
     fIndexBuffer.reset(indexBuffer);
-    fIndexCount = indexCount;
-    fBaseIndex = 0;
-    fPatternRepeatCount = patternRepeatCount;
-    fMaxPatternRepetitionsInIndexBuffer = maxPatternRepetitionsInIndexBuffer;
+    fIndexData.fIndexCount = indexCount;
+    fIndexData.fPatternRepeatCount = patternRepeatCount;
+    fPatternData.fVertexCount = vertexCount;
+    fPatternData.fMaxPatternRepetitionsInIndexBuffer = maxPatternRepetitionsInIndexBuffer;
 }
 
-inline void GrMesh::setVertices(const GrBuffer* vertexBuffer, int vertexCount, int baseVertex) {
+inline void GrMesh::setVertexData(const GrBuffer* vertexBuffer, int baseVertex) {
+    SkASSERT(baseVertex >= 0);
     fVertexBuffer.reset(vertexBuffer);
-    fVertexCount = vertexCount;
     fBaseVertex = baseVertex;
 }
 
-struct GrMesh::PatternBatch {
-    int   fBaseVertex;
-    int   fRepeatCount;
-};
-
-class GrMesh::PatternIterator {
-public:
-    PatternIterator(const GrMesh& mesh, int repetitionIdx)
-        : fMesh(mesh)
-        , fRepetitionIdx(repetitionIdx) {
-        SkASSERT(fMesh.isIndexed());
+inline void GrMesh::sendToGpu(const GrPrimitiveProcessor& primProc, SendToGpuImpl* impl) const {
+    if (!this->isIndexed()) {
+        SkASSERT(fNonIndexData.fVertexCount > 0);
+        impl->sendMeshToGpu(primProc, fPrimitiveType, fVertexBuffer.get(),
+                            fNonIndexData.fVertexCount, fBaseVertex);
+        return;
     }
 
-    bool operator!=(const PatternIterator& that) {
-        SkASSERT(&fMesh == &that.fMesh);
-        return fRepetitionIdx != that.fRepetitionIdx;
+    if (0 == fIndexData.fPatternRepeatCount) {
+        impl->sendIndexedMeshToGpu(primProc, fPrimitiveType, fIndexBuffer.get(),
+                                   fIndexData.fIndexCount, fNonPatternIndexData.fBaseIndex,
+                                   fNonPatternIndexData.fMinIndexValue,
+                                   fNonPatternIndexData.fMaxIndexValue, fVertexBuffer.get(),
+                                   fBaseVertex);
+        return;
     }
 
-    const PatternBatch operator*() {
-        PatternBatch batch;
-        batch.fBaseVertex = fMesh.fBaseVertex + fRepetitionIdx * fMesh.fVertexCount;
-        batch.fRepeatCount = SkTMin(fMesh.fPatternRepeatCount - fRepetitionIdx,
-                                    fMesh.fMaxPatternRepetitionsInIndexBuffer);
-        return batch;
-    }
-
-    void operator++() {
-        fRepetitionIdx = SkTMin(fRepetitionIdx + fMesh.fMaxPatternRepetitionsInIndexBuffer,
-                                fMesh.fPatternRepeatCount);
-    }
-
-private:
-    const GrMesh&    fMesh;
-    int              fRepetitionIdx;
-};
-
-inline GrMesh::PatternIterator begin(const GrMesh& mesh) {
-    return GrMesh::PatternIterator(mesh, 0);
-}
-
-inline GrMesh::PatternIterator end(const GrMesh& mesh) {
-    return GrMesh::PatternIterator(mesh, mesh.fPatternRepeatCount);
+    SkASSERT(fIndexData.fPatternRepeatCount > 0);
+    int baseRepetition = 0;
+    do {
+        int repeatCount = SkTMin(fPatternData.fMaxPatternRepetitionsInIndexBuffer,
+                                 fIndexData.fPatternRepeatCount - baseRepetition);
+        // A patterned index buffer must contain indices in the range [0..vertexCount].
+        int minIndexValue = 0;
+        int maxIndexValue = fPatternData.fVertexCount * repeatCount - 1;
+        impl->sendIndexedMeshToGpu(primProc, fPrimitiveType, fIndexBuffer.get(),
+                                   fIndexData.fIndexCount * repeatCount, 0, minIndexValue,
+                                   maxIndexValue, fVertexBuffer.get(),
+                                   fBaseVertex + fPatternData.fVertexCount * baseRepetition);
+        baseRepetition += repeatCount;
+    } while (baseRepetition < fIndexData.fPatternRepeatCount);
 }
 
 #endif
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 81ee87c..045804c 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -2450,41 +2450,7 @@
             this->xferBarrier(pipeline.getRenderTarget(), barrierType);
         }
 
-        const GrMesh& mesh = meshes[i];
-        const GrGLenum primType = gPrimitiveType2GLMode[mesh.primitiveType()];
-
-        if (mesh.isIndexed()) {
-            GrGLvoid* indices = reinterpret_cast<void*>(mesh.indexBuffer()->baseOffset() +
-                                                        sizeof(uint16_t) * mesh.baseIndex());
-            for (const GrMesh::PatternBatch batch : mesh) {
-                this->setupGeometry(primProc, mesh.indexBuffer(), mesh.vertexBuffer(),
-                                    batch.fBaseVertex);
-                // batch.fBaseVertex was accounted for by setupGeometry.
-                if (this->glCaps().drawRangeElementsSupport()) {
-                    // We assume here that the GrMeshDrawOps that generated the mesh used the full
-                    // 0..vertexCount()-1 range.
-                    int start = 0;
-                    int end = mesh.vertexCount() * batch.fRepeatCount - 1;
-                    GL_CALL(DrawRangeElements(primType, start, end,
-                                              mesh.indexCount() * batch.fRepeatCount,
-                                              GR_GL_UNSIGNED_SHORT, indices));
-                } else {
-                    GL_CALL(DrawElements(primType, mesh.indexCount() * batch.fRepeatCount,
-                                         GR_GL_UNSIGNED_SHORT, indices));
-                }
-                fStats.incNumDraws();
-            }
-        } else {
-            if (this->glCaps().drawArraysBaseVertexIsBroken()) {
-                this->setupGeometry(primProc, mesh.indexBuffer(), mesh.vertexBuffer(),
-                                    mesh.baseVertex());
-                GL_CALL(DrawArrays(primType, 0, mesh.vertexCount()));
-            } else {
-                this->setupGeometry(primProc, mesh.indexBuffer(), mesh.vertexBuffer(), 0);
-                GL_CALL(DrawArrays(primType, mesh.baseVertex(), mesh.vertexCount()));
-            }
-            fStats.incNumDraws();
-        }
+        meshes[i].sendToGpu(primProc, this);
     }
 
 #if SWAP_PER_DRAW
@@ -2501,6 +2467,40 @@
 #endif
 }
 
+void GrGLGpu::sendMeshToGpu(const GrPrimitiveProcessor& primProc, GrPrimitiveType primitiveType,
+                            const GrBuffer* vertexBuffer, int vertexCount, int baseVertex) {
+    const GrGLenum glPrimType = gPrimitiveType2GLMode[primitiveType];
+
+    if (this->glCaps().drawArraysBaseVertexIsBroken()) {
+        this->setupGeometry(primProc, nullptr, vertexBuffer, baseVertex);
+        GL_CALL(DrawArrays(glPrimType, 0, vertexCount));
+    } else {
+        this->setupGeometry(primProc, nullptr, vertexBuffer, 0);
+        GL_CALL(DrawArrays(glPrimType, baseVertex, vertexCount));
+    }
+    fStats.incNumDraws();
+}
+
+void GrGLGpu::sendIndexedMeshToGpu(const GrPrimitiveProcessor& primProc,
+                                   GrPrimitiveType primitiveType, const GrBuffer* indexBuffer,
+                                   int indexCount, int baseIndex, uint16_t minIndexValue,
+                                   uint16_t maxIndexValue, const GrBuffer* vertexBuffer,
+                                   int baseVertex) {
+    const GrGLenum glPrimType = gPrimitiveType2GLMode[primitiveType];
+    GrGLvoid* const indices = reinterpret_cast<void*>(indexBuffer->baseOffset() +
+                                                      sizeof(uint16_t) * baseIndex);
+
+    this->setupGeometry(primProc, indexBuffer, vertexBuffer, baseVertex);
+
+    if (this->glCaps().drawRangeElementsSupport()) {
+        GL_CALL(DrawRangeElements(glPrimType, minIndexValue, maxIndexValue, indexCount,
+                                  GR_GL_UNSIGNED_SHORT, indices));
+    } else {
+        GL_CALL(DrawElements(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, indices));
+    }
+    fStats.incNumDraws();
+}
+
 void GrGLGpu::onResolveRenderTarget(GrRenderTarget* target) {
     GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(target);
     if (rt->needsResolve()) {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index d19f2ef..c83ad85 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -17,6 +17,7 @@
 #include "GrGLTexture.h"
 #include "GrGLVertexArray.h"
 #include "GrGpu.h"
+#include "GrMesh.h"
 #include "GrTexturePriv.h"
 #include "GrWindowRectsState.h"
 #include "GrXferProcessor.h"
@@ -34,7 +35,7 @@
 #define PROGRAM_CACHE_STATS
 #endif
 
-class GrGLGpu final : public GrGpu {
+class GrGLGpu final : public GrGpu, private GrMesh::SendToGpuImpl {
 public:
     static GrGpu* Create(GrBackendContext backendContext, const GrContextOptions& options,
                          GrContext* context);
@@ -103,6 +104,17 @@
               const GrMesh*,
               int meshCount);
 
+
+    // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls.
+    // Marked final as a hint to the compiler to not use virtual dispatch.
+    void sendMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                       const GrBuffer* vertexBuffer, int vertexCount, int baseVertex) final;
+
+    void sendIndexedMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                              const GrBuffer* indexBuffer, int indexCount, int baseIndex,
+                              uint16_t minIndexValue, uint16_t maxIndexValue,
+                              const GrBuffer* vertexBuffer, int baseVertex) final;
+
     // The GrGLGpuCommandBuffer does not buffer up draws before submitting them to the gpu.
     // Thus this is the implementation of the clear call for the corresponding passthrough function
     // on GrGLGpuCommandBuffer.
@@ -248,9 +260,7 @@
     // willDrawPoints must be true if point primitives will be rendered after setting the GL state.
     bool flushGLState(const GrPipeline&, const GrPrimitiveProcessor&, bool willDrawPoints);
 
-    // Sets up vertex attribute pointers and strides. On return indexOffsetInBytes gives the offset
-    // an into the index buffer. It does not account for vertices.startIndex() but rather the start
-    // index is relative to the returned offset.
+    // Sets up vertex/instance attribute pointers and strides.
     void setupGeometry(const GrPrimitiveProcessor&,
                        const GrBuffer* indexBuffer,
                        const GrBuffer* vertexBuffer,
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 161e96d..cd69c6a 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -813,8 +813,8 @@
             extract_verts(tess, verts, vertexStride, fColor, idxs, canTweakAlphaForCoverage);
 
             GrMesh mesh(kTriangles_GrPrimitiveType);
-            mesh.setIndexed(indexBuffer, tess.numIndices(), firstIndex);
-            mesh.setVertices(vertexBuffer, tess.numPts(), firstVertex);
+            mesh.setIndexed(indexBuffer, tess.numIndices(), firstIndex, 0, tess.numPts() - 1);
+            mesh.setVertexData(vertexBuffer, firstVertex);
             target->draw(gp.get(), this->pipeline(), mesh);
         }
     }
@@ -901,8 +901,8 @@
 
             for (int j = 0; j < draws.count(); ++j) {
                 const Draw& draw = draws[j];
-                mesh.setIndexed(indexBuffer, draw.fIndexCnt, firstIndex);
-                mesh.setVertices(vertexBuffer, draw.fVertexCnt, firstVertex);
+                mesh.setIndexed(indexBuffer, draw.fIndexCnt, firstIndex, 0, draw.fVertexCnt - 1);
+                mesh.setVertexData(vertexBuffer, firstVertex);
                 target->draw(quadProcessor.get(), this->pipeline(), mesh);
                 firstIndex += draw.fIndexCnt;
                 firstVertex += draw.fVertexCnt;
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index 5618fd1..26d58f4 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -861,9 +861,9 @@
         }
 
         GrMesh mesh(kTriangles_GrPrimitiveType);
-        mesh.setIndexedPatterned(linesIndexBuffer.get(), kIdxsPerLineSeg,
+        mesh.setIndexedPatterned(linesIndexBuffer.get(), kIdxsPerLineSeg, kLineSegNumVertices,
                                  lineCount, kLineSegsNumInIdxBuffer);
-        mesh.setVertices(vertexBuffer, kLineSegNumVertices, firstVertex);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(lineGP.get(), this->pipeline(), mesh);
     }
 
@@ -918,18 +918,18 @@
 
         if (quadCount > 0) {
             GrMesh mesh(kTriangles_GrPrimitiveType);
-            mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, quadCount,
-                                     kQuadsNumInIdxBuffer);
-            mesh.setVertices(vertexBuffer, kQuadNumVertices, firstVertex);
+            mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, kQuadNumVertices,
+                                     quadCount, kQuadsNumInIdxBuffer);
+            mesh.setVertexData(vertexBuffer, firstVertex);
             target->draw(quadGP.get(), this->pipeline(), mesh);
             firstVertex += quadCount * kQuadNumVertices;
         }
 
         if (conicCount > 0) {
             GrMesh mesh(kTriangles_GrPrimitiveType);
-            mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, conicCount,
-                                     kQuadsNumInIdxBuffer);
-            mesh.setVertices(vertexBuffer, kQuadNumVertices, firstVertex);
+            mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, kQuadNumVertices,
+                                     conicCount, kQuadsNumInIdxBuffer);
+            mesh.setVertexData(vertexBuffer, firstVertex);
             target->draw(conicGP.get(), this->pipeline(), mesh);
         }
     }
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index b2d9599..5cfe5cb 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -208,8 +208,8 @@
             return;
         }
         memcpy(idxs, indices, indexCount * sizeof(uint16_t));
-        mesh.setIndexed(indexBuffer, indexCount, firstIndex);
-        mesh.setVertices(vertexBuffer, vertexCount, firstVertex);
+        mesh.setIndexed(indexBuffer, indexCount, firstIndex, 0, vertexCount - 1);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp, this->pipeline(), mesh);
     }
 
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 21b7f6d..69e8b4e 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -151,9 +151,9 @@
     GrMesh mesh(kTriangles_GrPrimitiveType);
     int maxGlyphsPerDraw =
             static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
-    mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph,
+    mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph, kVerticesPerGlyph,
                              flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
-    mesh.setVertices(flushInfo->fVertexBuffer.get(), kVerticesPerGlyph, flushInfo->fVertexOffset);
+    mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
     target->draw(flushInfo->fGeometryProcessor.get(), this->pipeline(), mesh);
     flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
     flushInfo->fGlyphsToFlush = 0;
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index c696dda..7633868 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -248,10 +248,12 @@
         }
 
         GrMesh mesh(primitiveType);
-        if (isIndexed) {
-            mesh.setIndexed(indexBuffer, indexOffset, firstIndex);
+        if (!isIndexed) {
+            mesh.setNonIndexed(vertexOffset);
+        } else {
+            mesh.setIndexed(indexBuffer, indexOffset, firstIndex, 0, vertexOffset - 1);
         }
-        mesh.setVertices(vertexBuffer, vertexOffset, firstVertex);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp.get(), this->pipeline(), mesh);
 
         // put back reserves
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 2a61f3c..1e04c68 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -217,10 +217,12 @@
     }
 
     GrMesh mesh(this->primitiveType());
-    if (indices) {
-        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex);
+    if (!indices) {
+        mesh.setNonIndexed(fVertexCount);
+    } else {
+        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertexCount - 1);
     }
-    mesh.setVertices(vertexBuffer, fVertexCount, firstVertex);
+    mesh.setVertexData(vertexBuffer, firstVertex);
     target->draw(gp.get(), this->pipeline(), mesh);
 }
 
diff --git a/src/gpu/ops/GrMSAAPathRenderer.cpp b/src/gpu/ops/GrMSAAPathRenderer.cpp
index c3a5440..36faf2f 100644
--- a/src/gpu/ops/GrMSAAPathRenderer.cpp
+++ b/src/gpu/ops/GrMSAAPathRenderer.cpp
@@ -411,10 +411,13 @@
             SkASSERT(lineVertexStride == lineGP->getVertexStride());
 
             GrMesh lineMeshes(primitiveType);
-            if (fIsIndexed) {
-                lineMeshes.setIndexed(lineIndexBuffer, lineIndexOffset, firstLineIndex);
+            if (!fIsIndexed) {
+                lineMeshes.setNonIndexed(lineVertexOffset);
+            } else {
+                lineMeshes.setIndexed(lineIndexBuffer, lineIndexOffset, firstLineIndex,
+                                      0, lineVertexOffset - 1);
             }
-            lineMeshes.setVertices(lineVertexBuffer, lineVertexOffset, firstLineVertex);
+            lineMeshes.setVertexData(lineVertexBuffer, firstLineVertex);
 
             // We can get line vertices from path moveTos with no actual segments and thus no index
             // count. We assert that indexed draws contain a positive index count, so bail here in
@@ -435,16 +438,19 @@
                                             &firstQuadVertex);
             memcpy(quadVertices, quads.vertices, quadVertexStride * quadVertexOffset);
             GrMesh quadMeshes(kTriangles_GrPrimitiveType);
-            if (fIsIndexed) {
+            if (!fIsIndexed) {
+                quadMeshes.setNonIndexed(quadVertexOffset);
+            } else {
                 const GrBuffer* quadIndexBuffer;
                 int firstQuadIndex;
                 uint16_t* quadIndices = (uint16_t*) target->makeIndexSpace(quadIndexOffset,
                                                                            &quadIndexBuffer,
                                                                            &firstQuadIndex);
                 memcpy(quadIndices, quads.indices, sizeof(uint16_t) * quadIndexOffset);
-                quadMeshes.setIndexed(quadIndexBuffer, quadIndexOffset, firstQuadIndex);
+                quadMeshes.setIndexed(quadIndexBuffer, quadIndexOffset, firstQuadIndex,
+                                      0, quadVertexOffset - 1);
             }
-            quadMeshes.setVertices(quadVertexBuffer, quadVertexOffset, firstQuadVertex);
+            quadMeshes.setVertexData(quadVertexBuffer, firstQuadVertex);
             target->draw(quadGP.get(), this->pipeline(), quadMeshes);
         }
     }
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index 9382fd7..d6c2ced 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -37,8 +37,9 @@
     size_t ibSize = indexBuffer->gpuMemorySize();
     int maxRepetitions = static_cast<int>(ibSize / (sizeof(uint16_t) * indicesPerRepetition));
 
-    fMesh.setIndexedPatterned(indexBuffer, indicesPerRepetition, repeatCount, maxRepetitions);
-    fMesh.setVertices(vertexBuffer, verticesPerRepetition, firstVertex);
+    fMesh.setIndexedPatterned(indexBuffer, indicesPerRepetition, verticesPerRepetition,
+                              repeatCount, maxRepetitions);
+    fMesh.setVertexData(vertexBuffer, firstVertex);
     return vertices;
 }
 
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
index 4e00847..f386984 100644
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
@@ -157,7 +157,8 @@
         }
 
         GrMesh mesh(primType);
-        mesh.setVertices(vertexBuffer, vertexCount, firstVertex);
+        mesh.setNonIndexed(vertexCount);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp.get(), this->pipeline(), mesh);
     }
 
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 623ec7a..174dc40 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -1106,8 +1106,8 @@
         }
 
         GrMesh mesh(kTriangles_GrPrimitiveType);
-        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex);
-        mesh.setVertices(vertexBuffer, fVertCount, firstVertex);
+        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp.get(),  fHelper.makePipeline(target), mesh);
     }
 
@@ -2030,8 +2030,8 @@
         }
 
         GrMesh mesh(kTriangles_GrPrimitiveType);
-        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex);
-        mesh.setVertices(vertexBuffer, fVertCount, firstVertex);
+        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp.get(), fHelper.makePipeline(target), mesh);
     }
 
diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp
index a9d057c..af41bd4 100644
--- a/src/gpu/ops/GrShadowRRectOp.cpp
+++ b/src/gpu/ops/GrShadowRRectOp.cpp
@@ -629,8 +629,8 @@
         }
 
         GrMesh mesh(kTriangles_GrPrimitiveType);
-        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex);
-        mesh.setVertices(vertexBuffer, fVertCount, firstVertex);
+        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1);
+        mesh.setVertexData(vertexBuffer, firstVertex);
         target->draw(gp.get(), this->pipeline(), mesh);
     }
 
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index 9d9cf27..048351b 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -683,9 +683,9 @@
             int maxInstancesPerDraw =
                 static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
             mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerQuad,
-                                     flushInfo->fInstancesToFlush, maxInstancesPerDraw);
-            mesh.setVertices(flushInfo->fVertexBuffer.get(), kVerticesPerQuad,
-                             flushInfo->fVertexOffset);
+                                     kVerticesPerQuad, flushInfo->fInstancesToFlush,
+                                     maxInstancesPerDraw);
+            mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
             target->draw(flushInfo->fGeometryProcessor.get(), this->pipeline(), mesh);
             flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
             flushInfo->fInstancesToFlush = 0;
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index 45e9465..9860d9c 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -314,7 +314,8 @@
     void drawVertices(Target* target, const GrGeometryProcessor* gp, const GrBuffer* vb,
                       int firstVertex, int count) const {
         GrMesh mesh(TESSELLATOR_WIREFRAME ? kLines_GrPrimitiveType : kTriangles_GrPrimitiveType);
-        mesh.setVertices(vb, count, firstVertex);
+        mesh.setNonIndexed(count);
+        mesh.setVertexData(vb, firstVertex);
         target->draw(gp, this->pipeline(), mesh);
     }
 
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index ad2dc0c..2e9373b 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -544,8 +544,6 @@
         return;
     }
 
-    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
-
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
         if (mesh.primitiveType() != primitiveType) {
@@ -564,32 +562,12 @@
         }
 
         SkASSERT(pipelineState);
-        this->bindGeometry(primProc, mesh.indexBuffer(), mesh.vertexBuffer());
-
-        if (mesh.isIndexed()) {
-            for (const GrMesh::PatternBatch batch : mesh) {
-                cbInfo.currentCmdBuf()->drawIndexed(fGpu,
-                                                    mesh.indexCount() * batch.fRepeatCount,
-                                                    1,
-                                                    mesh.baseIndex(),
-                                                    batch.fBaseVertex,
-                                                    0);
-                cbInfo.fIsEmpty = false;
-                fGpu->stats()->incNumDraws();
-            }
-        } else {
-            cbInfo.currentCmdBuf()->draw(fGpu,
-                                         mesh.vertexCount(),
-                                         1,
-                                         mesh.baseVertex(),
-                                         0);
-            cbInfo.fIsEmpty = false;
-            fGpu->stats()->incNumDraws();
-        }
+        mesh.sendToGpu(primProc, this);
     }
 
-    // Update command buffer bounds
+    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
     cbInfo.fBounds.join(bounds);
+    cbInfo.fIsEmpty = false;
 
     // Technically we don't have to call this here (since there is a safety check in
     // pipelineState:setData but this will allow for quicker freeing of resources if the
@@ -597,3 +575,29 @@
     pipelineState->freeTempResources(fGpu);
 }
 
+void GrVkGpuCommandBuffer::sendMeshToGpu(const GrPrimitiveProcessor& primProc,
+                                         GrPrimitiveType,
+                                         const GrBuffer* vertexBuffer,
+                                         int vertexCount,
+                                         int baseVertex) {
+    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
+    this->bindGeometry(primProc, nullptr, vertexBuffer);
+    cbInfo.currentCmdBuf()->draw(fGpu, vertexCount, 1, baseVertex, 0);
+    fGpu->stats()->incNumDraws();
+}
+
+void GrVkGpuCommandBuffer::sendIndexedMeshToGpu(const GrPrimitiveProcessor& primProc,
+                                                GrPrimitiveType,
+                                                const GrBuffer* indexBuffer,
+                                                int indexCount,
+                                                int baseIndex,
+                                                uint16_t /*minIndexValue*/,
+                                                uint16_t /*maxIndexValue*/,
+                                                const GrBuffer* vertexBuffer,
+                                                int baseVertex) {
+    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
+    this->bindGeometry(primProc, indexBuffer, vertexBuffer);
+    cbInfo.currentCmdBuf()->drawIndexed(fGpu, indexCount, 1, baseIndex, baseVertex, 0);
+    fGpu->stats()->incNumDraws();
+}
+
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index 3d4d3ee..b1e96a3 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -11,6 +11,7 @@
 #include "GrGpuCommandBuffer.h"
 
 #include "GrColor.h"
+#include "GrMesh.h"
 #include "GrTypes.h"
 #include "GrVkPipelineState.h"
 
@@ -20,7 +21,7 @@
 class GrVkRenderTarget;
 class GrVkSecondaryCommandBuffer;
 
-class GrVkGpuCommandBuffer : public GrGpuCommandBuffer {
+class GrVkGpuCommandBuffer : public GrGpuCommandBuffer, private GrMesh::SendToGpuImpl {
 public:
     GrVkGpuCommandBuffer(GrVkGpu* gpu,
                          const LoadAndStoreInfo& colorInfo,
@@ -59,6 +60,16 @@
                 int meshCount,
                 const SkRect& bounds) override;
 
+    // GrMesh::SendToGpuImpl methods. These issue the actual Vulkan draw commands.
+    // Marked final as a hint to the compiler to not use virtual dispatch.
+    void sendMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                       const GrBuffer* vertexBuffer, int vertexCount, int baseVertex) final;
+
+    void sendIndexedMeshToGpu(const GrPrimitiveProcessor&, GrPrimitiveType,
+                              const GrBuffer* indexBuffer, int indexCount, int baseIndex,
+                              uint16_t minIndexValue, uint16_t maxIndexValue,
+                              const GrBuffer* vertexBuffer, int baseVertex) final;
+
     void onClear(GrRenderTarget*, const GrFixedClip&, GrColor color) override;
 
     void onClearStencilClip(GrRenderTarget*, const GrFixedClip&, bool insideStencilMask) override;