Reland "Prefer using GrOvalOpFactory over GrFillRRect for circles and axis-aligned circular roundrects."

This is a reland of 731454a08543be3793bb7b28391a7ff69db09884

Original change's description:
> Prefer using GrOvalOpFactory over GrFillRRect for circles and
> axis-aligned circular roundrects.
> 
> Bug: chromium:971936
> Change-Id: I4cd0cd9047b9b06d657826820ba5a937547f87c3
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/221000
> Commit-Queue: Jim Van Verth <jvanverth@google.com>
> Reviewed-by: Khushal Sagar <khushalsagar@chromium.org>

Bug: chromium:971936
Change-Id: I8a61cff3e065177a5b2320072b45c1a619970ff6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222794
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 2b37973..43a7086 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -1126,7 +1126,16 @@
     GrAAType aaType = this->chooseAAType(aa);
 
     std::unique_ptr<GrDrawOp> op;
-    if (style.isSimpleFill()) {
+    if (GrAAType::kCoverage == aaType && rrect.isSimple() &&
+        rrect.getSimpleRadii().fX == rrect.getSimpleRadii().fY &&
+        viewMatrix.rectStaysRect() && viewMatrix.isSimilarity()) {
+        // In coverage mode, we draw axis-aligned circular roundrects with the GrOvalOpFactory
+        // to avoid perf regressions on some platforms.
+        assert_alive(paint);
+        op = GrOvalOpFactory::MakeCircularRRectOp(
+                fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
+    }
+    if (!op && style.isSimpleFill()) {
         assert_alive(paint);
         op = GrFillRRectOp::Make(
                 fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
@@ -1135,7 +1144,6 @@
         assert_alive(paint);
         op = GrOvalOpFactory::MakeRRectOp(
                 fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
-
     }
     if (op) {
         this->addDrawOp(*clip, std::move(op));
@@ -1534,20 +1542,23 @@
     GrAAType aaType = this->chooseAAType(aa);
 
     std::unique_ptr<GrDrawOp> op;
-    if (style.isSimpleFill()) {
+    if (GrAAType::kCoverage == aaType && oval.width() == oval.height() &&
+        viewMatrix.isSimilarity()) {
+        // We don't draw true circles as round rects in coverage mode, because it can
+        // cause perf regressions on some platforms as compared to the dedicated circle Op.
+        assert_alive(paint);
+        op = GrOvalOpFactory::MakeCircleOp(fContext, std::move(paint), viewMatrix, oval, style,
+                                           this->caps()->shaderCaps());
+    }
+    if (!op && style.isSimpleFill()) {
         // GrFillRRectOp has special geometry and a fragment-shader branch to conditionally evaluate
         // the arc equation. This same special geometry and fragment branch also turn out to be a
         // substantial optimization for drawing ovals (namely, by not evaluating the arc equation
         // inside the oval's inner diamond). Given these optimizations, it's a clear win to draw
         // ovals the exact same way we do round rects.
-        //
-        // However, we still don't draw true circles as round rects in coverage mode, because it can
-        // cause perf regressions on some platforms as compared to the dedicated circle Op.
-        if (GrAAType::kCoverage != aaType || oval.height() != oval.width()) {
-            assert_alive(paint);
-            op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
-                                     *this->caps(), std::move(paint));
-        }
+        assert_alive(paint);
+        op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
+                                 *this->caps(), std::move(paint));
     }
     if (!op && GrAAType::kCoverage == aaType) {
         assert_alive(paint);
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index f6dfc8e..f9d004f 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -2907,6 +2907,58 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context,
+                                                               GrPaint&& paint,
+                                                               const SkMatrix& viewMatrix,
+                                                               const SkRRect& rrect,
+                                                               const SkStrokeRec& stroke,
+                                                               const GrShaderCaps* shaderCaps) {
+    SkASSERT(viewMatrix.rectStaysRect());
+    SkASSERT(viewMatrix.isSimilarity());
+    SkASSERT(rrect.isSimple());
+    SkASSERT(!rrect.isOval());
+    SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY);
+
+    // RRect ops only handle simple, but not too simple, rrects.
+    // Do any matrix crunching before we reset the draw state for device coords.
+    const SkRect& rrectBounds = rrect.getBounds();
+    SkRect bounds;
+    viewMatrix.mapRect(&bounds, rrectBounds);
+
+    SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
+    SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] +
+                                                  viewMatrix[SkMatrix::kMSkewY]));
+
+    // Do mapping of stroke. Use -1 to indicate fill-only draws.
+    SkScalar scaledStroke = -1;
+    SkScalar strokeWidth = stroke.getWidth();
+    SkStrokeRec::Style style = stroke.getStyle();
+
+    bool isStrokeOnly =
+        SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
+    bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+
+    if (hasStroke) {
+        if (SkStrokeRec::kHairline_Style == style) {
+            scaledStroke = SK_Scalar1;
+        } else {
+            scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] +                                                                        viewMatrix[SkMatrix::kMSkewY]));
+        }
+    }
+
+    // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
+    // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
+    // patch will have fractional coverage. This only matters when the interior is actually filled.
+    // We could consider falling back to rect rendering here, since a tiny radius is
+    // indistinguishable from a square corner.
+    if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) {
+        return nullptr;
+    }
+
+    return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius,
+                                 scaledStroke, isStrokeOnly);
+}
+
 static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
                                                GrPaint&& paint,
                                                const SkMatrix& viewMatrix,
@@ -2938,7 +2990,6 @@
             SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
     bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
 
-    bool isCircular = (xRadius == yRadius);
     if (hasStroke) {
         if (SkStrokeRec::kHairline_Style == style) {
             scaledStroke.set(1, 1);
@@ -2949,17 +3000,15 @@
                     strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
         }
 
-        isCircular = isCircular && scaledStroke.fX == scaledStroke.fY;
-        // for non-circular rrects, if half of strokewidth is greater than radius,
-        // we don't handle that right now
-        if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius ||
-                            SK_ScalarHalf * scaledStroke.fY > yRadius)) {
+        // if half of strokewidth is greater than radius, we don't handle that right now
+        if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
+             SK_ScalarHalf * scaledStroke.fY > yRadius)) {
             return nullptr;
         }
     }
 
     // The matrix may have a rotation by an odd multiple of 90 degrees.
-    if (!isCircular && viewMatrix.getScaleX() == 0) {
+    if (viewMatrix.getScaleX() == 0) {
         std::swap(xRadius, yRadius);
         std::swap(scaledStroke.fX, scaledStroke.fY);
     }
@@ -2974,14 +3023,8 @@
     }
 
     // if the corners are circles, use the circle renderer
-    if (isCircular) {
-        return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
-                                     scaledStroke.fX, isStrokeOnly);
-        // otherwise we use the ellipse renderer
-    } else {
-        return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
-                                       xRadius, yRadius, scaledStroke, isStrokeOnly);
-    }
+    return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
+                                   xRadius, yRadius, scaledStroke, isStrokeOnly);
 }
 
 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
@@ -3004,6 +3047,48 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context,
+                                                        GrPaint&& paint,
+                                                        const SkMatrix& viewMatrix,
+                                                        const SkRect& oval,
+                                                        const GrStyle& style,
+                                                        const GrShaderCaps* shaderCaps) {
+    SkScalar width = oval.width();
+    SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
+             circle_stays_circle(viewMatrix));
+
+    auto r = width / 2.f;
+    SkPoint center = { oval.centerX(), oval.centerY() };
+    if (style.hasNonDashPathEffect()) {
+        return nullptr;
+    } else if (style.isDashed()) {
+        if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
+            style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
+            return nullptr;
+        }
+        auto onInterval = style.dashIntervals()[0];
+        auto offInterval = style.dashIntervals()[1];
+        if (offInterval == 0) {
+            GrStyle strokeStyle(style.strokeRec(), nullptr);
+            return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
+                              strokeStyle, shaderCaps);
+        } else if (onInterval == 0) {
+            // There is nothing to draw but we have no way to indicate that here.
+            return nullptr;
+        }
+        auto angularOnInterval = onInterval / r;
+        auto angularOffInterval = offInterval / r;
+        auto phaseAngle = style.dashPhase() / r;
+        // Currently this function doesn't accept ovals with different start angles, though
+        // it could.
+        static const SkScalar kStartAngle = 0.f;
+        return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
+                                           style.strokeRec().getWidth(), kStartAngle,
+                                           angularOnInterval, angularOffInterval, phaseAngle);
+    }
+    return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
+}
+
 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
                                                       GrPaint&& paint,
                                                       const SkMatrix& viewMatrix,
@@ -3014,36 +3099,7 @@
     SkScalar width = oval.width();
     if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
         circle_stays_circle(viewMatrix)) {
-        auto r = width / 2.f;
-        SkPoint center = {oval.centerX(), oval.centerY()};
-        if (style.hasNonDashPathEffect()) {
-            return nullptr;
-        } else if (style.isDashed()) {
-            if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
-                style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
-                return nullptr;
-            }
-            auto onInterval = style.dashIntervals()[0];
-            auto offInterval = style.dashIntervals()[1];
-            if (offInterval == 0) {
-                GrStyle strokeStyle(style.strokeRec(), nullptr);
-                return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
-                                  strokeStyle, shaderCaps);
-            } else if (onInterval == 0) {
-                // There is nothing to draw but we have no way to indicate that here.
-                return nullptr;
-            }
-            auto angularOnInterval = onInterval / r;
-            auto angularOffInterval = offInterval / r;
-            auto phaseAngle = style.dashPhase() / r;
-            // Currently this function doesn't accept ovals with different start angles, though
-            // it could.
-            static const SkScalar kStartAngle = 0.f;
-            return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
-                                               style.strokeRec().getWidth(), kStartAngle,
-                                               angularOnInterval, angularOffInterval, phaseAngle);
-        }
-        return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
+        return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps);
     }
 
     if (style.pathEffect()) {
@@ -3174,6 +3230,35 @@
                              GrTest::TestStrokeRec(random));
 }
 
+GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) {
+    do {
+        SkScalar rotate = random->nextSScalar1() * 360.f;
+        SkScalar translateX = random->nextSScalar1() * 1000.f;
+        SkScalar translateY = random->nextSScalar1() * 1000.f;
+        SkScalar scale;
+        do {
+            scale = random->nextSScalar1() * 100.f;
+        } while (scale == 0);
+        SkMatrix viewMatrix;
+        viewMatrix.setRotate(rotate);
+        viewMatrix.postTranslate(translateX, translateY);
+        viewMatrix.postScale(scale, scale);
+        SkRect rect = GrTest::TestRect(random);
+        SkScalar radius = random->nextRangeF(0.1f, 10.f);
+        SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius);
+        if (rrect.isOval()) {
+            continue;
+        }
+        std::unique_ptr<GrDrawOp> op =
+                GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect,
+                                                     GrTest::TestStrokeRec(random), nullptr);
+        if (op) {
+            return op;
+        }
+        assert_alive(paint);
+    } while (true);
+}
+
 GR_DRAW_OP_TEST_DEFINE(RRectOp) {
     SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
     const SkRRect& rrect = GrTest::TestRRectSimple(random);
diff --git a/src/gpu/ops/GrOvalOpFactory.h b/src/gpu/ops/GrOvalOpFactory.h
index bf86ca3..de25fa1 100644
--- a/src/gpu/ops/GrOvalOpFactory.h
+++ b/src/gpu/ops/GrOvalOpFactory.h
@@ -26,6 +26,13 @@
  */
 class GrOvalOpFactory {
 public:
+    static std::unique_ptr<GrDrawOp> MakeCircleOp(GrRecordingContext*,
+                                                  GrPaint&&,
+                                                  const SkMatrix&,
+                                                  const SkRect& oval,
+                                                  const GrStyle& style,
+                                                  const GrShaderCaps*);
+
     static std::unique_ptr<GrDrawOp> MakeOvalOp(GrRecordingContext*,
                                                 GrPaint&&,
                                                 const SkMatrix&,
@@ -33,6 +40,13 @@
                                                 const GrStyle& style,
                                                 const GrShaderCaps*);
 
+    static std::unique_ptr<GrDrawOp> MakeCircularRRectOp(GrRecordingContext*,
+                                                         GrPaint&&,
+                                                         const SkMatrix&,
+                                                         const SkRRect&,
+                                                         const SkStrokeRec&,
+                                                         const GrShaderCaps*);
+
     static std::unique_ptr<GrDrawOp> MakeRRectOp(GrRecordingContext*,
                                                  GrPaint&&,
                                                  const SkMatrix&,