Add GrAAHairLinePathRenderer

Review URL: http://codereview.appspot.com/4926045



git-svn-id: http://skia.googlecode.com/svn/trunk@2196 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/src/GrAAHairLinePathRenderer.cpp b/gpu/src/GrAAHairLinePathRenderer.cpp
new file mode 100644
index 0000000..40722b8
--- /dev/null
+++ b/gpu/src/GrAAHairLinePathRenderer.cpp
@@ -0,0 +1,633 @@
+#include "GrAAHairLinePathRenderer.h"
+
+#include "GrContext.h"
+#include "GrGpu.h"
+#include "GrIndexBuffer.h"
+#include "SkGeometry.h"
+#include "SkTemplates.h"
+
+namespace {
+// quadratics are rendered as 5-sided polys in order to bound the
+// AA stroke around the center-curve. See comments in push_quad_index_buffer and
+// bloat_quad.
+static const int kVertsPerQuad = 5;
+static const int kIdxsPerQuad = 9;
+
+static const int kVertsPerLineSeg = 4;
+static const int kIdxsPerLineSeg = 6;
+
+static const int kNumQuadsInIdxBuffer = 256;
+static const size_t kQuadIdxSBufize = kIdxsPerQuad *
+                                      sizeof(uint16_t) *
+                                      kNumQuadsInIdxBuffer;
+
+bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) {
+    uint16_t* data = (uint16_t*) qIdxBuffer->lock();
+    bool tempData = NULL == data;
+    if (tempData) {
+        data = new uint16_t[kNumQuadsInIdxBuffer * kIdxsPerQuad];
+    }
+    for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) {
+
+        // Each quadratic is rendered as a five sided polygon. This poly bounds
+        // the quadratic's bounding triangle but has been expanded so that the
+        // 1-pixel wide area around the curve is inside the poly.
+        // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
+        // that is rendered would look like this:
+        //              b0
+        //              b
+        //
+        //     a0              c0
+        //      a            c
+        //       a1       c1
+        // Each is drawn as three triagnles specified by these 9 indices:
+        int baseIdx = i * kIdxsPerQuad;
+        uint16_t baseVert = (uint16_t)(i * kVertsPerQuad);
+        data[0 + baseIdx] = baseVert + 0; // a0
+        data[1 + baseIdx] = baseVert + 1; // a1
+        data[2 + baseIdx] = baseVert + 2; // b0
+        data[3 + baseIdx] = baseVert + 2; // b0
+        data[4 + baseIdx] = baseVert + 4; // c1
+        data[5 + baseIdx] = baseVert + 3; // c0
+        data[6 + baseIdx] = baseVert + 1; // a1
+        data[7 + baseIdx] = baseVert + 4; // c1
+        data[8 + baseIdx] = baseVert + 2; // b0
+    }
+    if (tempData) {
+        bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize);
+        delete[] data;
+        return ret;
+    } else {
+        qIdxBuffer->unlock();
+        return true;
+    }
+}
+}
+
+GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) {
+    if (CanBeUsed(context)) {
+        const GrIndexBuffer* lIdxBuffer = context->getQuadIndexBuffer();
+        if (NULL == lIdxBuffer) {
+            return NULL;
+        }
+        GrGpu* gpu = context->getGpu();
+        GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false);
+        SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf); // cons will take a ref
+        if (NULL == qIdxBuf ||
+            !push_quad_index_data(qIdxBuffer.get())) {
+            return NULL;
+        }
+        return new GrAAHairLinePathRenderer(context,
+                                            lIdxBuffer,
+                                            qIdxBuf);
+    } else {
+        return NULL;
+    }
+}
+
+bool GrAAHairLinePathRenderer::CanBeUsed(const GrContext* context) {
+    return context->getGpu()->supportsShaderDerivatives();
+
+}
+
+GrAAHairLinePathRenderer::GrAAHairLinePathRenderer(
+                                        const GrContext* context,
+                                        const GrIndexBuffer* linesIndexBuffer,
+                                        const GrIndexBuffer* quadsIndexBuffer) {
+    GrAssert(CanBeUsed(context));
+    fLinesIndexBuffer = linesIndexBuffer;
+    linesIndexBuffer->ref();
+    fQuadsIndexBuffer = quadsIndexBuffer;
+    quadsIndexBuffer->ref();
+    this->resetGeom();
+}
+
+GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() {
+    fLinesIndexBuffer->unref();
+    fQuadsIndexBuffer->unref();
+}
+
+bool GrAAHairLinePathRenderer::supportsAA(GrDrawTarget* target,
+                                          const SkPath& path,
+                                          GrPathFill fill) { 
+    return kHairLine_PathFill == fill;
+}
+
+bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target,
+                                           const SkPath& path,
+                                           GrPathFill fill) const {
+    // TODO: support perspective
+    return kHairLine_PathFill == fill &&
+           !target->getViewMatrix().hasPerspective();
+}
+
+void GrAAHairLinePathRenderer::pathWillClear() {
+    this->resetGeom();
+}
+
+void GrAAHairLinePathRenderer::resetGeom() {
+    fPreviousStages = ~0;
+    fPreviousRTHeight = ~0;
+    fPreviousViewMatrix = GrMatrix::InvalidMatrix();
+    fLineSegmentCnt = 0;
+    fQuadCnt = 0; 
+    if ((fQuadCnt || fLineSegmentCnt) && NULL != fTarget) {
+        fTarget->resetVertexSource();
+    }
+}
+
+namespace {
+
+typedef GrTArray<SkPoint, true> PtArray;
+typedef GrTArray<int, true> IntArray;
+
+/**
+ * We convert cubics to quadratics (for now).
+ */
+void convert_noninflect_cubic_to_quads(const SkPoint p[4],
+                                       PtArray* quads,
+                                       int sublevel = 0) {
+    SkVector ab = p[1];
+    ab -= p[0];
+    SkVector dc = p[2];
+    dc -= p[3];
+
+    static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2;
+    static const SkScalar gDistanceSqdTol = 2 * SK_Scalar1;
+    static const int kMaxSubdivs = 30;
+
+    ab.scale(gLengthScale);
+    dc.scale(gLengthScale);
+
+    SkVector c0 = p[0];
+    c0 += ab;
+    SkVector c1 = p[3];
+    c1 += dc;
+
+    SkScalar dSqd = c0.distanceToSqd(c1);
+    if (sublevel > kMaxSubdivs || dSqd <= gDistanceSqdTol) {
+        SkPoint cAvg = c0;
+        cAvg += c1;
+        cAvg.scale(SK_ScalarHalf);
+
+        int idx = quads->count();
+        quads->push_back_n(3);
+        (*quads)[idx+0] = p[0];
+        (*quads)[idx+1] = cAvg;
+        (*quads)[idx+2] = p[3];
+
+        return;
+    } else {
+        SkPoint choppedPts[7];
+        SkChopCubicAtHalf(p, choppedPts);
+        convert_noninflect_cubic_to_quads(choppedPts + 0, quads, sublevel + 1);
+        convert_noninflect_cubic_to_quads(choppedPts + 3, quads, sublevel + 1);
+    }
+}
+
+void convert_cubic_to_quads(const SkPoint p[4], PtArray* quads) {
+    SkPoint chopped[13];
+    int count = SkChopCubicAtInflections(p, chopped);
+
+    for (int i = 0; i < count; ++i) {
+        SkPoint* cubic = chopped + 3*i;
+        convert_noninflect_cubic_to_quads(cubic, quads);
+    }
+}
+
+// Takes 178th time of logf on Z600 / VC2010
+int get_float_exp(float x) {
+    GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
+#if GR_DEBUG
+    static bool tested;
+    if (!tested) {
+        tested = true;
+        GrAssert(get_float_exp(0.25f) == -2);
+        GrAssert(get_float_exp(0.3f) == -2);
+        GrAssert(get_float_exp(0.5f) == -1);
+        GrAssert(get_float_exp(1.f) == 0);
+        GrAssert(get_float_exp(2.f) == 1);
+        GrAssert(get_float_exp(2.5f) == 1);
+        GrAssert(get_float_exp(8.f) == 3);
+        GrAssert(get_float_exp(100.f) == 6);
+        GrAssert(get_float_exp(1000.f) == 9);
+        GrAssert(get_float_exp(1024.f) == 10);
+        GrAssert(get_float_exp(3000000.f) == 21);
+    }
+#endif
+    return (((*(int*)&x) & 0x7f800000) >> 23) - 127;
+}
+
+// we subdivide the quads to avoid huge overfill
+// if it returns -1 then should be drawn as lines
+int num_quad_subdivs(const SkPoint p[3]) {
+    static const SkScalar gDegenerateToLineTol = SK_Scalar1;
+
+    GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
+    if (dsqd < gDegenerateToLineTol*gDegenerateToLineTol) {
+        return -1;
+    }
+    if (p[2].distanceToLineBetweenSqd(p[1],p[0]) <
+        gDegenerateToLineTol*gDegenerateToLineTol) {
+        return -1;
+    }
+
+    static const int kMaxSub = 4;
+    // tolerance of triangle height in pixels
+    // tuned on windows  Quadro FX 380 / Z600
+    // trade off of fill vs cpu time on verts
+    // maybe different when do this using gpu (geo or tess shaders)
+    static const SkScalar gSubdivTol = 175 * SK_Scalar1;
+
+    if (dsqd <= gSubdivTol*gSubdivTol) {
+        return 0;
+    } else {
+        // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
+        // = log4(d*d/tol*tol)/2
+        // = log2(d*d/tol*tol)
+
+#ifdef SK_SCALAR_IS_FLOAT
+        // +1 since we're ignoring the mantissa contribution.
+        int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
+        log = GrMin(GrMax(0, log), kMaxSub);
+        return log;
+#else
+        SkScalar log = SkScalarLog(SkScalarDiv(dsqd,kTol*kTol));
+        static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
+        log = SkScalarMul(log, conv);
+        return  GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
+#endif
+    }
+}
+
+int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
+                        PtArray* lines, PtArray* quads,
+                        IntArray* quadSubdivCnts) {
+    SkPath::Iter iter(path, false);
+
+    int totalQuadCount = 0;
+    GrRect bounds;
+    GrIRect ibounds;
+    for (;;) {
+        GrPoint pts[4];
+        GrPathCmd cmd = (GrPathCmd)iter.next(pts);
+        switch (cmd) {
+            case kMove_PathCmd:
+                break;
+            case kLine_PathCmd:
+                m.mapPoints(pts,2);
+                bounds.setBounds(pts, 2);
+                bounds.outset(SK_Scalar1, SK_Scalar1);
+                bounds.roundOut(&ibounds);
+                if (SkIRect::Intersects(clip, ibounds)) {
+                    lines->push_back() = pts[0];
+                    lines->push_back() = pts[1];
+                }
+                break;
+            case kQuadratic_PathCmd: {
+                bounds.setBounds(pts, 3);
+                bounds.outset(SK_Scalar1, SK_Scalar1);
+                bounds.roundOut(&ibounds);
+                if (SkIRect::Intersects(clip, ibounds)) {
+                    m.mapPoints(pts, 3);
+                    int subdiv = num_quad_subdivs(pts);
+                    GrAssert(subdiv >= -1);
+                    if (-1 == subdiv) {
+                        lines->push_back() = pts[0];
+                        lines->push_back() = pts[1];
+                        lines->push_back() = pts[1];
+                        lines->push_back() = pts[2];
+                    } else {
+                        quads->push_back() = pts[0];
+                        quads->push_back() = pts[1];
+                        quads->push_back() = pts[2];
+                        quadSubdivCnts->push_back() = subdiv;
+                        totalQuadCount += 1 << subdiv;
+                    }
+                }
+            } break;
+            case kCubic_PathCmd: {
+                bounds.setBounds(pts, 4);
+                bounds.outset(SK_Scalar1, SK_Scalar1);
+                bounds.roundOut(&ibounds);
+                if (SkIRect::Intersects(clip, ibounds)) {
+                    m.mapPoints(pts, 4);
+                    SkPoint stackStorage[32];
+                    PtArray q((void*)stackStorage, 32);
+                    convert_cubic_to_quads(pts, &q);
+                    for (int i = 0; i < q.count(); i += 3) {
+                        bounds.setBounds(&q[i], 3);
+                        bounds.outset(SK_Scalar1, SK_Scalar1);
+                        bounds.roundOut(&ibounds);
+                        if (SkIRect::Intersects(clip, ibounds)) {
+                            int subdiv = num_quad_subdivs(&q[i]);
+                            GrAssert(subdiv >= -1);
+                            if (-1 == subdiv) {
+                                lines->push_back() = q[0 + i];
+                                lines->push_back() = q[1 + i];
+                                lines->push_back() = q[1 + i];
+                                lines->push_back() = q[2 + i];
+                            } else {
+                                quads->push_back() = q[0 + i];
+                                quads->push_back() = q[1 + i];
+                                quads->push_back() = q[2 + i];
+                                quadSubdivCnts->push_back() = subdiv;
+                                totalQuadCount += 1 << subdiv;
+                            }
+                        }
+                    }
+                }
+            } break;
+            case kClose_PathCmd:
+                break;
+            case kEnd_PathCmd:
+                return totalQuadCount;
+        }
+    }
+}
+
+struct Vertex {
+    GrPoint fPos;
+    union {
+        struct {
+            GrScalar fA;
+            GrScalar fB;
+            GrScalar fC;
+        } fLine;
+        GrVec   fQuadCoord;
+        struct {
+            GrScalar fBogus[4];
+        };
+    };
+};
+GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint));
+
+void intersect_lines(const SkPoint& ptA, const SkVector& normA,
+                     const SkPoint& ptB, const SkVector& normB,
+                     SkPoint* result) {
+
+    SkScalar lineAW = -normA.dot(ptA);
+    SkScalar lineBW = -normB.dot(ptB);
+
+    SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
+                    SkScalarMul(normA.fY, normB.fX);
+    wInv = SkScalarInvert(wInv);
+
+    result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
+    result->fX = SkScalarMul(result->fX, wInv);
+    
+    result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
+    result->fY = SkScalarMul(result->fY, wInv);
+}
+
+void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
+    // original quad is specified by tri a,b,c
+    const SkPoint& a = qpts[0];
+    const SkPoint& b = qpts[1];
+    const SkPoint& c = qpts[2];
+    // make a new poly where we replace a and c by a 1-pixel wide edges orthog
+    // to edges ab and bc:
+    //
+    //   before       |        after
+    //                |              b0
+    //         b      |
+    //                |
+    //                |     a0            c0
+    // a         c    |        a1       c1
+    //
+    // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
+    // respectively.
+    Vertex& a0 = verts[0];
+    Vertex& a1 = verts[1];
+    Vertex& b0 = verts[2];
+    Vertex& c0 = verts[3];
+    Vertex& c1 = verts[4];
+
+    // compute a matrix that goes from device coords to U,V quad params
+    SkMatrix DevToUV;
+    DevToUV.setAll(a.fX,           b.fX,          c.fX,
+                   a.fY,           b.fY,          c.fY,
+                   SK_Scalar1,     SK_Scalar1,    SK_Scalar1);
+    DevToUV.invert(&DevToUV);
+    // can't make this static, no cons :(
+    SkMatrix UVpts;
+    UVpts.setAll(0,                 SK_ScalarHalf,  SK_Scalar1,
+                 0,                 0,              SK_Scalar1,
+                 SK_Scalar1,        SK_Scalar1,     SK_Scalar1);
+    DevToUV.postConcat(UVpts);
+
+    // We really want to avoid perspective matrix muls.
+    // These may wind up really close to zero
+    DevToUV.setPerspX(0);
+    DevToUV.setPerspY(0);
+
+    SkVector ab = b;
+    ab -= a;
+    SkVector ac = c;
+    ac -= a;
+    SkVector cb = b;
+    cb -= c;
+
+    // We should have already handled degenerates
+    GrAssert(ab.length() > 0 && cb.length() > 0);
+
+    ab.normalize();
+    SkVector abN;
+    abN.setOrthog(ab, SkVector::kLeft_Side);
+    if (abN.dot(ac) > 0) {
+        abN.negate();
+    }
+
+    cb.normalize();
+    SkVector cbN;
+    cbN.setOrthog(cb, SkVector::kLeft_Side);
+    if (cbN.dot(ac) < 0) {
+        cbN.negate();
+    }
+
+    a0.fPos = a;
+    a0.fPos += abN;
+    a1.fPos = a;
+    a1.fPos -= abN;
+
+    c0.fPos = c;
+    c0.fPos += cbN;
+    c1.fPos = c;
+    c1.fPos -= cbN;
+
+    intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
+
+    DevToUV.mapPointsWithStride(&verts[0].fQuadCoord,
+                                &verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
+}
+
+void add_quads(const SkPoint p[3],
+               int subdiv,
+               Vertex** vert) {
+    GrAssert(subdiv >= 0);
+    if (subdiv) {
+        SkPoint newP[5];
+        SkChopQuadAtHalf(p, newP);
+        add_quads(newP + 0, subdiv-1, vert);
+        add_quads(newP + 2, subdiv-1, vert);
+    } else {
+        bloat_quad(p, *vert);
+        *vert += kVertsPerQuad;
+    }
+}
+
+void add_line(const SkPoint p[2],
+              int rtHeight,
+              Vertex** vert) {
+    const SkPoint& a = p[0];
+    const SkPoint& b = p[1];
+
+    SkVector orthVec = b;
+    orthVec -= a;
+
+    if (orthVec.setLength(SK_Scalar1)) {
+        orthVec.setOrthog(orthVec);
+
+        // the values we pass down to the frag shader
+        // have to be in y-points-up space;
+        SkVector normal;
+        normal.fX = orthVec.fX;
+        normal.fY = -orthVec.fY;
+        SkPoint aYDown;
+        aYDown.fX = a.fX;
+        aYDown.fY = rtHeight - a.fY;
+
+        SkScalar lineC = -(aYDown.dot(normal));
+        for (int i = 0; i < kVertsPerLineSeg; ++i) {
+            (*vert)[i].fPos = (i < 2) ? a : b;
+            if (0 == i || 3 == i) {
+                (*vert)[i].fPos -= orthVec;
+            } else {
+                (*vert)[i].fPos += orthVec;
+            }
+            (*vert)[i].fLine.fA = normal.fX;
+            (*vert)[i].fLine.fB = normal.fY;
+            (*vert)[i].fLine.fC = lineC;
+        }
+    } else {
+        // just make it degenerate and likely offscreen
+        (*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
+        (*vert)[1].fPos.set(SK_ScalarMax, SK_ScalarMax);
+        (*vert)[2].fPos.set(SK_ScalarMax, SK_ScalarMax);
+        (*vert)[3].fPos.set(SK_ScalarMax, SK_ScalarMax);
+    }
+
+    *vert += kVertsPerLineSeg;
+}
+
+}
+
+bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
+
+    int rtHeight = fTarget->getRenderTarget()->height();
+
+    GrIRect clip;
+    if (fTarget->getClip().hasConservativeBounds()) {
+        GrRect clipRect =  fTarget->getClip().getConservativeBounds();
+        clipRect.roundOut(&clip);
+    } else {
+        clip.setLargest();
+    }
+
+    if (stages == fPreviousStages &&
+        fPreviousViewMatrix == fTarget->getViewMatrix() &&
+        rtHeight == fPreviousRTHeight &&
+        fClipRect == clip) {
+        return true;
+    }
+
+    GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit;
+    for (int s = 0; s < GrDrawTarget::kNumStages; ++s) {
+        if ((1 << s) & stages) {
+            layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
+        }
+    }
+
+    GrMatrix viewM = fTarget->getViewMatrix();
+    viewM.preTranslate(fTranslate.fX, fTranslate.fY);
+
+    GrAlignedSTStorage<128, GrPoint> lineStorage;
+    GrAlignedSTStorage<128, GrPoint> quadStorage;
+    PtArray lines(&lineStorage);
+    PtArray quads(&quadStorage);
+    IntArray qSubdivs;
+    fQuadCnt = get_lines_and_quads(*fPath, viewM, clip,
+                                   &lines, &quads, &qSubdivs);
+
+    fLineSegmentCnt = lines.count() / 2;
+    int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt;
+
+    GrAssert(sizeof(Vertex) == GrDrawTarget::VertexSize(layout));
+
+    Vertex* verts;
+    if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) {
+        return false;
+    }
+
+    for (int i = 0; i < fLineSegmentCnt; ++i) {
+        add_line(&lines[2*i], rtHeight, &verts);
+    }
+    int unsubdivQuadCnt = quads.count() / 3;
+    for (int i = 0; i < unsubdivQuadCnt; ++i) {
+        GrAssert(qSubdivs[i] >= 0);
+        add_quads(&quads[3*i], qSubdivs[i], &verts);
+    }
+
+    fPreviousStages = stages;
+    fPreviousViewMatrix = fTarget->getViewMatrix();
+    fPreviousRTHeight = rtHeight;
+    fClipRect = clip;
+    return true;
+}
+
+void GrAAHairLinePathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
+    GrDrawTarget::AutoStateRestore asr(fTarget);
+
+    GrMatrix ivm;
+
+    if (!this->createGeom(stages)) {
+        return;
+    }
+
+    if (fTarget->getViewInverse(&ivm)) {
+        fTarget->preConcatSamplerMatrices(stages, ivm);
+    }
+
+    fTarget->setViewMatrix(GrMatrix::I());
+
+    // TODO: See whether rendering lines as degenerate quads improves perf
+    // when we have a mix
+    fTarget->setIndexSourceToBuffer(fLinesIndexBuffer);
+    int lines = 0;
+    int nBufLines = fLinesIndexBuffer->maxQuads();
+    while (lines < fLineSegmentCnt) {
+        int n = GrMin(fLineSegmentCnt-lines, nBufLines);
+        fTarget->setVertexEdgeType(GrDrawTarget::kHairLine_EdgeType);
+        fTarget->drawIndexed(kTriangles_PrimitiveType,
+                             kVertsPerLineSeg*lines,    // startV
+                             0,                         // startI
+                             kVertsPerLineSeg*n,        // vCount
+                             kIdxsPerLineSeg*n);        // iCount
+        lines += n;
+    }
+
+    fTarget->setIndexSourceToBuffer(fQuadsIndexBuffer);
+    int quads = 0;
+    while (quads < fQuadCnt) {
+        int n = GrMin(fQuadCnt-quads, kNumQuadsInIdxBuffer);
+        fTarget->setVertexEdgeType(GrDrawTarget::kHairQuad_EdgeType);
+        fTarget->drawIndexed(kTriangles_PrimitiveType,
+                             4*fLineSegmentCnt + kVertsPerQuad*quads, // startV
+                             0,                                       // startI
+                             kVertsPerQuad*n,                         // vCount
+                             kIdxsPerQuad*n);                         // iCount
+        quads += n;
+    }
+
+}
+
diff --git a/gpu/src/GrAAHairLinePathRenderer.h b/gpu/src/GrAAHairLinePathRenderer.h
new file mode 100644
index 0000000..c7d2dc7
--- /dev/null
+++ b/gpu/src/GrAAHairLinePathRenderer.h
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrAAHairLinePathRenderer_DEFINED
+#define GrAAHairLinePathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+
+class GrAAHairLinePathRenderer : public GrPathRenderer {
+public:
+    virtual ~GrAAHairLinePathRenderer();
+
+    static GrPathRenderer* Create(GrContext* context);
+    // GrPathRenderer overrides
+    virtual bool supportsAA(GrDrawTarget* target,
+                            const SkPath& path,
+                            GrPathFill fill);
+    virtual bool canDrawPath(const GrDrawTarget* target,
+                             const SkPath& path,
+                             GrPathFill fill) const;
+    virtual void drawPath(GrDrawTarget::StageBitfield stages);
+
+protected:
+
+    // GrPathRenderer overrides
+    virtual void pathWillClear();
+
+private:
+    void resetGeom();
+
+    static bool CanBeUsed(const GrContext* context);
+    GrAAHairLinePathRenderer(const GrContext* context,
+                             const GrIndexBuffer* fLinesIndexBuffer,
+                             const GrIndexBuffer* fQuadsIndexBuffer);
+
+    bool createGeom(GrDrawTarget::StageBitfield stages);
+
+    GrContext*                  fContext;
+    const GrIndexBuffer*        fLinesIndexBuffer;
+    const GrIndexBuffer*        fQuadsIndexBuffer;
+
+    // have to recreate geometry if stages in use changes :(
+    GrDrawTarget::StageBitfield fPreviousStages;
+    int                         fPreviousRTHeight;
+    GrIRect                     fClipRect;
+
+    // this path renderer draws everything in device coordinates
+    GrMatrix                    fPreviousViewMatrix;
+    int                         fLineSegmentCnt;
+    int                         fQuadCnt;
+
+    typedef GrPathRenderer INHERITED;
+};
+
+
+#endif
+
diff --git a/gpu/src/GrAddPathRenderers_aahairline.cpp b/gpu/src/GrAddPathRenderers_aahairline.cpp
new file mode 100644
index 0000000..a7df66e
--- /dev/null
+++ b/gpu/src/GrAddPathRenderers_aahairline.cpp
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "GrAAHairLinePathRenderer.h"
+
+void GrPathRenderer::AddPathRenderers(GrContext* ctx,
+                                      GrPathRendererChain::UsageFlags flags,
+                                      GrPathRendererChain* chain) {
+    if (!(GrPathRendererChain::kNonAAOnly_UsageFlag & flags)) {
+        if (GrPathRenderer* pr = GrAAHairLinePathRenderer::Create(ctx)) {
+            chain->addPathRenderer(pr)->unref();
+        }
+    }
+}
diff --git a/gpu/src/GrContext.cpp b/gpu/src/GrContext.cpp
index 6692023..a97c857 100644
--- a/gpu/src/GrContext.cpp
+++ b/gpu/src/GrContext.cpp
@@ -1176,7 +1176,7 @@
 
     GrRect devRect = rect;
     GrMatrix combinedMatrix;
-    bool doAA = apply_aa_to_rect(target, paint, rect, width, matrix, 
+    bool doAA = apply_aa_to_rect(target, paint, rect, width, matrix,
                                  &combinedMatrix, &devRect);
 
     if (doAA) {
@@ -1378,9 +1378,12 @@
         }
         int texOffsets[GrDrawTarget::kMaxTexCoords];
         int colorOffset;
+        int edgeOffset;
         GrDrawTarget::VertexSizeAndOffsetsByIdx(layout,
                                                 texOffsets,
-                                                &colorOffset);
+                                                &colorOffset,
+                                                &edgeOffset);
+        GrAssert(-1 == edgeOffset);
         void* curVertex = geo.vertices();
 
         for (int i = 0; i < vertexCount; ++i) {
diff --git a/gpu/src/GrDefaultPathRenderer.h b/gpu/src/GrDefaultPathRenderer.h
index e11716e..dd59641 100644
--- a/gpu/src/GrDefaultPathRenderer.h
+++ b/gpu/src/GrDefaultPathRenderer.h
@@ -20,7 +20,8 @@
     GrDefaultPathRenderer(bool separateStencilSupport,
                           bool stencilWrapOpsSupport);
 
-    virtual bool canDrawPath(const SkPath& path,
+    virtual bool canDrawPath(const GrDrawTarget* target,
+                             const SkPath& path,
                              GrPathFill fill) const { return true; }
 
     virtual bool requiresStencilPass(const GrDrawTarget* target,
diff --git a/gpu/src/GrDrawTarget.cpp b/gpu/src/GrDrawTarget.cpp
index 29a37a4..51cff6d 100644
--- a/gpu/src/GrDrawTarget.cpp
+++ b/gpu/src/GrDrawTarget.cpp
@@ -63,6 +63,17 @@
     return true;
 }
 
+int num_tex_coords(GrVertexLayout layout) {
+    int cnt = 0;
+    // figure out how many tex coordinates are present
+    for (int t = 0; t < GrDrawTarget::kMaxTexCoords; ++t) {
+        if (tex_coord_idx_mask(t) & layout) {
+            ++cnt;
+        }
+    }
+    return cnt;
+}
+
 } //unnamed namespace
 
 size_t GrDrawTarget::VertexSize(GrVertexLayout vertexLayout) {
@@ -73,14 +84,13 @@
                         sizeof(GrPoint);
 
     size_t size = vecSize; // position
-    for (int t = 0; t < kMaxTexCoords; ++t) {
-        if (tex_coord_idx_mask(t) & vertexLayout) {
-            size += vecSize;
-        }
-    }
+    size += num_tex_coords(vertexLayout) * vecSize;
     if (vertexLayout & kColor_VertexLayoutBit) {
         size += sizeof(GrColor);
     }
+    if (vertexLayout & kEdge_VertexLayoutBit) {
+        size += 4 * sizeof(GrScalar);
+    }
     return size;
 }
 
@@ -111,16 +121,27 @@
 int  GrDrawTarget::VertexColorOffset(GrVertexLayout vertexLayout) {
     GrAssert(check_layout(vertexLayout));
 
+    // color is after the pos and tex coords
     if (vertexLayout & kColor_VertexLayoutBit) {
         int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ?
                                     sizeof(GrGpuTextVertex) :
                                     sizeof(GrPoint);
-        int offset = vecSize; // position
-        // figure out how many tex coordinates are present and precede this one.
-        for (int t = 0; t < kMaxTexCoords; ++t) {
-            if (tex_coord_idx_mask(t) & vertexLayout) {
-                offset += vecSize;
-            }
+        return vecSize * (num_tex_coords(vertexLayout) + 1); //+1 for pos
+    }
+    return -1;
+}
+
+int  GrDrawTarget::VertexEdgeOffset(GrVertexLayout vertexLayout) {
+    GrAssert(check_layout(vertexLayout));
+
+    // edge pts are after the pos, tex coords, and color
+    if (vertexLayout & kEdge_VertexLayoutBit) {
+        int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ?
+                                    sizeof(GrGpuTextVertex) :
+                                    sizeof(GrPoint);
+        int offset = vecSize * (num_tex_coords(vertexLayout) + 1); //+1 for pos
+        if (vertexLayout & kColor_VertexLayoutBit) {
+            offset += sizeof(GrColor);
         }
         return offset;
     }
@@ -129,11 +150,13 @@
 
 int GrDrawTarget::VertexSizeAndOffsetsByIdx(GrVertexLayout vertexLayout,
                                              int texCoordOffsetsByIdx[kMaxTexCoords],
-                                             int* colorOffset) {
+                                             int* colorOffset,
+                                             int* edgeOffset) {
     GrAssert(check_layout(vertexLayout));
 
     GrAssert(NULL != texCoordOffsetsByIdx);
     GrAssert(NULL != colorOffset);
+    GrAssert(NULL != edgeOffset);
 
     int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ?
                                                     sizeof(GrGpuTextVertex) :
@@ -154,21 +177,30 @@
     } else {
         *colorOffset = -1;
     }
+    if (kEdge_VertexLayoutBit & vertexLayout) {
+        *edgeOffset = size;
+        size += 4 * sizeof(GrScalar);
+    } else {
+        *edgeOffset = -1;
+    }
     return size;
 }
 
 int GrDrawTarget::VertexSizeAndOffsetsByStage(GrVertexLayout vertexLayout,
                                               int texCoordOffsetsByStage[kNumStages],
-                                              int* colorOffset) {
+                                              int* colorOffset,
+                                              int* edgeOffset) {
     GrAssert(check_layout(vertexLayout));
 
     GrAssert(NULL != texCoordOffsetsByStage);
     GrAssert(NULL != colorOffset);
+    GrAssert(NULL != edgeOffset);
 
     int texCoordOffsetsByIdx[kMaxTexCoords];
     int size = VertexSizeAndOffsetsByIdx(vertexLayout,
                                          texCoordOffsetsByIdx,
-                                         colorOffset);
+                                         colorOffset,
+                                         edgeOffset);
     for (int s = 0; s < kNumStages; ++s) {
         int tcIdx;
         if (StagePosAsTexCoordVertexLayoutBit(s) & vertexLayout) {
@@ -250,22 +282,39 @@
                     GrAssert(VertexUsesStage(s2, posAsTex));
                     GrAssert(2*sizeof(GrPoint) == VertexSize(posAsTex));
                     GrAssert(-1 == VertexTexCoordsForStage(s2, posAsTex));
+                    GrAssert(-1 == VertexEdgeOffset(posAsTex));
                 }
+                GrAssert(-1 == VertexEdgeOffset(tcMask));
+                GrAssert(-1 == VertexColorOffset(tcMask));
             #if GR_DEBUG
                 GrVertexLayout withColor = tcMask | kColor_VertexLayoutBit;
             #endif
                 GrAssert(2*sizeof(GrPoint) == VertexColorOffset(withColor));
                 GrAssert(2*sizeof(GrPoint) + sizeof(GrColor) == VertexSize(withColor));
+            #if GR_DEBUG
+                GrVertexLayout withEdge = tcMask | kEdge_VertexLayoutBit;
+            #endif
+                GrAssert(-1 == VertexColorOffset(withEdge));
+                GrAssert(2*sizeof(GrPoint) == VertexEdgeOffset(withEdge));
+                GrAssert(4*sizeof(GrPoint) == VertexSize(withEdge));
+            #if GR_DEBUG
+                GrVertexLayout withColorAndEdge = withColor | kEdge_VertexLayoutBit;
+            #endif
+                GrAssert(2*sizeof(GrPoint) == VertexColorOffset(withColorAndEdge));
+                GrAssert(2*sizeof(GrPoint) + sizeof(GrColor) == VertexEdgeOffset(withColorAndEdge));
+                GrAssert(4*sizeof(GrPoint) + sizeof(GrColor) == VertexSize(withColorAndEdge));
             }
             GrAssert(tex_coord_idx_mask(t) == tcMask);
             GrAssert(check_layout(tcMask));
 
             int stageOffsets[kNumStages];
             int colorOffset;
+            int edgeOffset;
             int size;
-            size = VertexSizeAndOffsetsByStage(tcMask, stageOffsets, &colorOffset);
+            size = VertexSizeAndOffsetsByStage(tcMask, stageOffsets, &colorOffset, &edgeOffset);
             GrAssert(2*sizeof(GrPoint) == size);
             GrAssert(-1 == colorOffset);
+            GrAssert(-1 == edgeOffset);
             for (int s = 0; s < kNumStages; ++s) {
                 GrAssert(VertexUsesStage(s, tcMask));
                 GrAssert(sizeof(GrPoint) == stageOffsets[s]);
@@ -698,7 +747,8 @@
 bool GrDrawTarget::CanDisableBlend(GrVertexLayout layout, const DrState& state) {
     // If we compute a coverage value (using edge AA or a coverage stage) then
     // we can't force blending off.
-    if (state.fEdgeAANumEdges > 0) {
+    if (state.fEdgeAANumEdges > 0 || 
+        layout & kEdge_VertexLayoutBit) {
         return false;
     }
     for (int s = state.fFirstCoverageStage; s < kNumStages; ++s) {
@@ -837,8 +887,11 @@
 
     int stageOffsets[kNumStages];
     int colorOffset;
-    int vsize = VertexSizeAndOffsetsByStage(layout, stageOffsets, &colorOffset);
+    int edgeOffset;
+    int vsize = VertexSizeAndOffsetsByStage(layout, stageOffsets, 
+                                            &colorOffset, &edgeOffset);
     GrAssert(-1 == colorOffset);
+    GrAssert(-1 == edgeOffset);
 
     GrTCast<GrPoint*>(vertices)->setRectFan(rect.fLeft, rect.fTop, 
                                             rect.fRight, rect.fBottom,
diff --git a/gpu/src/GrDrawTarget.h b/gpu/src/GrDrawTarget.h
index f170fda..42723f6 100644
--- a/gpu/src/GrDrawTarget.h
+++ b/gpu/src/GrDrawTarget.h
@@ -59,6 +59,20 @@
         kMaxEdges = 32
     };
 
+     /**
+     * When specifying edges as vertex data this enum specifies what type of
+     * edges are in use. The edges are always 4 GrScalars in memory, even when
+     * the edge type requires fewer than 4.
+     */
+    enum VertexEdgeType {
+        /* 1-pixel wide line
+           2D implicit line eq (a*x + b*y +c = 0). 4th component unused */
+        kHairLine_EdgeType,
+        /* 1-pixel wide quadratic
+           u^2-v canonical coords (only 2 components used) */
+        kHairQuad_EdgeType
+    };
+
     /**
      *  Bitfield used to indicate which stages are in use.
      */
@@ -167,6 +181,7 @@
 
         GrStencilSettings       fStencilSettings;
         GrMatrix                fViewMatrix;
+        VertexEdgeType          fVertexEdgeType;
         Edge                    fEdgeAAEdges[kMaxEdges];
         int                     fEdgeAANumEdges;
         bool operator ==(const DrState& s) const {
@@ -256,7 +271,7 @@
     }
 
     /**
-     * Shortcut for preConcatSamplerMatrix on all stages in mask with same 
+     * Shortcut for preConcatSamplerMatrix on all stages in mask with same
      * matrix
      */
     void preConcatSamplerMatrices(int stageMask, const GrMatrix& matrix) {
@@ -268,6 +283,18 @@
     }
 
     /**
+     * Shortcut for preConcatSamplerMatrix on all enabled stages in mask with
+     * same matrix
+     *
+     * @param stage   the stage of the sampler to set
+     * @param matrix  the matrix to concat
+     */
+    void preConcatEnabledSamplerMatrices(const GrMatrix& matrix) {
+        StageBitfield stageMask = this->enabledStages();
+        this->preConcatSamplerMatrices(stageMask, matrix);
+    }
+
+    /**
      * Gets the matrix of a stage's sampler
      *
      * @param stage     the stage to of sampler to get
@@ -535,6 +562,15 @@
      */
     bool canDisableBlend() const;
 
+     /**
+      * Determines the interpretation per-vertex edge data when the
+      * kEdge_VertexLayoutBit is set (see below). When per-vertex edges are not
+      * specified the value of this setting has no effect.
+      */
+    void setVertexEdgeType(VertexEdgeType type) {
+        fCurrDrawState.fVertexEdgeType = type;
+    }
+
     /**
      * Given the current draw state, vertex layout, and hw support, will HW AA
      * lines be used (if line primitive type is drawn)? (Note that lines are
@@ -575,15 +611,14 @@
      * Additional Bits that can be specified in GrVertexLayout.
      */
     enum VertexLayoutBits {
-
+        /* vertices have colors */
         kColor_VertexLayoutBit              = 1 << (STAGE_BIT_CNT + 0),
-                                                //<! vertices have colors
+                                                
+        /* Use text vertices. (Pos and tex coords may be a different type for
+           text [GrGpuTextVertex vs GrPoint].) */
         kTextFormat_VertexLayoutBit         = 1 << (STAGE_BIT_CNT + 1),
-                                                //<! use text vertices. (Pos
-                                                //   and tex coords may be
-                                                //   a different type for
-                                                //   text [GrGpuTextVertex vs
-                                                //   GrPoint].)
+
+        kEdge_VertexLayoutBit               = 1 << (STAGE_BIT_CNT + 2),
         // for below assert
         kDummyVertexLayoutBit,
         kHighVertexLayoutBit = kDummyVertexLayoutBit - 1
@@ -1030,6 +1065,13 @@
      */
     static int VertexColorOffset(GrVertexLayout vertexLayout);
 
+     /**
+      * Helper function to compute the offset of the edge pts in a vertex
+      * @return offset of edge in vertex layout or -1 if the
+      *         layout has no edge.
+      */
+     static int VertexEdgeOffset(GrVertexLayout vertexLayout);
+
     /**
      * Helper function to determine if vertex layout contains explicit texture
      * coordinates of some index.
@@ -1069,7 +1111,8 @@
      */
     static int VertexSizeAndOffsetsByIdx(GrVertexLayout vertexLayout,
                                          int texCoordOffsetsByIdx[kMaxTexCoords],
-                                         int *colorOffset);
+                                         int *colorOffset,
+                                         int* edgeOffset);
 
     /**
      * Helper function to compute the size of each vertex and the offsets of
@@ -1086,7 +1129,8 @@
      */
     static int VertexSizeAndOffsetsByStage(GrVertexLayout vertexLayout,
                                            int texCoordOffsetsByStage[kNumStages],
-                                           int *colorOffset);
+                                           int *colorOffset,
+                                           int* edgeOffset);
 
     /**
      * Accessing positions, texture coords, or colors, of a vertex within an
@@ -1185,7 +1229,7 @@
     
     // given a vertex layout and a draw state, will a stage be used?
     static bool StageWillBeUsed(int stage, GrVertexLayout layout, 
-                         const DrState& state) {
+                                const DrState& state) {
         return NULL != state.fTextures[stage] && VertexUsesStage(stage, layout);
     }
 
@@ -1194,6 +1238,14 @@
                                fCurrDrawState);
     }
 
+    StageBitfield enabledStages() const {
+        StageBitfield mask = 0;
+        for (int s = 0; s < kNumStages; ++s) {
+            mask |= this->isStageEnabled(s) ? 1 : 0;
+        }
+        return mask;
+    }
+
     // Helpers for GrDrawTarget subclasses that won't have private access to
     // SavedDrawState but need to peek at the state values.
     static DrState& accessSavedDrawState(SavedDrawState& sds)
diff --git a/gpu/src/GrGLProgram.cpp b/gpu/src/GrGLProgram.cpp
index ed3cd12..bfe6629 100644
--- a/gpu/src/GrGLProgram.cpp
+++ b/gpu/src/GrGLProgram.cpp
@@ -44,6 +44,7 @@
 
 #define POS_ATTR_NAME "aPosition"
 #define COL_ATTR_NAME "aColor"
+#define EDGE_ATTR_NAME "aEdge"
 #define COL_UNI_NAME "uColor"
 #define EDGES_UNI_NAME "uEdges"
 #define COL_FILTER_UNI_NAME "uColorFilter"
@@ -539,6 +540,25 @@
                 segments.fFSCode.append(";\n");
             }
             inCoverage = "edgeAlpha";
+        } else  if (layout & GrDrawTarget::kEdge_VertexLayoutBit) {
+            segments.fVSAttrs.append("attribute vec4 " EDGE_ATTR_NAME ";\n");
+            segments.fVaryings.append("varying vec4 vEdge;\n");
+            segments.fVSCode.append("\tvEdge = " EDGE_ATTR_NAME ";\n");
+            if (GrDrawTarget::kHairLine_EdgeType == fProgramDesc.fVertexEdgeType) {
+                segments.fFSCode.append("\tfloat edgeAlpha = abs(dot(vec3(gl_FragCoord.xy,1), vEdge.xyz));\n");
+            } else {
+                GrAssert(GrDrawTarget::kHairQuad_EdgeType == fProgramDesc.fVertexEdgeType);
+                // for now we know we're not in perspective, so we could compute this
+                // per-quadratic rather than per pixel
+                segments.fFSCode.append("\tvec2 duvdx = dFdx(vEdge.xy);\n");
+                segments.fFSCode.append("\tvec2 duvdy = dFdy(vEdge.xy);\n");
+                segments.fFSCode.append("\tfloat dfdx = 2.0*vEdge.x*duvdx.x - duvdx.y;\n");
+                segments.fFSCode.append("\tfloat dfdy = 2.0*vEdge.x*duvdy.x - duvdy.y;\n");
+                segments.fFSCode.append("\tfloat edgeAlpha = (vEdge.x*vEdge.x - vEdge.y);\n");
+                segments.fFSCode.append("\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / (dfdx*dfdx + dfdy*dfdy));\n");
+            }
+            segments.fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
+            inCoverage = "edgeAlpha";
         }
 
         GrStringBuilder outCoverage;
@@ -839,8 +859,10 @@
         }
     }
 
-    GR_GL_CALL(gl, BindAttribLocation(progID, ColorAttributeIdx(), 
+    GR_GL_CALL(gl, BindAttribLocation(progID, ColorAttributeIdx(),
                                       COL_ATTR_NAME));
+    GR_GL_CALL(gl, BindAttribLocation(progID, EdgeAttributeIdx(),
+                                      EDGE_ATTR_NAME));
 
     GR_GL_CALL(gl, LinkProgram(progID));
 
diff --git a/gpu/src/GrGLProgram.h b/gpu/src/GrGLProgram.h
index b89653f..78d25b5 100644
--- a/gpu/src/GrGLProgram.h
+++ b/gpu/src/GrGLProgram.h
@@ -65,6 +65,8 @@
     static int PositionAttributeIdx() { return 0; }
     static int TexCoordAttributeIdx(int tcIdx) { return 1 + tcIdx; }
     static int ColorAttributeIdx() { return 1 + GrDrawTarget::kMaxTexCoords; }
+    static int EdgeAttributeIdx() { return 2 + GrDrawTarget::kMaxTexCoords; }
+
     static int ViewMatrixAttributeIdx() {
         return 2 + GrDrawTarget::kMaxTexCoords;
     }
@@ -150,6 +152,8 @@
             kDualSrcOutputCnt
         };
 
+        GrDrawTarget::VertexEdgeType fVertexEdgeType;
+
         // stripped of bits that don't affect prog generation
         GrVertexLayout fVertexLayout;
 
diff --git a/gpu/src/GrGpuGLFixed.cpp b/gpu/src/GrGpuGLFixed.cpp
index 69bf94f..2c97e69 100644
--- a/gpu/src/GrGpuGLFixed.cpp
+++ b/gpu/src/GrGpuGLFixed.cpp
@@ -276,15 +276,22 @@
 
     int newColorOffset;
     int newTexCoordOffsets[kNumStages];
+    int newEdgeOffset;
 
     GrGLsizei newStride = VertexSizeAndOffsetsByStage(this->getGeomSrc().fVertexLayout,
                                                       newTexCoordOffsets,
-                                                      &newColorOffset);
+                                                      &newColorOffset,
+                                                      &newEdgeOffset);
+    GrAssert(-1 == newEdgeOffset); // not supported by fixed pipe
+
     int oldColorOffset;
     int oldTexCoordOffsets[kNumStages];
+    int oldEdgeOffset;
     GrGLsizei oldStride = VertexSizeAndOffsetsByStage(fHWGeometryState.fVertexLayout,
                                                       oldTexCoordOffsets,
-                                                      &oldColorOffset);
+                                                      &oldColorOffset,
+                                                      &oldEdgeOffset);
+    GrAssert(-1 == oldEdgeOffset);
 
     bool indexed = NULL != startIndex;
 
diff --git a/gpu/src/GrGpuGLShaders.cpp b/gpu/src/GrGpuGLShaders.cpp
index 05b1e88..a9a3953 100644
--- a/gpu/src/GrGpuGLShaders.cpp
+++ b/gpu/src/GrGpuGLShaders.cpp
@@ -185,11 +185,24 @@
         pdesc.fFirstCoverageStage = idx;
 
         bool edgeAA = random.nextF() > .5f;
-        if (edgeAA) {

-            pdesc.fEdgeAANumEdges = random.nextF() * this->getMaxEdges() + 1;

-            pdesc.fEdgeAAConcave = random.nextF() > .5f;

-        } else {

-            pdesc.fEdgeAANumEdges = 0;

+        if (edgeAA) {
+            bool vertexEdgeAA = random.nextF() > .5f;
+            if (vertexEdgeAA) {
+                pdesc.fVertexLayout |= GrDrawTarget::kEdge_VertexLayoutBit;
+                if (this->supportsShaderDerivatives()) {
+                    pdesc.fVertexEdgeType = random.nextF() > 0.5f ?
+                                                        kHairQuad_EdgeType :
+                                                        kHairLine_EdgeType;
+                } else {
+                    pdesc.fVertexEdgeType = kHairLine_EdgeType;
+                }
+                pdesc.fEdgeAANumEdges = 0;
+            } else {
+                pdesc.fEdgeAANumEdges = random.nextF() * this->getMaxEdges() + 1;
+                pdesc.fEdgeAAConcave = random.nextF() > .5f;
+            }
+        } else {
+            pdesc.fEdgeAANumEdges = 0;
         }
 
         if (fDualSourceBlendingSupport) {
@@ -307,6 +320,7 @@
     fHWGeometryState.fVertexLayout = 0;
     fHWGeometryState.fVertexOffset = ~0;
     GL_CALL(DisableVertexAttribArray(GrGLProgram::ColorAttributeIdx()));
+    GL_CALL(DisableVertexAttribArray(GrGLProgram::EdgeAttributeIdx()));
     for (int t = 0; t < kMaxTexCoords; ++t) {
         GL_CALL(DisableVertexAttribArray(GrGLProgram::TexCoordAttributeIdx(t)));
     }
@@ -641,17 +655,22 @@
 
     int newColorOffset;
     int newTexCoordOffsets[kMaxTexCoords];
+    int newEdgeOffset;
 
     GrGLsizei newStride = VertexSizeAndOffsetsByIdx(
                                             this->getGeomSrc().fVertexLayout,
                                             newTexCoordOffsets,
-                                            &newColorOffset);
+                                            &newColorOffset,
+                                            &newEdgeOffset);
     int oldColorOffset;
     int oldTexCoordOffsets[kMaxTexCoords];
+    int oldEdgeOffset;
+
     GrGLsizei oldStride = VertexSizeAndOffsetsByIdx(
                                             fHWGeometryState.fVertexLayout,
                                             oldTexCoordOffsets,
-                                            &oldColorOffset);
+                                            &oldColorOffset,
+                                            &oldEdgeOffset);
     bool indexed = NULL != startIndex;
 
     int extraVertexOffset;
@@ -727,6 +746,21 @@
         GL_CALL(DisableVertexAttribArray(GrGLProgram::ColorAttributeIdx()));
     }
 
+    if (newEdgeOffset > 0) {
+        GrGLvoid* edgeOffset = (int8_t*)(vertexOffset + newEdgeOffset);
+        int idx = GrGLProgram::EdgeAttributeIdx();
+        if (oldEdgeOffset <= 0) {
+            GL_CALL(EnableVertexAttribArray(idx));
+            GL_CALL(VertexAttribPointer(idx, 4, scalarType,
+                                        false, newStride, edgeOffset));
+        } else if (allOffsetsChange || newEdgeOffset != oldEdgeOffset) {
+            GL_CALL(VertexAttribPointer(idx, 4, scalarType,
+                                        false, newStride, edgeOffset));
+        }
+    } else if (oldEdgeOffset > 0) {
+        GL_CALL(DisableVertexAttribArray(GrGLProgram::EdgeAttributeIdx()));
+    }
+
     fHWGeometryState.fVertexLayout = this->getGeomSrc().fVertexLayout;
     fHWGeometryState.fArrayPtrsDirty = false;
 }
@@ -734,6 +768,11 @@
 void GrGpuGLShaders::buildProgram(GrPrimitiveType type) {
     ProgramDesc& desc = fCurrentProgram.fProgramDesc;
 
+    // The descriptor is used as a cache key. Thus when a field of the
+    // descriptor will not affect program generation (because of the vertex
+    // layout in use or other descriptor field settings) it should be set
+    // to a canonical value to avoid duplicate programs with different keys.
+
     // Must initialize all fields or cache will have false negatives!
     desc.fVertexLayout = this->getGeomSrc().fVertexLayout;
 
@@ -767,6 +806,13 @@
 
     int lastEnabledStage = -1;
 
+    if (desc.fVertexLayout & GrDrawTarget::kEdge_VertexLayoutBit) {
+        desc.fVertexEdgeType = fCurrDrawState.fVertexEdgeType;
+    } else {
+        // use canonical value when not set to avoid cache misses
+        desc.fVertexEdgeType = GrDrawTarget::kHairLine_EdgeType;
+    }
+
     for (int s = 0; s < kNumStages; ++s) {
         StageDesc& stage = desc.fStages[s];
 
diff --git a/gpu/src/GrPathRenderer.h b/gpu/src/GrPathRenderer.h
index 25dfafb..4694de5 100644
--- a/gpu/src/GrPathRenderer.h
+++ b/gpu/src/GrPathRenderer.h
@@ -52,13 +52,28 @@
     /**
      * Returns true if this path renderer is able to render the path.
      * Returning false allows the caller to fallback to another path renderer.
+     * When searching for a path renderer capable of rendering a path this
+     * function is called. The path renderer can examine the path, fill rule,
+     * and draw settings that will be used (via the targetparameter). If "true"
+     * is reported note that the caller is permitted to make modifications to
+     * the following settings of the target between the calls to canDrawPath and
+     * drawPath:
+     *     1. view matrix: The matrix at drawPath time may have additional scale
+     *                     scale and translation applied
+     *     2. render target: The render target may change between canDrawPath 
+     *                       and drawPath.
+     * The GrPathRenderer subclass's decision about whether to return true
+     * or false in its implementation of this function should consider these
+     * possible state changes.
      *
      * @param path      The path to draw
      * @param fill      The fill rule to use
      *
      * @return  true if the path can be drawn by this object, false otherwise.
      */
-    virtual bool canDrawPath(const SkPath& path, GrPathFill fill) const = 0;
+    virtual bool canDrawPath(const GrDrawTarget* target,
+                             const SkPath& path,
+                             GrPathFill fill) const = 0;
 
     /**
      * For complex clips Gr uses the stencil buffer. The path renderer must be
diff --git a/gpu/src/GrPathRendererChain.cpp b/gpu/src/GrPathRendererChain.cpp
index 4c76720..dc09d43 100644
--- a/gpu/src/GrPathRendererChain.cpp
+++ b/gpu/src/GrPathRendererChain.cpp
@@ -43,7 +43,7 @@
                     !target->getRenderTarget()->isMultisampled();
     GrPathRenderer* nonAAPR = NULL;
     for (int i = 0; i < fChain.count(); ++i) {
-        if (fChain[i]->canDrawPath(path, fill)) {
+        if (fChain[i]->canDrawPath(target, path, fill)) {
             if (!preferAA || fChain[i]->supportsAA(target, path, fill)) {
                 return fChain[i];
             } else {
diff --git a/gpu/src/GrTesselatedPathRenderer.cpp b/gpu/src/GrTesselatedPathRenderer.cpp
index aca6ec2..15e3cc0 100644
--- a/gpu/src/GrTesselatedPathRenderer.cpp
+++ b/gpu/src/GrTesselatedPathRenderer.cpp
@@ -599,7 +599,8 @@
     }
 }
 
-bool GrTesselatedPathRenderer::canDrawPath(const SkPath& path,
+bool GrTesselatedPathRenderer::canDrawPath(const GrDrawTarget* target,
+                                           const SkPath& path,
                                            GrPathFill fill) const {
     return kHairLine_PathFill != fill;
 }
diff --git a/gpu/src/GrTesselatedPathRenderer.h b/gpu/src/GrTesselatedPathRenderer.h
index c815f50..d5bf157 100644
--- a/gpu/src/GrTesselatedPathRenderer.h
+++ b/gpu/src/GrTesselatedPathRenderer.h
@@ -17,7 +17,8 @@
     GrTesselatedPathRenderer();
 
     virtual void drawPath(GrDrawTarget::StageBitfield stages);
-    virtual bool canDrawPath(const GrPath& path,
+    virtual bool canDrawPath(const GrDrawTarget* target,
+                             const GrPath& path,
                              GrPathFill fill) const;
 
     virtual bool requiresStencilPass(const GrDrawTarget* target,