Add stroked ovals and CircleEdgeEffect.

Adds some optimizations to the circle and ellipse shaders, static effect 
instances for their GrEffects, and some minor changes to GrDrawState::setEffect
to make GrEffect setup faster.


git-svn-id: http://skia.googlecode.com/svn/trunk@8238 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index d964f20..6b039d0 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -122,6 +122,8 @@
       '<(skia_src_path)/gpu/gr_unittests.cpp',
 
       '<(skia_src_path)/gpu/effects/Gr1DKernelEffect.h',
+      '<(skia_src_path)/gpu/effects/GrCircleEdgeEffect.cpp',
+      '<(skia_src_path)/gpu/effects/GrCircleEdgeEffect.h',
       '<(skia_src_path)/gpu/effects/GrConfigConversionEffect.cpp',
       '<(skia_src_path)/gpu/effects/GrConfigConversionEffect.h',
       '<(skia_src_path)/gpu/effects/GrConvolutionEffect.cpp',
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index c5572ac..79edd0b 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -894,7 +894,8 @@
     void internalDrawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke);
 
     void internalDrawOval(const GrPaint& paint, const GrRect& oval, const SkStrokeRec& stroke);
-    bool canDrawOval(const GrPaint& paint, const GrRect& oval, const SkStrokeRec& stroke) const;
+    void internalDrawCircle(const GrPaint& paint, const GrRect& circle, const SkStrokeRec& stroke);
+    bool canDrawOval(const GrPaint& paint, const GrRect& oval, bool* isCircle) const;
 
     GrTexture* createResizedTexture(const GrTextureDesc& desc,
                                     const GrCacheID& cacheID,
diff --git a/include/gpu/GrEffectStage.h b/include/gpu/GrEffectStage.h
index ff118c3..cd9a091 100644
--- a/include/gpu/GrEffectStage.h
+++ b/include/gpu/GrEffectStage.h
@@ -116,7 +116,8 @@
                 stage.fEffectRef->get()->incDeferredRefCounts();
                 fEffect = stage.fEffectRef->get();
                 fCoordChangeMatrix = stage.fCoordChangeMatrix;
-                fVertexAttribIndices = stage.fVertexAttribIndices;
+                fVertexAttribIndices[0] = stage.fVertexAttribIndices[0];
+                fVertexAttribIndices[1] = stage.fVertexAttribIndices[1];
             }
             SkDEBUGCODE(fInitialized = true;)
         }
@@ -127,7 +128,8 @@
             if (NULL != fEffect) {
                 stage->fEffectRef = GrEffect::CreateEffectRef(fEffect);
                 stage->fCoordChangeMatrix = fCoordChangeMatrix;
-                stage->fVertexAttribIndices = fVertexAttribIndices;
+                stage->fVertexAttribIndices[0] = fVertexAttribIndices[0];
+                stage->fVertexAttribIndices[1] = fVertexAttribIndices[1];
             } else {
                 stage->fEffectRef = NULL;
             }
@@ -141,7 +143,8 @@
                 return false;
             }
 
-            if (fVertexAttribIndices != stage.fVertexAttribIndices) {
+            if (fVertexAttribIndices[0] != stage.fVertexAttribIndices[0]
+                || fVertexAttribIndices[1] != stage.fVertexAttribIndices[1]) {
                 return false;
             }
 
@@ -155,7 +158,8 @@
     private:
         const GrEffect*               fEffect;
         SkMatrix                      fCoordChangeMatrix;
-        SkSTArray<GrEffect::kMaxVertexAttribs, int, true> fVertexAttribIndices;
+        int                           fVertexAttribIndices[2];
+        int                           fVertexAttribCount;
         SkDEBUGCODE(bool fInitialized;)
     };
 
@@ -169,28 +173,37 @@
         GrSafeSetNull(fEffectRef);
     }
 
-    const GrEffectRef* setEffect(const GrEffectRef* EffectRef, const int* attribIndices = NULL) {
+    const GrEffectRef* setEffect(const GrEffectRef* EffectRef) {
         GrAssert(0 == fSavedCoordChangeCnt);
         GrSafeAssign(fEffectRef, EffectRef);
         fCoordChangeMatrix.reset();
 
-        fVertexAttribIndices.reset();
-        int numVertexAttribs = (EffectRef == NULL) ? 0 : EffectRef->get()->numVertexAttribs();
-        GrAssert(numVertexAttribs == 0 || attribIndices != NULL);
-        fVertexAttribIndices.push_back_n(numVertexAttribs, attribIndices);
-
+        fVertexAttribIndices[0] = -1;
+        fVertexAttribIndices[1] = -1;
+        
         return EffectRef;
     }
 
+    const GrEffectRef* setEffect(const GrEffectRef* EffectRef, int attr0, int attr1 = -1) {
+        GrAssert(0 == fSavedCoordChangeCnt);
+        GrSafeAssign(fEffectRef, EffectRef);
+        fCoordChangeMatrix.reset();
+        
+        fVertexAttribIndices[0] = attr0;
+        fVertexAttribIndices[1] = attr1;
+        
+        return EffectRef;
+    }
+    
     const GrEffectRef* getEffect() const { return fEffectRef; }
 
-    const int* getVertexAttribIndices() const { return fVertexAttribIndices.begin(); }
-    int getVertexAttribIndexCount() const { return fVertexAttribIndices.count(); }
+    const int* getVertexAttribIndices() const { return fVertexAttribIndices; }
+    int getVertexAttribIndexCount() const { return fEffectRef->get()->numVertexAttribs(); }
 
 private:
     SkMatrix                fCoordChangeMatrix;
     const GrEffectRef*      fEffectRef;
-    SkSTArray<2, int, true> fVertexAttribIndices;
+    int                     fVertexAttribIndices[2];
 
     GR_DEBUGCODE(mutable int fSavedCoordChangeCnt;)
 };
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index b719ec5..cf74c8e 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -12,6 +12,7 @@
 #include "effects/GrConvolutionEffect.h"
 #include "effects/GrSingleTextureEffect.h"
 #include "effects/GrConfigConversionEffect.h"
+#include "effects/GrCircleEdgeEffect.h"
 #include "effects/GrEllipseEdgeEffect.h"
 
 #include "GrBufferAllocPool.h"
@@ -36,7 +37,8 @@
 // It can be useful to set this to false to test whether a bug is caused by using the
 // InOrderDrawBuffer, to compare performance of using/not using InOrderDrawBuffer, or to make
 // debugging simpler.
-SK_CONF_DECLARE(bool, c_Defer, "gpu.deferContext", true, "Defers rendering in GrContext via GrInOrderDrawBuffer.");
+SK_CONF_DECLARE(bool, c_Defer, "gpu.deferContext", true,
+                "Defers rendering in GrContext via GrInOrderDrawBuffer.");
 
 #define BUFFERED_DRAW (c_Defer ? kYes_BufferedDraw : kNo_BufferedDraw)
 
@@ -358,7 +360,8 @@
             {kVec2f_GrVertexAttribType, 0},
             {kVec2f_GrVertexAttribType, sizeof(GrPoint)}
         };
-        static const GrAttribBindings kAttribBindings = GrDrawState::ExplicitTexCoordAttribBindingsBit(0);
+        static const GrAttribBindings kAttribBindings =
+            GrDrawState::ExplicitTexCoordAttribBindingsBit(0);
         drawState->setAttribBindings(kAttribBindings);
         drawState->setVertexAttribs(kVertexAttribs, SK_ARRAY_COUNT(kVertexAttribs));
         drawState->setAttribIndex(GrDrawState::kPosition_AttribIndex, 0);
@@ -389,7 +392,8 @@
 
         size_t stretchedRowBytes = rtDesc.fWidth * bpp;
 
-        SkDEBUGCODE(GrTexture* texture = )fGpu->createTexture(rtDesc, stretchedPixels.get(), stretchedRowBytes);
+        SkDEBUGCODE(GrTexture* texture = )fGpu->createTexture(rtDesc, stretchedPixels.get(),
+                                                              stretchedRowBytes);
         GrAssert(NULL != texture);
     }
 
@@ -999,6 +1003,15 @@
     SkScalar fInnerRadius;
 };
 
+struct EllipseVertex {
+    GrPoint fPos;
+    GrPoint fCenter;
+    SkScalar fOuterXRadius;
+    SkScalar fOuterXYRatio;
+    SkScalar fInnerXRadius;
+    SkScalar fInnerXYRatio;
+};
+
 inline bool circleStaysCircle(const SkMatrix& m) {
     return m.isSimilarity();
 }
@@ -1009,64 +1022,186 @@
                          const GrRect& oval,
                          const SkStrokeRec& stroke) {
 
-    if (!canDrawOval(paint, oval, stroke)) {
+    bool isCircle;
+    if (!canDrawOval(paint, oval, &isCircle)) {
         SkPath path;
         path.addOval(oval);
         this->drawPath(paint, path, stroke);
         return;
     }
 
-    internalDrawOval(paint, oval, stroke);
+    if (isCircle) {
+        this->internalDrawCircle(paint, oval, stroke);
+    } else {
+        this->internalDrawOval(paint, oval, stroke);
+    }
 }
 
-bool GrContext::canDrawOval(const GrPaint& paint, const GrRect& oval, const SkStrokeRec& stroke) const {
+bool GrContext::canDrawOval(const GrPaint& paint, const GrRect& oval, bool* isCircle) const {
+    GrAssert(isCircle != NULL);
 
     if (!paint.isAntiAlias()) {
         return false;
     }
 
-    // we can draw circles in any style
-    bool isCircle = SkScalarNearlyEqual(oval.width(), oval.height())
-                    && circleStaysCircle(this->getMatrix());
-    // and for now, axis-aligned ellipses only with fill or stroke-and-fill
-    SkStrokeRec::Style style = stroke.getStyle();
-    bool isStroke = (style == SkStrokeRec::kStroke_Style || style == SkStrokeRec::kHairline_Style);
-    bool isFilledAxisAlignedEllipse = this->getMatrix().rectStaysRect() && !isStroke;
+    // we can draw circles 
+    *isCircle = SkScalarNearlyEqual(oval.width(), oval.height())
+                && circleStaysCircle(this->getMatrix());
+    // and axis-aligned ellipses only
+    bool isAxisAlignedEllipse = this->getMatrix().rectStaysRect();
 
-    return isCircle || isFilledAxisAlignedEllipse;
+    return *isCircle || isAxisAlignedEllipse;
 }
 
 void GrContext::internalDrawOval(const GrPaint& paint,
                                  const GrRect& oval,
                                  const SkStrokeRec& stroke) {
-
-    SkScalar xRadius = SkScalarHalf(oval.width());
-    SkScalar yRadius = SkScalarHalf(oval.height());
-
-    SkScalar strokeWidth = stroke.getWidth();
-    SkStrokeRec::Style style = stroke.getStyle();
-
-    bool isCircle = SkScalarNearlyEqual(xRadius, yRadius) && circleStaysCircle(this->getMatrix());
 #ifdef SK_DEBUG
     {
         // we should have checked for this previously
-        bool isStroke = (style == SkStrokeRec::kStroke_Style || style == SkStrokeRec::kHairline_Style);
-        bool isFilledAxisAlignedEllipse = this->getMatrix().rectStaysRect() && !isStroke;
-        SkASSERT(paint.isAntiAlias() && (isCircle || isFilledAxisAlignedEllipse));
+        bool isAxisAlignedEllipse = this->getMatrix().rectStaysRect();
+        SkASSERT(paint.isAntiAlias() && isAxisAlignedEllipse);
     }
 #endif
-
     GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW);
 
     GrDrawState* drawState = target->drawState();
     GrDrawState::AutoStageDisable atr(fDrawState);
-    const SkMatrix vm = drawState->getViewMatrix();
 
     const GrRenderTarget* rt = drawState->getRenderTarget();
     if (NULL == rt) {
         return;
     }
 
+    const SkMatrix vm = drawState->getViewMatrix();
+
+    GrDrawState::AutoDeviceCoordDraw adcd(drawState);
+    if (!adcd.succeeded()) {
+        return;
+    }
+
+    // position + edge
+    static const GrVertexAttrib kVertexAttribs[] = {
+        {kVec2f_GrVertexAttribType, 0},
+        {kVec2f_GrVertexAttribType, sizeof(GrPoint)},
+        {kVec4f_GrVertexAttribType, 2*sizeof(GrPoint)}
+    };
+    drawState->setVertexAttribs(kVertexAttribs, SK_ARRAY_COUNT(kVertexAttribs));
+    drawState->setAttribIndex(GrDrawState::kPosition_AttribIndex, 0);
+    GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize());
+
+    GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
+    if (!geo.succeeded()) {
+        GrPrintf("Failed to get space for vertices!\n");
+        return;
+    }
+
+    EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices());
+
+    GrPoint center = GrPoint::Make(oval.centerX(), oval.centerY());
+    vm.mapPoints(&center, 1);
+
+    SkStrokeRec::Style style = stroke.getStyle();
+    bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style);
+    enum {
+        // the edge effects share this stage with glyph rendering
+        // (kGlyphMaskStage in GrTextContext) && SW path rendering
+        // (kPathMaskStage in GrSWMaskHelper)
+        kEdgeEffectStage = GrPaint::kTotalStages,
+    };
+    drawState->setAttribBindings(GrDrawState::kDefault_AttribBindings);
+
+    GrEffectRef* effect = GrEllipseEdgeEffect::Create(isStroked);
+    static const int kEllipseCenterAttrIndex = 1;
+    static const int kEllipseEdgeAttrIndex = 2;
+    drawState->setEffect(kEdgeEffectStage, effect,
+                         kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref();
+
+    SkRect xformedRect;
+    vm.mapRect(&xformedRect, oval);
+
+    SkScalar xRadius = SkScalarHalf(xformedRect.width());
+    SkScalar yRadius = SkScalarHalf(xformedRect.height());
+    SkScalar innerXRadius = 0.0f;
+    SkScalar innerRatio = 1.0f;
+
+    if (SkStrokeRec::kFill_Style != style) {
+        SkScalar strokeWidth = stroke.getWidth();
+
+        // do (potentially) anisotropic mapping
+        SkVector scaledStroke;
+        scaledStroke.set(strokeWidth, strokeWidth);
+        vm.mapVectors(&scaledStroke, 1);
+
+        if (SkScalarNearlyZero(scaledStroke.length())) {
+            scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
+        } else {
+            scaledStroke.scale(0.5f);
+        }
+
+        // this is legit only if scale & translation (which should be the case at the moment)
+        if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style) {
+            SkScalar innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY);
+            if (innerYRadius > SK_ScalarNearlyZero) {
+                innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX);
+                innerRatio = innerXRadius/innerYRadius;
+            }
+        }
+        xRadius += scaledStroke.fX;
+        yRadius += scaledStroke.fY;
+    }
+
+    SkScalar outerRatio = SkScalarDiv(xRadius, yRadius);
+
+    for (int i = 0; i < 4; ++i) {
+        verts[i].fCenter = center;
+        verts[i].fOuterXRadius = xRadius + 0.5f;
+        verts[i].fOuterXYRatio = outerRatio;
+        verts[i].fInnerXRadius = innerXRadius - 0.5f;
+        verts[i].fInnerXYRatio = innerRatio;
+    }
+
+    SkScalar L = -xRadius;
+    SkScalar R = +xRadius;
+    SkScalar T = -yRadius;
+    SkScalar B = +yRadius;
+
+    // We've extended the outer x radius out half a pixel to antialias.
+    // Expand the drawn rect here so all the pixels will be captured.
+    L += center.fX - SK_ScalarHalf;
+    R += center.fX + SK_ScalarHalf;
+    T += center.fY - SK_ScalarHalf;
+    B += center.fY + SK_ScalarHalf;
+
+    verts[0].fPos = SkPoint::Make(L, T);
+    verts[1].fPos = SkPoint::Make(R, T);
+    verts[2].fPos = SkPoint::Make(L, B);
+    verts[3].fPos = SkPoint::Make(R, B);
+
+    target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4);
+}
+
+void GrContext::internalDrawCircle(const GrPaint& paint,
+                                   const GrRect& circle,
+                                   const SkStrokeRec& stroke) {
+
+    SkScalar radius = SkScalarHalf(circle.width());
+
+    SkScalar strokeWidth = stroke.getWidth();
+    SkStrokeRec::Style style = stroke.getStyle();
+
+    GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW);
+
+    GrDrawState* drawState = target->drawState();
+    GrDrawState::AutoStageDisable atr(fDrawState);
+
+    const GrRenderTarget* rt = drawState->getRenderTarget();
+    if (NULL == rt) {
+        return;
+    }
+
+    const SkMatrix vm = drawState->getViewMatrix();
+
     GrDrawState::AutoDeviceCoordDraw adcd(drawState);
     if (!adcd.succeeded()) {
         return;
@@ -1089,95 +1224,54 @@
 
     CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
 
-    GrPoint center = GrPoint::Make(oval.centerX(), oval.centerY());
+    GrPoint center = GrPoint::Make(circle.centerX(), circle.centerY());
     vm.mapPoints(&center, 1);
 
-    SkScalar L;
-    SkScalar R;
-    SkScalar T;
-    SkScalar B;
+    bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style);
+    enum {
+        // the edge effects share this stage with glyph rendering
+        // (kGlyphMaskStage in GrTextContext) && SW path rendering
+        // (kPathMaskStage in GrSWMaskHelper)
+        kEdgeEffectStage = GrPaint::kTotalStages,
+    };
+    drawState->setAttribBindings(GrDrawState::kDefault_AttribBindings);
+    
+    GrEffectRef* effect = GrCircleEdgeEffect::Create(isStroked);
+    static const int kCircleEdgeAttrIndex = 1;
+    drawState->setEffect(kEdgeEffectStage, effect, kCircleEdgeAttrIndex)->unref();
 
-    if (isCircle) {
-        drawState->setAttribBindings(GrDrawState::kEdge_AttribBindingsBit);
-        drawState->setVertexEdgeType(GrDrawState::kCircle_EdgeType);
-        drawState->setAttribIndex(GrDrawState::kEdge_AttribIndex, 1);
+    radius = vm.mapRadius(radius);
 
-        xRadius = vm.mapRadius(xRadius);
-
-        SkScalar outerRadius = xRadius;
-        SkScalar innerRadius = 0;
-        SkScalar halfWidth = 0;
-        if (style != SkStrokeRec::kFill_Style) {
-            strokeWidth = vm.mapRadius(strokeWidth);
-            if (SkScalarNearlyZero(strokeWidth)) {
-                halfWidth = SK_ScalarHalf;
-            } else {
-                halfWidth = SkScalarHalf(strokeWidth);
-            }
-
-            outerRadius += halfWidth;
-            if (style == SkStrokeRec::kStroke_Style || style == SkStrokeRec::kHairline_Style) {
-                innerRadius = SkMaxScalar(0, xRadius - halfWidth);
-            }
+    SkScalar innerRadius = -2.0f;
+    SkScalar outerRadius = radius;
+    SkScalar halfWidth = 0;
+    if (style != SkStrokeRec::kFill_Style) {
+        strokeWidth = vm.mapRadius(strokeWidth);
+        if (SkScalarNearlyZero(strokeWidth)) {
+            halfWidth = SK_ScalarHalf;
+        } else {
+            halfWidth = SkScalarHalf(strokeWidth);
         }
 
-        for (int i = 0; i < 4; ++i) {
-            verts[i].fCenter = center;
-            verts[i].fOuterRadius = outerRadius;
-            verts[i].fInnerRadius = innerRadius;
+        outerRadius += halfWidth;
+        if (isStroked) {
+            innerRadius = SkMaxScalar(0, radius - halfWidth);
         }
-
-        L = -outerRadius;
-        R = +outerRadius;
-        T = -outerRadius;
-        B = +outerRadius;
-    } else {  // is axis-aligned ellipse
-        drawState->setAttribBindings(GrDrawState::kDefault_AttribBindings);
-
-        enum {
-            // the edge effects share this stage with glyph rendering
-            // (kGlyphMaskStage in GrTextContext) && SW path rendering
-            // (kPathMaskStage in GrSWMaskHelper)
-            kEdgeEffectStage = GrPaint::kTotalStages,
-        };
-        GrEffectRef* effect = GrEllipseEdgeEffect::Create();
-        static const int kEdgeAttrIndex = 1;
-        drawState->setEffect(kEdgeEffectStage, effect, &kEdgeAttrIndex)->unref();
-
-        SkRect xformedRect;
-        vm.mapRect(&xformedRect, oval);
-
-        xRadius = SkScalarHalf(xformedRect.width());
-        yRadius = SkScalarHalf(xformedRect.height());
-
-        if (style == SkStrokeRec::kStrokeAndFill_Style && strokeWidth > 0.0f) {
-            SkScalar halfWidth = SkScalarHalf(strokeWidth);
-            // do (potentially) anisotropic mapping
-            SkVector scaledStroke;
-            scaledStroke.set(halfWidth, halfWidth);
-            vm.mapVectors(&scaledStroke, 1);
-            // this is legit only if scale & translation (which should be the case at the moment)
-            xRadius += scaledStroke.fX;
-            yRadius += scaledStroke.fY;
-        }
-
-        SkScalar ratio = SkScalarDiv(xRadius, yRadius);
-
-        for (int i = 0; i < 4; ++i) {
-            verts[i].fCenter = center;
-            verts[i].fOuterRadius = xRadius;
-            verts[i].fInnerRadius = ratio;
-        }
-
-        L = -xRadius;
-        R = +xRadius;
-        T = -yRadius;
-        B = +yRadius;
     }
 
-    // The fragment shader will extend the radius out half a pixel
-    // to antialias. Expand the drawn rect here so all the pixels
-    // will be captured.
+    for (int i = 0; i < 4; ++i) {
+        verts[i].fCenter = center;
+        verts[i].fOuterRadius = outerRadius + 0.5f;
+        verts[i].fInnerRadius = innerRadius - 0.5f;
+    }
+
+    SkScalar L = -outerRadius;
+    SkScalar R = +outerRadius;
+    SkScalar T = -outerRadius;
+    SkScalar B = +outerRadius;
+
+    // We've extended the outer radius out half a pixel to antialias.
+    // Expand the drawn rect here so all the pixels will be captured.
     L += center.fX - SK_ScalarHalf;
     R += center.fX + SK_ScalarHalf;
     T += center.fY - SK_ScalarHalf;
@@ -1203,15 +1297,21 @@
     SkRect ovalRect;
     bool isOval = path.isOval(&ovalRect);
 
-    if (isOval && !path.isInverseFillType() && this->canDrawOval(paint, ovalRect, stroke)) {
-        this->drawOval(paint, ovalRect, stroke);
+    bool isCircle;
+    if (isOval && !path.isInverseFillType() && this->canDrawOval(paint, ovalRect, &isCircle)) {
+        if (isCircle) {
+            this->internalDrawCircle(paint, ovalRect, stroke);
+        } else {
+            this->internalDrawOval(paint, ovalRect, stroke);
+        }
         return;
     }
 
     this->internalDrawPath(paint, path, stroke);
 }
 
-void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) {
+void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path,
+                                 const SkStrokeRec& stroke) {
 
     // Note that below we may sw-rasterize the path into a scratch texture.
     // Scratch textures can be recycled after they are returned to the texture
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index 4e9747d..6ee6df5 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -485,9 +485,14 @@
     /// @name Effect Stages
     ////
 
+    const GrEffectRef* setEffect(int stageIdx, const GrEffectRef* effect) {
+        fStages[stageIdx].setEffect(effect);
+        return effect;
+    }
+    
     const GrEffectRef* setEffect(int stageIdx, const GrEffectRef* effect,
-                                 const int* indices = NULL) {
-        fStages[stageIdx].setEffect(effect, indices);
+                                 int attr0, int attr1 = -1) {
+        fStages[stageIdx].setEffect(effect, attr0, attr1);
         return effect;
     }
 
@@ -518,7 +523,7 @@
     }
 
     void disableStage(int stageIdx) {
-        this->setEffect(stageIdx, NULL, NULL);
+        this->setEffect(stageIdx, NULL);
     }
 
     /**
@@ -1023,9 +1028,6 @@
         /* Same as above but for hairline quadratics. Uses unsigned distance.
            Coverage is min(0, 1-distance). */
         kHairQuad_EdgeType,
-        /* Circle specified as center_x, center_y, outer_radius, inner_radius
-           all in window space (y-down). */
-        kCircle_EdgeType,
 
         kVertexEdgeTypeCnt
     };
diff --git a/src/gpu/effects/GrCircleEdgeEffect.cpp b/src/gpu/effects/GrCircleEdgeEffect.cpp
new file mode 100644
index 0000000..e430464
--- /dev/null
+++ b/src/gpu/effects/GrCircleEdgeEffect.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCircleEdgeEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrTexture.h"
+
+#include "SkRTConf.h"
+
+class GrGLCircleEdgeEffect : public GrGLEffect {
+public:
+    GrGLCircleEdgeEffect(const GrBackendEffectFactory& factory, const GrEffectRef&)
+    : INHERITED (factory) {}
+
+    virtual void emitCode(GrGLShaderBuilder* builder,
+                          const GrEffectStage& stage,
+                          EffectKey key,
+                          const char* vertexCoords,
+                          const char* outputColor,
+                          const char* inputColor,
+                          const TextureSamplerArray& samplers) SK_OVERRIDE {
+        const GrCircleEdgeEffect& effect = GetEffectFromStage<GrCircleEdgeEffect>(stage);
+        
+        const char *vsName, *fsName;
+        builder->addVarying(kVec4f_GrSLType, "CircleEdge", &vsName, &fsName);
+
+        const SkString* attrName =
+            builder->getEffectAttributeName(stage.getVertexAttribIndices()[0]);
+        builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
+
+        builder->fsCodeAppendf("\tfloat d = distance(%s.xy, %s.xy);\n",
+                               builder->fragmentPosition(), fsName);
+        builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.z - d, 0.0, 1.0);\n", fsName);
+        if (effect.isStroked()) {
+            builder->fsCodeAppendf("\tfloat innerAlpha = clamp(d - %s.w, 0.0, 1.0);\n", fsName);
+            builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n");
+        }
+        SkString modulate;
+        GrGLSLModulate4f(&modulate, inputColor, "edgeAlpha");
+        builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+    }
+
+    static inline EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
+        const GrCircleEdgeEffect& effect = GetEffectFromStage<GrCircleEdgeEffect>(stage);
+
+        return effect.isStroked() ? 0x1 : 0x0;
+    }
+
+    virtual void setData(const GrGLUniformManager& uman, const GrEffectStage& stage) SK_OVERRIDE {
+    }
+
+private:
+    typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrCircleEdgeEffect::GrCircleEdgeEffect(bool stroke) : GrEffect() {
+    this->addVertexAttrib(kVec4f_GrSLType);
+    fStroke = stroke;
+}
+
+void GrCircleEdgeEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+    *validFlags = 0;
+}
+
+const GrBackendEffectFactory& GrCircleEdgeEffect::getFactory() const {
+    return GrTBackendEffectFactory<GrCircleEdgeEffect>::getInstance();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrCircleEdgeEffect);
+
+GrEffectRef* GrCircleEdgeEffect::TestCreate(SkMWCRandom* random,
+                                            GrContext* context,
+                                            GrTexture* textures[]) {
+    return GrCircleEdgeEffect::Create(random->nextBool());
+}
diff --git a/src/gpu/effects/GrCircleEdgeEffect.h b/src/gpu/effects/GrCircleEdgeEffect.h
new file mode 100644
index 0000000..6bd892e
--- /dev/null
+++ b/src/gpu/effects/GrCircleEdgeEffect.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCircleEdgeEffect_DEFINED
+#define GrCircleEdgeEffect_DEFINED
+
+#include "GrEffect.h"
+
+class GrGLCircleEdgeEffect;
+
+/**
+ * The output of this effect is a modulation of the input color and coverage for a circle,
+ * specified as center_x, center_y, x_radius, inner radius and outer radius in window space 
+ * (y-down).
+ */
+
+class GrCircleEdgeEffect : public GrEffect {
+public:
+    static GrEffectRef* Create(bool stroke) {
+        // we go through this so we only have one copy of each effect (stroked/filled)
+        static SkAutoTUnref<GrEffectRef> gCircleStrokeEdgeEffectRef(
+                        CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrCircleEdgeEffect, (true)))));
+        static SkAutoTUnref<GrEffectRef> gCircleFillEdgeEffectRef(
+                        CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrCircleEdgeEffect, (false)))));
+        
+        if (stroke) {
+            gCircleStrokeEdgeEffectRef.get()->ref();
+            return gCircleStrokeEdgeEffectRef;
+        } else {
+            gCircleFillEdgeEffectRef.get()->ref();
+            return gCircleFillEdgeEffectRef;
+        }
+    }
+
+    virtual ~GrCircleEdgeEffect() {}
+
+    static const char* Name() { return "CircleEdge"; }
+
+    virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+    typedef GrGLCircleEdgeEffect GLEffect;
+
+    virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+    
+    inline bool isStroked() const { return fStroke; }
+
+private:
+    GrCircleEdgeEffect(bool stroke);
+
+    virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+        return true;
+    }
+    
+    bool fStroke;
+
+    GR_DECLARE_EFFECT_TEST;
+
+    typedef GrEffect INHERITED;
+};
+
+#endif
diff --git a/src/gpu/effects/GrEllipseEdgeEffect.cpp b/src/gpu/effects/GrEllipseEdgeEffect.cpp
index 18301e9..f4965f7 100644
--- a/src/gpu/effects/GrEllipseEdgeEffect.cpp
+++ b/src/gpu/effects/GrEllipseEdgeEffect.cpp
@@ -25,27 +25,49 @@
                           const char* outputColor,
                           const char* inputColor,
                           const TextureSamplerArray& samplers) SK_OVERRIDE {
-        const char *vsName, *fsName;
-        builder->addVarying(kVec4f_GrSLType, "EllipseEdge", &vsName, &fsName);
+        const GrEllipseEdgeEffect& effect = GetEffectFromStage<GrEllipseEdgeEffect>(stage);
+        
+        const char *vsCenterName, *fsCenterName;
+        const char *vsEdgeName, *fsEdgeName;
 
-        const SkString* attrName = builder->getEffectAttributeName(stage.getVertexAttribIndices()[0]);
-        builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
+        builder->addVarying(kVec2f_GrSLType, "EllipseCenter", &vsCenterName, &fsCenterName);
+        const SkString* attr0Name =
+            builder->getEffectAttributeName(stage.getVertexAttribIndices()[0]);
+        builder->vsCodeAppendf("\t%s = %s;\n", vsCenterName, attr0Name->c_str());
 
-        builder->fsCodeAppend("\tfloat edgeAlpha;\n");
+        builder->addVarying(kVec4f_GrSLType, "EllipseEdge", &vsEdgeName, &fsEdgeName);
+        const SkString* attr1Name =
+            builder->getEffectAttributeName(stage.getVertexAttribIndices()[1]);
+        builder->vsCodeAppendf("\t%s = %s;\n", vsEdgeName, attr1Name->c_str());
+
         // translate to origin
-        builder->fsCodeAppendf("\tvec2 offset = (%s.xy - %s.xy);\n", builder->fragmentPosition(), fsName);
+        builder->fsCodeAppendf("\tvec2 outerOffset = (%s.xy - %s.xy);\n",
+                               builder->fragmentPosition(), fsCenterName);
+        builder->fsCodeAppend("\tvec2 innerOffset = outerOffset;\n");
         // scale y by xRadius/yRadius
-        builder->fsCodeAppendf("\toffset.y *= %s.w;\n", fsName);
-        builder->fsCodeAppend("\tfloat d = length(offset);\n");
-        // compare length against xRadius
-        builder->fsCodeAppendf("\tedgeAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
+        builder->fsCodeAppendf("\touterOffset.y *= %s.y;\n", fsEdgeName);
+        builder->fsCodeAppend("\tfloat dOuter = length(outerOffset);\n");
+        // compare outer lengths against xOuterRadius
+        builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.x-dOuter, 0.0, 1.0);\n", fsEdgeName);
+        
+        if (effect.isStroked()) {
+            builder->fsCodeAppendf("\tinnerOffset.y *= %s.w;\n", fsEdgeName);
+            builder->fsCodeAppend("\tfloat dInner = length(innerOffset);\n");
+        
+            // compare inner lengths against xInnerRadius
+            builder->fsCodeAppendf("\tfloat innerAlpha = clamp(dInner-%s.z, 0.0, 1.0);\n", fsEdgeName);
+            builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n");
+        }
+        
         SkString modulate;
         GrGLSLModulate4f(&modulate, inputColor, "edgeAlpha");
         builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
     }
 
     static inline EffectKey GenKey(const GrEffectStage& stage, const GrGLCaps&) {
-        return 0;
+        const GrEllipseEdgeEffect& effect = GetEffectFromStage<GrEllipseEdgeEffect>(stage);
+
+        return effect.isStroked() ? 0x1 : 0x0;
     }
 
     virtual void setData(const GrGLUniformManager& uman, const GrEffectStage& stage) SK_OVERRIDE {
@@ -57,8 +79,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrEllipseEdgeEffect::GrEllipseEdgeEffect() : GrEffect() {
+GrEllipseEdgeEffect::GrEllipseEdgeEffect(bool stroke) : GrEffect() {
+    this->addVertexAttrib(kVec2f_GrSLType);
     this->addVertexAttrib(kVec4f_GrSLType);
+    
+    fStroke = stroke;
 }
 
 void GrEllipseEdgeEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
@@ -76,5 +101,5 @@
 GrEffectRef* GrEllipseEdgeEffect::TestCreate(SkMWCRandom* random,
                                                GrContext* context,
                                                GrTexture* textures[]) {
-    return GrEllipseEdgeEffect::Create();
+    return GrEllipseEdgeEffect::Create(random->nextBool());
 }
diff --git a/src/gpu/effects/GrEllipseEdgeEffect.h b/src/gpu/effects/GrEllipseEdgeEffect.h
index 31c6b09..5eada2c 100644
--- a/src/gpu/effects/GrEllipseEdgeEffect.h
+++ b/src/gpu/effects/GrEllipseEdgeEffect.h
@@ -19,10 +19,20 @@
 
 class GrEllipseEdgeEffect : public GrEffect {
 public:
-    static GrEffectRef* Create() {
-        // maybe only have one static copy?
-        AutoEffectUnref effect(SkNEW(GrEllipseEdgeEffect));
-        return CreateEffectRef(effect);
+    static GrEffectRef* Create(bool stroke) {
+        // we go through this so we only have one copy of each effect (stroked/filled)
+        static SkAutoTUnref<GrEffectRef> gEllipseStrokeEdgeEffectRef(
+                        CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrEllipseEdgeEffect, (true)))));
+        static SkAutoTUnref<GrEffectRef> gEllipseFillEdgeEffectRef(
+                        CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrEllipseEdgeEffect, (false)))));
+        
+        if (stroke) {
+            gEllipseStrokeEdgeEffectRef.get()->ref();
+            return gEllipseStrokeEdgeEffectRef;
+        } else {
+            gEllipseFillEdgeEffectRef.get()->ref();
+            return gEllipseFillEdgeEffectRef;
+        }
     }
 
     virtual ~GrEllipseEdgeEffect() {}
@@ -35,13 +45,17 @@
 
     virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
 
+    inline bool isStroked() const { return fStroke; }
+    
 private:
-    GrEllipseEdgeEffect();
+    GrEllipseEdgeEffect(bool stroke);
 
     virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE {
         return true;
     }
 
+    bool fStroke;
+
     GR_DECLARE_EFFECT_TEST;
 
     typedef GrEffect INHERITED;
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 850f0bb..7d6420e 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -23,7 +23,8 @@
 #define GL_CALL(X) GR_GL_CALL(fContext.interface(), X)
 #define GL_CALL_RET(R, X) GR_GL_CALL_RET(fContext.interface(), R, X)
 
-SK_CONF_DECLARE(bool, c_PrintShaders, "gpu.printShaders", false, "Print the source code for all shaders generated.");
+SK_CONF_DECLARE(bool, c_PrintShaders, "gpu.printShaders", false,
+                "Print the source code for all shaders generated.");
 
 #define TEX_ATTR_NAME "aTexCoord"
 #define COL_ATTR_NAME "aColor"
@@ -69,15 +70,16 @@
 
     desc->fEmitsPointSize = isPoints;
 
-    bool requiresAttributeColors = !skipColor &&
-                                   SkToBool(desc->fAttribBindings & GrDrawState::kColor_AttribBindingsBit);
-    bool requiresAttributeCoverage = !skipCoverage &&
-                                     SkToBool(desc->fAttribBindings & GrDrawState::kCoverage_AttribBindingsBit);
+    bool requiresAttributeColors =
+        !skipColor && SkToBool(desc->fAttribBindings & GrDrawState::kColor_AttribBindingsBit);
+    bool requiresAttributeCoverage =
+        !skipCoverage && SkToBool(desc->fAttribBindings & GrDrawState::kCoverage_AttribBindingsBit);
 
     // fColorInput/fCoverageInput records how colors are specified for the program So we strip the
     // bits from the bindings to avoid false negatives when searching for an existing program in the
     // cache.
-    desc->fAttribBindings &= ~(GrDrawState::kColor_AttribBindingsBit | GrDrawState::kCoverage_AttribBindingsBit);
+    desc->fAttribBindings &=
+        ~(GrDrawState::kColor_AttribBindingsBit | GrDrawState::kCoverage_AttribBindingsBit);
 
     desc->fColorFilterXfermode = skipColor ?
                                 SkXfermode::kDst_Mode :
@@ -423,7 +425,8 @@
         builder->vsCodeAppendf("\t%s = " EDGE_ATTR_NAME ";\n", vsName);
         switch (fDesc.fVertexEdgeType) {
         case GrDrawState::kHairLine_EdgeType:
-            builder->fsCodeAppendf("\tfloat edgeAlpha = abs(dot(vec3(%s.xy,1), %s.xyz));\n", builder->fragmentPosition(), fsName);
+            builder->fsCodeAppendf("\tfloat edgeAlpha = abs(dot(vec3(%s.xy,1), %s.xyz));\n",
+                                   builder->fragmentPosition(), fsName);
             builder->fsCodeAppendf("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
             break;
         case GrDrawState::kQuad_EdgeType:
@@ -433,7 +436,8 @@
             builder->fsCodeAppendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
             builder->fsCodeAppendf("\tif (%s.z > 0.0 && %s.w > 0.0) {\n", fsName, fsName);
             // today we know z and w are in device space. We could use derivatives
-            builder->fsCodeAppendf("\t\tedgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);\n", fsName, fsName);
+            builder->fsCodeAppendf("\t\tedgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);\n", fsName,
+                                   fsName);
             builder->fsCodeAppendf ("\t} else {\n");
             builder->fsCodeAppendf("\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
                                    "\t\t               2.0*%s.x*duvdy.x - duvdy.y);\n",
@@ -451,20 +455,14 @@
             builder->fsCodeAppendf("\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
                                    "\t               2.0*%s.x*duvdy.x - duvdy.y);\n",
                                    fsName, fsName);
-            builder->fsCodeAppendf("\tfloat edgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName, fsName);
+            builder->fsCodeAppendf("\tfloat edgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName,
+                                   fsName);
             builder->fsCodeAppend("\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
             builder->fsCodeAppend("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
             if (kES2_GrGLBinding == fContext.info().binding()) {
                 builder->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
             }
             break;
-        case GrDrawState::kCircle_EdgeType:
-            builder->fsCodeAppend("\tfloat edgeAlpha;\n");
-            builder->fsCodeAppendf("\tfloat d = distance(%s.xy, %s.xy);\n", builder->fragmentPosition(), fsName);
-            builder->fsCodeAppendf("\tfloat outerAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
-            builder->fsCodeAppendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName);
-            builder->fsCodeAppend("\tedgeAlpha = outerAlpha * innerAlpha;\n");
-            break;
         default:
             GrCrash("Unknown Edge Type!");
             break;
@@ -905,7 +903,9 @@
 
             // discard if coverage is zero
             if (fDesc.fDiscardIfOutsideEdge && !outCoverage.isEmpty()) {
-                builder.fsCodeAppendf("\tif (all(lessThanEqual(%s, vec4(0.0)))) {\n\t\tdiscard;\n\t}\n", outCoverage.c_str());
+                builder.fsCodeAppendf(
+                    "\tif (all(lessThanEqual(%s, vec4(0.0)))) {\n\t\tdiscard;\n\t}\n",
+                    outCoverage.c_str());
             }
         }
 
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index b56c788..d12aeb5 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -134,7 +134,7 @@
                 for (int i = 0; i < effect.get()->get()->numVertexAttribs(); ++i) {
                     attribIndices[i] = currAttribIndex++;
                 }
-                stages[s].setEffect(effect.get(), attribIndices);
+                stages[s].setEffect(effect.get(), attribIndices[0], attribIndices[1]);
             }
         }
         pdesc.setRandom(&random, this, stages);