Hairline batch

BUG=skia:

Committed: https://skia.googlesource.com/skia/+/6eff8701f027016fbb3147412ec2292dcec2b7f5

Committed: https://skia.googlesource.com/skia/+/658d55cd6121c67488aaf5d0832c9712737f26a5

Review URL: https://codereview.chromium.org/876673002
diff --git a/src/gpu/GrAAHairLinePathRenderer.cpp b/src/gpu/GrAAHairLinePathRenderer.cpp
index ce24cf8..d2113ca 100644
--- a/src/gpu/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/GrAAHairLinePathRenderer.cpp
@@ -7,6 +7,9 @@
 
 #include "GrAAHairLinePathRenderer.h"
 
+#include "GrBatch.h"
+#include "GrBatchTarget.h"
+#include "GrBufferAllocPool.h"
 #include "GrContext.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawTargetCaps.h"
@@ -253,14 +256,14 @@
  * subdivide large quads to reduce over-fill. This subdivision has to be
  * performed before applying the perspective matrix.
  */
-int generate_lines_and_quads(const SkPath& path,
-                             const SkMatrix& m,
-                             const SkIRect& devClipBounds,
-                             GrAAHairLinePathRenderer::PtArray* lines,
-                             GrAAHairLinePathRenderer::PtArray* quads,
-                             GrAAHairLinePathRenderer::PtArray* conics,
-                             GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
-                             GrAAHairLinePathRenderer::FloatArray* conicWeights) {
+int gather_lines_and_quads(const SkPath& path,
+                           const SkMatrix& m,
+                           const SkIRect& devClipBounds,
+                           GrAAHairLinePathRenderer::PtArray* lines,
+                           GrAAHairLinePathRenderer::PtArray* quads,
+                           GrAAHairLinePathRenderer::PtArray* conics,
+                           GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
+                           GrAAHairLinePathRenderer::FloatArray* conicWeights) {
     SkPath::Iter iter(path, false);
 
     int totalQuadCount = 0;
@@ -469,8 +472,7 @@
 }
 
 void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
-                const SkMatrix* toSrc, BezierVertex verts[kQuadNumVertices],
-                SkRect* devBounds) {
+                const SkMatrix* toSrc, BezierVertex verts[kQuadNumVertices]) {
     SkASSERT(!toDevice == !toSrc);
     // original quad is specified by tri a,b,c
     SkPoint a = qpts[0];
@@ -535,7 +537,6 @@
     c1.fPos -= cbN;
 
     intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
-    devBounds->growToInclude(&verts[0].fPos, sizeof(BezierVertex), kQuadNumVertices);
 
     if (toSrc) {
         toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kQuadNumVertices);
@@ -567,9 +568,8 @@
                 const SkScalar weight,
                 const SkMatrix* toDevice,
                 const SkMatrix* toSrc,
-                BezierVertex** vert,
-                SkRect* devBounds) {
-    bloat_quad(p, toDevice, toSrc, *vert, devBounds);
+                BezierVertex** vert) {
+    bloat_quad(p, toDevice, toSrc, *vert);
     set_conic_coeffs(p, *vert, weight);
     *vert += kQuadNumVertices;
 }
@@ -578,16 +578,15 @@
                int subdiv,
                const SkMatrix* toDevice,
                const SkMatrix* toSrc,
-               BezierVertex** vert,
-               SkRect* devBounds) {
+               BezierVertex** vert) {
     SkASSERT(subdiv >= 0);
     if (subdiv) {
         SkPoint newP[5];
         SkChopQuadAtHalf(p, newP);
-        add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert, devBounds);
-        add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert, devBounds);
+        add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert);
+        add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert);
     } else {
-        bloat_quad(p, toDevice, toSrc, *vert, devBounds);
+        bloat_quad(p, toDevice, toSrc, *vert);
         set_uv_quad(p, *vert);
         *vert += kQuadNumVertices;
     }
@@ -642,106 +641,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-bool GrAAHairLinePathRenderer::createLineGeom(GrDrawTarget* target,
-                                              GrPipelineBuilder* pipelineBuilder,
-                                              const SkMatrix& viewMatrix,
-                                              uint8_t coverage,
-                                              size_t vertexStride,
-                                              GrDrawTarget::AutoReleaseGeometry* arg,
-                                              SkRect* devBounds,
-                                              const SkPath& path,
-                                              const PtArray& lines,
-                                              int lineCnt) {
-    int vertCnt = kLineSegNumVertices * lineCnt;
-
-    SkASSERT(vertexStride == sizeof(LineVertex));
-    if (!arg->set(target, vertCnt, vertexStride,  0)) {
-        return false;
-    }
-
-    LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices());
-
-    const SkMatrix* toSrc = NULL;
-    SkMatrix ivm;
-
-    if (viewMatrix.hasPerspective()) {
-        if (viewMatrix.invert(&ivm)) {
-            toSrc = &ivm;
-        }
-    }
-    devBounds->set(lines.begin(), lines.count());
-    for (int i = 0; i < lineCnt; ++i) {
-        add_line(&lines[2*i], toSrc, coverage, &verts);
-    }
-    // All the verts computed by add_line are within sqrt(1^2 + 0.5^2) of the end points.
-    static const SkScalar kSqrtOfOneAndAQuarter = 1.118f;
-    // Add a little extra to account for vector normalization precision.
-    static const SkScalar kOutset = kSqrtOfOneAndAQuarter + SK_Scalar1 / 20;
-    devBounds->outset(kOutset, kOutset);
-
-    return true;
-}
-
-bool GrAAHairLinePathRenderer::createBezierGeom(GrDrawTarget* target,
-                                                GrPipelineBuilder* pipelineBuilder,
-                                                const SkMatrix& viewMatrix,
-                                                GrDrawTarget::AutoReleaseGeometry* arg,
-                                                SkRect* devBounds,
-                                                const SkPath& path,
-                                                const PtArray& quads,
-                                                int quadCnt,
-                                                const PtArray& conics,
-                                                int conicCnt,
-                                                const IntArray& qSubdivs,
-                                                const FloatArray& cWeights,
-                                                size_t vertexStride) {
-    int vertCnt = kQuadNumVertices * quadCnt + kQuadNumVertices * conicCnt;
-
-    if (!arg->set(target, vertCnt, vertexStride, 0)) {
-        return false;
-    }
-
-    BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices());
-
-    const SkMatrix* toDevice = NULL;
-    const SkMatrix* toSrc = NULL;
-    SkMatrix ivm;
-
-    if (viewMatrix.hasPerspective()) {
-        if (viewMatrix.invert(&ivm)) {
-            toDevice = &viewMatrix;
-            toSrc = &ivm;
-        }
-    }
-
-    // Seed the dev bounds with some pts known to be inside. Each quad and conic grows the bounding
-    // box to include its vertices.
-    SkPoint seedPts[2];
-    if (quadCnt) {
-        seedPts[0] = quads[0];
-        seedPts[1] = quads[2];
-    } else if (conicCnt) {
-        seedPts[0] = conics[0];
-        seedPts[1] = conics[2];
-    }
-    if (toDevice) {
-        toDevice->mapPoints(seedPts, 2);
-    }
-    devBounds->set(seedPts[0], seedPts[1]);
-
-    int unsubdivQuadCnt = quads.count() / 3;
-    for (int i = 0; i < unsubdivQuadCnt; ++i) {
-        SkASSERT(qSubdivs[i] >= 0);
-        add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds);
-    }
-
-    // Start Conics
-    for (int i = 0; i < conicCnt; ++i) {
-        add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds);
-    }
-    return true;
-}
-
 bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target,
                                            const GrPipelineBuilder* pipelineBuilder,
                                            const SkMatrix& viewMatrix,
@@ -800,13 +699,342 @@
     return true;
 }
 
+class AAHairlineBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        uint8_t fCoverage;
+        SkMatrix fViewMatrix;
+        SkPath fPath;
+        SkDEBUGCODE(SkRect fDevBounds;)
+        SkIRect fDevClipBounds;
+    };
+
+    // TODO Batch itself should not hold on to index buffers.  Instead, these should live in the
+    // cache.
+    static GrBatch* Create(const Geometry& geometry, const GrIndexBuffer* linesIndexBuffer,
+                           const GrIndexBuffer* quadsIndexBuffer) {
+        return SkNEW_ARGS(AAHairlineBatch, (geometry, linesIndexBuffer, quadsIndexBuffer));
+    }
+
+    const char* name() const SK_OVERRIDE { return "AAHairlineBatch"; }
+
+    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.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+        fBatch.fCoverage = fGeoData[0].fCoverage;
+        SkDEBUGCODE(fBatch.fDevBounds = fGeoData[0].fDevBounds;)
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE;
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    typedef SkTArray<SkPoint, true> PtArray;
+    typedef SkTArray<int, true> IntArray;
+    typedef SkTArray<float, true> FloatArray;
+
+    AAHairlineBatch(const Geometry& geometry, const GrIndexBuffer* linesIndexBuffer,
+                    const GrIndexBuffer* quadsIndexBuffer)
+        : fLinesIndexBuffer(linesIndexBuffer)
+        , fQuadsIndexBuffer(quadsIndexBuffer) {
+        SkASSERT(linesIndexBuffer && quadsIndexBuffer);
+        this->initClassID<AAHairlineBatch>();
+        fGeoData.push_back(geometry);
+    }
+
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        AAHairlineBatch* that = t->cast<AAHairlineBatch>();
+
+        if (this->viewMatrix().hasPerspective() != that->viewMatrix().hasPerspective()) {
+            return false;
+        }
+
+        // We go to identity if we don't have perspective
+        if (this->viewMatrix().hasPerspective() &&
+            !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        // TODO we can actually batch hairlines if they are the same color in a kind of bulk method
+        // but we haven't implemented this yet
+        // TODO investigate going to vertex color and coverage?
+        if (this->coverage() != that->coverage()) {
+            return false;
+        }
+
+        if (this->color() != that->color()) {
+            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; }
+    uint8_t coverage() const { return fBatch.fCoverage; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        uint8_t fCoverage;
+        SkRect fDevBounds;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+    };
+
+    GrBatchOpt fBatchOpt;
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+    const GrIndexBuffer* fLinesIndexBuffer;
+    const GrIndexBuffer* fQuadsIndexBuffer;
+};
+
+void AAHairlineBatch::generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) {
+    // Setup the viewmatrix and localmatrix for the GrGeometryProcessor.
+    SkMatrix invert;
+    if (!this->viewMatrix().invert(&invert)) {
+        return;
+    }
+
+    // we will transform to identity space if the viewmatrix does not have perspective
+    bool hasPerspective = this->viewMatrix().hasPerspective();
+    const SkMatrix* geometryProcessorViewM = &SkMatrix::I();
+    const SkMatrix* geometryProcessorLocalM = &invert;
+    const SkMatrix* toDevice = NULL;
+    const SkMatrix* toSrc = NULL;
+    if (hasPerspective) {
+        geometryProcessorViewM = &this->viewMatrix();
+        geometryProcessorLocalM = &SkMatrix::I();
+        toDevice = &this->viewMatrix();
+        toSrc = &invert;
+    }
+
+    // Setup geometry processors for worst case
+    uint32_t gpFlags = GrDefaultGeoProcFactory::kPosition_GPType |
+                       GrDefaultGeoProcFactory::kCoverage_GPType;
+
+    SkAutoTUnref<const GrGeometryProcessor> lineGP(
+            GrDefaultGeoProcFactory::Create(gpFlags,
+                                            this->color(),
+                                            *geometryProcessorViewM,
+                                            *geometryProcessorLocalM,
+                                            false,
+                                            this->coverage()));
+
+    SkAutoTUnref<const GrGeometryProcessor> quadGP(
+            GrQuadEffect::Create(this->color(),
+                                 *geometryProcessorViewM,
+                                 kHairlineAA_GrProcessorEdgeType,
+                                 batchTarget->caps(),
+                                 *geometryProcessorLocalM,
+                                 this->coverage()));
+
+    SkAutoTUnref<const GrGeometryProcessor> conicGP(
+            GrConicEffect::Create(this->color(),
+                                  *geometryProcessorViewM,
+                                  kHairlineAA_GrProcessorEdgeType,
+                                  batchTarget->caps(),
+                                  *geometryProcessorLocalM,
+                                  this->coverage()));
+
+    // This is hand inlined for maximum performance.
+    PREALLOC_PTARRAY(128) lines;
+    PREALLOC_PTARRAY(128) quads;
+    PREALLOC_PTARRAY(128) conics;
+    IntArray qSubdivs;
+    FloatArray cWeights;
+
+    int instanceCount = fGeoData.count();
+    for (int i = 0; i < instanceCount; i++) {
+        const Geometry& args = fGeoData[i];
+        gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds,
+                               &lines, &quads, &conics, &qSubdivs, &cWeights);
+    }
+
+    int quadCount = quads.count() / 3;
+    int lineCount = lines.count() / 2;
+    int conicCount = conics.count() / 3;
+
+    // do lines first
+    if (lineCount) {
+        batchTarget->initDraw(lineGP, pipeline);
+
+        // TODO remove this when batch is everywhere
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        lineGP->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        size_t vertexStride = lineGP->getVertexStride();
+        int vertexCount = kLineSegNumVertices * lineCount;
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        SkASSERT(lineGP->getVertexStride() == sizeof(LineVertex));
+
+        LineVertex* verts = reinterpret_cast<LineVertex*>(vertices);
+        for (int i = 0; i < lineCount; ++i) {
+            add_line(&lines[2*i], toSrc, this->coverage(), &verts);
+        }
+
+        {
+            GrDrawTarget::DrawInfo info;
+            info.setVertexBuffer(vertexBuffer);
+            info.setIndexBuffer(fLinesIndexBuffer);
+            info.setPrimitiveType(kTriangles_GrPrimitiveType);
+            info.setStartIndex(0);
+
+            int lines = 0;
+            while (lines < lineCount) {
+                int n = SkTMin(lineCount - lines, kLineSegsNumInIdxBuffer);
+
+                info.setStartVertex(kLineSegNumVertices*lines + firstVertex);
+                info.setVertexCount(kLineSegNumVertices*n);
+                info.setIndexCount(kIdxsPerLineSeg*n);
+                batchTarget->draw(info);
+
+                lines += n;
+            }
+        }
+    }
+
+    if (quadCount || conicCount) {
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        size_t vertexStride = sizeof(BezierVertex);
+        int vertexCount = kQuadNumVertices * quadCount + kQuadNumVertices * conicCount;
+        void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              vertexCount,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        // Setup vertices
+        BezierVertex* verts = reinterpret_cast<BezierVertex*>(vertices);
+
+        // is this the same as quadcount? TODO
+        int unsubdivQuadCnt = quads.count() / 3;
+        for (int i = 0; i < unsubdivQuadCnt; ++i) {
+            SkASSERT(qSubdivs[i] >= 0);
+            add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts);
+        }
+
+        // Start Conics
+        for (int i = 0; i < conicCount; ++i) {
+            add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts);
+        }
+
+        if (quadCount > 0) {
+            batchTarget->initDraw(quadGP, pipeline);
+
+            // TODO remove this when batch is everywhere
+            GrPipelineInfo init;
+            init.fColorIgnored = fBatch.fColorIgnored;
+            init.fOverrideColor = GrColor_ILLEGAL;
+            init.fCoverageIgnored = fBatch.fCoverageIgnored;
+            init.fUsesLocalCoords = this->usesLocalCoords();
+            quadGP->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+            {
+               GrDrawTarget::DrawInfo info;
+               info.setVertexBuffer(vertexBuffer);
+               info.setIndexBuffer(fQuadsIndexBuffer);
+               info.setPrimitiveType(kTriangles_GrPrimitiveType);
+               info.setStartIndex(0);
+
+               int quads = 0;
+               while (quads < quadCount) {
+                   int n = SkTMin(quadCount - quads, kQuadsNumInIdxBuffer);
+
+                   info.setStartVertex(kQuadNumVertices*quads + firstVertex);
+                   info.setVertexCount(kQuadNumVertices*n);
+                   info.setIndexCount(kIdxsPerQuad*n);
+                   batchTarget->draw(info);
+
+                   quads += n;
+               }
+           }
+        }
+
+        if (conicCount > 0) {
+            batchTarget->initDraw(conicGP, pipeline);
+
+            // TODO remove this when batch is everywhere
+            GrPipelineInfo init;
+            init.fColorIgnored = fBatch.fColorIgnored;
+            init.fOverrideColor = GrColor_ILLEGAL;
+            init.fCoverageIgnored = fBatch.fCoverageIgnored;
+            init.fUsesLocalCoords = this->usesLocalCoords();
+            conicGP->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+            {
+                GrDrawTarget::DrawInfo info;
+                info.setVertexBuffer(vertexBuffer);
+                info.setIndexBuffer(fQuadsIndexBuffer);
+                info.setPrimitiveType(kTriangles_GrPrimitiveType);
+                info.setStartIndex(0);
+
+                int conics = 0;
+                while (conics < conicCount) {
+                    int n = SkTMin(conicCount - conics, kQuadsNumInIdxBuffer);
+
+                    info.setStartVertex(kQuadNumVertices*(quadCount + conics) + firstVertex);
+                    info.setVertexCount(kQuadNumVertices*n);
+                    info.setIndexCount(kIdxsPerQuad*n);
+                    batchTarget->draw(info);
+
+                    conics += n;
+                }
+            }
+        }
+    }
+}
+
 bool GrAAHairLinePathRenderer::onDrawPath(GrDrawTarget* target,
                                           GrPipelineBuilder* pipelineBuilder,
                                           GrColor color,
                                           const SkMatrix& viewMatrix,
                                           const SkPath& path,
                                           const SkStrokeRec& stroke,
-                                          bool antiAlias) {
+                                          bool) {
     SkScalar hairlineCoverage;
     uint8_t newCoverage = 0xff;
     if (IsStrokeHairlineOrEquivalent(stroke, viewMatrix, &hairlineCoverage)) {
@@ -816,163 +1044,22 @@
     SkIRect devClipBounds;
     target->getClip()->getConservativeBounds(pipelineBuilder->getRenderTarget(), &devClipBounds);
 
-    int lineCnt;
-    int quadCnt;
-    int conicCnt;
-    PREALLOC_PTARRAY(128) lines;
-    PREALLOC_PTARRAY(128) quads;
-    PREALLOC_PTARRAY(128) conics;
-    IntArray qSubdivs;
-    FloatArray cWeights;
-    quadCnt = generate_lines_and_quads(path, viewMatrix, devClipBounds,
-                                       &lines, &quads, &conics, &qSubdivs, &cWeights);
-    lineCnt = lines.count() / 2;
-    conicCnt = conics.count() / 3;
+    // This outset was determined experimentally by running skps and gms.  It probably could be a
+    // bit tighter
+    SkRect devRect = path.getBounds();
+    viewMatrix.mapRect(&devRect);
+    devRect.outset(2, 2);
 
-    // createGeom transforms the geometry to device space when the matrix does not have
-    // perspective.
-    SkMatrix vm = viewMatrix;
-    SkMatrix invert = SkMatrix::I();
-    if (!viewMatrix.hasPerspective()) {
-        vm = SkMatrix::I();
-        if (!viewMatrix.invert(&invert)) {
-            return false;
-        }
-    }
+    AAHairlineBatch::Geometry geometry;
+    geometry.fColor = color;
+    geometry.fCoverage = newCoverage;
+    geometry.fViewMatrix = viewMatrix;
+    geometry.fPath = path;
+    SkDEBUGCODE(geometry.fDevBounds = devRect;)
+    geometry.fDevClipBounds = devClipBounds;
 
-    // do lines first
-    if (lineCnt) {
-        GrDrawTarget::AutoReleaseGeometry arg;
-        SkRect devBounds;
-
-        GrPipelineBuilder::AutoRestoreEffects are(pipelineBuilder);
-        uint32_t gpFlags = GrDefaultGeoProcFactory::kPosition_GPType |
-                           GrDefaultGeoProcFactory::kCoverage_GPType;
-        SkAutoTUnref<const GrGeometryProcessor> gp(GrDefaultGeoProcFactory::Create(gpFlags,
-                                                                                   color,
-                                                                                   vm,
-                                                                                   invert,
-                                                                                   false,
-                                                                                   newCoverage));
-
-        if (!this->createLineGeom(target,
-                                  pipelineBuilder,
-                                  viewMatrix,
-                                  newCoverage,
-                                  gp->getVertexStride(),
-                                  &arg,
-                                  &devBounds,
-                                  path,
-                                  lines,
-                                  lineCnt)) {
-            return false;
-        }
-
-        // Check devBounds
-        SkASSERT(check_bounds<LineVertex>(viewMatrix.hasPerspective() ? viewMatrix : SkMatrix::I(),
-                                          devBounds,
-                                          arg.vertices(),
-                                          kLineSegNumVertices * lineCnt));
-
-        {
-            target->setIndexSourceToBuffer(fLinesIndexBuffer);
-            int lines = 0;
-            while (lines < lineCnt) {
-                int n = SkTMin(lineCnt - lines, kLineSegsNumInIdxBuffer);
-                target->drawIndexed(pipelineBuilder,
-                                    gp,
-                                    kTriangles_GrPrimitiveType,
-                                    kLineSegNumVertices*lines,     // startV
-                                    0,                             // startI
-                                    kLineSegNumVertices*n,         // vCount
-                                    kIdxsPerLineSeg*n,             // iCount
-                                    &devBounds);
-                lines += n;
-            }
-        }
-    }
-
-    // then quadratics/conics
-    if (quadCnt || conicCnt) {
-        GrDrawTarget::AutoReleaseGeometry arg;
-        SkRect devBounds;
-
-        if (!this->createBezierGeom(target,
-                                    pipelineBuilder,
-                                    viewMatrix,
-                                    &arg,
-                                    &devBounds,
-                                    path,
-                                    quads,
-                                    quadCnt,
-                                    conics,
-                                    conicCnt,
-                                    qSubdivs,
-                                    cWeights,
-                                    sizeof(BezierVertex))) {
-            return false;
-        }
-
-        // Check devBounds
-        SkASSERT(check_bounds<BezierVertex>(viewMatrix.hasPerspective() ? viewMatrix :
-                                                                          SkMatrix::I(),
-                                            devBounds,
-                                            arg.vertices(),
-                                            kQuadNumVertices * quadCnt +
-                                            kQuadNumVertices * conicCnt));
-
-        if (quadCnt > 0) {
-            SkAutoTUnref<GrGeometryProcessor> hairQuadProcessor(
-                    GrQuadEffect::Create(color,
-                                         vm,
-                                         kHairlineAA_GrProcessorEdgeType,
-                                         *target->caps(),
-                                         invert,
-                                         newCoverage));
-            SkASSERT(hairQuadProcessor);
-            GrPipelineBuilder::AutoRestoreEffects are(pipelineBuilder);
-            target->setIndexSourceToBuffer(fQuadsIndexBuffer);
-
-            int quads = 0;
-            while (quads < quadCnt) {
-                int n = SkTMin(quadCnt - quads, kQuadsNumInIdxBuffer);
-                target->drawIndexed(pipelineBuilder,
-                                    hairQuadProcessor,
-                                    kTriangles_GrPrimitiveType,
-                                    kQuadNumVertices*quads,               // startV
-                                    0,                                    // startI
-                                    kQuadNumVertices*n,                   // vCount
-                                    kIdxsPerQuad*n,                       // iCount
-                                    &devBounds);
-                quads += n;
-            }
-        }
-
-        if (conicCnt > 0) {
-            SkAutoTUnref<GrGeometryProcessor> hairConicProcessor(
-                    GrConicEffect::Create(color, vm, kHairlineAA_GrProcessorEdgeType,
-                                          *target->caps(), invert, newCoverage));
-            SkASSERT(hairConicProcessor);
-            GrPipelineBuilder::AutoRestoreEffects are(pipelineBuilder);
-            target->setIndexSourceToBuffer(fQuadsIndexBuffer);
-
-            int conics = 0;
-            while (conics < conicCnt) {
-                int n = SkTMin(conicCnt - conics, kQuadsNumInIdxBuffer);
-                target->drawIndexed(pipelineBuilder,
-                                    hairConicProcessor,
-                                    kTriangles_GrPrimitiveType,
-                                    kQuadNumVertices*(quadCnt + conics),  // startV
-                                    0,                                    // startI
-                                    kQuadNumVertices*n,                   // vCount
-                                    kIdxsPerQuad*n,                       // iCount
-                                    &devBounds);
-                conics += n;
-            }
-        }
-    }
-
-    target->resetIndexSource();
+    GrBatch* batch = AAHairlineBatch::Create(geometry, fLinesIndexBuffer, fQuadsIndexBuffer);
+    target->drawBatch(pipelineBuilder, batch, &devRect);
 
     return true;
 }
diff --git a/src/gpu/GrAAHairLinePathRenderer.h b/src/gpu/GrAAHairLinePathRenderer.h
index 91c00d7..ea1d8ed 100644
--- a/src/gpu/GrAAHairLinePathRenderer.h
+++ b/src/gpu/GrAAHairLinePathRenderer.h
@@ -42,31 +42,6 @@
                              const GrIndexBuffer* fLinesIndexBuffer,
                              const GrIndexBuffer* fQuadsIndexBuffer);
 
-    bool createLineGeom(GrDrawTarget* target,
-                        GrPipelineBuilder*,
-                        const SkMatrix& viewMatrix,
-                        uint8_t coverage,
-                        size_t vertexStride,
-                        GrDrawTarget::AutoReleaseGeometry* arg,
-                        SkRect* devBounds,
-                        const SkPath& path,
-                        const PtArray& lines,
-                        int lineCnt);
-
-    bool createBezierGeom(GrDrawTarget* target,
-                          GrPipelineBuilder*,
-                          const SkMatrix& viewMatrix,
-                          GrDrawTarget::AutoReleaseGeometry* arg,
-                          SkRect* devBounds,
-                          const SkPath& path,
-                          const PtArray& quads,
-                          int quadCnt,
-                          const PtArray& conics,
-                          int conicCnt,
-                          const IntArray& qSubdivs,
-                          const FloatArray& cWeights,
-                          size_t vertexStride);
-
     const GrIndexBuffer*        fLinesIndexBuffer;
     const GrIndexBuffer*        fQuadsIndexBuffer;
 
diff --git a/src/gpu/GrAARectRenderer.cpp b/src/gpu/GrAARectRenderer.cpp
index 48692eb..420c9e7 100644
--- a/src/gpu/GrAARectRenderer.cpp
+++ b/src/gpu/GrAARectRenderer.cpp
@@ -208,10 +208,7 @@
             return false;
         }
 
-        if (this->usesLocalCoords() != that->usesLocalCoords()) {
-            return false;
-        }
-
+        SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
         // We apply the viewmatrix to the rect points on the cpu.  However, if the pipeline uses
         // local coords then we won't be able to batch.  We could actually upload the viewmatrix
         // using vertex attributes in these cases, but haven't investigated that
diff --git a/src/gpu/GrBatch.h b/src/gpu/GrBatch.h
index ceb2c5c..2f1afee 100644
--- a/src/gpu/GrBatch.h
+++ b/src/gpu/GrBatch.h
@@ -46,7 +46,7 @@
 class GrBatch : public SkRefCnt {
 public:
     SK_DECLARE_INST_COUNT(GrBatch)
-    GrBatch() { SkDEBUGCODE(fUsed = false;) }
+    GrBatch() : fNumberOfDraws(0) { SkDEBUGCODE(fUsed = false;) }
     virtual ~GrBatch() {}
 
     virtual const char* name() const = 0;
@@ -75,6 +75,10 @@
 
     virtual void generateGeometry(GrBatchTarget*, const GrPipeline*) = 0;
 
+    // TODO this goes away when batches are everywhere
+    void setNumberOfDraws(int numberOfDraws) { fNumberOfDraws = numberOfDraws; }
+    int numberOfDraws() const { return fNumberOfDraws; }
+
     void* operator new(size_t size);
     void operator delete(void* target);
 
@@ -126,6 +130,8 @@
 
     SkDEBUGCODE(bool fUsed;)
 
+    int fNumberOfDraws;
+
     typedef SkRefCnt INHERITED;
 };
 
diff --git a/src/gpu/GrBatchTarget.cpp b/src/gpu/GrBatchTarget.cpp
index d5d8d8f..7a2d14e 100644
--- a/src/gpu/GrBatchTarget.cpp
+++ b/src/gpu/GrBatchTarget.cpp
@@ -30,17 +30,20 @@
     }
     fFlushBuffer.reset();
 }*/
+/*
+void GrBatchTarget::flushNext(int n) {
+    for (; n > 0; n--) {
+        SkDEBUGCODE(bool verify =) fIter.next();
+        SkASSERT(verify);
+        GrProgramDesc desc;
+        BufferedFlush* bf = fIter.get();
+        const GrPipeline* pipeline = bf->fPipeline;
+        const GrPrimitiveProcessor* primProc = bf->fPrimitiveProcessor.get();
+        fGpu->buildProgramDesc(&desc, *primProc, *pipeline, bf->fBatchTracker);
 
-void GrBatchTarget::flushNext() {
-    fIter.next();
-    GrProgramDesc desc;
-    BufferedFlush* bf = fIter.get();
-    const GrPipeline* pipeline = bf->fPipeline;
-    const GrPrimitiveProcessor* primProc = bf->fPrimitiveProcessor.get();
-    fGpu->buildProgramDesc(&desc, *primProc, *pipeline, bf->fBatchTracker);
-
-    GrGpu::DrawArgs args(primProc, pipeline, &desc, &bf->fBatchTracker);
-    for (int i = 0; i < bf->fDraws.count(); i++) {
-        fGpu->draw(args, bf->fDraws[i]);
+        GrGpu::DrawArgs args(primProc, pipeline, &desc, &bf->fBatchTracker);
+        for (int i = 0; i < bf->fDraws.count(); i++) {
+            fGpu->draw(args, bf->fDraws[i]);
+        }
     }
-}
+}*/
diff --git a/src/gpu/GrBatchTarget.h b/src/gpu/GrBatchTarget.h
index b739077..8cd91d3 100644
--- a/src/gpu/GrBatchTarget.h
+++ b/src/gpu/GrBatchTarget.h
@@ -9,6 +9,7 @@
 #define GrBatchBuffer_DEFINED
 
 #include "GrPendingProgramElement.h"
+#include "GrPipeline.h"
 #include "GrGpu.h"
 #include "GrTRecorder.h"
 
@@ -17,6 +18,9 @@
  * that render their batch.
  */
 
+class GrIndexBufferAllocPool;
+class GrVertexBufferAllocPool;
+
 class GrBatchTarget : public SkNoncopyable {
 public:
     GrBatchTarget(GrGpu* gpu,
@@ -26,11 +30,13 @@
         , fVertexPool(vpool)
         , fIndexPool(ipool)
         , fFlushBuffer(kFlushBufferInitialSizeInBytes)
-        , fIter(fFlushBuffer) {}
+        , fIter(fFlushBuffer)
+        , fNumberOfDraws(0) {}
 
     typedef GrDrawTarget::DrawInfo DrawInfo;
     void initDraw(const GrPrimitiveProcessor* primProc, const GrPipeline* pipeline) {
         GrNEW_APPEND_TO_RECORDER(fFlushBuffer, BufferedFlush, (primProc, pipeline));
+        fNumberOfDraws++;
     }
 
     void draw(const GrDrawTarget::DrawInfo& draw) {
@@ -39,8 +45,28 @@
 
     // TODO this is temporary until batch is everywhere
     //void flush();
+    void resetNumberOfDraws() { fNumberOfDraws = 0; }
+    int numberOfDraws() const { return fNumberOfDraws; }
     void preFlush() { fIter = FlushBuffer::Iter(fFlushBuffer); }
-    void flushNext();
+    void flushNext(int n) {
+        for (; n > 0; n--) {
+            SkDEBUGCODE(bool verify =) fIter.next();
+            SkASSERT(verify);
+            GrProgramDesc desc;
+            BufferedFlush* bf = fIter.get();
+            const GrPipeline* pipeline = bf->fPipeline;
+            const GrPrimitiveProcessor* primProc = bf->fPrimitiveProcessor.get();
+            fGpu->buildProgramDesc(&desc, *primProc, *pipeline, bf->fBatchTracker);
+
+            GrGpu::DrawArgs args(primProc, pipeline, &desc, &bf->fBatchTracker);
+
+            int drawCount = bf->fDraws.count();
+            const SkSTArray<1, DrawInfo, true>& draws = bf->fDraws;
+            for (int i = 0; i < drawCount; i++) {
+                fGpu->draw(args, draws[i]);
+            }
+        }
+    }
     void postFlush() { SkASSERT(!fIter.next()); fFlushBuffer.reset(); }
 
     // TODO This goes away when everything uses batch
@@ -49,6 +75,8 @@
         return &fFlushBuffer.back().fBatchTracker;
     }
 
+    const GrDrawTargetCaps& caps() const { return *fGpu->caps(); }
+
     GrVertexBufferAllocPool* vertexPool() { return fVertexPool; }
     GrIndexBufferAllocPool* indexPool() { return fIndexPool; }
 
@@ -62,18 +90,16 @@
     struct BufferedFlush {
         BufferedFlush(const GrPrimitiveProcessor* primProc, const GrPipeline* pipeline)
             : fPrimitiveProcessor(primProc)
-            , fPipeline(pipeline)
-            , fDraws(kDrawRecorderInitialSizeInBytes) {}
+            , fPipeline(pipeline) {}
         typedef GrPendingProgramElement<const GrPrimitiveProcessor> ProgramPrimitiveProcessor;
         ProgramPrimitiveProcessor fPrimitiveProcessor;
         const GrPipeline* fPipeline;
         GrBatchTracker fBatchTracker;
-        SkSTArray<4, DrawInfo, true> fDraws;
+        SkSTArray<1, DrawInfo, true> fDraws;
     };
 
     enum {
         kFlushBufferInitialSizeInBytes = 8 * sizeof(BufferedFlush),
-        kDrawRecorderInitialSizeInBytes = 8 * sizeof(DrawInfo),
     };
 
     typedef GrTRecorder<BufferedFlush, TBufferAlign> FlushBuffer;
@@ -81,6 +107,7 @@
     FlushBuffer fFlushBuffer;
     // TODO this is temporary
     FlushBuffer::Iter fIter;
+    int fNumberOfDraws;
 };
 
 #endif
diff --git a/src/gpu/GrInOrderDrawBuffer.cpp b/src/gpu/GrInOrderDrawBuffer.cpp
index 4629d4f..3e177ed 100644
--- a/src/gpu/GrInOrderDrawBuffer.cpp
+++ b/src/gpu/GrInOrderDrawBuffer.cpp
@@ -460,7 +460,9 @@
 
     int currCmdMarker = 0;
 
+    int i = 0;
     while (iter.next()) {
+        i++;
         GrGpuTraceMarker newMarker("", -1);
         SkString traceString;
         if (cmd_has_trace_marker(iter->fType)) {
@@ -472,7 +474,8 @@
 
         // TODO temporary hack
         if (kDrawBatch_Cmd == strip_trace_bit(iter->fType)) {
-            fBatchTarget.flushNext();
+            DrawBatch* db = reinterpret_cast<DrawBatch*>(iter.get());
+            fBatchTarget.flushNext(db->fBatch->numberOfDraws());
             continue;
         }
 
@@ -652,7 +655,9 @@
 
 void GrInOrderDrawBuffer::closeBatch() {
     if (fDrawBatch) {
+        fBatchTarget.resetNumberOfDraws();
         fDrawBatch->execute(this, fPrevState);
+        fDrawBatch->fBatch->setNumberOfDraws(fBatchTarget.numberOfDraws());
         fDrawBatch = NULL;
     }
 }