Ovals batch

BUG=skia:

Review URL: https://codereview.chromium.org/904753002
diff --git a/src/gpu/GrBatchTarget.h b/src/gpu/GrBatchTarget.h
index 8cd91d3..51dd8ca 100644
--- a/src/gpu/GrBatchTarget.h
+++ b/src/gpu/GrBatchTarget.h
@@ -80,6 +80,8 @@
     GrVertexBufferAllocPool* vertexPool() { return fVertexPool; }
     GrIndexBufferAllocPool* indexPool() { return fIndexPool; }
 
+    const GrIndexBuffer* quadIndexBuffer() const { return fGpu->getQuadIndexBuffer(); }
+
 private:
     GrGpu* fGpu;
     GrVertexBufferAllocPool* fVertexPool;
diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp
index b1ef8d3..f324018 100644
--- a/src/gpu/GrOvalRenderer.cpp
+++ b/src/gpu/GrOvalRenderer.cpp
@@ -7,12 +7,15 @@
 
 #include "GrOvalRenderer.h"
 
-#include "GrProcessor.h"
+#include "GrBatch.h"
+#include "GrBatchTarget.h"
+#include "GrBufferAllocPool.h"
 #include "GrDrawTarget.h"
 #include "GrGeometryProcessor.h"
 #include "GrGpu.h"
 #include "GrInvariantOutput.h"
 #include "GrPipelineBuilder.h"
+#include "GrProcessor.h"
 #include "SkRRect.h"
 #include "SkStrokeRec.h"
 #include "SkTLazy.h"
@@ -22,6 +25,8 @@
 #include "gl/GrGLGeometryProcessor.h"
 #include "gl/builders/GrGLProgramBuilder.h"
 
+// TODO(joshualitt) - Break this file up during GrBatch post implementation cleanup
+
 namespace {
 // TODO(joshualitt) add per vertex colors
 struct CircleVertex {
@@ -692,6 +697,199 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+class CircleBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkScalar fInnerRadius;
+        SkScalar fOuterRadius;
+        bool fStroke;
+        SkRect fDevBounds;
+    };
+
+    static GrBatch* Create(const Geometry& geometry) {
+        return SkNEW_ARGS(CircleBatch, (geometry));
+    }
+
+    const char* name() const SK_OVERRIDE { return "CircleBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        // When this is called on a batch, there is only one geometry bundle
+        out->setKnownFourComponents(fGeoData[0].fColor);
+    }
+
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        out->setUnknownSingleComponent();
+    }
+
+    void initBatchOpt(const GrBatchOpt& batchOpt) {
+        fBatchOpt = batchOpt;
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fGeoData[0].fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fGeoData[0].fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fStroke = fGeoData[0].fStroke;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        SkMatrix invert;
+        if (!this->viewMatrix().invert(&invert)) {
+            return;
+        }
+
+        // Setup geometry processor
+        SkAutoTUnref<GrGeometryProcessor> gp(CircleEdgeEffect::Create(this->color(),
+                                                                      this->stroke(),
+                                                                      invert));
+
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO this is hacky, but the only way we have to initialize the GP is to use the
+        // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
+        // everywhere we can remove this nastiness
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        int instanceCount = fGeoData.count();
+        int vertexCount = kVertsPerCircle * instanceCount;
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        CircleVertex* verts = reinterpret_cast<CircleVertex*>(vertices);
+
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            SkScalar innerRadius = args.fInnerRadius;
+            SkScalar outerRadius = args.fOuterRadius;
+
+            const SkRect& bounds = args.fDevBounds;
+
+            // The inner radius in the vertex data must be specified in normalized space.
+            innerRadius = innerRadius / outerRadius;
+            verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
+            verts[0].fOffset = SkPoint::Make(-1, -1);
+            verts[0].fOuterRadius = outerRadius;
+            verts[0].fInnerRadius = innerRadius;
+
+            verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+            verts[1].fOffset = SkPoint::Make(-1, 1);
+            verts[1].fOuterRadius = outerRadius;
+            verts[1].fInnerRadius = innerRadius;
+
+            verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+            verts[2].fOffset = SkPoint::Make(1, 1);
+            verts[2].fOuterRadius = outerRadius;
+            verts[2].fInnerRadius = innerRadius;
+
+            verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+            verts[3].fOffset = SkPoint::Make(1, -1);
+            verts[3].fOuterRadius = outerRadius;
+            verts[3].fInnerRadius = innerRadius;
+
+            verts += kVertsPerCircle;
+        }
+
+        const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerCircle);
+        drawInfo.setIndicesPerInstance(kIndicesPerCircle);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(quadIndexBuffer);
+
+        int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
+
+        while (instanceCount) {
+            drawInfo.setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            instanceCount -= drawInfo.instanceCount();
+        }
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    CircleBatch(const Geometry& geometry) {
+        this->initClassID<CircleBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        CircleBatch* that = t->cast<CircleBatch>();
+
+        // TODO use vertex color to avoid breaking batches
+        if (this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->stroke() != that->stroke()) {
+            return false;
+        }
+
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    bool stroke() const { return fBatch.fStroke; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        bool fStroke;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    static const int kVertsPerCircle = 4;
+    static const int kIndicesPerCircle = 6;
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+};
+
 void GrOvalRenderer::drawCircle(GrDrawTarget* target,
                                 GrPipelineBuilder* pipelineBuilder,
                                 GrColor color,
@@ -704,11 +902,6 @@
     SkScalar radius = viewMatrix.mapRadius(SkScalarHalf(circle.width()));
     SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
 
-    SkMatrix invert;
-    if (!viewMatrix.invert(&invert)) {
-        return;
-    }
-
     SkStrokeRec::Style style = stroke.getStyle();
     bool isStrokeOnly = SkStrokeRec::kStroke_Style == style ||
                         SkStrokeRec::kHairline_Style == style;
@@ -730,18 +923,6 @@
         }
     }
 
-    SkAutoTUnref<GrGeometryProcessor> gp(
-            CircleEdgeEffect::Create(color, isStrokeOnly && innerRadius > 0,invert));
-
-    GrDrawTarget::AutoReleaseGeometry geo(target, 4, gp->getVertexStride(),  0);
-    SkASSERT(gp->getVertexStride() == sizeof(CircleVertex));
-    if (!geo.succeeded()) {
-        SkDebugf("Failed to get space for vertices!\n");
-        return;
-    }
-
-    CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
-
     // The radii are outset for two reasons. First, it allows the shader to simply perform simpler
     // computation because the computed alpha is zero, rather than 50%, at the radius.
     // Second, the outer radius is used to compute the verts of the bounding box that is rendered
@@ -756,35 +937,220 @@
         center.fY + outerRadius
     );
 
-    // The inner radius in the vertex data must be specified in normalized space.
-    innerRadius = innerRadius / outerRadius;
-    verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
-    verts[0].fOffset = SkPoint::Make(-1, -1);
-    verts[0].fOuterRadius = outerRadius;
-    verts[0].fInnerRadius = innerRadius;
+    CircleBatch::Geometry geometry;
+    geometry.fViewMatrix = viewMatrix;
+    geometry.fColor = color;
+    geometry.fInnerRadius = innerRadius;
+    geometry.fOuterRadius = outerRadius;
+    geometry.fStroke = isStrokeOnly && innerRadius > 0;
+    geometry.fDevBounds = bounds;
 
-    verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
-    verts[1].fOffset = SkPoint::Make(-1, 1);
-    verts[1].fOuterRadius = outerRadius;
-    verts[1].fInnerRadius = innerRadius;
-
-    verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
-    verts[2].fOffset = SkPoint::Make(1, 1);
-    verts[2].fOuterRadius = outerRadius;
-    verts[2].fInnerRadius = innerRadius;
-
-    verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
-    verts[3].fOffset = SkPoint::Make(1, -1);
-    verts[3].fOuterRadius = outerRadius;
-    verts[3].fInnerRadius = innerRadius;
-
-    target->setIndexSourceToBuffer(fGpu->getQuadIndexBuffer());
-    target->drawIndexedInstances(pipelineBuilder, gp, kTriangles_GrPrimitiveType, 1, 4, 6, &bounds);
-    target->resetIndexSource();
+    SkAutoTUnref<GrBatch> batch(CircleBatch::Create(geometry));
+    target->drawBatch(pipelineBuilder, batch, &bounds);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
+class EllipseBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkScalar fXRadius;
+        SkScalar fYRadius;
+        SkScalar fInnerXRadius;
+        SkScalar fInnerYRadius;
+        bool fStroke;
+        SkRect fDevBounds;
+    };
+
+    static GrBatch* Create(const Geometry& geometry) {
+        return SkNEW_ARGS(EllipseBatch, (geometry));
+    }
+
+    const char* name() const SK_OVERRIDE { return "EllipseBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        // When this is called on a batch, there is only one geometry bundle
+        out->setKnownFourComponents(fGeoData[0].fColor);
+    }
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        out->setUnknownSingleComponent();
+    }
+
+    void initBatchOpt(const GrBatchOpt& batchOpt) {
+        fBatchOpt = batchOpt;
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fGeoData[0].fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fGeoData[0].fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fStroke = fGeoData[0].fStroke;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        SkMatrix invert;
+        if (!this->viewMatrix().invert(&invert)) {
+            return;
+        }
+
+        // Setup geometry processor
+        SkAutoTUnref<GrGeometryProcessor> gp(EllipseEdgeEffect::Create(this->color(),
+                                                                       this->stroke(),
+                                                                       invert));
+        SkASSERT(gp->getVertexStride() == sizeof(EllipseVertex));
+
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO this is hacky, but the only way we have to initialize the GP is to use the
+        // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
+        // everywhere we can remove this nastiness
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        int instanceCount = fGeoData.count();
+        int vertexCount = kVertsPerEllipse * instanceCount;
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(vertices);
+
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            SkScalar xRadius = args.fXRadius;
+            SkScalar yRadius = args.fYRadius;
+
+            // Compute the reciprocals of the radii here to save time in the shader
+            SkScalar xRadRecip = SkScalarInvert(xRadius);
+            SkScalar yRadRecip = SkScalarInvert(yRadius);
+            SkScalar xInnerRadRecip = SkScalarInvert(args.fInnerXRadius);
+            SkScalar yInnerRadRecip = SkScalarInvert(args.fInnerYRadius);
+
+            const SkRect& bounds = args.fDevBounds;
+
+            // The inner radius in the vertex data must be specified in normalized space.
+            verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
+            verts[0].fOffset = SkPoint::Make(-xRadius, -yRadius);
+            verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+            verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+            verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+            verts[1].fOffset = SkPoint::Make(-xRadius, yRadius);
+            verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+            verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+            verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+            verts[2].fOffset = SkPoint::Make(xRadius, yRadius);
+            verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+            verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+            verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+            verts[3].fOffset = SkPoint::Make(xRadius, -yRadius);
+            verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+            verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+            verts += kVertsPerEllipse;
+        }
+
+        const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerEllipse);
+        drawInfo.setIndicesPerInstance(kIndicesPerEllipse);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(quadIndexBuffer);
+
+        int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
+
+        while (instanceCount) {
+            drawInfo.setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            instanceCount -= drawInfo.instanceCount();
+        }
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    EllipseBatch(const Geometry& geometry) {
+        this->initClassID<EllipseBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        EllipseBatch* that = t->cast<EllipseBatch>();
+
+        // TODO use vertex color to avoid breaking batches
+        if (this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->stroke() != that->stroke()) {
+            return false;
+        }
+
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    bool stroke() const { return fBatch.fStroke; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        bool fStroke;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    static const int kVertsPerEllipse = 4;
+    static const int kIndicesPerEllipse = 6;
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+};
+
 bool GrOvalRenderer::drawEllipse(GrDrawTarget* target,
                                  GrPipelineBuilder* pipelineBuilder,
                                  GrColor color,
@@ -854,31 +1220,6 @@
         yRadius += scaledStroke.fY;
     }
 
-    SkMatrix invert;
-    if (!viewMatrix.invert(&invert)) {
-        return false;
-    }
-
-    SkAutoTUnref<GrGeometryProcessor> gp(
-            EllipseEdgeEffect::Create(color,
-                                      isStrokeOnly && innerXRadius > 0 && innerYRadius > 0,
-                                      invert));
-
-    GrDrawTarget::AutoReleaseGeometry geo(target, 4, gp->getVertexStride(),  0);
-    SkASSERT(gp->getVertexStride() == sizeof(EllipseVertex));
-    if (!geo.succeeded()) {
-        SkDebugf("Failed to get space for vertices!\n");
-        return false;
-    }
-
-    EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices());
-
-    // Compute the reciprocals of the radii here to save time in the shader
-    SkScalar xRadRecip = SkScalarInvert(xRadius);
-    SkScalar yRadRecip = SkScalarInvert(yRadius);
-    SkScalar xInnerRadRecip = SkScalarInvert(innerXRadius);
-    SkScalar yInnerRadRecip = SkScalarInvert(innerYRadius);
-
     // We've extended the outer x radius out half a pixel to antialias.
     // This will also expand the rect so all the pixels will be captured.
     // TODO: Consider if we should use sqrt(2)/2 instead
@@ -892,33 +1233,218 @@
         center.fY + yRadius
     );
 
-    verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
-    verts[0].fOffset = SkPoint::Make(-xRadius, -yRadius);
-    verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-    verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+    EllipseBatch::Geometry geometry;
+    geometry.fViewMatrix = viewMatrix;
+    geometry.fColor = color;
+    geometry.fXRadius = xRadius;
+    geometry.fYRadius = yRadius;
+    geometry.fInnerXRadius = innerXRadius;
+    geometry.fInnerYRadius = innerYRadius;
+    geometry.fStroke = isStrokeOnly && innerXRadius > 0 && innerYRadius > 0;
+    geometry.fDevBounds = bounds;
 
-    verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
-    verts[1].fOffset = SkPoint::Make(-xRadius, yRadius);
-    verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-    verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-
-    verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
-    verts[2].fOffset = SkPoint::Make(xRadius, yRadius);
-    verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-    verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-
-    verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
-    verts[3].fOffset = SkPoint::Make(xRadius, -yRadius);
-    verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-    verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-
-    target->setIndexSourceToBuffer(fGpu->getQuadIndexBuffer());
-    target->drawIndexedInstances(pipelineBuilder, gp, kTriangles_GrPrimitiveType, 1, 4, 6, &bounds);
-    target->resetIndexSource();
+    SkAutoTUnref<GrBatch> batch(EllipseBatch::Create(geometry));
+    target->drawBatch(pipelineBuilder, batch, &bounds);
 
     return true;
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+class DIEllipseBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkScalar fXRadius;
+        SkScalar fYRadius;
+        SkScalar fInnerXRadius;
+        SkScalar fInnerYRadius;
+        SkScalar fGeoDx;
+        SkScalar fGeoDy;
+        DIEllipseEdgeEffect::Mode fMode;
+        SkRect fDevBounds;
+    };
+
+    static GrBatch* Create(const Geometry& geometry) {
+        return SkNEW_ARGS(DIEllipseBatch, (geometry));
+    }
+
+    const char* name() const SK_OVERRIDE { return "DIEllipseBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        // When this is called on a batch, there is only one geometry bundle
+        out->setKnownFourComponents(fGeoData[0].fColor);
+    }
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        out->setUnknownSingleComponent();
+    }
+
+    void initBatchOpt(const GrBatchOpt& batchOpt) {
+        fBatchOpt = batchOpt;
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fGeoData[0].fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fGeoData[0].fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fMode = fGeoData[0].fMode;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        // Setup geometry processor
+        SkAutoTUnref<GrGeometryProcessor> gp(DIEllipseEdgeEffect::Create(this->color(),
+                                                                         this->viewMatrix(),
+                                                                         this->mode()));
+
+        SkASSERT(gp->getVertexStride() == sizeof(DIEllipseVertex));
+
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO this is hacky, but the only way we have to initialize the GP is to use the
+        // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
+        // everywhere we can remove this nastiness
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        int instanceCount = fGeoData.count();
+        int vertexCount = kVertsPerEllipse * instanceCount;
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>(vertices);
+
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            SkScalar xRadius = args.fXRadius;
+            SkScalar yRadius = args.fYRadius;
+
+            const SkRect& bounds = args.fDevBounds;
+
+            // This adjusts the "radius" to include the half-pixel border
+            SkScalar offsetDx = SkScalarDiv(args.fGeoDx, xRadius);
+            SkScalar offsetDy = SkScalarDiv(args.fGeoDy, yRadius);
+
+            SkScalar innerRatioX = SkScalarDiv(xRadius, args.fInnerXRadius);
+            SkScalar innerRatioY = SkScalarDiv(yRadius, args.fInnerYRadius);
+
+            verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop);
+            verts[0].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, -1.0f - offsetDy);
+            verts[0].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, -innerRatioY - offsetDy);
+
+            verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+            verts[1].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, 1.0f + offsetDy);
+            verts[1].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, innerRatioY + offsetDy);
+
+            verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+            verts[2].fOuterOffset = SkPoint::Make(1.0f + offsetDx, 1.0f + offsetDy);
+            verts[2].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, innerRatioY + offsetDy);
+
+            verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+            verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offsetDy);
+            verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -innerRatioY - offsetDy);
+
+            verts += kVertsPerEllipse;
+        }
+
+        const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerEllipse);
+        drawInfo.setIndicesPerInstance(kIndicesPerEllipse);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(quadIndexBuffer);
+
+        int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
+
+        while (instanceCount) {
+            drawInfo.setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            instanceCount -= drawInfo.instanceCount();
+        }
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    DIEllipseBatch(const Geometry& geometry) {
+        this->initClassID<DIEllipseBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        DIEllipseBatch* that = t->cast<DIEllipseBatch>();
+
+        // TODO use vertex color to avoid breaking batches
+        if (this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->mode() != that->mode()) {
+            return false;
+        }
+
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    DIEllipseEdgeEffect::Mode mode() const { return fBatch.fMode; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        DIEllipseEdgeEffect::Mode fMode;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    static const int kVertsPerEllipse = 4;
+    static const int kIndicesPerEllipse = 6;
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+};
+
 bool GrOvalRenderer::drawDIEllipse(GrDrawTarget* target,
                                    GrPipelineBuilder* pipelineBuilder,
                                    GrColor color,
@@ -972,19 +1498,6 @@
         mode = (innerXRadius > 0 && innerYRadius > 0) ? DIEllipseEdgeEffect::kStroke :
                                                         DIEllipseEdgeEffect::kFill;
     }
-    SkScalar innerRatioX = SkScalarDiv(xRadius, innerXRadius);
-    SkScalar innerRatioY = SkScalarDiv(yRadius, innerYRadius);
-
-    SkAutoTUnref<GrGeometryProcessor> gp(DIEllipseEdgeEffect::Create(color, viewMatrix, mode));
-
-    GrDrawTarget::AutoReleaseGeometry geo(target, 4, gp->getVertexStride(),  0);
-    SkASSERT(gp->getVertexStride() == sizeof(DIEllipseVertex));
-    if (!geo.succeeded()) {
-        SkDebugf("Failed to get space for vertices!\n");
-        return false;
-    }
-
-    DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>(geo.vertices());
 
     // This expands the outer rect so that after CTM we end up with a half-pixel border
     SkScalar a = viewMatrix[SkMatrix::kMScaleX];
@@ -993,9 +1506,6 @@
     SkScalar d = viewMatrix[SkMatrix::kMScaleY];
     SkScalar geoDx = SkScalarDiv(SK_ScalarHalf, SkScalarSqrt(a*a + c*c));
     SkScalar geoDy = SkScalarDiv(SK_ScalarHalf, SkScalarSqrt(b*b + d*d));
-    // This adjusts the "radius" to include the half-pixel border
-    SkScalar offsetDx = SkScalarDiv(geoDx, xRadius);
-    SkScalar offsetDy = SkScalarDiv(geoDy, yRadius);
 
     SkRect bounds = SkRect::MakeLTRB(
         center.fX - xRadius - geoDx,
@@ -1004,25 +1514,20 @@
         center.fY + yRadius + geoDy
     );
 
-    verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop);
-    verts[0].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, -1.0f - offsetDy);
-    verts[0].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, -innerRatioY - offsetDy);
+    DIEllipseBatch::Geometry geometry;
+    geometry.fViewMatrix = viewMatrix;
+    geometry.fColor = color;
+    geometry.fXRadius = xRadius;
+    geometry.fYRadius = yRadius;
+    geometry.fInnerXRadius = innerXRadius;
+    geometry.fInnerYRadius = innerYRadius;
+    geometry.fGeoDx = geoDx;
+    geometry.fGeoDy = geoDy;
+    geometry.fMode = mode;
+    geometry.fDevBounds = bounds;
 
-    verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
-    verts[1].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, 1.0f + offsetDy);
-    verts[1].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, innerRatioY + offsetDy);
-
-    verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
-    verts[2].fOuterOffset = SkPoint::Make(1.0f + offsetDx, 1.0f + offsetDy);
-    verts[2].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, innerRatioY + offsetDy);
-
-    verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
-    verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offsetDy);
-    verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -innerRatioY - offsetDy);
-
-    target->setIndexSourceToBuffer(fGpu->getQuadIndexBuffer());
-    target->drawIndexedInstances(pipelineBuilder, gp, kTriangles_GrPrimitiveType, 1, 4, 6, &bounds);
-    target->resetIndexSource();
+    SkAutoTUnref<GrBatch> batch(DIEllipseBatch::Create(geometry));
+    target->drawBatch(pipelineBuilder, batch, &bounds);
 
     return true;
 }
@@ -1115,7 +1620,7 @@
         }
     }
     GrPrimitiveEdgeType edgeType = applyAA ? kFillAA_GrProcessorEdgeType :
-                                          kFillBW_GrProcessorEdgeType;
+                                             kFillBW_GrProcessorEdgeType;
     GrFragmentProcessor* effect = GrRRectEffect::Create(edgeType, *outer);
     if (NULL == effect) {
         return false;
@@ -1138,6 +1643,434 @@
     return true;
 }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RRectCircleRendererBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkScalar fInnerRadius;
+        SkScalar fOuterRadius;
+        bool fStroke;
+        SkRect fDevBounds;
+    };
+
+    static GrBatch* Create(const Geometry& geometry, const GrIndexBuffer* indexBuffer) {
+        return SkNEW_ARGS(RRectCircleRendererBatch, (geometry, indexBuffer));
+    }
+
+    const char* name() const SK_OVERRIDE { return "RRectCircleBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        // When this is called on a batch, there is only one geometry bundle
+        out->setKnownFourComponents(fGeoData[0].fColor);
+    }
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        out->setUnknownSingleComponent();
+    }
+
+    void initBatchOpt(const GrBatchOpt& batchOpt) {
+        fBatchOpt = batchOpt;
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fGeoData[0].fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fGeoData[0].fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fStroke = fGeoData[0].fStroke;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        // reset to device coordinates
+        SkMatrix invert;
+        if (!this->viewMatrix().invert(&invert)) {
+            SkDebugf("Failed to invert\n");
+            return;
+        }
+
+        // Setup geometry processor
+        SkAutoTUnref<GrGeometryProcessor> gp(CircleEdgeEffect::Create(this->color(),
+                                                                      this->stroke(),
+                                                                      invert));
+
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO this is hacky, but the only way we have to initialize the GP is to use the
+        // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
+        // everywhere we can remove this nastiness
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        int instanceCount = fGeoData.count();
+        int vertexCount = kVertsPerRRect * instanceCount;
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        CircleVertex* verts = reinterpret_cast<CircleVertex*>(vertices);
+
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            SkScalar outerRadius = args.fOuterRadius;
+
+            const SkRect& bounds = args.fDevBounds;
+
+            SkScalar yCoords[4] = {
+                bounds.fTop,
+                bounds.fTop + outerRadius,
+                bounds.fBottom - outerRadius,
+                bounds.fBottom
+            };
+
+            SkScalar yOuterRadii[4] = {-1, 0, 0, 1 };
+            // The inner radius in the vertex data must be specified in normalized space.
+            SkScalar innerRadius = args.fInnerRadius / args.fOuterRadius;
+            for (int i = 0; i < 4; ++i) {
+                verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+                verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fInnerRadius = innerRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
+                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fInnerRadius = innerRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
+                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fInnerRadius = innerRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+                verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fInnerRadius = innerRadius;
+                verts++;
+            }
+        }
+
+        // drop out the middle quad if we're stroked
+        int indexCnt = this->stroke() ? SK_ARRAY_COUNT(gRRectIndices) - 6 :
+                                        SK_ARRAY_COUNT(gRRectIndices);
+
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerRRect);
+        drawInfo.setIndicesPerInstance(indexCnt);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(fIndexBuffer);
+
+        int maxInstancesPerDraw = kNumRRectsInIndexBuffer;
+
+        while (instanceCount) {
+            drawInfo.setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            instanceCount -= drawInfo.instanceCount();
+        }
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    RRectCircleRendererBatch(const Geometry& geometry, const GrIndexBuffer* indexBuffer)
+        : fIndexBuffer(indexBuffer) {
+        this->initClassID<RRectCircleRendererBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        RRectCircleRendererBatch* that = t->cast<RRectCircleRendererBatch>();
+
+        // TODO use vertex color to avoid breaking batches
+        if (this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->stroke() != that->stroke()) {
+            return false;
+        }
+
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    bool stroke() const { return fBatch.fStroke; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        bool fStroke;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+    const GrIndexBuffer* fIndexBuffer;
+};
+
+class RRectEllipseRendererBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkScalar fXRadius;
+        SkScalar fYRadius;
+        SkScalar fInnerXRadius;
+        SkScalar fInnerYRadius;
+        bool fStroke;
+        SkRect fDevBounds;
+    };
+
+    static GrBatch* Create(const Geometry& geometry, const GrIndexBuffer* indexBuffer) {
+        return SkNEW_ARGS(RRectEllipseRendererBatch, (geometry, indexBuffer));
+    }
+
+    const char* name() const SK_OVERRIDE { return "RRectEllipseRendererBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        // When this is called on a batch, there is only one geometry bundle
+        out->setKnownFourComponents(fGeoData[0].fColor);
+    }
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
+        out->setUnknownSingleComponent();
+    }
+
+    void initBatchOpt(const GrBatchOpt& batchOpt) {
+        fBatchOpt = batchOpt;
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fGeoData[0].fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fGeoData[0].fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fStroke = fGeoData[0].fStroke;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        // reset to device coordinates
+        SkMatrix invert;
+        if (!this->viewMatrix().invert(&invert)) {
+            SkDebugf("Failed to invert\n");
+            return;
+        }
+
+        // Setup geometry processor
+        SkAutoTUnref<GrGeometryProcessor> gp(EllipseEdgeEffect::Create(this->color(),
+                                                                       this->stroke(),
+                                                                       invert));
+
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO this is hacky, but the only way we have to initialize the GP is to use the
+        // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
+        // everywhere we can remove this nastiness
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        int instanceCount = fGeoData.count();
+        int vertexCount = kVertsPerRRect * instanceCount;
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(EllipseVertex));
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(vertices);
+
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            // Compute the reciprocals of the radii here to save time in the shader
+            SkScalar xRadRecip = SkScalarInvert(args.fXRadius);
+            SkScalar yRadRecip = SkScalarInvert(args.fYRadius);
+            SkScalar xInnerRadRecip = SkScalarInvert(args.fInnerXRadius);
+            SkScalar yInnerRadRecip = SkScalarInvert(args.fInnerYRadius);
+
+            // Extend the radii out half a pixel to antialias.
+            SkScalar xOuterRadius = args.fXRadius + SK_ScalarHalf;
+            SkScalar yOuterRadius = args.fYRadius + SK_ScalarHalf;
+
+            const SkRect& bounds = args.fDevBounds;
+
+            SkScalar yCoords[4] = {
+                bounds.fTop,
+                bounds.fTop + yOuterRadius,
+                bounds.fBottom - yOuterRadius,
+                bounds.fBottom
+            };
+            SkScalar yOuterOffsets[4] = {
+                yOuterRadius,
+                SK_ScalarNearlyZero, // we're using inversesqrt() in shader, so can't be exactly 0
+                SK_ScalarNearlyZero,
+                yOuterRadius
+            };
+
+            for (int i = 0; i < 4; ++i) {
+                verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+                verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
+                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]);
+                verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
+                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]);
+                verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
+                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+                verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
+                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+                verts++;
+            }
+        }
+
+        // drop out the middle quad if we're stroked
+        int indexCnt = this->stroke() ? SK_ARRAY_COUNT(gRRectIndices) - 6 :
+                                        SK_ARRAY_COUNT(gRRectIndices);
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerRRect);
+        drawInfo.setIndicesPerInstance(indexCnt);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(fIndexBuffer);
+
+        int maxInstancesPerDraw = kNumRRectsInIndexBuffer;
+
+        while (instanceCount) {
+            drawInfo.setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            instanceCount -= drawInfo.instanceCount();
+        }
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    RRectEllipseRendererBatch(const Geometry& geometry, const GrIndexBuffer* indexBuffer)
+        : fIndexBuffer(indexBuffer) {
+        this->initClassID<RRectEllipseRendererBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        RRectEllipseRendererBatch* that = t->cast<RRectEllipseRendererBatch>();
+
+        // TODO use vertex color to avoid breaking batches
+        if (this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->stroke() != that->stroke()) {
+            return false;
+        }
+
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    bool stroke() const { return fBatch.fStroke; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        bool fStroke;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+    const GrIndexBuffer* fIndexBuffer;
+};
+
 bool GrOvalRenderer::drawRRect(GrDrawTarget* target,
                                GrPipelineBuilder* pipelineBuilder,
                                GrColor color,
@@ -1209,13 +2142,6 @@
         return false;
     }
 
-    // reset to device coordinates
-    SkMatrix invert;
-    if (!viewMatrix.invert(&invert)) {
-        SkDebugf("Failed to invert\n");
-        return false;
-    }
-
     GrIndexBuffer* indexBuffer = this->rRectIndexBuffer(isStrokeOnly);
     if (NULL == indexBuffer) {
         SkDebugf("Failed to create index buffer!\n");
@@ -1243,18 +2169,6 @@
 
         isStrokeOnly = (isStrokeOnly && innerRadius >= 0);
 
-        SkAutoTUnref<GrGeometryProcessor> effect(CircleEdgeEffect::Create(color,
-                                                                          isStrokeOnly,
-                                                                          invert));
-
-        GrDrawTarget::AutoReleaseGeometry geo(target, 16, effect->getVertexStride(),  0);
-        SkASSERT(effect->getVertexStride() == sizeof(CircleVertex));
-        if (!geo.succeeded()) {
-            SkDebugf("Failed to get space for vertices!\n");
-            return false;
-        }
-        CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
-
         // The radii are outset for two reasons. First, it allows the shader to simply perform
         // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
         // Second, the outer radius is used to compute the verts of the bounding box that is
@@ -1266,47 +2180,16 @@
         // Expand the rect so all the pixels will be captured.
         bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
 
-        SkScalar yCoords[4] = {
-            bounds.fTop,
-            bounds.fTop + outerRadius,
-            bounds.fBottom - outerRadius,
-            bounds.fBottom
-        };
-        SkScalar yOuterRadii[4] = {-1, 0, 0, 1 };
-        // The inner radius in the vertex data must be specified in normalized space.
-        innerRadius = innerRadius / outerRadius;
-        for (int i = 0; i < 4; ++i) {
-            verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
-            verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
-            verts->fOuterRadius = outerRadius;
-            verts->fInnerRadius = innerRadius;
-            verts++;
+        RRectCircleRendererBatch::Geometry geometry;
+        geometry.fViewMatrix = viewMatrix;
+        geometry.fColor = color;
+        geometry.fInnerRadius = innerRadius;
+        geometry.fOuterRadius = outerRadius;
+        geometry.fStroke = isStrokeOnly;
+        geometry.fDevBounds = bounds;
 
-            verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
-            verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
-            verts->fOuterRadius = outerRadius;
-            verts->fInnerRadius = innerRadius;
-            verts++;
-
-            verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
-            verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
-            verts->fOuterRadius = outerRadius;
-            verts->fInnerRadius = innerRadius;
-            verts++;
-
-            verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
-            verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
-            verts->fOuterRadius = outerRadius;
-            verts->fInnerRadius = innerRadius;
-            verts++;
-        }
-
-        // drop out the middle quad if we're stroked
-        int indexCnt = isStrokeOnly ? SK_ARRAY_COUNT(gRRectIndices) - 6 :
-                                      SK_ARRAY_COUNT(gRRectIndices);
-        target->setIndexSourceToBuffer(indexBuffer);
-        target->drawIndexedInstances(pipelineBuilder, effect, kTriangles_GrPrimitiveType, 1, 16,
-                                     indexCnt, &bounds);
+        SkAutoTUnref<GrBatch> batch(RRectCircleRendererBatch::Create(geometry, indexBuffer));
+        target->drawBatch(pipelineBuilder, batch, &bounds);
 
     // otherwise we use the ellipse renderer
     } else {
@@ -1344,79 +2227,21 @@
 
         isStrokeOnly = (isStrokeOnly && innerXRadius >= 0 && innerYRadius >= 0);
 
-        SkAutoTUnref<GrGeometryProcessor> effect(EllipseEdgeEffect::Create(color,
-                                                                           isStrokeOnly,
-                                                                           invert));
-
-        GrDrawTarget::AutoReleaseGeometry geo(target, 16, effect->getVertexStride(),  0);
-        SkASSERT(effect->getVertexStride() == sizeof(EllipseVertex));
-        if (!geo.succeeded()) {
-            SkDebugf("Failed to get space for vertices!\n");
-            return false;
-        }
-
-        EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices());
-
-        // Compute the reciprocals of the radii here to save time in the shader
-        SkScalar xRadRecip = SkScalarInvert(xRadius);
-        SkScalar yRadRecip = SkScalarInvert(yRadius);
-        SkScalar xInnerRadRecip = SkScalarInvert(innerXRadius);
-        SkScalar yInnerRadRecip = SkScalarInvert(innerYRadius);
-
-        // Extend the radii out half a pixel to antialias.
-        SkScalar xOuterRadius = xRadius + SK_ScalarHalf;
-        SkScalar yOuterRadius = yRadius + SK_ScalarHalf;
-
         // Expand the rect so all the pixels will be captured.
         bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
 
-        SkScalar yCoords[4] = {
-            bounds.fTop,
-            bounds.fTop + yOuterRadius,
-            bounds.fBottom - yOuterRadius,
-            bounds.fBottom
-        };
-        SkScalar yOuterOffsets[4] = {
-            yOuterRadius,
-            SK_ScalarNearlyZero, // we're using inversesqrt() in the shader, so can't be exactly 0
-            SK_ScalarNearlyZero,
-            yOuterRadius
-        };
+        RRectEllipseRendererBatch::Geometry geometry;
+        geometry.fViewMatrix = viewMatrix;
+        geometry.fColor = color;
+        geometry.fXRadius = xRadius;
+        geometry.fYRadius = yRadius;
+        geometry.fInnerXRadius = innerXRadius;
+        geometry.fInnerYRadius = innerYRadius;
+        geometry.fStroke = isStrokeOnly;
+        geometry.fDevBounds = bounds;
 
-        for (int i = 0; i < 4; ++i) {
-            verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
-            verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
-            verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-            verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-            verts++;
-
-            verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]);
-            verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
-            verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-            verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-            verts++;
-
-            verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]);
-            verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
-            verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-            verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-            verts++;
-
-            verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
-            verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
-            verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-            verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-            verts++;
-        }
-
-        // drop out the middle quad if we're stroked
-        int indexCnt = isStrokeOnly ? SK_ARRAY_COUNT(gRRectIndices) - 6 :
-                                      SK_ARRAY_COUNT(gRRectIndices);
-        target->setIndexSourceToBuffer(indexBuffer);
-        target->drawIndexedInstances(pipelineBuilder, effect, kTriangles_GrPrimitiveType, 1, 16,
-                                     indexCnt, &bounds);
+        SkAutoTUnref<GrBatch> batch(RRectEllipseRendererBatch::Create(geometry, indexBuffer));
+        target->drawBatch(pipelineBuilder, batch, &bounds);
     }
-
-    target->resetIndexSource();
     return true;
 }