Add GPU support for axis-aligned ovals:
- Add drawOval base function to SkDevice, and override in SkGpuDevice
- Move isSimilarityMatrix to SkMatrix (renamed to isSimilarity) and fixed up unit test
- Since both SkGpuDevice::drawOval() and GrContext::drawPath() can try to draw ovals, added GrContext::canDrawOval() and GrContext::internalDrawOval() to avoid duplicate code
- Hooked in axis-aligned oval fill shader
- Enabled GPU stroked circles
- Added stroked circle bench test

Review URL: https://codereview.appspot.com/7137050



git-svn-id: http://skia.googlecode.com/svn/trunk@7304 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index 390c532..ad9ca1d 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -122,7 +122,7 @@
         name->append("oval");
     }
     virtual void makePath(SkPath* path) SK_OVERRIDE {
-        SkRect r = { 10, 10, 20, 20 };
+        SkRect r = { 10, 10, 30, 20 };
         path->addOval(r);
     }
 private:
@@ -624,13 +624,14 @@
 class CirclesBench : public SkBenchmark {
 protected:
     SkString            fName;
+    Flags               fFlags;
 
     enum {
         N = SkBENCHLOOP(100)
     };
 public:
-    CirclesBench(void* param) : INHERITED(param) {
-        fName.printf("circles");
+    CirclesBench(void* param, Flags flags) : INHERITED(param), fFlags(flags) {
+        fName.printf("circles_%s", fFlags & kStroke_Flag ? "stroke" : "fill");
     }
 
 protected:
@@ -643,6 +644,9 @@
 
         paint.setColor(SK_ColorBLACK);
         paint.setAntiAlias(true);
+        if (fFlags & kStroke_Flag) {
+            paint.setStyle(SkPaint::kStroke_Style);
+        }
 
         SkRandom rand;
 
@@ -655,6 +659,10 @@
             r.fRight =  r.fLeft + 2 * radius;
             r.fBottom = r.fTop + 2 * radius;
 
+            if (fFlags & kStroke_Flag) {
+                paint.setStrokeWidth(rand.nextUScalar1() * 5.0f);
+            }
+
             SkPath temp;
 
             // mimic how Chrome does circles
@@ -671,6 +679,7 @@
     typedef SkBenchmark INHERITED;
 };
 
+
 // Chrome creates its own round rects with each corner possibly being different.
 // In its "zero radius" incarnation it creates degenerate round rects.
 // Note: PathTest::test_arb_round_rect_is_convex and
@@ -959,9 +968,12 @@
 static BenchRegistry gRegReverseAdd(FactReverseAdd);
 static BenchRegistry gRegReverseTo(FactReverseTo);
 
-static SkBenchmark* CirclesTest(void* p) { return new CirclesBench(p); }
+static SkBenchmark* CirclesTest(void* p) { return new CirclesBench(p, FLAGS00); }
 static BenchRegistry gRegCirclesTest(CirclesTest);
 
+static SkBenchmark* CirclesStrokeTest(void* p) { return new CirclesBench(p, FLAGS01); }
+static BenchRegistry gRegCirclesStrokeTest(CirclesStrokeTest);
+
 static SkBenchmark* ArbRoundRectTest(void* p) { return new ArbRoundRectBench(p, false); }
 static BenchRegistry gRegArbRoundRectTest(ArbRoundRectTest);
 
diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h
index 33be2f6..5c32f76 100644
--- a/include/core/SkDevice.h
+++ b/include/core/SkDevice.h
@@ -226,6 +226,8 @@
                             const SkPoint[], const SkPaint& paint);
     virtual void drawRect(const SkDraw&, const SkRect& r,
                           const SkPaint& paint);
+    virtual void drawOval(const SkDraw&, const SkRect& oval,
+                          const SkPaint& paint);
     /**
      *  If pathIsMutable, then the implementation is allowed to cast path to a
      *  non-const pointer and modify it in place (as an optimization). Canvas
diff --git a/include/core/SkDrawFilter.h b/include/core/SkDrawFilter.h
index 3944257..6a50ca7 100644
--- a/include/core/SkDrawFilter.h
+++ b/include/core/SkDrawFilter.h
@@ -31,6 +31,7 @@
         kLine_Type,
         kBitmap_Type,
         kRect_Type,
+        kOval_Type,
         kPath_Type,
         kText_Type,
     };
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index f9b72d7..87599d4 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -85,6 +85,11 @@
                         kPerspective_Mask);
     }
 
+    /** Returns true if the matrix contains only translation, rotation or uniform scale
+        Returns false if other transformation types are included or is degenerate
+     */
+    bool isSimilarity(SkScalar tol = SK_ScalarNearlyZero) const;
+
     enum {
         kMScaleX,
         kMSkewX,
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index b1e9dd6..399b372 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -442,15 +442,12 @@
      * Draws an oval.
      *
      * @param paint         describes how to color pixels.
-     * @param rect          the bounding rect of the oval.
-     * @param strokeWidth   if strokeWidth < 0, then the oval is filled, else
-     *                      the rect is stroked based on strokeWidth. If
-     *                      strokeWidth == 0, then the stroke is always a single
-     *                      pixel thick.
+     * @param oval          the bounding rect of the oval.
+     * @param stroke        the stroke information (width, style)
      */
     void drawOval(const GrPaint& paint,
-                  const GrRect& rect,
-                  SkScalar strokeWidth);
+                  const GrRect& oval,
+                  const SkStrokeRec& stroke);
 
     ///////////////////////////////////////////////////////////////////////////
     // Misc.
@@ -910,6 +907,9 @@
 
     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;
+
     GrTexture* createResizedTexture(const GrTextureDesc& desc,
                                     const GrCacheID& cacheID,
                                     void* srcData,
diff --git a/include/gpu/SkGpuDevice.h b/include/gpu/SkGpuDevice.h
index 45b0693..815376f 100644
--- a/include/gpu/SkGpuDevice.h
+++ b/include/gpu/SkGpuDevice.h
@@ -64,6 +64,8 @@
                             const SkPoint[], const SkPaint& paint) SK_OVERRIDE;
     virtual void drawRect(const SkDraw&, const SkRect& r,
                           const SkPaint& paint) SK_OVERRIDE;
+    virtual void drawOval(const SkDraw&, const SkRect& oval,
+                          const SkPaint& paint) SK_OVERRIDE;
     virtual void drawPath(const SkDraw&, const SkPath& path,
                           const SkPaint& paint, const SkMatrix* prePathMatrix,
                           bool pathIsMutable) SK_OVERRIDE;
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 67fbcca..587304b 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1542,10 +1542,13 @@
         }
     }
 
-    SkPath  path;
-    path.addOval(oval);
-    // call the non-virtual version
-    this->SkCanvas::drawPath(path, paint);
+    LOOPER_BEGIN(paint, SkDrawFilter::kOval_Type)
+
+    while (iter.next()) {
+        iter.fDevice->drawOval(iter, oval, looper.paint());
+    }
+
+    LOOPER_END
 }
 
 void SkCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 1e7a9ba..b3209b3 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -317,16 +317,24 @@
 }
 
 void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
-                              const SkPoint pts[], const SkPaint& paint) {
+                          const SkPoint pts[], const SkPaint& paint) {
     draw.drawPoints(mode, count, pts, paint);
 }
 
-void SkDevice::drawRect(const SkDraw& draw, const SkRect& r,
-                            const SkPaint& paint) {
+void SkDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
     CHECK_FOR_NODRAW_ANNOTATION(paint);
     draw.drawRect(r, paint);
 }
 
+void SkDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
+    CHECK_FOR_NODRAW_ANNOTATION(paint);
+
+    SkPath path;
+    path.addOval(oval);
+    // call the non-virtual version
+    this->SkDevice::drawPath(draw, path, paint, NULL, true);
+}
+
 void SkDevice::drawPath(const SkDraw& draw, const SkPath& path,
                         const SkPaint& paint, const SkMatrix* prePathMatrix,
                         bool pathIsMutable) {
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index 532a534..eda8f14 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -170,6 +170,43 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+bool SkMatrix::isSimilarity(SkScalar tol) const {
+    // if identity or translate matrix
+    TypeMask mask = this->getType();
+    if (mask <= kTranslate_Mask) {
+        return true;
+    }
+    if (mask & kPerspective_Mask) {
+        return false;
+    }
+
+    SkScalar mx = fMat[kMScaleX];
+    SkScalar my = fMat[kMScaleY];
+    // if no skew, can just compare scale factors
+    if (!(mask & kAffine_Mask)) {
+        return !SkScalarNearlyZero(mx) && SkScalarNearlyEqual(SkScalarAbs(mx), SkScalarAbs(my));
+    }
+    SkScalar sx = fMat[kMSkewX];
+    SkScalar sy = fMat[kMSkewY];
+
+    // degenerate matrix, non-similarity
+    if (SkScalarNearlyZero(mx) && SkScalarNearlyZero(my)
+        && SkScalarNearlyZero(sx) && SkScalarNearlyZero(sy)) {
+        return false;
+    }
+
+    // it has scales and skews, but it could also be rotation, check it out.
+    SkVector vec[2];
+    vec[0].set(mx, sx);
+    vec[1].set(sy, my);
+
+    return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
+           SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
+                SkScalarSquare(tol));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 void SkMatrix::setTranslate(SkScalar dx, SkScalar dy) {
     if (SkScalarToCompareType(dx) || SkScalarToCompareType(dy)) {
         fMat[kMTransX] = dx;
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 43dda9c..9739199 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -935,59 +935,62 @@
     SkScalar fInnerRadius;
 };
 
-/* Returns true if will map a circle to another circle. This can be true
- * if the matrix only includes square-scale, rotation, translation.
- */
-inline bool isSimilarityTransformation(const SkMatrix& matrix,
-                                       SkScalar tol = SK_ScalarNearlyZero) {
-    if (matrix.isIdentity() || matrix.getType() == SkMatrix::kTranslate_Mask) {
-        return true;
-    }
-    if (matrix.hasPerspective()) {
-        return false;
-    }
-
-    SkScalar mx = matrix.get(SkMatrix::kMScaleX);
-    SkScalar sx = matrix.get(SkMatrix::kMSkewX);
-    SkScalar my = matrix.get(SkMatrix::kMScaleY);
-    SkScalar sy = matrix.get(SkMatrix::kMSkewY);
-
-    if (mx == 0 && sx == 0 && my == 0 && sy == 0) {
-        return false;
-    }
-
-    // it has scales or skews, but it could also be rotation, check it out.
-    SkVector vec[2];
-    vec[0].set(mx, sx);
-    vec[1].set(sy, my);
-
-    return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
-           SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
-                SkScalarSquare(tol));
+inline bool circleStaysCircle(const SkMatrix& m) {
+    return m.isSimilarity();
 }
 
 }
 
-// TODO: strokeWidth can't be larger than zero right now.
-// It will be fixed when drawPath() can handle strokes.
 void GrContext::drawOval(const GrPaint& paint,
-                         const GrRect& rect,
-                         SkScalar strokeWidth) {
-    GrAssert(strokeWidth <= 0);
-    if (!isSimilarityTransformation(this->getMatrix()) ||
-        !paint.isAntiAlias() ||
-        rect.height() != rect.width()) {
+                         const GrRect& oval,
+                         const SkStrokeRec& stroke) {
+
+    if (!canDrawOval(paint, oval, stroke)) {
         SkPath path;
-        path.addOval(rect);
-        path.setFillType(SkPath::kWinding_FillType);
-        SkStrokeRec stroke(0 == strokeWidth ? SkStrokeRec::kHairline_InitStyle :
-                                           SkStrokeRec::kFill_InitStyle);
-        if (strokeWidth > 0) {
-            stroke.setStrokeStyle(strokeWidth, true);
-        }
-        this->internalDrawPath(paint, path, stroke);
+        path.addOval(oval);
+        this->drawPath(paint, path, stroke);
         return;
+    } 
+
+    internalDrawOval(paint, oval, stroke);
+}
+
+bool GrContext::canDrawOval(const GrPaint& paint, const GrRect& oval, const SkStrokeRec& stroke) const {
+
+    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;
+
+    return isCircle || isFilledAxisAlignedEllipse;
+}
+
+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));
+    }
+#endif
 
     GrDrawTarget* target = this->prepareToDraw(&paint, DEFAULT_BUFFERING);
 
@@ -1008,22 +1011,6 @@
     GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit;
     GrAssert(sizeof(CircleVertex) == GrDrawTarget::VertexSize(layout));
 
-    GrPoint center = GrPoint::Make(rect.centerX(), rect.centerY());
-    SkScalar radius = SkScalarHalf(rect.width());
-
-    vm.mapPoints(&center, 1);
-    radius = vm.mapRadius(radius);
-
-    SkScalar outerRadius = radius;
-    SkScalar innerRadius = 0;
-    SkScalar halfWidth = 0;
-    if (strokeWidth == 0) {
-        halfWidth = SkScalarHalf(SK_Scalar1);
-
-        outerRadius += halfWidth;
-        innerRadius = SkMaxScalar(0, radius - halfWidth);
-    }
-
     GrDrawTarget::AutoReleaseGeometry geo(target, layout, 4, 0);
     if (!geo.succeeded()) {
         GrPrintf("Failed to get space for vertices!\n");
@@ -1032,26 +1019,93 @@
 
     CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
 
+    GrPoint center = GrPoint::Make(oval.centerX(), oval.centerY());
+    vm.mapPoints(&center, 1);
+
+    SkScalar L;
+    SkScalar R;
+    SkScalar T;
+    SkScalar B;
+
+    if (isCircle) {
+        drawState->setVertexEdgeType(GrDrawState::kCircle_EdgeType);
+
+        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);
+            }
+        }
+
+        for (int i = 0; i < 4; ++i) {
+            verts[i].fCenter = center;
+            verts[i].fOuterRadius = outerRadius;
+            verts[i].fInnerRadius = innerRadius;
+        }
+
+        L = -outerRadius;
+        R = +outerRadius;
+        T = -outerRadius;
+        B = +outerRadius;
+    } else {  // is axis-aligned ellipse
+        drawState->setVertexEdgeType(GrDrawState::kEllipse_EdgeType);
+
+        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.
-    SkScalar L = center.fX - outerRadius - SkFloatToScalar(0.5f);
-    SkScalar R = center.fX + outerRadius + SkFloatToScalar(0.5f);
-    SkScalar T = center.fY - outerRadius - SkFloatToScalar(0.5f);
-    SkScalar B = center.fY + outerRadius + SkFloatToScalar(0.5f);
+    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);
 
-    for (int i = 0; i < 4; ++i) {
-        verts[i].fCenter = center;
-        verts[i].fOuterRadius = outerRadius;
-        verts[i].fInnerRadius = innerRadius;
-    }
-
-    drawState->setVertexEdgeType(GrDrawState::kCircle_EdgeType);
     target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4);
 }
 
@@ -1065,10 +1119,10 @@
     }
 
     SkRect ovalRect;
-    if ((stroke.isHairlineStyle() || stroke.isFillStyle()) && !path.isInverseFillType() &&
-        path.isOval(&ovalRect)) {
-        SkScalar width = stroke.isHairlineStyle() ? 0 : -SK_Scalar1;
-        this->drawOval(paint, ovalRect, width);
+    bool isOval = path.isOval(&ovalRect);
+
+    if (isOval && !path.isInverseFillType() && this->canDrawOval(paint, ovalRect, stroke)) {
+        this->drawOval(paint, ovalRect, stroke);
         return;
     }
 
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index 13ed287..4a99ecb 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -681,6 +681,9 @@
         /* Circle specified as center_x, center_y, outer_radius, inner_radius
            all in window space (y-down). */
         kCircle_EdgeType,
+        /* Axis-aligned ellipse specified as center_x, center_y, x_radius, x_radius/y_radius
+           all in window space (y-down). */
+        kEllipse_EdgeType,
 
         kVertexEdgeTypeCnt
     };
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 0cf3b35..cedb7d4 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -674,6 +674,35 @@
     fContext->drawRect(grPaint, rect, doStroke ? width : -1);
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval,
+                           const SkPaint& paint) {
+    CHECK_FOR_NODRAW_ANNOTATION(paint);
+    CHECK_SHOULD_DRAW(draw, false);
+
+    bool usePath = false;
+    // some basic reasons we might need to call drawPath...
+    if (paint.getMaskFilter() || paint.getPathEffect()) {
+        usePath = true;
+    }
+
+    if (usePath) {
+        SkPath path;
+        path.addOval(oval);
+        this->drawPath(draw, path, paint, NULL, true);
+        return;
+    }
+
+    GrPaint grPaint;
+    if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+        return;
+    }
+    SkStrokeRec stroke(paint);
+
+    fContext->drawOval(grPaint, oval, stroke);
+}
+
 #include "SkMaskFilter.h"
 #include "SkBounder.h"
 
@@ -912,7 +941,7 @@
         return;
     }
 
-    // can we cheat, and threat a thin stroke as a hairline w/ coverage
+    // can we cheat, and treat a thin stroke as a hairline w/ coverage
     // if we can, we draw lots faster (raster device does this same test)
     SkScalar hairlineCoverage;
     bool doHairLine = SkDrawTreatAsHairline(paint, fContext->getMatrix(), &hairlineCoverage);
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 9853af2..26127e4 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -276,6 +276,13 @@
             builder->fFSCode.appendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName);
             builder->fFSCode.append("\tedgeAlpha = outerAlpha * innerAlpha;\n");
             break;
+        case GrDrawState::kEllipse_EdgeType:
+            builder->fFSCode.append("\tfloat edgeAlpha;\n");
+            builder->fFSCode.appendf("\tvec2 offset = (%s.xy - %s.xy);\n", builder->fragmentPosition(), fsName);
+            builder->fFSCode.appendf("\toffset.y *= %s.w;\n", fsName);
+            builder->fFSCode.append("\tfloat d = length(offset);\n");
+            builder->fFSCode.appendf("\tedgeAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
+            break;
         default:
             GrCrash("Unknown Edge Type!");
             break;
diff --git a/tests/MatrixTest.cpp b/tests/MatrixTest.cpp
index bba2e08..1987f3b 100644
--- a/tests/MatrixTest.cpp
+++ b/tests/MatrixTest.cpp
@@ -216,106 +216,76 @@
     }
 }
 
-// This function is extracted from src/gpu/SkGpuDevice.cpp,
-// in order to make sure this function works correctly.
-static bool isSimilarityTransformation(const SkMatrix& matrix,
-                                       SkScalar tol = SK_ScalarNearlyZero) {
-    if (matrix.isIdentity() || matrix.getType() == SkMatrix::kTranslate_Mask) {
-        return true;
-    }
-    if (matrix.hasPerspective()) {
-        return false;
-    }
-
-    SkScalar mx = matrix.get(SkMatrix::kMScaleX);
-    SkScalar sx = matrix.get(SkMatrix::kMSkewX);
-    SkScalar my = matrix.get(SkMatrix::kMScaleY);
-    SkScalar sy = matrix.get(SkMatrix::kMSkewY);
-
-    if (mx == 0 && sx == 0 && my == 0 && sy == 0) {
-        return false;
-    }
-
-    // it has scales or skews, but it could also be rotation, check it out.
-    SkVector vec[2];
-    vec[0].set(mx, sx);
-    vec[1].set(sy, my);
-
-    return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
-           SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
-                SkScalarSquare(tol));
-}
-
-static void test_matrix_is_similarity_transform(skiatest::Reporter* reporter) {
+static void test_matrix_is_similarity(skiatest::Reporter* reporter) {
     SkMatrix mat;
 
     // identity
     mat.setIdentity();
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // translation only
     mat.reset();
     mat.setTranslate(SkIntToScalar(100), SkIntToScalar(100));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // scale with same size
     mat.reset();
     mat.setScale(SkIntToScalar(15), SkIntToScalar(15));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // scale with one negative
     mat.reset();
     mat.setScale(SkIntToScalar(-15), SkIntToScalar(15));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // scale with different size
     mat.reset();
     mat.setScale(SkIntToScalar(15), SkIntToScalar(20));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // scale with same size at a pivot point
     mat.reset();
     mat.setScale(SkIntToScalar(15), SkIntToScalar(15),
                  SkIntToScalar(2), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // scale with different size at a pivot point
     mat.reset();
     mat.setScale(SkIntToScalar(15), SkIntToScalar(20),
                  SkIntToScalar(2), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // skew with same size
     mat.reset();
     mat.setSkew(SkIntToScalar(15), SkIntToScalar(15));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // skew with different size
     mat.reset();
     mat.setSkew(SkIntToScalar(15), SkIntToScalar(20));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // skew with same size at a pivot point
     mat.reset();
     mat.setSkew(SkIntToScalar(15), SkIntToScalar(15),
                 SkIntToScalar(2), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // skew with different size at a pivot point
     mat.reset();
     mat.setSkew(SkIntToScalar(15), SkIntToScalar(20),
                 SkIntToScalar(2), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // perspective x
     mat.reset();
     mat.setPerspX(SkScalarToPersp(SK_Scalar1 / 2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // perspective y
     mat.reset();
     mat.setPerspY(SkScalarToPersp(SK_Scalar1 / 2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
 #if SK_SCALAR_IS_FLOAT
     /* We bypass the following tests for SK_SCALAR_IS_FIXED build.
@@ -331,7 +301,7 @@
     for (int angle = 0; angle < 360; ++angle) {
         mat.reset();
         mat.setRotate(SkIntToScalar(angle));
-        REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+        REPORTER_ASSERT(reporter, mat.isSimilarity());
     }
 
     // see if there are any accumulated precision issues
@@ -339,40 +309,40 @@
     for (int i = 1; i < 360; i++) {
         mat.postRotate(SkIntToScalar(1));
     }
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // rotate + translate
     mat.reset();
     mat.setRotate(SkIntToScalar(30));
     mat.postTranslate(SkIntToScalar(10), SkIntToScalar(20));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // rotate + uniform scale
     mat.reset();
     mat.setRotate(SkIntToScalar(30));
     mat.postScale(SkIntToScalar(2), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 
     // rotate + non-uniform scale
     mat.reset();
     mat.setRotate(SkIntToScalar(30));
     mat.postScale(SkIntToScalar(3), SkIntToScalar(2));
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 #endif
 
     // all zero
     mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, 0);
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // all zero except perspective
     mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, SK_Scalar1);
-    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, !mat.isSimilarity());
 
     // scales zero, only skews
     mat.setAll(0, SK_Scalar1, 0,
                SK_Scalar1, 0, 0,
                0, 0, SkMatrix::I()[8]);
-    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
+    REPORTER_ASSERT(reporter, mat.isSimilarity());
 }
 
 static void TestMatrix(skiatest::Reporter* reporter) {
@@ -491,7 +461,7 @@
 #endif
 
     test_matrix_max_stretch(reporter);
-    test_matrix_is_similarity_transform(reporter);
+    test_matrix_is_similarity(reporter);
     test_matrix_recttorect(reporter);
 }