GrPathRangeBatch

BUG=skia:

Review URL: https://codereview.chromium.org/1315563003
diff --git a/src/gpu/GrBufferedDrawTarget.cpp b/src/gpu/GrBufferedDrawTarget.cpp
index 7ba6a2d..0b6e885 100644
--- a/src/gpu/GrBufferedDrawTarget.cpp
+++ b/src/gpu/GrBufferedDrawTarget.cpp
@@ -16,9 +16,6 @@
 GrBufferedDrawTarget::GrBufferedDrawTarget(GrContext* context)
     : INHERITED(context)
     , fCommands(GrCommandBuilder::Create(context->getGpu(), allow_reordering(context->caps())))
-    , fPathIndexBuffer(kPathIdxBufferMinReserve * sizeof(char)/4)
-    , fPathTransformBuffer(kPathXformBufferMinReserve * sizeof(float)/4)
-    , fPipelineBuffer(kPipelineBufferMinReserve)
     , fDrawID(0) {
 }
 
@@ -30,66 +27,11 @@
     fCommands->recordDrawBatch(batch, *this->caps());
 }
 
-void GrBufferedDrawTarget::onDrawPaths(const GrPathProcessor* pathProc,
-                                       const GrPathRange* pathRange,
-                                       const void* indices,
-                                       PathIndexType indexType,
-                                       const float transformValues[],
-                                       PathTransformType transformType,
-                                       int count,
-                                       const GrStencilSettings& stencilSettings,
-                                       const PipelineInfo& pipelineInfo) {
-    GrPipelineOptimizations opts;
-    StateForPathDraw* state = this->createStateForPathDraw(pathProc, pipelineInfo, &opts);
-    if (!state) {
-        return;
-    }
-    fCommands->recordDrawPaths(state, this, pathProc, pathRange, indices, indexType,
-                               transformValues, transformType, count, stencilSettings,
-                               opts);
-}
-
-void GrBufferedDrawTarget::onReset() {
-    fCommands->reset();
-    fPathIndexBuffer.rewind();
-    fPathTransformBuffer.rewind();
-
-    fPrevState.reset(nullptr);
-    // Note, fPrevState points into fPipelineBuffer's allocation, so we have to reset first.
-    // Furthermore, we have to reset fCommands before fPipelineBuffer too.
-    if (fDrawID % kPipelineBufferHighWaterMark) {
-        fPipelineBuffer.rewind();
-    } else {
-        fPipelineBuffer.reset();
-    }
-}
-
 void GrBufferedDrawTarget::onFlush() {
     fCommands->flush(this->getGpu(), this->getContext()->resourceProvider());
     ++fDrawID;
 }
 
-GrTargetCommands::StateForPathDraw*
-GrBufferedDrawTarget::createStateForPathDraw(const GrPrimitiveProcessor* primProc,
-                                             const GrDrawTarget::PipelineInfo& pipelineInfo,
-                                             GrPipelineOptimizations* opts) {
-    StateForPathDraw* state = this->allocState(primProc);
-    if (!GrPipeline::CreateAt(state->pipelineLocation(), pipelineInfo.pipelineCreateArgs(), opts)) {
-        this->unallocState(state);
-        return nullptr;
-    }
-
-    state->fPrimitiveProcessor->initBatchTracker(&state->fBatchTracker, *opts);
-
-    if (fPrevState && fPrevState->fPrimitiveProcessor.get() &&
-        fPrevState->fPrimitiveProcessor->canMakeEqual(fPrevState->fBatchTracker,
-                                                      *state->fPrimitiveProcessor,
-                                                      state->fBatchTracker) &&
-        GrPipeline::AreEqual(*fPrevState->getPipeline(), *state->getPipeline(), false)) {
-        this->unallocState(state);
-    } else {
-        fPrevState.reset(state);
-    }
-
-    return fPrevState;
+void GrBufferedDrawTarget::onReset() {
+    fCommands->reset();
 }
diff --git a/src/gpu/GrBufferedDrawTarget.h b/src/gpu/GrBufferedDrawTarget.h
index 5a80a3c..79b1a71 100644
--- a/src/gpu/GrBufferedDrawTarget.h
+++ b/src/gpu/GrBufferedDrawTarget.h
@@ -20,7 +20,6 @@
  */
 class GrBufferedDrawTarget : public GrClipTarget {
 public:
-
     /**
      * Creates a GrBufferedDrawTarget
      *
@@ -31,82 +30,14 @@
     ~GrBufferedDrawTarget() override;
 
 protected:
-    void appendIndicesAndTransforms(const void* indexValues, PathIndexType indexType, 
-                                    const float* transformValues, PathTransformType transformType,
-                                    int count, char** indicesLocation, float** xformsLocation) {
-        int indexBytes = GrPathRange::PathIndexSizeInBytes(indexType);
-        *indicesLocation = (char*) fPathIndexBuffer.alloc(count * indexBytes,
-                                                          SkChunkAlloc::kThrow_AllocFailType);
-        SkASSERT(SkIsAlign4((uintptr_t)*indicesLocation));
-        memcpy(*indicesLocation, reinterpret_cast<const char*>(indexValues), count * indexBytes);
-
-        const int xformBytes = GrPathRendering::PathTransformSize(transformType) * sizeof(float);
-        *xformsLocation = nullptr;
-
-        if (0 != xformBytes) {
-            *xformsLocation = (float*) fPathTransformBuffer.alloc(count * xformBytes,
-                                                               SkChunkAlloc::kThrow_AllocFailType);
-            SkASSERT(SkIsAlign4((uintptr_t)*xformsLocation));
-            memcpy(*xformsLocation, transformValues, count * xformBytes);
-        }
-    }
-
     void onDrawBatch(GrBatch*) override;
 
 private:
-    friend class GrInOrderCommandBuilder;
-    friend class GrTargetCommands;
-
-    typedef GrTargetCommands::StateForPathDraw StateForPathDraw;
-
-    StateForPathDraw* allocState(const GrPrimitiveProcessor* primProc = nullptr) {
-        void* allocation = fPipelineBuffer.alloc(sizeof(StateForPathDraw),
-                                                 SkChunkAlloc::kThrow_AllocFailType);
-        return new (allocation) StateForPathDraw(primProc);
-    }
-
-    void unallocState(StateForPathDraw* state) {
-        state->unref();
-        fPipelineBuffer.unalloc(state);
-    }
-
     void onReset() override;
     void onFlush() override;
 
-    void onDrawPaths(const GrPathProcessor*,
-                     const GrPathRange*,
-                     const void* indices,
-                     PathIndexType,
-                     const float transformValues[],
-                     PathTransformType,
-                     int count,
-                     const GrStencilSettings&,
-                     const PipelineInfo&) override;
-
-    bool isIssued(uint32_t drawID) override { return drawID != fDrawID; }
-
-    StateForPathDraw* SK_WARN_UNUSED_RESULT createStateForPathDraw(
-        const GrPrimitiveProcessor*,
-        const PipelineInfo&,
-        GrPipelineOptimizations* opts);
-
-    // TODO: Use a single allocator for commands and records
-    enum {
-        kPathIdxBufferMinReserve     = 2 * 64,  // 64 uint16_t's
-        kPathXformBufferMinReserve   = 2 * 64,  // 64 two-float transforms
-        kPipelineBufferMinReserve    = 32 * sizeof(StateForPathDraw),
-    };
-
-    // every 100 flushes we should reset our fPipelineBuffer to prevent us from holding at a
-    // highwater mark
-    static const int kPipelineBufferHighWaterMark = 100;
-
     SkAutoTDelete<GrCommandBuilder>     fCommands;
-    SkChunkAlloc                        fPathIndexBuffer;
-    SkChunkAlloc                        fPathTransformBuffer;
-    SkChunkAlloc                        fPipelineBuffer;
     uint32_t                            fDrawID;
-    SkAutoTUnref<StateForPathDraw>      fPrevState;
 
     typedef GrClipTarget INHERITED;
 };
diff --git a/src/gpu/GrCommandBuilder.h b/src/gpu/GrCommandBuilder.h
index 876e9b1..29dcb33 100644
--- a/src/gpu/GrCommandBuilder.h
+++ b/src/gpu/GrCommandBuilder.h
@@ -17,7 +17,6 @@
 class GrCommandBuilder : ::SkNoncopyable {
 public:
     typedef GrTargetCommands::Cmd               Cmd;
-    typedef GrTargetCommands::StateForPathDraw  State;
 
     static GrCommandBuilder* Create(GrGpu* gpu, bool reorder);
 
@@ -27,22 +26,9 @@
     void flush(GrGpu* gpu, GrResourceProvider* rp) { fCommands.flush(gpu, rp); }
 
     virtual Cmd* recordDrawBatch(GrBatch*, const GrCaps&) = 0;
-    virtual Cmd* recordDrawPaths(State*,
-                                 GrBufferedDrawTarget*,
-                                 const GrPathProcessor*,
-                                 const GrPathRange*,
-                                 const void*,
-                                 GrDrawTarget::PathIndexType,
-                                 const float transformValues[],
-                                 GrDrawTarget::PathTransformType ,
-                                 int,
-                                 const GrStencilSettings&,
-                                 const GrPipelineOptimizations&) = 0;
 
 protected:
     typedef GrTargetCommands::DrawBatch DrawBatch;
-    typedef GrTargetCommands::DrawPath DrawPath;
-    typedef GrTargetCommands::DrawPaths DrawPaths;
 
     GrCommandBuilder() {}
 
diff --git a/src/gpu/GrDrawContext.cpp b/src/gpu/GrDrawContext.cpp
index e03075e..8068403 100644
--- a/src/gpu/GrDrawContext.cpp
+++ b/src/gpu/GrDrawContext.cpp
@@ -111,20 +111,12 @@
                                clip, skPaint, viewMatrix, blob, x, y, filter, clipBounds);
 }
 
-void GrDrawContext::drawPaths(GrPipelineBuilder* pipelineBuilder,
-                              const GrPathProcessor* pathProc,
-                              const GrPathRange* pathRange,
-                              const void* indices,
-                              int /*GrDrawTarget::PathIndexType*/ indexType,
-                              const float transformValues[],
-                              int /*GrDrawTarget::PathTransformType*/ transformType,
-                              int count,
-                              int /*GrPathRendering::FillType*/ fill) {
-    fDrawTarget->drawPaths(*pipelineBuilder, pathProc, pathRange,
-                           indices, (GrDrawTarget::PathIndexType) indexType,
-                           transformValues,
-                           (GrDrawTarget::PathTransformType) transformType,
-                           count, (GrPathRendering::FillType) fill);
+void GrDrawContext::drawPathsFromRange(const GrPipelineBuilder* pipelineBuilder,
+                                       const GrPathProcessor* pathProc,
+                                       GrPathRangeDraw* draw,
+                                       int /*GrPathRendering::FillType*/ fill) {
+    fDrawTarget->drawPathsFromRange(*pipelineBuilder, pathProc, draw,
+                                    (GrPathRendering::FillType) fill);
 }
 
 void GrDrawContext::discard(GrRenderTarget* renderTarget) {
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index 3f472d0..0e7e6bc 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -224,8 +224,23 @@
     SkASSERT(path);
     SkASSERT(this->caps()->shaderCaps()->pathRenderingSupport());
 
-    GrDrawPathBatch* batch = GrDrawPathBatch::Create(pathProc, path);
+    GrDrawPathBatchBase* batch = GrDrawPathBatch::Create(pathProc, path);
+    this->drawPathBatch(pipelineBuilder, batch, fill);
+    batch->unref();
+}
 
+void GrDrawTarget::drawPathsFromRange(const GrPipelineBuilder& pipelineBuilder,
+                                      const GrPathProcessor* pathProc,
+                                      GrPathRangeDraw* draw,
+                                      GrPathRendering::FillType fill) {
+    GrDrawPathBatchBase* batch = GrDrawPathRangeBatch::Create(pathProc, draw);
+    this->drawPathBatch(pipelineBuilder, batch, fill);
+    batch->unref();
+}
+
+void GrDrawTarget::drawPathBatch(const GrPipelineBuilder& pipelineBuilder,
+                                 GrDrawPathBatchBase* batch,
+                                 GrPathRendering::FillType fill) {
     // This looks like drawBatch() but there is an added wrinkle that stencil settings get inserted
     // after setupClip() but before onDrawBatch(). TODO: Figure out a better model for handling
     // stencil settings WRT interactions between pipeline(builder), clipmaskmanager, and batches.
@@ -244,11 +259,8 @@
     this->getPathStencilSettingsForFilltype(fill, sb, &stencilSettings);
     batch->setStencilSettings(stencilSettings);
 
-    // Don't compute a bounding box for dst copy texture, we'll opt
-    // instead for it to just copy the entire dst. Realistically this is a moot
-    // point, because any context that supports NV_path_rendering will also
-    // support NV_blend_equation_advanced.
-    GrDrawTarget::PipelineInfo pipelineInfo(&pipelineBuilder, &scissorState, batch, nullptr, this);
+    GrDrawTarget::PipelineInfo pipelineInfo(&pipelineBuilder, &scissorState, batch,
+                                            &batch->bounds(), this);
 
     if (!pipelineInfo.valid()) {
         return;
@@ -258,50 +270,6 @@
     }
 
     this->onDrawBatch(batch);
-    batch->unref();
-}
-
-void GrDrawTarget::drawPaths(const GrPipelineBuilder& pipelineBuilder,
-                             const GrPathProcessor* pathProc,
-                             const GrPathRange* pathRange,
-                             const void* indices,
-                             PathIndexType indexType,
-                             const float transformValues[],
-                             PathTransformType transformType,
-                             int count,
-                             GrPathRendering::FillType fill) {
-    SkASSERT(this->caps()->shaderCaps()->pathRenderingSupport());
-    SkASSERT(pathRange);
-    SkASSERT(indices);
-    SkASSERT(0 == reinterpret_cast<intptr_t>(indices) %
-             GrPathRange::PathIndexSizeInBytes(indexType));
-    SkASSERT(transformValues);
-
-    // Setup clip
-    GrScissorState scissorState;
-    GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps;
-    GrPipelineBuilder::AutoRestoreStencil ars;
-    if (!this->setupClip(pipelineBuilder, &arfps, &ars, &scissorState, nullptr)) {
-        return;
-    }
-
-    // set stencil settings for path
-    GrStencilSettings stencilSettings;
-    GrRenderTarget* rt = pipelineBuilder.getRenderTarget();
-    GrStencilAttachment* sb = rt->renderTargetPriv().attachStencilAttachment();
-    this->getPathStencilSettingsForFilltype(fill, sb, &stencilSettings);
-
-    // Don't compute a bounding box for dst copy texture, we'll opt
-    // instead for it to just copy the entire dst. Realistically this is a moot
-    // point, because any context that supports NV_path_rendering will also
-    // support NV_blend_equation_advanced.
-    GrDrawTarget::PipelineInfo pipelineInfo(&pipelineBuilder, &scissorState, pathProc, nullptr, this);
-    if (!pipelineInfo.valid()) {
-        return;
-    }
-
-    this->onDrawPaths(pathProc, pathRange, indices, indexType, transformValues,
-                      transformType, count, stencilSettings, pipelineInfo);
 }
 
 void GrDrawTarget::drawNonAARect(const GrPipelineBuilder& pipelineBuilder,
@@ -406,22 +374,6 @@
 
 GrDrawTarget::PipelineInfo::PipelineInfo(const GrPipelineBuilder* pipelineBuilder,
                                          const GrScissorState* scissor,
-                                         const GrPrimitiveProcessor* primProc,
-                                         const SkRect* devBounds,
-                                         GrDrawTarget* target) {
-    fArgs.fPipelineBuilder = pipelineBuilder;
-    fArgs.fCaps = target->caps();
-    fArgs.fScissor = scissor;
-    fArgs.fColorPOI = fArgs.fPipelineBuilder->colorProcInfo(primProc);
-    fArgs.fCoveragePOI = fArgs.fPipelineBuilder->coverageProcInfo(primProc);
-    if (!target->setupDstReadIfNecessary(*fArgs.fPipelineBuilder, fArgs.fColorPOI,
-                                         fArgs.fCoveragePOI, &fArgs.fDstTexture, devBounds)) {
-        fArgs.fPipelineBuilder = nullptr;
-    }
-}
-
-GrDrawTarget::PipelineInfo::PipelineInfo(const GrPipelineBuilder* pipelineBuilder,
-                                         const GrScissorState* scissor,
                                          const GrDrawBatch* batch,
                                          const SkRect* devBounds,
                                          GrDrawTarget* target) {
diff --git a/src/gpu/GrDrawTarget.h b/src/gpu/GrDrawTarget.h
index 572e4cb..13f35ac 100644
--- a/src/gpu/GrDrawTarget.h
+++ b/src/gpu/GrDrawTarget.h
@@ -33,8 +33,9 @@
 class GrClip;
 class GrCaps;
 class GrPath;
-class GrPathRange;
 class GrDrawBatch;
+class GrDrawPathBatchBase;
+class GrPathRangeDraw;
 
 class GrDrawTarget : public SkRefCnt {
 public:
@@ -81,6 +82,8 @@
     /**
      * Draws a path. Fill must not be a hairline. It will respect the HW
      * antialias flag on the GrPipelineBuilder (if possible in the 3D API).
+     *
+     * TODO: Remove this function and construct the batch outside GrDrawTarget.
      */
     void drawPath(const GrPipelineBuilder&, const GrPathProcessor*, const GrPath*,
                   GrPathRendering::FillType);
@@ -90,23 +93,17 @@
      * always be equivalent to back-to-back calls to drawPath(). It will respect
      * the HW antialias flag on the GrPipelineBuilder (if possible in the 3D API).
      *
-     * @param pathRange       Source paths to draw from
-     * @param indices         Array of path indices to draw
-     * @param indexType       Data type of the array elements in indexBuffer
-     * @param transformValues Array of transforms for the individual paths
-     * @param transformType   Type of transforms in transformBuffer
-     * @param count           Number of paths to draw
+     * TODO: Remove this function and construct the batch outside GrDrawTarget.
+     *
+     * @param draw            The range, transforms, and indices for the draw.
+     *                        This object must only be drawn once. The draw
+     *                        may modify its contents.
      * @param fill            Fill type for drawing all the paths
      */
-    void drawPaths(const GrPipelineBuilder&,
-                   const GrPathProcessor*,
-                   const GrPathRange* pathRange,
-                   const void* indices,
-                   PathIndexType indexType,
-                   const float transformValues[],
-                   PathTransformType transformType,
-                   int count,
-                   GrPathRendering::FillType fill);
+    void drawPathsFromRange(const GrPipelineBuilder&,
+                            const GrPathProcessor*,
+                            GrPathRangeDraw* draw,
+                            GrPathRendering::FillType fill);
 
     /**
      * Helper function for drawing rects.
@@ -187,10 +184,6 @@
 
     struct PipelineInfo {
         PipelineInfo(const GrPipelineBuilder* pipelineBuilder, const GrScissorState* scissor,
-                     const GrPrimitiveProcessor* primProc,
-                     const SkRect* devBounds, GrDrawTarget* target);
-
-        PipelineInfo(const GrPipelineBuilder* pipelineBuilder, const GrScissorState* scissor,
                      const GrDrawBatch* batch, const SkRect* devBounds,
                      GrDrawTarget* target);
 
@@ -225,18 +218,9 @@
 
     virtual void onFlush() = 0;
 
-    virtual void onDrawPaths(const GrPathProcessor*,
-                             const GrPathRange*,
-                             const void* indices,
-                             PathIndexType,
-                             const float transformValues[],
-                             PathTransformType,
-                             int count,
-                             const GrStencilSettings&,
-                             const PipelineInfo&) = 0;
-
+    void drawPathBatch(const GrPipelineBuilder& pipelineBuilder, GrDrawPathBatchBase* batch,
+                       GrPathRendering::FillType fill);
     // Check to see if this set of draw commands has been sent out
-    virtual bool       isIssued(uint32_t drawID) { return true; }
     void getPathStencilSettingsForFilltype(GrPathRendering::FillType,
                                            const GrStencilAttachment*,
                                            GrStencilSettings*);
diff --git a/src/gpu/GrGeometryProcessor.h b/src/gpu/GrGeometryProcessor.h
index 72f4d24..232b73e 100644
--- a/src/gpu/GrGeometryProcessor.h
+++ b/src/gpu/GrGeometryProcessor.h
@@ -29,14 +29,6 @@
     // TODO delete when paths are in batch
     void initBatchTracker(GrBatchTracker*, const GrPipelineOptimizations&) const override {}
 
-    // TODO delete this when paths are in batch
-    bool canMakeEqual(const GrBatchTracker& mine,
-                      const GrPrimitiveProcessor& that,
-                      const GrBatchTracker& theirs) const override {
-        SkFAIL("Unsupported\n");
-        return false;
-    }
-
     // TODO Delete when paths are in batch
     void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
         SkFAIL("Unsupported\n");
diff --git a/src/gpu/GrImmediateDrawTarget.h b/src/gpu/GrImmediateDrawTarget.h
index 1fabce4..4791de9 100644
--- a/src/gpu/GrImmediateDrawTarget.h
+++ b/src/gpu/GrImmediateDrawTarget.h
@@ -33,20 +33,6 @@
     void onReset() override;
     void onFlush() override;
 
-    void onDrawPaths(const GrPathProcessor*,
-                     const GrPathRange*,
-                     const void* indices,
-                     PathIndexType,
-                     const float transformValues[],
-                     PathTransformType,
-                     int count,
-                     const GrStencilSettings&,
-                     const PipelineInfo&) override {
-        SkFAIL("Only batch implemented\n");
-    }
-
-    bool isIssued(uint32_t drawID) override { return drawID != fDrawID; }
-
     uint32_t fDrawID;
 
     typedef GrClipTarget INHERITED;
diff --git a/src/gpu/GrInOrderCommandBuilder.cpp b/src/gpu/GrInOrderCommandBuilder.cpp
index b9387e9..bbe42a6 100644
--- a/src/gpu/GrInOrderCommandBuilder.cpp
+++ b/src/gpu/GrInOrderCommandBuilder.cpp
@@ -12,19 +12,6 @@
 #include "GrColor.h"
 #include "SkPoint.h"
 
-static bool path_fill_type_is_winding(const GrStencilSettings& pathStencilSettings) {
-    static const GrStencilSettings::Face pathFace = GrStencilSettings::kFront_Face;
-    bool isWinding = kInvert_StencilOp != pathStencilSettings.passOp(pathFace);
-    if (isWinding) {
-        // Double check that it is in fact winding.
-        SkASSERT(kIncClamp_StencilOp == pathStencilSettings.passOp(pathFace));
-        SkASSERT(kIncClamp_StencilOp == pathStencilSettings.failOp(pathFace));
-        SkASSERT(0x1 != pathStencilSettings.writeMask(pathFace));
-        SkASSERT(!pathStencilSettings.isTwoSided());
-    }
-    return isWinding;
-}
-
 GrTargetCommands::Cmd* GrInOrderCommandBuilder::recordDrawBatch(GrBatch* batch,
                                                                 const GrCaps& caps) {
     GrBATCH_INFO("In-Recording (%s, %u)\n", batch->name(), batch->uniqueID());
@@ -40,65 +27,3 @@
 
     return GrNEW_APPEND_TO_RECORDER(*this->cmdBuffer(), DrawBatch, (batch));
 }
-
-GrTargetCommands::Cmd*
-GrInOrderCommandBuilder::recordDrawPaths(State* state,
-                                         GrBufferedDrawTarget* bufferedDrawTarget,
-                                         const GrPathProcessor* pathProc,
-                                         const GrPathRange* pathRange,
-                                         const void* indexValues,
-                                         GrDrawTarget::PathIndexType indexType,
-                                         const float transformValues[],
-                                         GrDrawTarget::PathTransformType transformType,
-                                         int count,
-                                         const GrStencilSettings& stencilSettings,
-                                         const GrPipelineOptimizations& opts) {
-    SkASSERT(pathRange);
-    SkASSERT(indexValues);
-    SkASSERT(transformValues);
-
-    char* savedIndices;
-    float* savedTransforms;
-
-    bufferedDrawTarget->appendIndicesAndTransforms(indexValues, indexType,
-                                                   transformValues, transformType,
-                                                   count, &savedIndices, &savedTransforms);
-
-    if (!this->cmdBuffer()->empty() &&
-        Cmd::kDrawPaths_CmdType == this->cmdBuffer()->back().type()) {
-        // Try to combine this call with the previous DrawPaths. We do this by stenciling all the
-        // paths together and then covering them in a single pass. This is not equivalent to two
-        // separate draw calls, so we can only do it if there is no blending (no overlap would also
-        // work). Note that it's also possible for overlapping paths to cancel each other's winding
-        // numbers, and we only partially account for this by not allowing even/odd paths to be
-        // combined. (Glyphs in the same font tend to wind the same direction so it works out OK.)
-        DrawPaths* previous = static_cast<DrawPaths*>(&this->cmdBuffer()->back());
-        if (pathRange == previous->pathRange() &&
-            indexType == previous->fIndexType &&
-            transformType == previous->fTransformType &&
-            stencilSettings == previous->fStencilSettings &&
-            path_fill_type_is_winding(stencilSettings) &&
-            previous->fState == state &&
-            !opts.willColorBlendWithDst()) {
-
-            const int indexBytes = GrPathRange::PathIndexSizeInBytes(indexType);
-            const int xformSize = GrPathRendering::PathTransformSize(transformType);
-            if (&previous->fIndices[previous->fCount * indexBytes] == savedIndices &&
-                (0 == xformSize ||
-                 &previous->fTransforms[previous->fCount * xformSize] == savedTransforms)) {
-                // Combine this DrawPaths call with the one previous.
-                previous->fCount += count;
-                return nullptr;
-            }
-        }
-    }
-
-    DrawPaths* dp = GrNEW_APPEND_TO_RECORDER(*this->cmdBuffer(), DrawPaths, (state, pathRange));
-    dp->fIndices = savedIndices;
-    dp->fIndexType = indexType;
-    dp->fTransforms = savedTransforms;
-    dp->fTransformType = transformType;
-    dp->fCount = count;
-    dp->fStencilSettings = stencilSettings;
-    return dp;
-}
diff --git a/src/gpu/GrInOrderCommandBuilder.h b/src/gpu/GrInOrderCommandBuilder.h
index dbc978d..e205ae4 100644
--- a/src/gpu/GrInOrderCommandBuilder.h
+++ b/src/gpu/GrInOrderCommandBuilder.h
@@ -13,22 +13,10 @@
 class GrInOrderCommandBuilder : public GrCommandBuilder {
 public:
     typedef GrCommandBuilder::Cmd Cmd;
-    typedef GrCommandBuilder::State State;
 
     GrInOrderCommandBuilder() : INHERITED() { }
 
     Cmd* recordDrawBatch(GrBatch*, const GrCaps&) override;
-    Cmd* recordDrawPaths(State*,
-                         GrBufferedDrawTarget*,
-                         const GrPathProcessor*,
-                         const GrPathRange*,
-                         const void*,
-                         GrDrawTarget::PathIndexType,
-                         const float transformValues[],
-                         GrDrawTarget::PathTransformType ,
-                         int,
-                         const GrStencilSettings&,
-                         const GrPipelineOptimizations&) override;
 
 private:
     typedef GrCommandBuilder INHERITED;
diff --git a/src/gpu/GrPathProcessor.cpp b/src/gpu/GrPathProcessor.cpp
index 3a5ab44..b6f174d 100644
--- a/src/gpu/GrPathProcessor.cpp
+++ b/src/gpu/GrPathProcessor.cpp
@@ -46,9 +46,9 @@
     local->fUsesLocalCoords = opt.readsLocalCoords();
 }
 
-bool GrPathProcessor::canMakeEqual(const GrBatchTracker& m,
-                                   const GrPrimitiveProcessor& that,
-                                   const GrBatchTracker& t) const {
+bool GrPathProcessor::isEqual(const GrBatchTracker& m,
+                              const GrPrimitiveProcessor& that,
+                              const GrBatchTracker& t) const {
     if (this->classID() != that.classID() || !this->hasSameTextureAccesses(that)) {
         return false;
     }
diff --git a/src/gpu/GrPathProcessor.h b/src/gpu/GrPathProcessor.h
index 57c5a8b..2a10110 100644
--- a/src/gpu/GrPathProcessor.h
+++ b/src/gpu/GrPathProcessor.h
@@ -31,9 +31,9 @@
 
     void initBatchTracker(GrBatchTracker*, const GrPipelineOptimizations&) const override;
 
-    bool canMakeEqual(const GrBatchTracker& mine,
-                      const GrPrimitiveProcessor& that,
-                      const GrBatchTracker& theirs) const override;
+    bool isEqual(const GrBatchTracker& mine,
+                 const GrPrimitiveProcessor& that,
+                 const GrBatchTracker& theirs) const;
 
     const char* name() const override { return "PathProcessor"; }
 
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index f6ad6b1..cba869e 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -174,10 +174,6 @@
 public:
     virtual void initBatchTracker(GrBatchTracker*, const GrPipelineOptimizations&) const = 0;
 
-    virtual bool canMakeEqual(const GrBatchTracker& mine,
-                              const GrPrimitiveProcessor& that,
-                              const GrBatchTracker& theirs) const = 0;
-
     virtual void getInvariantOutputColor(GrInitInvariantOutput* out) const = 0;
     virtual void getInvariantOutputCoverage(GrInitInvariantOutput* out) const = 0;
 
diff --git a/src/gpu/GrReorderCommandBuilder.h b/src/gpu/GrReorderCommandBuilder.h
index a5d6e48..4670f1b 100644
--- a/src/gpu/GrReorderCommandBuilder.h
+++ b/src/gpu/GrReorderCommandBuilder.h
@@ -13,30 +13,13 @@
 class GrReorderCommandBuilder : public GrCommandBuilder {
 public:
     typedef GrCommandBuilder::Cmd Cmd;
-    typedef GrCommandBuilder::State State;
 
     GrReorderCommandBuilder() : INHERITED() {}
 
     Cmd* recordDrawBatch(GrBatch*, const GrCaps&) override;
 
-    Cmd* recordDrawPaths(State*,
-                         GrBufferedDrawTarget*,
-                         const GrPathProcessor*,
-                         const GrPathRange*,
-                         const void*,
-                         GrDrawTarget::PathIndexType,
-                         const float transformValues[],
-                         GrDrawTarget::PathTransformType ,
-                         int,
-                         const GrStencilSettings&,
-                         const GrPipelineOptimizations&) override {
-        SkFAIL("Unsupported\n");
-        return nullptr;
-    }
-
 private:
     typedef GrCommandBuilder INHERITED;
-
 };
 
 #endif
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp
index 479213b..7325f15 100644
--- a/src/gpu/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp
@@ -21,12 +21,13 @@
 #include "SkTextMapStateProc.h"
 #include "SkTextFormatParams.h"
 
+#include "batches/GrDrawPathBatch.h"
+
 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(GrContext* context,
                                                            const SkSurfaceProps& surfaceProps)
-    : GrTextContext(context, surfaceProps)
-    , fStroke(SkStrokeRec::kFill_InitStyle)
-    , fQueuedGlyphCount(0)
-    , fFallbackGlyphsIdx(kGlyphBufferSize) {
+    : INHERITED(context, surfaceProps)
+    , fDraw(nullptr)
+    , fStroke(SkStrokeRec::kFill_InitStyle) {
 }
 
 GrStencilAndCoverTextContext*
@@ -154,7 +155,7 @@
         const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
         fx += SkFixedMul(autokern.adjust(glyph), fixedSizeRatio);
         if (glyph.fWidth) {
-            this->appendGlyph(dc, glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy)));
+            this->appendGlyph(glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy)));
         }
 
         fx += SkFixedMul(glyph.fAdvanceX, fixedSizeRatio);
@@ -208,7 +209,7 @@
             SkPoint loc;
             alignProc(tmsLoc, glyph, &loc);
 
-            this->appendGlyph(dc, glyph, loc);
+            this->appendGlyph(glyph, loc);
         }
         pos += scalarsPerPosition;
     }
@@ -398,20 +399,22 @@
     return true;
 }
 
-inline void GrStencilAndCoverTextContext::appendGlyph(GrDrawContext* dc,
-                                                      const SkGlyph& glyph,
-                                                      const SkPoint& pos) {
-    if (fQueuedGlyphCount >= fFallbackGlyphsIdx) {
-        SkASSERT(fQueuedGlyphCount == fFallbackGlyphsIdx);
-        this->flush(dc);
+inline void GrStencilAndCoverTextContext::appendGlyph(const SkGlyph& glyph, const SkPoint& pos) {
+    // Stick the glyphs we can't draw into the fallback arrays.
+    if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
+        fFallbackIndices.push_back(glyph.getGlyphID());
+        fFallbackPositions.push_back().set(fTextInverseRatio * pos.x(),
+                                           -fTextInverseRatio * pos.y());
+    } else {
+        // TODO: infer the reserve count from the text length.
+        if (!fDraw) {
+            fDraw = GrPathRangeDraw::Create(fGlyphs,
+                                            GrPathRendering::kTranslate_PathTransformType,
+                                            64);
+        }
+        float translate[] = { fTextInverseRatio * pos.x(), -fTextInverseRatio * pos.y() };
+        fDraw->append(glyph.getGlyphID(), translate);
     }
-
-    // Stick the glyphs we can't draw at the end of the buffer, growing backwards.
-    int index = (SkMask::kARGB32_Format == glyph.fMaskFormat) ?
-                --fFallbackGlyphsIdx : fQueuedGlyphCount++;
-
-    fGlyphIndices[index] = glyph.getGlyphID();
-    fGlyphPositions[index].set(fTextInverseRatio * pos.x(), -fTextInverseRatio * pos.y());
 }
 
 static const SkScalar* get_xy_scalar_array(const SkPoint* pointArray) {
@@ -422,7 +425,8 @@
 }
 
 void GrStencilAndCoverTextContext::flush(GrDrawContext* dc) {
-    if (fQueuedGlyphCount > 0) {
+    if (fDraw) {
+        SkASSERT(fDraw->count());
         SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(fPaint.getColor(),
                                                                  fViewMatrix,
                                                                  fLocalMatrix));
@@ -443,20 +447,13 @@
 
         *pipelineBuilder.stencil() = kStencilPass;
 
-        SkASSERT(kGlyphBufferSize == fFallbackGlyphsIdx);
-
-        dc->drawPaths(&pipelineBuilder, pp, fGlyphs,
-                      fGlyphIndices, GrPathRange::kU16_PathIndexType,
-                      get_xy_scalar_array(fGlyphPositions),
-                      GrPathRendering::kTranslate_PathTransformType,
-                      fQueuedGlyphCount, GrPathRendering::kWinding_FillType);
-
-        fQueuedGlyphCount = 0;
+        dc->drawPathsFromRange(&pipelineBuilder, pp, fDraw, GrPathRendering::kWinding_FillType);
+        fDraw->unref();
+        fDraw = nullptr;
     }
 
-    if (fFallbackGlyphsIdx < kGlyphBufferSize) {
-        int fallbackGlyphCount = kGlyphBufferSize - fFallbackGlyphsIdx;
-
+    if (fFallbackIndices.count()) {
+        SkASSERT(fFallbackPositions.count() == fFallbackIndices.count());
         GrPaint paintFallback(fPaint);
 
         SkPaint skPaintFallback(fSkPaint);
@@ -468,22 +465,26 @@
 
         SkMatrix inverse;
         if (this->mapToFallbackContext(&inverse)) {
-            inverse.mapPoints(&fGlyphPositions[fFallbackGlyphsIdx], fallbackGlyphCount);
+            inverse.mapPoints(fFallbackPositions.begin(), fFallbackPositions.count());
         }
 
         fFallbackTextContext->drawPosText(dc, fRenderTarget, fClip, paintFallback, skPaintFallback,
-                                          fViewMatrix, (char*)&fGlyphIndices[fFallbackGlyphsIdx],
-                                          2 * fallbackGlyphCount,
-                                          get_xy_scalar_array(&fGlyphPositions[fFallbackGlyphsIdx]),
+                                          fViewMatrix, (char*)fFallbackIndices.begin(),
+                                          sizeof(uint16_t) * fFallbackIndices.count(),
+                                          get_xy_scalar_array(fFallbackPositions.begin()),
                                           2, SkPoint::Make(0, 0), fRegionClipBounds);
-
-        fFallbackGlyphsIdx = kGlyphBufferSize;
+        fFallbackIndices.reset();
+        fFallbackPositions.reset();
     }
 }
 
 void GrStencilAndCoverTextContext::finish(GrDrawContext* dc) {
     this->flush(dc);
 
+    SkASSERT(!fDraw);
+    SkASSERT(!fFallbackIndices.count());
+    SkASSERT(!fFallbackPositions.count());
+
     fGlyphs->unref();
     fGlyphs = nullptr;
 
@@ -492,4 +493,3 @@
 
     fViewMatrix = fContextInitialMatrix;
 }
-
diff --git a/src/gpu/GrStencilAndCoverTextContext.h b/src/gpu/GrStencilAndCoverTextContext.h
index 295e958..4c57d3c 100644
--- a/src/gpu/GrStencilAndCoverTextContext.h
+++ b/src/gpu/GrStencilAndCoverTextContext.h
@@ -15,6 +15,7 @@
 class GrTextStrike;
 class GrPath;
 class GrPathRange;
+class GrPathRangeDraw;
 class SkSurfaceProps;
 
 /*
@@ -29,8 +30,6 @@
     virtual ~GrStencilAndCoverTextContext();
 
 private:
-    static const int kGlyphBufferSize = 1024;
-
     enum RenderMode {
         /**
          * This is the render mode used by drawText(), which is mainly used by
@@ -55,12 +54,13 @@
     SkScalar                                            fTextRatio;
     float                                               fTextInverseRatio;
     SkGlyphCache*                                       fGlyphCache;
+
     GrPathRange*                                        fGlyphs;
+    GrPathRangeDraw*                                    fDraw;
     GrStrokeInfo                                        fStroke;
-    uint16_t                                            fGlyphIndices[kGlyphBufferSize];
-    SkPoint                                             fGlyphPositions[kGlyphBufferSize];
-    int                                                 fQueuedGlyphCount;
-    int                                                 fFallbackGlyphsIdx;
+    SkSTArray<32, uint16_t, true>                       fFallbackIndices;
+    SkSTArray<32, SkPoint, true>                        fFallbackPositions;
+
     SkMatrix                                            fContextInitialMatrix;
     SkMatrix                                            fViewMatrix;
     SkMatrix                                            fLocalMatrix;
@@ -92,10 +92,11 @@
               size_t textByteLength, RenderMode, const SkMatrix& viewMatrix,
               const SkIRect& regionClipBounds);
     bool mapToFallbackContext(SkMatrix* inverse);
-    void appendGlyph(GrDrawContext* dc, const SkGlyph&, const SkPoint&);
+    void appendGlyph(const SkGlyph&, const SkPoint&);
     void flush(GrDrawContext* dc);
     void finish(GrDrawContext* dc);
 
+    typedef GrTextContext INHERITED;
 };
 
 #endif
diff --git a/src/gpu/GrTargetCommands.cpp b/src/gpu/GrTargetCommands.cpp
index f2e6964..2194e08 100644
--- a/src/gpu/GrTargetCommands.cpp
+++ b/src/gpu/GrTargetCommands.cpp
@@ -43,29 +43,6 @@
     fLastFlushToken = flushState.lastFlushedToken();
 }
 
-void GrTargetCommands::DrawPath::execute(GrBatchFlushState* state) {
-    if (!fState->fCompiled) {
-        state->gpu()->buildProgramDesc(&fState->fDesc, *fState->fPrimitiveProcessor,
-                                       *fState->getPipeline(), fState->fBatchTracker);
-        fState->fCompiled = true;
-    }
-    GrPathRendering::DrawPathArgs args(fState->fPrimitiveProcessor.get(), fState->getPipeline(),
-                                       &fState->fDesc, &fState->fBatchTracker, &fStencilSettings);
-    state->gpu()->pathRendering()->drawPath(args, this->path());
-}
-
-void GrTargetCommands::DrawPaths::execute(GrBatchFlushState* state) {
-    if (!fState->fCompiled) {
-        state->gpu()->buildProgramDesc(&fState->fDesc, *fState->fPrimitiveProcessor,
-                                       *fState->getPipeline(), fState->fBatchTracker);
-        fState->fCompiled = true;
-    }
-    GrPathRendering::DrawPathArgs args(fState->fPrimitiveProcessor.get(), fState->getPipeline(),
-                                       &fState->fDesc, &fState->fBatchTracker, &fStencilSettings);
-    state->gpu()->pathRendering()->drawPaths(args, this->pathRange(), fIndices, fIndexType,
-                                             fTransforms, fTransformType, fCount);
-}
-
 void GrTargetCommands::DrawBatch::execute(GrBatchFlushState* state) {
     fBatch->draw(state);
 }
diff --git a/src/gpu/GrTargetCommands.h b/src/gpu/GrTargetCommands.h
index 4f804c3..5d9cdfc 100644
--- a/src/gpu/GrTargetCommands.h
+++ b/src/gpu/GrTargetCommands.h
@@ -30,8 +30,6 @@
     class Cmd : ::SkNoncopyable {
     public:
         enum CmdType {
-            kDrawPath_CmdType          = 2,
-            kDrawPaths_CmdType         = 3,
             kDrawBatch_CmdType         = 4,
         };
 
@@ -73,88 +71,6 @@
 
     typedef GrGpu::DrawArgs DrawArgs;
 
-    // TODO: This can be just a pipeline once paths are in batch, and it should live elsewhere
-    struct StateForPathDraw : public SkNVRefCnt<StateForPathDraw> {
-        // TODO get rid of the prim proc parameter when we use batch everywhere
-        StateForPathDraw(const GrPrimitiveProcessor* primProc = nullptr)
-            : fPrimitiveProcessor(primProc)
-            , fCompiled(false) {}
-
-        ~StateForPathDraw() { reinterpret_cast<GrPipeline*>(fPipeline.get())->~GrPipeline(); }
-
-        // This function is only for getting the location in memory where we will create our
-        // pipeline object.
-        void* pipelineLocation() { return fPipeline.get(); }
-
-        const GrPipeline* getPipeline() const {
-            return reinterpret_cast<const GrPipeline*>(fPipeline.get());
-        }
-        GrRenderTarget* getRenderTarget() const {
-            return this->getPipeline()->getRenderTarget();
-        }
-        const GrXferProcessor* getXferProcessor() const {
-            return this->getPipeline()->getXferProcessor();
-        }
-
-        void operator delete(void* p) {}
-        void* operator new(size_t) {
-            SkFAIL("All States are created by placement new.");
-            return sk_malloc_throw(0);
-        }
-
-        void* operator new(size_t, void* p) { return p; }
-        void operator delete(void* target, void* placement) {
-            ::operator delete(target, placement);
-        }
-
-        typedef GrPendingProgramElement<const GrPrimitiveProcessor> ProgramPrimitiveProcessor;
-        ProgramPrimitiveProcessor               fPrimitiveProcessor;
-        SkAlignedSStorage<sizeof(GrPipeline)>   fPipeline;
-        GrProgramDesc                           fDesc;
-        GrBatchTracker                          fBatchTracker;
-        bool                                    fCompiled;
-    };
-    // TODO remove this when State is just a pipeline
-    friend SkNVRefCnt<StateForPathDraw>;
-
-    struct DrawPath : public Cmd {
-        DrawPath(StateForPathDraw* state, const GrPath* path)
-            : Cmd(kDrawPath_CmdType)
-            , fState(SkRef(state))
-            , fPath(path) {}
-
-        const GrPath* path() const { return fPath.get(); }
-
-        void execute(GrBatchFlushState*) override;
-
-        SkAutoTUnref<StateForPathDraw>  fState;
-        GrStencilSettings               fStencilSettings;
-    private:
-        GrPendingIOResource<const GrPath, kRead_GrIOType> fPath;
-    };
-
-    struct DrawPaths : public Cmd {
-        DrawPaths(StateForPathDraw* state, const GrPathRange* pathRange)
-            : Cmd(kDrawPaths_CmdType)
-            , fState(SkRef(state))
-            , fPathRange(pathRange) {}
-
-        const GrPathRange* pathRange() const { return fPathRange.get();  }
-
-        void execute(GrBatchFlushState*) override;
-
-        SkAutoTUnref<StateForPathDraw>  fState;
-        char*                           fIndices;
-        GrDrawTarget::PathIndexType     fIndexType;
-        float*                          fTransforms;
-        GrDrawTarget::PathTransformType fTransformType;
-        int                             fCount;
-        GrStencilSettings               fStencilSettings;
-
-    private:
-        GrPendingIOResource<const GrPathRange, kRead_GrIOType> fPathRange;
-    };
-
     struct DrawBatch : public Cmd {
         DrawBatch(GrBatch* batch)
             : Cmd(kDrawBatch_CmdType)
diff --git a/src/gpu/batches/GrDrawPathBatch.cpp b/src/gpu/batches/GrDrawPathBatch.cpp
new file mode 100644
index 0000000..300024e
--- /dev/null
+++ b/src/gpu/batches/GrDrawPathBatch.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrDrawPathBatch.h"
+
+SkString GrDrawPathBatch::dumpInfo() const {
+    SkString string;
+    string.printf("PATH: 0x%p", fPath.get());
+    return string;
+}
+
+void GrDrawPathBatch::onDraw(GrBatchFlushState* state) {
+    GrProgramDesc  desc;
+    state->gpu()->buildProgramDesc(&desc, *this->pathProcessor(),
+                                    *this->pipeline(), *this->tracker());
+    GrPathRendering::DrawPathArgs args(this->pathProcessor(), this->pipeline(),
+                                        &desc, this->tracker(), &this->stencilSettings());
+    state->gpu()->pathRendering()->drawPath(args, fPath.get());
+}
+
+GrDrawPathRangeBatch::~GrDrawPathRangeBatch() {
+    for (DrawList::Iter iter(fDraws); iter.get(); iter.next()) {
+        (*iter.get())->unref();
+    }
+}
+
+SkString GrDrawPathRangeBatch::dumpInfo() const {
+    SkString string;
+    string.printf("RANGE: 0x%p COUNTS: [", *fDraws.head());
+    for (DrawList::Iter iter(fDraws); iter.get(); iter.next()) {
+        string.appendf("%d ,", (*iter.get())->count());
+    }
+    string.remove(string.size() - 2, 2);
+    string.append("]");
+    return string;
+}
+
+bool GrDrawPathRangeBatch::isWinding() const {
+    static const GrStencilSettings::Face pathFace = GrStencilSettings::kFront_Face;
+    bool isWinding = kInvert_StencilOp != this->stencilSettings().passOp(pathFace);
+    if (isWinding) {
+        // Double check that it is in fact winding.
+        SkASSERT(kIncClamp_StencilOp == this->stencilSettings().passOp(pathFace));
+        SkASSERT(kIncClamp_StencilOp == this->stencilSettings().failOp(pathFace));
+        SkASSERT(0x1 != this->stencilSettings().writeMask(pathFace));
+        SkASSERT(!this->stencilSettings().isTwoSided());
+    }
+    return isWinding;
+}
+
+GrDrawPathRangeBatch::GrDrawPathRangeBatch(const GrPathProcessor* pathProc,
+                                           GrPathRangeDraw* pathRangeDraw)
+    : INHERITED(pathProc)
+    , fDraws(4) {
+    SkDEBUGCODE(pathRangeDraw->fUsedInBatch = true;)
+    this->initClassID<GrDrawPathRangeBatch>();
+    fDraws.addToHead(SkRef(pathRangeDraw));
+    fTotalPathCount = pathRangeDraw->count();
+    // Don't compute a bounding box. For dst copy texture, we'll opt instead for it to just copy
+    // the entire dst. Realistically this is a moot point, because any context that supports
+    // NV_path_rendering will also support NV_blend_equation_advanced.
+    // For clipping we'll just skip any optimizations based on the bounds.
+    fBounds.setLargest();
+}
+
+bool GrDrawPathRangeBatch::onCombineIfPossible(GrBatch* t, const GrCaps& caps) {
+    GrDrawPathRangeBatch* that = t->cast<GrDrawPathRangeBatch>();
+    if (!GrPathRangeDraw::CanMerge(**this->fDraws.head(), **that->fDraws.head())) {
+        return false;
+    }
+    if (!GrPipeline::AreEqual(*this->pipeline(), *that->pipeline(), false)) {
+        return false;
+    }
+    if (!this->pathProcessor()->isEqual(*this->tracker(), *that->pathProcessor(),
+                                        *that->tracker())) {
+        return false;
+    }
+    // TODO: Check some other things here. (winding, opaque, pathProc color, vm, ...)
+    // Try to combine this call with the previous DrawPaths. We do this by stenciling all the
+    // paths together and then covering them in a single pass. This is not equivalent to two
+    // separate draw calls, so we can only do it if there is no blending (no overlap would also
+    // work). Note that it's also possible for overlapping paths to cancel each other's winding
+    // numbers, and we only partially account for this by not allowing even/odd paths to be
+    // combined. (Glyphs in the same font tend to wind the same direction so it works out OK.)
+    if (!this->isWinding() ||
+        this->stencilSettings() != that->stencilSettings() ||
+        this->opts().willColorBlendWithDst()) {
+        return false;
+    }
+    SkASSERT(!that->opts().willColorBlendWithDst());
+    fTotalPathCount += that->fTotalPathCount;
+    while (GrPathRangeDraw** head = that->fDraws.head()) {
+        fDraws.addToTail(*head);
+        // We're stealing that's refs, so pop without unreffing.
+        that->fDraws.popHead();
+    }
+    return true;
+}
+
+void GrDrawPathRangeBatch::onDraw(GrBatchFlushState* state) {
+    GrProgramDesc  desc;
+    state->gpu()->buildProgramDesc(&desc, *this->pathProcessor(), *this->pipeline(),
+                                    *this->tracker());
+    GrPathRendering::DrawPathArgs args(this->pathProcessor(), this->pipeline(),
+                                        &desc, this->tracker(), &this->stencilSettings());
+    if (fDraws.count() == 1) {
+        const GrPathRangeDraw& draw = **fDraws.head();
+        state->gpu()->pathRendering()->drawPaths(args, draw.range(), draw.indices(),
+            GrPathRange::kU16_PathIndexType, draw.transforms(), draw.transformType(),
+            draw.count());
+        return;
+    }
+
+    const GrPathRange* range = (*fDraws.head())->range();
+    GrPathRendering::PathTransformType transformType = (*fDraws.head())->transformType();
+    int floatsPerTransform = GrPathRendering::PathTransformSize(transformType);
+    SkAutoSTMalloc<512, float> transformStorage(floatsPerTransform * fTotalPathCount);
+    SkAutoSTMalloc<256, uint16_t> indexStorage(fTotalPathCount);
+    uint16_t* indices = indexStorage.get();
+    float* transforms = transformStorage.get();
+    for (DrawList::Iter iter(fDraws); iter.get(); iter.next()) {
+        SkASSERT((*iter.get())->transformType() == transformType);
+        SkASSERT((*iter.get())->range() == range);
+        int cnt = (*iter.get())->count();
+        memcpy(indices, (*iter.get())->indices(), cnt * sizeof(uint16_t));
+        indices += cnt;
+        memcpy(transforms, (*iter.get())->transforms(), cnt * floatsPerTransform * sizeof(float));
+        transforms += cnt * floatsPerTransform;
+    }
+    SkASSERT(indices - indexStorage.get() == fTotalPathCount);
+    state->gpu()->pathRendering()->drawPaths(args, range, indexStorage.get(),
+        GrPathRange::kU16_PathIndexType, transformStorage.get(), transformType,
+        fTotalPathCount);
+}
diff --git a/src/gpu/batches/GrDrawPathBatch.h b/src/gpu/batches/GrDrawPathBatch.h
index cf67cd9..e605b3e 100644
--- a/src/gpu/batches/GrDrawPathBatch.h
+++ b/src/gpu/batches/GrDrawPathBatch.h
@@ -8,68 +8,170 @@
 #ifndef GrDrawPathBatch_DEFINED
 #define GrDrawPathBatch_DEFINED
 
+#include "GrBatchFlushState.h"
 #include "GrDrawBatch.h"
 #include "GrGpu.h"
 #include "GrPath.h"
 #include "GrPathRendering.h"
 #include "GrPathProcessor.h"
 
-class GrDrawPathBatch final : public GrDrawBatch {
+#include "SkTLList.h"
+
+class GrDrawPathBatchBase : public GrDrawBatch {
 public:
-    // This must return the concrete type because we install the stencil settings late :(
-    static GrDrawPathBatch* Create(const GrPathProcessor* primProc, const GrPath* path) {
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
+        this->pathProcessor()->getInvariantOutputColor(out);
+    }
+
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
+        this->pathProcessor()->getInvariantOutputCoverage(out);
+    }
+
+    void setStencilSettings(const GrStencilSettings& stencil) { fStencilSettings = stencil; }
+
+protected:
+    GrDrawPathBatchBase(const GrPathProcessor* pathProc) : fPrimitiveProcessor(pathProc) {}
+
+    GrBatchTracker* tracker() { return reinterpret_cast<GrBatchTracker*>(&fWhatchamacallit); }
+    const GrPathProcessor* pathProcessor() const { return fPrimitiveProcessor.get(); }
+    const GrStencilSettings& stencilSettings() const { return fStencilSettings; }
+    const GrPipelineOptimizations& opts() const { return fOpts; }
+
+private:
+    void initBatchTracker(const GrPipelineOptimizations& opts) override {
+        this->pathProcessor()->initBatchTracker(this->tracker(), opts);
+        fOpts = opts;
+    }
+
+    GrPendingProgramElement<const GrPathProcessor>          fPrimitiveProcessor;
+    PathBatchTracker                                        fWhatchamacallit; // TODO: delete this
+    GrStencilSettings                                       fStencilSettings;
+    GrPipelineOptimizations                                 fOpts;
+
+    typedef GrDrawBatch INHERITED;
+};
+
+class GrDrawPathBatch final : public GrDrawPathBatchBase {
+public:
+    // This can't return a more abstract type because we install the stencil settings late :(
+    static GrDrawPathBatchBase* Create(const GrPathProcessor* primProc, const GrPath* path) {
         return new GrDrawPathBatch(primProc, path);
     }
 
     const char* name() const override { return "DrawPath"; }
 
-    SkString dumpInfo() const override {
-        SkString string;
-        string.printf("PATH: 0x%p", fPath.get());
-        return string;
-    }
-
-    void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
-        fPrimitiveProcessor->getInvariantOutputColor(out);
-    }
-
-    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
-        fPrimitiveProcessor->getInvariantOutputCoverage(out);
-    }
-
-    void setStencilSettings(const GrStencilSettings& stencil) { fStencilSettings = stencil; }
+    SkString dumpInfo() const override;
 
 private:
-    GrBatchTracker* tracker() { return reinterpret_cast<GrBatchTracker*>(&fWhatchamacallit); }
-    GrDrawPathBatch(const GrPathProcessor* primProc, const GrPath* path)
-    : fPrimitiveProcessor(primProc)
-    , fPath(path) {
+    GrDrawPathBatch(const GrPathProcessor* pathProc, const GrPath* path)
+        : INHERITED(pathProc)
+        , fPath(path) {
         fBounds = path->getBounds();
-        primProc->viewMatrix().mapRect(&fBounds);
+        this->pathProcessor()->viewMatrix().mapRect(&fBounds);
         this->initClassID<GrDrawPathBatch>();
     }
 
-    void initBatchTracker(const GrPipelineOptimizations& opts) override {
-        fPrimitiveProcessor->initBatchTracker(this->tracker(), opts);
-    }
-
     bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { return false; }
 
     void onPrepare(GrBatchFlushState*) override {}
 
-    void onDraw(GrBatchFlushState* state) override {
-        GrProgramDesc  desc;
-        state->gpu()->buildProgramDesc(&desc, *fPrimitiveProcessor.get(),
-                                       *this->pipeline(), *this->tracker());
-        GrPathRendering::DrawPathArgs args(fPrimitiveProcessor.get(), this->pipeline(),
-                                           &desc, this->tracker(), &fStencilSettings);
-        state->gpu()->pathRendering()->drawPath(args, fPath.get());
+    void onDraw(GrBatchFlushState* state) override;
+
+    GrPendingIOResource<const GrPath, kRead_GrIOType> fPath;
+
+    typedef GrDrawPathBatchBase INHERITED;
+};
+
+/**
+ * This could be nested inside the batch class, but for now it must be declarable in a public
+ * header (GrDrawContext)
+ */
+class GrPathRangeDraw : public GrNonAtomicRef {
+public:
+    typedef GrPathRendering::PathTransformType TransformType;
+
+    static GrPathRangeDraw* Create(GrPathRange* range, TransformType transformType,
+        int reserveCnt) {
+        return SkNEW_ARGS(GrPathRangeDraw, (range, transformType, reserveCnt));
     }
 
-    GrPendingProgramElement<const GrPathProcessor>      fPrimitiveProcessor;
-    PathBatchTracker                                    fWhatchamacallit; // TODO: delete this
-    GrStencilSettings                                   fStencilSettings;
-    GrPendingIOResource<const GrPath, kRead_GrIOType>   fPath;
+    void append(uint16_t index, float transform[]) {
+        fTransforms.push_back_n(GrPathRendering::PathTransformSize(fTransformType), transform);
+        fIndices.push_back(index);
+    }
+
+    int count() const { return fIndices.count(); }
+
+    TransformType transformType() const { return fTransformType; }
+
+    const float* transforms() const { return fTransforms.begin(); }
+
+    const uint16_t* indices() const { return fIndices.begin(); }
+
+    const GrPathRange* range() const { return fPathRange.get(); }
+
+    static bool CanMerge(const GrPathRangeDraw& a, const GrPathRangeDraw& b) {
+        return a.transformType() == b.transformType() && a.range() == b.range();
+    }
+
+private:
+    GrPathRangeDraw(GrPathRange* range, TransformType transformType, int reserveCnt)
+        : fPathRange(SkRef(range))
+        , fTransformType(transformType)
+        , fIndices(reserveCnt)
+        , fTransforms(reserveCnt * GrPathRendering::PathTransformSize(transformType)) {
+        SkDEBUGCODE(fUsedInBatch = false;)
+    }
+
+    // Reserve space for 64 paths where indices are 16 bit and transforms are translations.
+    static const int kIndexReserveCnt = 64;
+    static const int kTransformBufferReserveCnt = 2 * 64;
+
+    GrPendingIOResource<const GrPathRange, kRead_GrIOType> fPathRange;
+    GrPathRendering::PathTransformType                     fTransformType;
+    SkSTArray<kIndexReserveCnt, uint16_t, true>            fIndices;
+    SkSTArray<kTransformBufferReserveCnt, float, true>     fTransforms;
+
+    // To ensure we don't reuse these across batches.
+#ifdef SK_DEBUG
+    bool fUsedInBatch;
+    friend class GrDrawPathRangeBatch;
+#endif
+
+    typedef GrNonAtomicRef INHERITED;
+};
+
+// Template this if we decide to support index types other than 16bit
+class GrDrawPathRangeBatch final : public GrDrawPathBatchBase {
+public:
+    // This can't return a more abstracet type because we install the stencil settings late :(
+    static GrDrawPathBatchBase* Create(const GrPathProcessor* pathProc,
+                                       GrPathRangeDraw* pathRangeDraw) {
+        return SkNEW_ARGS(GrDrawPathRangeBatch, (pathProc, pathRangeDraw));
+    }
+
+    ~GrDrawPathRangeBatch() override;
+
+    const char* name() const override { return "DrawPathRange"; }
+
+    SkString dumpInfo() const override;
+
+private:
+    inline bool isWinding() const;
+
+    GrDrawPathRangeBatch(const GrPathProcessor* pathProc, GrPathRangeDraw* pathRangeDraw);
+
+    bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override;
+
+    void onPrepare(GrBatchFlushState*) override {}
+
+    void onDraw(GrBatchFlushState* state) override;
+
+    typedef SkTLList<GrPathRangeDraw*> DrawList;
+    DrawList    fDraws;
+    int         fTotalPathCount;
+
+    typedef GrDrawPathBatchBase INHERITED;
 };
 
 #endif