Dash batch
authorjoshualitt <joshualitt@chromium.org>
Fri, 27 Feb 2015 19:41:49 +0000 (11:41 -0800)
committerCommit bot <commit-bot@chromium.org>
Fri, 27 Feb 2015 19:41:50 +0000 (11:41 -0800)
BUG=skia:

Review URL: https://codereview.chromium.org/925673002

src/gpu/effects/GrDashingEffect.cpp

index afeff43..f9d526f 100644 (file)
@@ -7,8 +7,9 @@
 
 #include "GrDashingEffect.h"
 
-#include "../GrAARectRenderer.h"
-
+#include "GrBatch.h"
+#include "GrBatchTarget.h"
+#include "GrBufferAllocPool.h"
 #include "GrGeometryProcessor.h"
 #include "GrContext.h"
 #include "GrCoordTransform.h"
@@ -114,21 +115,21 @@ static void align_to_x_axis(const SkPoint pts[2], SkMatrix* rotMatrix, SkPoint p
 }
 
 // Assumes phase < sum of all intervals
-static SkScalar calc_start_adjustment(const SkPathEffect::DashInfo& info) {
-    SkASSERT(info.fPhase < info.fIntervals[0] + info.fIntervals[1]);
-    if (info.fPhase >= info.fIntervals[0] && info.fPhase != 0) {
-        SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1];
-        return srcIntervalLen - info.fPhase;
+static SkScalar calc_start_adjustment(const SkScalar intervals[2], SkScalar phase) {
+    SkASSERT(phase < intervals[0] + intervals[1]);
+    if (phase >= intervals[0] && phase != 0) {
+        SkScalar srcIntervalLen = intervals[0] + intervals[1];
+        return srcIntervalLen - phase;
     }
     return 0;
 }
 
-static SkScalar calc_end_adjustment(const SkPathEffect::DashInfo& info, const SkPoint pts[2],
+static SkScalar calc_end_adjustment(const SkScalar intervals[2], const SkPoint pts[2],
                                     SkScalar phase, SkScalar* endingInt) {
     if (pts[1].fX <= pts[0].fX) {
         return 0;
     }
-    SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1];
+    SkScalar srcIntervalLen = intervals[0] + intervals[1];
     SkScalar totalLen = pts[1].fX - pts[0].fX;
     SkScalar temp = SkScalarDiv(totalLen, srcIntervalLen);
     SkScalar numFullIntervals = SkScalarFloorToScalar(temp);
@@ -138,11 +139,11 @@ static SkScalar calc_end_adjustment(const SkPathEffect::DashInfo& info, const Sk
     if (0 == *endingInt) {
         *endingInt = srcIntervalLen;
     }
-    if (*endingInt > info.fIntervals[0]) {
-        if (0 == info.fIntervals[0]) {
+    if (*endingInt > intervals[0]) {
+        if (0 == intervals[0]) {
             *endingInt -= 0.01f; // make sure we capture the last zero size pnt (used if has caps)
         }
-        return *endingInt - info.fIntervals[0];
+        return *endingInt - intervals[0];
     }
     return 0;
 }
@@ -237,285 +238,502 @@ static GrGeometryProcessor* create_dash_gp(GrColor,
                                            DashCap cap,
                                            const SkMatrix& localMatrix);
 
-bool GrDashingEffect::DrawDashLine(GrGpu* gpu, GrDrawTarget* target,
-                                   GrPipelineBuilder* pipelineBuilder, GrColor color,
-                                   const SkMatrix& viewMatrix, const SkPoint pts[2],
-                                   const GrPaint& paint, const GrStrokeInfo& strokeInfo) {
-    if (!can_fast_path_dash(pts, strokeInfo, *target, *pipelineBuilder, viewMatrix)) {
-        return false;
+class DashBatch : public GrBatch {
+public:
+    struct Geometry {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        SkMatrix fSrcRotInv;
+        SkPoint fPtsRot[2];
+        SkScalar fSrcStrokeWidth;
+        SkScalar fPhase;
+        SkScalar fIntervals[2];
+        SkScalar fParallelScale;
+        SkScalar fPerpendicularScale;
+        SkDEBUGCODE(SkRect fDevBounds;)
+    };
+
+    static GrBatch* Create(const Geometry& geometry, SkPaint::Cap cap, bool useAA, bool fullDash) {
+        return SkNEW_ARGS(DashBatch, (geometry, cap, useAA, fullDash));
     }
 
-    const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo();
+    const char* name() const SK_OVERRIDE { return "DashBatch"; }
 
-    SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap();
+    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();
+    }
 
-    SkScalar srcStrokeWidth = strokeInfo.getStrokeRec().getWidth();
+    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;
+        }
 
-    // the phase should be normalized to be [0, sum of all intervals)
-    SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fIntervals[1]);
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fColor = fGeoData[0].fColor;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
 
-    SkScalar srcPhase = info.fPhase;
+    struct DashDraw {
+        SkScalar fStartOffset;
+        SkScalar fStrokeWidth;
+        SkScalar fLineLength;
+        SkScalar fHalfDevStroke;
+        SkScalar fDevBloat;
+        bool fLineDone;
+        bool fHasStartRect;
+        bool fHasEndRect;
+    };
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
+        int instanceCount = fGeoData.count();
+
+        SkMatrix invert;
+        if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
+            SkDebugf("Failed to invert\n");
+            return;
+        }
 
-    // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX
-    SkMatrix srcRotInv;
-    SkPoint ptsRot[2];
-    if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) {
-        SkMatrix rotMatrix;
-        align_to_x_axis(pts, &rotMatrix, ptsRot);
-        if(!rotMatrix.invert(&srcRotInv)) {
-            SkDebugf("Failed to create invertible rotation matrix!\n");
-            return false;
+        SkPaint::Cap cap = this->cap();
+
+        SkAutoTUnref<const GrGeometryProcessor> gp;
+
+        bool isRoundCap = SkPaint::kRound_Cap == cap;
+        DashCap capType = isRoundCap ? kRound_DashCap : kNonRound_DashCap;
+        if (this->fullDash()) {
+            GrPrimitiveEdgeType edgeType = this->useAA() ? kFillAA_GrProcessorEdgeType :
+                                                           kFillBW_GrProcessorEdgeType;
+            gp.reset(create_dash_gp(this->color(), edgeType, capType, invert));
+        } else {
+            // Set up the vertex data for the line and start/end dashes
+            gp.reset(GrDefaultGeoProcFactory::Create(GrDefaultGeoProcFactory::kPosition_GPType,
+                                                     this->color(),
+                                                     SkMatrix::I(),
+                                                     invert));
         }
-    } else {
-        srcRotInv.reset();
-        memcpy(ptsRot, pts, 2 * sizeof(SkPoint));
-    }
 
-    bool useAA = paint.isAntiAlias();
+        batchTarget->initDraw(gp, 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();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+
+        bool useAA = this->useAA();
+        bool fullDash = this->fullDash();
+
+        // We do two passes over all of the dashes.  First we setup the start, end, and bounds,
+        // rectangles.  We preserve all of this work in the rects / draws arrays below.  Then we
+        // iterate again over these decomposed dashes to generate vertices
+        SkSTArray<128, SkRect, true> rects;
+        SkSTArray<128, DashDraw, true> draws;
+
+        int totalRectCount = 0;
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            bool hasCap = SkPaint::kButt_Cap != cap && 0 != args.fSrcStrokeWidth;
+
+            // We always want to at least stroke out half a pixel on each side in device space
+            // so 0.5f / perpScale gives us this min in src space
+            SkScalar halfSrcStroke =
+                    SkMaxScalar(args.fSrcStrokeWidth * 0.5f, 0.5f / args.fPerpendicularScale);
+
+            SkScalar strokeAdj;
+            if (!hasCap) {
+                strokeAdj = 0.f;
+            } else {
+                strokeAdj = halfSrcStroke;
+            }
 
-    // Scale corrections of intervals and stroke from view matrix
-    SkScalar parallelScale;
-    SkScalar perpScale;
-    calc_dash_scaling(&parallelScale, &perpScale, viewMatrix, ptsRot);
+            SkScalar startAdj = 0;
+
+            SkMatrix& combinedMatrix = args.fSrcRotInv;
+            combinedMatrix.postConcat(args.fViewMatrix);
+
+            bool lineDone = false;
+
+            // Too simplify the algorithm, we always push back rects for start and end rect.
+            // Otherwise we'd have to track start / end rects for each individual geometry
+            SkRect& bounds = rects.push_back();
+            SkRect& startRect = rects.push_back();
+            SkRect& endRect = rects.push_back();
+
+            bool hasStartRect = false;
+            // If we are using AA, check to see if we are drawing a partial dash at the start. If so
+            // draw it separately here and adjust our start point accordingly
+            if (useAA) {
+                if (args.fPhase > 0 && args.fPhase < args.fIntervals[0]) {
+                    SkPoint startPts[2];
+                    startPts[0] = args.fPtsRot[0];
+                    startPts[1].fY = startPts[0].fY;
+                    startPts[1].fX = SkMinScalar(startPts[0].fX + args.fIntervals[0] - args.fPhase,
+                                                 args.fPtsRot[1].fX);
+                    startRect.set(startPts, 2);
+                    startRect.outset(strokeAdj, halfSrcStroke);
+
+                    hasStartRect = true;
+                    startAdj = args.fIntervals[0] + args.fIntervals[1] - args.fPhase;
+                }
+            }
 
-    bool hasCap = SkPaint::kButt_Cap != cap && 0 != srcStrokeWidth;
+            // adjustments for start and end of bounding rect so we only draw dash intervals
+            // contained in the original line segment.
+            startAdj += calc_start_adjustment(args.fIntervals, args.fPhase);
+            if (startAdj != 0) {
+                args.fPtsRot[0].fX += startAdj;
+                args.fPhase = 0;
+            }
+            SkScalar endingInterval = 0;
+            SkScalar endAdj = calc_end_adjustment(args.fIntervals, args.fPtsRot, args.fPhase,
+                                                  &endingInterval);
+            args.fPtsRot[1].fX -= endAdj;
+            if (args.fPtsRot[0].fX >= args.fPtsRot[1].fX) {
+                lineDone = true;
+            }
 
-    // We always want to at least stroke out half a pixel on each side in device space
-    // so 0.5f / perpScale gives us this min in src space
-    SkScalar halfSrcStroke = SkMaxScalar(srcStrokeWidth * 0.5f, 0.5f / perpScale);
+            bool hasEndRect = false;
+            // If we are using AA, check to see if we are drawing a partial dash at then end. If so
+            // draw it separately here and adjust our end point accordingly
+            if (useAA && !lineDone) {
+                // If we adjusted the end then we will not be drawing a partial dash at the end.
+                // If we didn't adjust the end point then we just need to make sure the ending
+                // dash isn't a full dash
+                if (0 == endAdj && endingInterval != args.fIntervals[0]) {
+                    SkPoint endPts[2];
+                    endPts[1] = args.fPtsRot[1];
+                    endPts[0].fY = endPts[1].fY;
+                    endPts[0].fX = endPts[1].fX - endingInterval;
+
+                    endRect.set(endPts, 2);
+                    endRect.outset(strokeAdj, halfSrcStroke);
+
+                    hasEndRect = true;
+                    endAdj = endingInterval + args.fIntervals[1];
+
+                    args.fPtsRot[1].fX -= endAdj;
+                    if (args.fPtsRot[0].fX >= args.fPtsRot[1].fX) {
+                        lineDone = true;
+                    }
+                }
+            }
 
-    SkScalar strokeAdj;
-    if (!hasCap) {
-        strokeAdj = 0.f;
-    } else {
-        strokeAdj = halfSrcStroke;
-    }
+            if (startAdj != 0) {
+                args.fPhase = 0;
+            }
 
-    SkScalar startAdj = 0;
-
-    SkMatrix combinedMatrix = srcRotInv;
-    combinedMatrix.postConcat(viewMatrix);
-
-    bool lineDone = false;
-    SkRect startRect;
-    bool hasStartRect = false;
-    // If we are using AA, check to see if we are drawing a partial dash at the start. If so
-    // draw it separately here and adjust our start point accordingly
-    if (useAA) {
-        if (srcPhase > 0 && srcPhase < info.fIntervals[0]) {
-            SkPoint startPts[2];
-            startPts[0] = ptsRot[0];
-            startPts[1].fY = startPts[0].fY;
-            startPts[1].fX = SkMinScalar(startPts[0].fX + info.fIntervals[0] - srcPhase,
-                                         ptsRot[1].fX);
-            startRect.set(startPts, 2);
-            startRect.outset(strokeAdj, halfSrcStroke);
-
-            hasStartRect = true;
-            startAdj = info.fIntervals[0] + info.fIntervals[1] - srcPhase;
-        }
-    }
+            // Change the dashing info from src space into device space
+            SkScalar* devIntervals = args.fIntervals;
+            devIntervals[0] = args.fIntervals[0] * args.fParallelScale;
+            devIntervals[1] = args.fIntervals[1] * args.fParallelScale;
+            SkScalar devPhase = args.fPhase * args.fParallelScale;
+            SkScalar strokeWidth = args.fSrcStrokeWidth * args.fPerpendicularScale;
 
-    // adjustments for start and end of bounding rect so we only draw dash intervals
-    // contained in the original line segment.
-    startAdj += calc_start_adjustment(info);
-    if (startAdj != 0) {
-        ptsRot[0].fX += startAdj;
-        srcPhase = 0;
-    }
-    SkScalar endingInterval = 0;
-    SkScalar endAdj = calc_end_adjustment(info, ptsRot, srcPhase, &endingInterval);
-    ptsRot[1].fX -= endAdj;
-    if (ptsRot[0].fX >= ptsRot[1].fX) {
-        lineDone = true;
-    }
+            if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) {
+                strokeWidth = 1.f;
+            }
+
+            SkScalar halfDevStroke = strokeWidth * 0.5f;
 
-    SkRect endRect;
-    bool hasEndRect = false;
-    // If we are using AA, check to see if we are drawing a partial dash at then end. If so
-    // draw it separately here and adjust our end point accordingly
-    if (useAA && !lineDone) {
-        // If we adjusted the end then we will not be drawing a partial dash at the end.
-        // If we didn't adjust the end point then we just need to make sure the ending
-        // dash isn't a full dash
-        if (0 == endAdj && endingInterval != info.fIntervals[0]) {
-            SkPoint endPts[2];
-            endPts[1] = ptsRot[1];
-            endPts[0].fY = endPts[1].fY;
-            endPts[0].fX = endPts[1].fX - endingInterval;
-
-            endRect.set(endPts, 2);
-            endRect.outset(strokeAdj, halfSrcStroke);
-
-            hasEndRect = true;
-            endAdj = endingInterval + info.fIntervals[1];
-
-            ptsRot[1].fX -= endAdj;
-            if (ptsRot[0].fX >= ptsRot[1].fX) {
+            if (SkPaint::kSquare_Cap == cap && 0 != args.fSrcStrokeWidth) {
+                // add cap to on interval and remove from off interval
+                devIntervals[0] += strokeWidth;
+                devIntervals[1] -= strokeWidth;
+            }
+            SkScalar startOffset = devIntervals[1] * 0.5f + devPhase;
+
+            SkScalar bloatX = useAA ? 0.5f / args.fParallelScale : 0.f;
+            SkScalar bloatY = useAA ? 0.5f / args.fPerpendicularScale : 0.f;
+
+            SkScalar devBloat = useAA ? 0.5f : 0.f;
+
+            if (devIntervals[1] <= 0.f && useAA) {
+                // Case when we end up drawing a solid AA rect
+                // Reset the start rect to draw this single solid rect
+                // but it requires to upload a new intervals uniform so we can mimic
+                // one giant dash
+                args.fPtsRot[0].fX -= hasStartRect ? startAdj : 0;
+                args.fPtsRot[1].fX += hasEndRect ? endAdj : 0;
+                startRect.set(args.fPtsRot, 2);
+                startRect.outset(strokeAdj, halfSrcStroke);
+                hasStartRect = true;
+                hasEndRect = false;
                 lineDone = true;
+
+                SkPoint devicePts[2];
+                args.fViewMatrix.mapPoints(devicePts, args.fPtsRot, 2);
+                SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
+                if (hasCap) {
+                    lineLength += 2.f * halfDevStroke;
+                }
+                devIntervals[0] = lineLength;
+            }
+
+            totalRectCount += !lineDone ? 1 : 0;
+            totalRectCount += hasStartRect ? 1 : 0;
+            totalRectCount += hasEndRect ? 1 : 0;
+
+            if (SkPaint::kRound_Cap == cap && 0 != args.fSrcStrokeWidth) {
+                // need to adjust this for round caps to correctly set the dashPos attrib on
+                // vertices
+                startOffset -= halfDevStroke;
+            }
+
+            DashDraw& draw = draws.push_back();
+            if (!lineDone) {
+                SkPoint devicePts[2];
+                args.fViewMatrix.mapPoints(devicePts, args.fPtsRot, 2);
+                draw.fLineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
+                if (hasCap) {
+                    draw.fLineLength += 2.f * halfDevStroke;
+                }
+
+                bounds.set(args.fPtsRot[0].fX, args.fPtsRot[0].fY,
+                           args.fPtsRot[1].fX, args.fPtsRot[1].fY);
+                bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke);
+            }
+
+            if (hasStartRect) {
+                SkASSERT(useAA);  // so that we know bloatX and bloatY have been set
+                startRect.outset(bloatX, bloatY);
+            }
+
+            if (hasEndRect) {
+                SkASSERT(useAA);  // so that we know bloatX and bloatY have been set
+                endRect.outset(bloatX, bloatY);
+            }
+
+            draw.fStartOffset = startOffset;
+            draw.fDevBloat = devBloat;
+            draw.fHalfDevStroke = halfDevStroke;
+            draw.fStrokeWidth = strokeWidth;
+            draw.fHasStartRect = hasStartRect;
+            draw.fLineDone = lineDone;
+            draw.fHasEndRect = hasEndRect;
+        }
+
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        size_t vertexStride = gp->getVertexStride();
+        void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              totalRectCount * kVertsPerDash,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+
+        int curVIdx = 0;
+        int rectIndex = 0;
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+
+            if (!draws[i].fLineDone) {
+                if (fullDash) {
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, args.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloat,
+                                      draws[i].fLineLength, draws[i].fHalfDevStroke,
+                                      args.fIntervals[0], args.fIntervals[1], draws[i].fStrokeWidth,
+                                      capType, gp->getVertexStride());
+                } else {
+                    SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
+                    setup_dashed_rect_pos(rects[rectIndex], curVIdx, args.fSrcRotInv, verts);
+                }
+                curVIdx += 4;
+            }
+            rectIndex++;
+
+            if (draws[i].fHasStartRect) {
+                if (fullDash) {
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, args.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloat, args.fIntervals[0],
+                                      draws[i].fHalfDevStroke, args.fIntervals[0],
+                                      args.fIntervals[1], draws[i].fStrokeWidth, capType,
+                                      gp->getVertexStride());
+                } else {
+                    SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
+                    setup_dashed_rect_pos(rects[rectIndex], curVIdx, args.fSrcRotInv, verts);
+                }
+
+                curVIdx += 4;
             }
+            rectIndex++;
+
+            if (draws[i].fHasEndRect) {
+                if (fullDash) {
+                    setup_dashed_rect(rects[rectIndex], vertices, curVIdx, args.fSrcRotInv,
+                                      draws[i].fStartOffset, draws[i].fDevBloat, args.fIntervals[0],
+                                      draws[i].fHalfDevStroke, args.fIntervals[0],
+                                      args.fIntervals[1], draws[i].fStrokeWidth, capType,
+                                      gp->getVertexStride());
+                } else {
+                    SkPoint* verts = reinterpret_cast<SkPoint*>(vertices);
+                    SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
+                    setup_dashed_rect_pos(rects[rectIndex], curVIdx, args.fSrcRotInv, verts);
+                }
+                curVIdx += 4;
+            }
+            rectIndex++;
         }
-    }
 
-    if (startAdj != 0) {
-        srcPhase = 0;
+        const GrIndexBuffer* dashIndexBuffer = batchTarget->quadIndexBuffer();
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVertsPerDash);
+        drawInfo.setIndicesPerInstance(kIndicesPerDash);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(dashIndexBuffer);
+
+        int maxInstancesPerDraw = dashIndexBuffer->maxQuads();
+        while (totalRectCount) {
+            drawInfo.setInstanceCount(SkTMin(totalRectCount, maxInstancesPerDraw));
+            drawInfo.setVertexCount(drawInfo.instanceCount() * drawInfo.verticesPerInstance());
+            drawInfo.setIndexCount(drawInfo.instanceCount() * drawInfo.indicesPerInstance());
+
+            batchTarget->draw(drawInfo);
+
+            drawInfo.setStartVertex(drawInfo.startVertex() + drawInfo.vertexCount());
+            totalRectCount -= drawInfo.instanceCount();
+       }
     }
 
-    // Change the dashing info from src space into device space
-    SkScalar devIntervals[2];
-    devIntervals[0] = info.fIntervals[0] * parallelScale;
-    devIntervals[1] = info.fIntervals[1] * parallelScale;
-    SkScalar devPhase = srcPhase * parallelScale;
-    SkScalar strokeWidth = srcStrokeWidth * perpScale;
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    DashBatch(const Geometry& geometry, SkPaint::Cap cap, bool useAA, bool fullDash) {
+        this->initClassID<DashBatch>();
+        fGeoData.push_back(geometry);
 
-    if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) {
-        strokeWidth = 1.f;
+        fBatch.fUseAA = useAA;
+        fBatch.fCap = cap;
+        fBatch.fFullDash = fullDash;
     }
 
-    SkScalar halfDevStroke = strokeWidth * 0.5f;
+    bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
+        DashBatch* that = t->cast<DashBatch>();
 
-    if (SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth) {
-        // add cap to on interveal and remove from off interval
-        devIntervals[0] += strokeWidth;
-        devIntervals[1] -= strokeWidth;
-    }
-    SkScalar startOffset = devIntervals[1] * 0.5f + devPhase;
-
-    SkScalar bloatX = useAA ? 0.5f / parallelScale : 0.f;
-    SkScalar bloatY = useAA ? 0.5f / perpScale : 0.f;
-
-    SkScalar devBloat = useAA ? 0.5f : 0.f;
-
-    if (devIntervals[1] <= 0.f && useAA) {
-        // Case when we end up drawing a solid AA rect
-        // Reset the start rect to draw this single solid rect
-        // but it requires to upload a new intervals uniform so we can mimic
-        // one giant dash
-        ptsRot[0].fX -= hasStartRect ? startAdj : 0;
-        ptsRot[1].fX += hasEndRect ? endAdj : 0;
-        startRect.set(ptsRot, 2);
-        startRect.outset(strokeAdj, halfSrcStroke);
-        hasStartRect = true;
-        hasEndRect = false;
-        lineDone = true;
-
-        SkPoint devicePts[2];
-        viewMatrix.mapPoints(devicePts, ptsRot, 2);
-        SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
-        if (hasCap) {
-            lineLength += 2.f * halfDevStroke;
+        if (this->useAA() != that->useAA()) {
+            return false;
         }
-        devIntervals[0] = lineLength;
-    }
 
-    // reset to device coordinates
-    SkMatrix invert;
-    if (!viewMatrix.invert(&invert)) {
-        SkDebugf("Failed to invert\n");
-        return false;
-    }
+        if (this->fullDash() != that->fullDash()) {
+            return false;
+        }
 
-    bool isRoundCap = SkPaint::kRound_Cap == cap;
-    DashCap capType = isRoundCap ? kRound_DashCap : kNonRound_DashCap;
-
-    SkAutoTUnref<const GrGeometryProcessor> gp;
-    bool fullDash = devIntervals[1] > 0.f || useAA;
-    if (fullDash) {
-        SkPathEffect::DashInfo devInfo;
-        devInfo.fPhase = devPhase;
-        devInfo.fCount = 2;
-        devInfo.fIntervals = devIntervals;
-        GrPrimitiveEdgeType edgeType = useAA ? kFillAA_GrProcessorEdgeType :
-            kFillBW_GrProcessorEdgeType;
-        gp.reset(create_dash_gp(color, edgeType, capType, invert));
-    } else {
-        // Set up the vertex data for the line and start/end dashes
-        gp.reset(GrDefaultGeoProcFactory::Create(GrDefaultGeoProcFactory::kPosition_GPType,
-                                                 color,
-                                                 SkMatrix::I(),
-                                                 invert));
+        if (this->cap() != that->cap()) {
+            return false;
+        }
+
+        // TODO vertex color
+        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;
     }
 
-    int totalRectCnt = 0;
+    GrColor color() const { return fBatch.fColor; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
+    bool useAA() const { return fBatch.fUseAA; }
+    bool fullDash() const { return fBatch.fFullDash; }
+    SkPaint::Cap cap() const { return fBatch.fCap; }
+
+    struct BatchTracker {
+        GrColor fColor;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+        SkPaint::Cap fCap;
+        bool fUseAA;
+        bool fFullDash;
+    };
+
+    static const int kVertsPerDash = 4;
+    static const int kIndicesPerDash = 6;
+
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+};
 
-    totalRectCnt += !lineDone ? 1 : 0;
-    totalRectCnt += hasStartRect ? 1 : 0;
-    totalRectCnt += hasEndRect ? 1 : 0;
 
-    GrDrawTarget::AutoReleaseGeometry geo(target,
-                                          totalRectCnt * 4,
-                                          gp->getVertexStride(), 0);
-    if (!geo.succeeded()) {
-        SkDebugf("Failed to get space for vertices!\n");
+bool GrDashingEffect::DrawDashLine(GrGpu* gpu, GrDrawTarget* target,
+                                   GrPipelineBuilder* pipelineBuilder, GrColor color,
+                                   const SkMatrix& viewMatrix, const SkPoint pts[2],
+                                   const GrPaint& paint, const GrStrokeInfo& strokeInfo) {
+    if (!can_fast_path_dash(pts, strokeInfo, *target, *pipelineBuilder, viewMatrix)) {
         return false;
     }
 
-    int curVIdx = 0;
+    const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo();
 
-    if (SkPaint::kRound_Cap == cap && 0 != srcStrokeWidth) {
-        // need to adjust this for round caps to correctly set the dashPos attrib on vertices
-        startOffset -= halfDevStroke;
-    }
+    SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap();
 
-    // Draw interior part of dashed line
-    if (!lineDone) {
-        SkPoint devicePts[2];
-        viewMatrix.mapPoints(devicePts, ptsRot, 2);
-        SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
-        if (hasCap) {
-            lineLength += 2.f * halfDevStroke;
-        }
+    DashBatch::Geometry geometry;
+    geometry.fSrcStrokeWidth = strokeInfo.getStrokeRec().getWidth();
 
-        SkRect bounds;
-        bounds.set(ptsRot[0].fX, ptsRot[0].fY, ptsRot[1].fX, ptsRot[1].fY);
-        bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke);
-        if (fullDash) {
-            setup_dashed_rect(bounds, geo.vertices(), curVIdx, combinedMatrix, startOffset,
-                              devBloat, lineLength, halfDevStroke, devIntervals[0], devIntervals[1],
-                              strokeWidth, capType, gp->getVertexStride());
-        } else {
-            SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices());
-            SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
-            setup_dashed_rect_pos(bounds, curVIdx, combinedMatrix, verts);
+    // the phase should be normalized to be [0, sum of all intervals)
+    SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fIntervals[1]);
+
+    // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX
+    if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) {
+        SkMatrix rotMatrix;
+        align_to_x_axis(pts, &rotMatrix, geometry.fPtsRot);
+        if(!rotMatrix.invert(&geometry.fSrcRotInv)) {
+            SkDebugf("Failed to create invertible rotation matrix!\n");
+            return false;
         }
-        curVIdx += 4;
+    } else {
+        geometry.fSrcRotInv.reset();
+        memcpy(geometry.fPtsRot, pts, 2 * sizeof(SkPoint));
     }
 
-    if (hasStartRect) {
-        SkASSERT(useAA);  // so that we know bloatX and bloatY have been set
-        startRect.outset(bloatX, bloatY);
-        if (fullDash) {
-            setup_dashed_rect(startRect, geo.vertices(), curVIdx, combinedMatrix, startOffset,
-                              devBloat, devIntervals[0], halfDevStroke, devIntervals[0],
-                              devIntervals[1], strokeWidth, capType, gp->getVertexStride());
-        } else {
-            SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices());
-            SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
-            setup_dashed_rect_pos(startRect, curVIdx, combinedMatrix, verts);
-        }
+    // Scale corrections of intervals and stroke from view matrix
+    calc_dash_scaling(&geometry.fParallelScale, &geometry.fPerpendicularScale, viewMatrix,
+                      geometry.fPtsRot);
 
-        curVIdx += 4;
+    SkScalar offInterval = info.fIntervals[1] * geometry.fParallelScale;
+    SkScalar strokeWidth = geometry.fSrcStrokeWidth * geometry.fPerpendicularScale;
+
+    if (SkPaint::kSquare_Cap == cap && 0 != geometry.fSrcStrokeWidth) {
+        // add cap to on interveal and remove from off interval
+        offInterval -= strokeWidth;
     }
 
-    if (hasEndRect) {
-        SkASSERT(useAA);  // so that we know bloatX and bloatY have been set
-        endRect.outset(bloatX, bloatY);
-        if (fullDash) {
-            setup_dashed_rect(endRect, geo.vertices(), curVIdx, combinedMatrix, startOffset,
-                              devBloat, devIntervals[0], halfDevStroke, devIntervals[0],
-                              devIntervals[1], strokeWidth, capType, gp->getVertexStride());
-        } else {
-            SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices());
-            SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
-            setup_dashed_rect_pos(endRect, curVIdx, combinedMatrix, verts);
-        }
+    bool useAA = paint.isAntiAlias();
+    // TODO we can do a real rect call if not using fulldash(ie no off interval, not using AA)
+    bool fullDash = offInterval > 0.f || useAA;
 
-    }
+    geometry.fColor = color;
+    geometry.fViewMatrix = viewMatrix;
+    geometry.fPhase = info.fPhase;
+    geometry.fIntervals[0] = info.fIntervals[0];
+    geometry.fIntervals[1] = info.fIntervals[1];
+
+    SkAutoTUnref<GrBatch> batch(DashBatch::Create(geometry, cap, useAA, fullDash));
+    target->drawBatch(pipelineBuilder, batch);
 
-    target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer());
-    target->drawIndexedInstances(pipelineBuilder, gp, kTriangles_GrPrimitiveType, totalRectCnt,
-                                 4, 6);
-    target->resetIndexSource();
     return true;
 }