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/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