Add non-AA support to OvalFactoryOps.

Non-AA stroked ovals and rounded rects were falling back to a path renderer, when they can use
the much faster OvalFactoryOps. And since CircleOp is slightly faster than FillRRectOp for AA
circles, this extends it to work with non-AA circles as well.

Change-Id: I7e3576e081f076056a00ed57ac6a805be7348b28
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/206700
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index c4db904..a9c6021 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -1235,11 +1235,11 @@
         op = GrFillRRectOp::Make(
                 fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
     }
-    if (!op && GrAAType::kCoverage == aaType) {
+    if (!op) {
         assert_alive(paint);
         op = GrOvalOpFactory::MakeRRectOp(
-                fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
-
+                fContext, std::move(paint), aaType, viewMatrix, rrect, stroke,
+                this->caps()->shaderCaps());
     }
     if (op) {
         this->addDrawOp(*clip, std::move(op));
@@ -1470,8 +1470,7 @@
         return false;
     }
 
-    if (GrAAType::kCoverage == aaType && SkRRectPriv::IsCircle(*inner)
-                                      && SkRRectPriv::IsCircle(*outer)) {
+    if (SkRRectPriv::IsCircle(*inner) && SkRRectPriv::IsCircle(*outer)) {
         auto outerR = outer->width() / 2.f;
         auto innerR = inner->width() / 2.f;
         auto cx = outer->getBounds().fLeft + outerR;
@@ -1482,7 +1481,7 @@
             auto circleBounds = SkRect::MakeLTRB(cx - avgR, cy - avgR, cx + avgR, cy + avgR);
             SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
             stroke.setStrokeStyle(outerR - innerR);
-            auto op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix,
+            auto op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), aaType, viewMatrix,
                                                   circleBounds, GrStyle(stroke, nullptr),
                                                   this->caps()->shaderCaps());
             if (op) {
@@ -1643,18 +1642,18 @@
         // 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()) {
+        // However, we still don't draw true circles as round rects, because it can cause perf
+        // regressions on some platforms as compared to the dedicated circle Op.
+        if (!SkScalarNearlyEqual(oval.height(), oval.width())) {
             assert_alive(paint);
             op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
                                      *this->caps(), std::move(paint));
         }
     }
-    if (!op && GrAAType::kCoverage == aaType) {
+    if (!op) {
         assert_alive(paint);
-        op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style,
-                                         this->caps()->shaderCaps());
+        op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), aaType, viewMatrix, oval,
+                                         style, this->caps()->shaderCaps());
     }
     if (op) {
         this->addDrawOp(clip, std::move(op));
@@ -1684,23 +1683,23 @@
     AutoCheckFlush acf(this->drawingManager());
 
     GrAAType aaType = this->chooseAAType(aa);
-    if (GrAAType::kCoverage == aaType) {
-        const GrShaderCaps* shaderCaps = this->caps()->shaderCaps();
-        std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeArcOp(fContext,
-                                                                  std::move(paint),
-                                                                  viewMatrix,
-                                                                  oval,
-                                                                  startAngle,
-                                                                  sweepAngle,
-                                                                  useCenter,
-                                                                  style,
-                                                                  shaderCaps);
-        if (op) {
-            this->addDrawOp(clip, std::move(op));
-            return;
-        }
-        assert_alive(paint);
+    const GrShaderCaps* shaderCaps = this->caps()->shaderCaps();
+    std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeArcOp(fContext,
+                                                              std::move(paint),
+                                                              aaType,
+                                                              viewMatrix,
+                                                              oval,
+                                                              startAngle,
+                                                              sweepAngle,
+                                                              useCenter,
+                                                              style,
+                                                              shaderCaps);
+    if (op) {
+        this->addDrawOp(clip, std::move(op));
+        return;
     }
+    assert_alive(paint);
+
     this->drawShapeUsingPathRenderer(
             clip, std::move(paint), aa, viewMatrix,
             GrShape::MakeArc(oval, startAngle, sweepAngle, useCenter, style));
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index f7ea93e..730c0fc 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -63,10 +63,11 @@
 
 class CircleGeometryProcessor : public GrGeometryProcessor {
 public:
-    CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
+    CircleGeometryProcessor(bool aa, bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
                             bool roundCaps, bool wideColor, const SkMatrix& localMatrix)
             : INHERITED(kCircleGeometryProcessor_ClassID)
             , fLocalMatrix(localMatrix)
+            , fAA(aa)
             , fStroke(stroke) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInColor = MakeColorAttribute("inColor", wideColor);
@@ -159,27 +160,35 @@
 
             fragBuilder->codeAppend("float d = length(circleEdge.xy);");
             fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));");
-            fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);");
+            if (cgp.fAA) {
+                fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);");
+            } else {
+                fragBuilder->codeAppend("half edgeAlpha = step(0, distanceToOuterEdge);");
+            }
             if (cgp.fStroke) {
                 fragBuilder->codeAppend(
                         "half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));");
-                fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);");
+                if (cgp.fAA) {
+                    fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);");
+                } else {
+                    fragBuilder->codeAppend("half innerAlpha = step(0, distanceToInnerEdge);");
+                }
                 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
             }
 
             if (cgp.fInClipPlane.isInitialized()) {
                 fragBuilder->codeAppend(
-                        "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, "
-                        "clipPlane.xy) + clipPlane.z));");
+                    "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, "
+                    "clipPlane.xy) + clipPlane.z));");
                 if (cgp.fInIsectPlane.isInitialized()) {
                     fragBuilder->codeAppend(
-                            "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, "
-                            "isectPlane.xy) + isectPlane.z));");
+                        "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, "
+                        "isectPlane.xy) + isectPlane.z));");
                 }
                 if (cgp.fInUnionPlane.isInitialized()) {
                     fragBuilder->codeAppend(
-                            "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy,"
-                            " unionPlane.xy) + unionPlane.z)));");
+                        "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy,"
+                        " unionPlane.xy) + unionPlane.z)));");
                 }
                 fragBuilder->codeAppend("edgeAlpha *= clip;");
                 if (cgp.fInRoundCapCenters.isInitialized()) {
@@ -210,6 +219,7 @@
             key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0;
             key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0;
             key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0;
+            key |= cgp.fAA ? 0x40 : 0x0;
             b->add32(key);
         }
 
@@ -234,6 +244,7 @@
     Attribute fInUnionPlane;
     Attribute fInRoundCapCenters;
 
+    bool fAA;
     bool fStroke;
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -244,6 +255,7 @@
 
 #if GR_TEST_UTILS
 sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
+    bool aa = d->fRandom->nextBool();
     bool stroke = d->fRandom->nextBool();
     bool roundCaps = stroke ? d->fRandom->nextBool() : false;
     bool wideColor = d->fRandom->nextBool();
@@ -252,7 +264,7 @@
     bool unionPlane = d->fRandom->nextBool();
     const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
     return sk_sp<GrGeometryProcessor>(new CircleGeometryProcessor(
-            stroke, clipPlane, isectPlane, unionPlane, roundCaps, wideColor, matrix));
+            aa, stroke, clipPlane, isectPlane, unionPlane, roundCaps, wideColor, matrix));
 }
 #endif
 
@@ -507,10 +519,11 @@
 
 class EllipseGeometryProcessor : public GrGeometryProcessor {
 public:
-    EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale,
+    EllipseGeometryProcessor(bool aa, bool stroke, bool wideColor, bool useScale,
                              const SkMatrix& localMatrix)
     : INHERITED(kEllipseGeometryProcessor_ClassID)
     , fLocalMatrix(localMatrix)
+    , fAA(aa)
     , fStroke(stroke)
     , fUseScale(useScale) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
@@ -611,7 +624,11 @@
             } else {
                 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
             }
-            fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
+            if (egp.fAA) {
+                fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
+            } else {
+                fragBuilder->codeAppend("float edgeAlpha = step(0, -test*invlen);");
+            }
 
             // for inner curve
             if (egp.fStroke) {
@@ -634,7 +651,12 @@
                 } else {
                     fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
                 }
-                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
+                if (egp.fAA) {
+                    fragBuilder->codeAppend("float innerEdgeAlpha = saturate(0.5+test*invlen);");
+                } else {
+                    fragBuilder->codeAppend("float innerEdgeAlpha = step(0, test*invlen);");
+                }
+                fragBuilder->codeAppend("edgeAlpha *= innerEdgeAlpha;");
             }
 
             fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
@@ -646,6 +668,7 @@
             const EllipseGeometryProcessor& egp = gp.cast<EllipseGeometryProcessor>();
             uint16_t key = egp.fStroke ? 0x1 : 0x0;
             key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0;
+            key |= egp.fAA ? 0x4 : 0x0;
             b->add32(key);
         }
 
@@ -665,6 +688,7 @@
     Attribute fInEllipseRadii;
 
     SkMatrix fLocalMatrix;
+    bool fAA;
     bool fStroke;
     bool fUseScale;
 
@@ -679,7 +703,8 @@
 sk_sp<GrGeometryProcessor> EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
     return sk_sp<GrGeometryProcessor>(
             new EllipseGeometryProcessor(d->fRandom->nextBool(), d->fRandom->nextBool(),
-                                         d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom)));
+                                         d->fRandom->nextBool(), d->fRandom->nextBool(),
+                                         GrTest::TestMatrix(d->fRandom)));
 }
 #endif
 
@@ -698,10 +723,11 @@
 
 class DIEllipseGeometryProcessor : public GrGeometryProcessor {
 public:
-    DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix,
+    DIEllipseGeometryProcessor(bool aa, bool wideColor, bool useScale, const SkMatrix& viewMatrix,
                                DIEllipseStyle style)
             : INHERITED(kDIEllipseGeometryProcessor_ClassID)
             , fViewMatrix(viewMatrix)
+            , fAA(aa)
             , fUseScale(useScale)
             , fStyle(style) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
@@ -796,10 +822,19 @@
             }
             if (DIEllipseStyle::kHairline == diegp.fStyle) {
                 // can probably do this with one step
-                fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);");
-                fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
+                if (diegp.fAA) {
+                    fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);");
+                    fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
+                } else {
+                    fragBuilder->codeAppend("float edgeAlpha = step(0, 0.5-test*invlen);");
+                    fragBuilder->codeAppend("edgeAlpha *= step(0, 0.5+test*invlen);");
+                }
             } else {
-                fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
+                if (diegp.fAA) {
+                    fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
+                } else {
+                    fragBuilder->codeAppend("float edgeAlpha = step(0, -test*invlen);");
+                }
             }
 
             // for inner curve
@@ -823,7 +858,11 @@
                 if (diegp.fUseScale) {
                     fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
                 }
-                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
+                if (diegp.fAA) {
+                    fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
+                } else {
+                    fragBuilder->codeAppend("edgeAlpha *= step(0, test*invlen);");
+                }
             }
 
             fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
@@ -833,8 +872,9 @@
                            const GrShaderCaps&,
                            GrProcessorKeyBuilder* b) {
             const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
-            uint16_t key = static_cast<uint16_t>(diegp.fStyle);
-            key |= ComputePosKey(diegp.fViewMatrix) << 10;
+            uint16_t key = diegp.fAA ? 0x1 : 0x0;
+            key |= static_cast<uint16_t>(diegp.fStyle) << 1;
+            key |= ComputePosKey(diegp.fViewMatrix) << 11;
             b->add32(key);
         }
 
@@ -865,6 +905,7 @@
     Attribute fInEllipseOffsets1;
 
     SkMatrix fViewMatrix;
+    bool fAA;
     bool fUseScale;
     DIEllipseStyle fStyle;
 
@@ -878,8 +919,8 @@
 #if GR_TEST_UTILS
 sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
     return sk_sp<GrGeometryProcessor>(new DIEllipseGeometryProcessor(
-            d->fRandom->nextBool(), d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom),
-            (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2))));
+            d->fRandom->nextBool(), d->fRandom->nextBool(), d->fRandom->nextBool(),
+            GrTest::TestMatrix(d->fRandom), (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2))));
 }
 #endif
 
@@ -976,6 +1017,7 @@
 
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
+                                          GrAAType aaType,
                                           const SkMatrix& viewMatrix,
                                           SkPoint center,
                                           SkScalar radius,
@@ -1012,11 +1054,11 @@
                     break;
             }
         }
-        return Helper::FactoryHelper<CircleOp>(context, std::move(paint), viewMatrix, center,
-                                               radius, style, arcParams);
+        return Helper::FactoryHelper<CircleOp>(context, std::move(paint), aaType, viewMatrix,
+                                               center, radius, style, arcParams);
     }
 
-    CircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+    CircleOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, GrAAType aaType,
              const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, const GrStyle& style,
              const ArcParams* arcParams)
             : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
@@ -1024,7 +1066,7 @@
         SkStrokeRec::Style recStyle = stroke.getStyle();
 
         fRoundCaps = false;
-
+        fAA = (GrAAType::kCoverage == aaType);
         viewMatrix.mapPoints(&center, 1);
         radius = viewMatrix.mapRadius(radius);
         SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
@@ -1053,8 +1095,10 @@
         // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
         // Second, the outer radius is used to compute the verts of the bounding box that is
         // rendered and the outset ensures the box will cover all partially covered by the circle.
-        outerRadius += SK_ScalarHalf;
-        innerRadius -= SK_ScalarHalf;
+        if (fAA) {
+            outerRadius += SK_ScalarHalf;
+            innerRadius -= SK_ScalarHalf;
+        }
         bool stroked = isStrokeOnly && innerRadius > 0.0f;
         fViewMatrixIfUsingLocalCoords = viewMatrix;
 
@@ -1191,7 +1235,7 @@
         radius += halfWidth;
         this->setBounds(
                 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
-                HasAABloat::kYes, IsZeroArea::kNo);
+                fAA ? HasAABloat::kYes : HasAABloat::kNo, IsZeroArea::kNo);
         fVertCount = circle_type_to_vert_count(stroked);
         fIndexCount = circle_type_to_index_count(stroked);
         fAllFill = !stroked;
@@ -1240,8 +1284,8 @@
 
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(new CircleGeometryProcessor(
-                !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps, fWideColor,
-                localMatrix));
+                fAA, !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, fRoundCaps,
+                fWideColor, localMatrix));
 
         sk_sp<const GrBuffer> vertexBuffer;
         int firstVertex;
@@ -1387,6 +1431,10 @@
             return CombineResult::kCannotCombine;
         }
 
+        if (fAA != that->fAA) {
+            return CombineResult::kCannotCombine;
+        }
+
         // Because we've set up the ops that don't use the planes with noop values
         // we can just accumulate used planes by later ops.
         fClipPlane |= that->fClipPlane;
@@ -1419,6 +1467,7 @@
     SkSTArray<1, Circle, true> fCircles;
     int fVertCount;
     int fIndexCount;
+    bool fAA;
     bool fAllFill;
     bool fClipPlane;
     bool fClipPlaneIsect;
@@ -1723,6 +1772,7 @@
 
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
+                                          GrAAType aaType,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& ellipse,
                                           const SkStrokeRec& stroke) {
@@ -1794,15 +1844,16 @@
             return nullptr;
         }
 
-        return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
+        return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), aaType, viewMatrix,
                                                 params, stroke);
     }
 
     EllipseOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-              const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
+              GrAAType aaType, const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
               const SkStrokeRec& stroke)
             : INHERITED(ClassID())
             , fHelper(helperArgs, GrAAType::kCoverage)
+            , fAA(GrAAType::kCoverage == aaType)
             , fUseScale(false) {
         SkStrokeRec::Style style = stroke.getStyle();
         bool isStrokeOnly =
@@ -1815,10 +1866,13 @@
                                                         params.fCenter.fX + params.fXRadius,
                                                         params.fCenter.fY + params.fYRadius)});
 
-        this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsZeroArea::kNo);
+        this->setBounds(fEllipses.back().fDevBounds, fAA ? HasAABloat::kYes : HasAABloat::kNo,
+                        IsZeroArea::kNo);
 
         // Outset bounds to include half-pixel width antialiasing.
-        fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        if (fAA) {
+            fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        }
 
         fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
         fViewMatrixIfUsingLocalCoords = viewMatrix;
@@ -1868,8 +1922,8 @@
         }
 
         // Setup geometry processor
-        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
-                                                                   localMatrix));
+        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fAA, fStroked, fWideColor,
+                                                                   fUseScale, localMatrix));
         QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
         GrVertexWriter verts{helper.vertices()};
         if (!verts.fPtr) {
@@ -1888,8 +1942,12 @@
                 SkScalarInvert(ellipse.fInnerXRadius),
                 SkScalarInvert(ellipse.fInnerYRadius)
             };
-            SkScalar xMaxOffset = xRadius + SK_ScalarHalf;
-            SkScalar yMaxOffset = yRadius + SK_ScalarHalf;
+            SkScalar xMaxOffset = xRadius;
+            SkScalar yMaxOffset = yRadius;
+            if (fAA) {
+                xMaxOffset += SK_ScalarHalf;
+                yMaxOffset += SK_ScalarHalf;
+            }
 
             if (!fStroked) {
                 // For filled ellipses we map a unit circle in the vertex attributes rather than
@@ -1923,6 +1981,10 @@
             return CombineResult::kCannotCombine;
         }
 
+        if (fAA != that->fAA) {
+            return CombineResult::kCannotCombine;
+        }
+
         if (fHelper.usesLocalCoords() &&
             !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
             return CombineResult::kCannotCombine;
@@ -1944,6 +2006,7 @@
 
     SkMatrix fViewMatrixIfUsingLocalCoords;
     Helper fHelper;
+    bool fAA;
     bool fStroked;
     bool fWideColor;
     bool fUseScale;
@@ -1972,6 +2035,7 @@
 
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
+                                          GrAAType aaType,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& ellipse,
                                           const SkStrokeRec& stroke) {
@@ -2039,21 +2103,27 @@
             (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
             params.fStyle = DIEllipseStyle::kFill;
         }
-        return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix);
+        return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), aaType, params,
+                                                  viewMatrix);
     }
 
-    DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+    DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color, GrAAType aaType,
                 const DeviceSpaceParams& params, const SkMatrix& viewMatrix)
             : INHERITED(ClassID())
             , fHelper(helperArgs, GrAAType::kCoverage)
+            , fAA(GrAAType::kCoverage == aaType)
             , fUseScale(false) {
-        // This expands the outer rect so that after CTM we end up with a half-pixel border
-        SkScalar a = viewMatrix[SkMatrix::kMScaleX];
-        SkScalar b = viewMatrix[SkMatrix::kMSkewX];
-        SkScalar c = viewMatrix[SkMatrix::kMSkewY];
-        SkScalar d = viewMatrix[SkMatrix::kMScaleY];
-        SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
-        SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
+        SkScalar geoDx = 0;
+        SkScalar geoDy = 0;
+        if (fAA) {
+            // This expands the outer rect so that after CTM we end up with a half-pixel border
+            SkScalar a = viewMatrix[SkMatrix::kMScaleX];
+            SkScalar b = viewMatrix[SkMatrix::kMSkewX];
+            SkScalar c = viewMatrix[SkMatrix::kMSkewY];
+            SkScalar d = viewMatrix[SkMatrix::kMScaleY];
+            geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
+            geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
+        }
 
         fEllipses.emplace_back(
                 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius,
@@ -2062,7 +2132,8 @@
                                          params.fCenter.fY - params.fYRadius - geoDy,
                                          params.fCenter.fX + params.fXRadius + geoDx,
                                          params.fCenter.fY + params.fYRadius + geoDy)});
-        this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
+        this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix,
+                                   fAA ? HasAABloat::kYes : HasAABloat::kNo,
                                    IsZeroArea::kNo);
     }
 
@@ -2106,7 +2177,7 @@
     void onPrepareDraws(Target* target) override {
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(
-                new DIEllipseGeometryProcessor(fWideColor, fUseScale, this->viewMatrix(),
+                new DIEllipseGeometryProcessor(fAA, fWideColor, fUseScale, this->viewMatrix(),
                                                this->style()));
 
         QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
@@ -2154,6 +2225,10 @@
             return CombineResult::kCannotCombine;
         }
 
+        if (this->fAA != that->fAA) {
+            return CombineResult::kCannotCombine;
+        }
+
         if (this->style() != that->style()) {
             return CombineResult::kCannotCombine;
         }
@@ -2185,6 +2260,7 @@
     };
 
     Helper fHelper;
+    bool fAA;
     bool fWideColor;
     bool fUseScale;
     SkSTArray<1, Ellipse, true> fEllipses;
@@ -2329,21 +2405,23 @@
     // whether the rrect is only stroked or stroked and filled.
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
+                                          GrAAType aaType,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& devRect,
                                           float devRadius,
                                           float devStrokeWidth,
                                           bool strokeOnly) {
-        return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix,
+        return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), aaType, viewMatrix,
                                                       devRect, devRadius,
                                                       devStrokeWidth, strokeOnly);
     }
-    CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+    CircularRRectOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color, GrAAType aaType,
                     const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius,
                     float devStrokeWidth, bool strokeOnly)
             : INHERITED(ClassID())
             , fViewMatrixIfUsingLocalCoords(viewMatrix)
-            , fHelper(helperArgs, GrAAType::kCoverage) {
+            , fHelper(helperArgs, GrAAType::kCoverage)
+            , fAA(GrAAType::kCoverage == aaType) {
         SkRect bounds = devRect;
         SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
         SkScalar innerRadius = 0.0f;
@@ -2371,18 +2449,20 @@
             bounds.outset(halfWidth, halfWidth);
         }
 
-        // The radii are outset for two reasons. First, it allows the shader to simply perform
-        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
-        // Second, the outer radius is used to compute the verts of the bounding box that is
-        // rendered and the outset ensures the box will cover all partially covered by the rrect
-        // corners.
-        outerRadius += SK_ScalarHalf;
-        innerRadius -= SK_ScalarHalf;
+        this->setBounds(bounds, fAA ? HasAABloat::kYes : HasAABloat::kNo, IsZeroArea::kNo);
 
-        this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
+        if (fAA) {
+            // The radii are outset for two reasons. First, it allows the shader to simply perform
+            // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+            // Second, the outer radius is used to compute the verts of the bounding box that is
+            // rendered and the outset ensures the box will cover all partially covered by the rrect
+            // corners.
+            outerRadius += SK_ScalarHalf;
+            innerRadius -= SK_ScalarHalf;
 
-        // Expand the rect for aa to generate correct vertices.
-        bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+            // Expand the rect for aa to generate correct vertices.
+            bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        }
 
         fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type});
         fVertCount = rrect_type_to_vert_count(type);
@@ -2484,8 +2564,8 @@
 
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(
-                new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor,
-                                            localMatrix));
+                new CircleGeometryProcessor(fAA, !fAllFill, false, false, false, false,
+                                            fWideColor, localMatrix));
 
         sk_sp<const GrBuffer> vertexBuffer;
         int firstVertex;
@@ -2598,6 +2678,10 @@
             return CombineResult::kCannotCombine;
         }
 
+        if (fAA != that->fAA) {
+            return CombineResult::kCannotCombine;
+        }
+
         fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
         fVertCount += that->fVertCount;
         fIndexCount += that->fIndexCount;
@@ -2618,6 +2702,7 @@
     Helper fHelper;
     int fVertCount;
     int fIndexCount;
+    bool fAA;
     bool fAllFill;
     bool fWideColor;
     SkSTArray<1, RRect, true> fRRects;
@@ -2659,6 +2744,7 @@
     // whether the rrect is only stroked or stroked and filled.
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
+                                          GrAAType aaType,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& devRect,
                                           float devXRadius,
@@ -2694,16 +2780,17 @@
             }
         }
         return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint),
-                                                        viewMatrix, devRect,
+                                                        aaType, viewMatrix, devRect,
                                                         devXRadius, devYRadius, devStrokeWidths,
                                                         strokeOnly);
     }
 
-    EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color,
+    EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color, GrAAType aaType,
                       const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
                       float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
             : INHERITED(ClassID())
             , fHelper(helperArgs, GrAAType::kCoverage)
+            , fAA(GrAAType::kCoverage == aaType)
             , fUseScale(false) {
         SkScalar innerXRadius = 0.0f;
         SkScalar innerYRadius = 0.0f;
@@ -2724,9 +2811,11 @@
 
         fStroked = stroked;
         fViewMatrixIfUsingLocalCoords = viewMatrix;
-        this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
-        // Expand the rect for aa in order to generate the correct vertices.
-        bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        this->setBounds(bounds, fAA ? HasAABloat::kYes : HasAABloat::kNo, IsZeroArea::kNo);
+        if (fAA) {
+            // Expand the rect for aa in order to generate the correct vertices.
+            bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        }
         fRRects.emplace_back(
                 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
     }
@@ -2774,7 +2863,7 @@
         }
 
         // Setup geometry processor
-        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
+        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fAA, fStroked, fWideColor, fUseScale,
                                                                    localMatrix));
 
         // drop out the middle quad if we're stroked
@@ -2806,8 +2895,12 @@
             };
 
             // Extend the radii out half a pixel to antialias.
-            SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
-            SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf;
+            SkScalar xOuterRadius = rrect.fXRadius;
+            SkScalar yOuterRadius = rrect.fYRadius;
+            if (fAA) {
+                xOuterRadius += SK_ScalarHalf;
+                yOuterRadius += SK_ScalarHalf;
+            }
 
             SkScalar xMaxOffset = xOuterRadius;
             SkScalar yMaxOffset = yOuterRadius;
@@ -2872,6 +2965,10 @@
             return CombineResult::kCannotCombine;
         }
 
+        if (fAA != that->fAA) {
+            return CombineResult::kCannotCombine;
+        }
+
         if (fHelper.usesLocalCoords() &&
             !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
             return CombineResult::kCannotCombine;
@@ -2893,6 +2990,7 @@
 
     SkMatrix fViewMatrixIfUsingLocalCoords;
     Helper fHelper;
+    bool fAA;
     bool fStroked;
     bool fWideColor;
     bool fUseScale;
@@ -2903,9 +3001,14 @@
 
 static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
                                                GrPaint&& paint,
+                                               GrAAType aaType,
                                                const SkMatrix& viewMatrix,
                                                const SkRRect& rrect,
                                                const SkStrokeRec& stroke) {
+    if (GrAAType::kMSAA == aaType) {
+        return nullptr;
+    }
+
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(rrect.isSimple());
     SkASSERT(!rrect.isOval());
@@ -2963,23 +3066,24 @@
 
     // if the corners are circles, use the circle renderer
     if (isCircular) {
-        return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
+        return CircularRRectOp::Make(context, std::move(paint), aaType, viewMatrix, bounds, xRadius,
                                      scaledStroke.fX, isStrokeOnly);
         // otherwise we use the ellipse renderer
     } else {
-        return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
+        return EllipticalRRectOp::Make(context, std::move(paint), aaType, viewMatrix, bounds,
                                        xRadius, yRadius, scaledStroke, isStrokeOnly);
     }
 }
 
 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
                                                        GrPaint&& paint,
+                                                       GrAAType aaType,
                                                        const SkMatrix& viewMatrix,
                                                        const SkRRect& rrect,
                                                        const SkStrokeRec& stroke,
                                                        const GrShaderCaps* shaderCaps) {
     if (rrect.isOval()) {
-        return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(),
+        return MakeOvalOp(context, std::move(paint), aaType, viewMatrix, rrect.getBounds(),
                           GrStyle(stroke, nullptr), shaderCaps);
     }
 
@@ -2987,17 +3091,21 @@
         return nullptr;
     }
 
-    return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke);
+    return make_rrect_op(context, std::move(paint), aaType, viewMatrix, rrect, stroke);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
                                                       GrPaint&& paint,
+                                                      GrAAType aaType,
                                                       const SkMatrix& viewMatrix,
                                                       const SkRect& oval,
                                                       const GrStyle& style,
                                                       const GrShaderCaps* shaderCaps) {
+    if (GrAAType::kMSAA == aaType) {
+        return nullptr;
+    }
     // we can draw circles
     SkScalar width = oval.width();
     if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
@@ -3015,12 +3123,16 @@
             auto offInterval = style.dashIntervals()[1];
             if (offInterval == 0) {
                 GrStyle strokeStyle(style.strokeRec(), nullptr);
-                return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
+                return MakeOvalOp(context, std::move(paint), aaType, viewMatrix, oval,
                                   strokeStyle, shaderCaps);
             } else if (onInterval == 0) {
                 // There is nothing to draw but we have no way to indicate that here.
                 return nullptr;
             }
+            // TODO: handle non-AA butt caps
+            if (GrAAType::kCoverage != aaType) {
+                return nullptr;
+            }
             auto angularOnInterval = onInterval / r;
             auto angularOffInterval = offInterval / r;
             auto phaseAngle = style.dashPhase() / r;
@@ -3031,7 +3143,7 @@
                                                style.strokeRec().getWidth(), kStartAngle,
                                                angularOnInterval, angularOffInterval, phaseAngle);
         }
-        return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
+        return CircleOp::Make(context, std::move(paint), aaType, viewMatrix, center, r, style);
     }
 
     if (style.pathEffect()) {
@@ -3040,7 +3152,8 @@
 
     // prefer the device space ellipse op for batchability
     if (viewMatrix.rectStaysRect()) {
-        return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec());
+        return EllipseOp::Make(context, std::move(paint), aaType, viewMatrix, oval,
+                               style.strokeRec());
     }
 
     // Otherwise, if we have shader derivative support, render as device-independent
@@ -3051,7 +3164,7 @@
         SkScalar d = viewMatrix[SkMatrix::kMScaleY];
         // Check for near-degenerate matrix
         if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) {
-            return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval,
+            return DIEllipseOp::Make(context, std::move(paint), aaType, viewMatrix, oval,
                                      style.strokeRec());
         }
     }
@@ -3063,11 +3176,15 @@
 
 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
                                                      GrPaint&& paint,
+                                                     GrAAType aaType,
                                                      const SkMatrix& viewMatrix,
                                                      const SkRect& oval, SkScalar startAngle,
                                                      SkScalar sweepAngle, bool useCenter,
                                                      const GrStyle& style,
                                                      const GrShaderCaps* shaderCaps) {
+    if (GrAAType::kMSAA == aaType) {
+        return nullptr;
+    }
     SkASSERT(!oval.isEmpty());
     SkASSERT(sweepAngle);
     SkScalar width = oval.width();
@@ -3080,7 +3197,7 @@
     SkPoint center = {oval.centerX(), oval.centerY()};
     CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle),
                                      useCenter};
-    return CircleOp::Make(context, std::move(paint), viewMatrix,
+    return CircleOp::Make(context, std::move(paint), aaType, viewMatrix,
                           center, width / 2.f, style, &arcParams);
 }
 
@@ -3104,6 +3221,8 @@
         SkRect circle = GrTest::TestSquare(random);
         SkPoint center = {circle.centerX(), circle.centerY()};
         SkScalar radius = circle.width() / 2.f;
+        // Only test kNone and kCoverage aaTypes (kMSAA returns nullptr)
+        GrAAType aaType = random->nextBool() ? GrAAType::kNone : GrAAType::kCoverage;
         SkStrokeRec stroke = GrTest::TestStrokeRec(random);
         CircleOp::ArcParams arcParamsTmp;
         const CircleOp::ArcParams* arcParams = nullptr;
@@ -3113,7 +3232,7 @@
             arcParamsTmp.fUseCenter = random->nextBool();
             arcParams = &arcParamsTmp;
         }
-        std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), viewMatrix,
+        std::unique_ptr<GrDrawOp> op = CircleOp::Make(context, std::move(paint), aaType, viewMatrix,
                                                       center, radius,
                                                       GrStyle(stroke, nullptr), arcParams);
         if (op) {
@@ -3149,23 +3268,29 @@
 }
 
 GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
+    // Only test kNone and kCoverage aaTypes (kMSAA returns nullptr)
+    GrAAType aaType = random->nextBool() ? GrAAType::kNone : GrAAType::kCoverage;
     SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
     SkRect ellipse = GrTest::TestSquare(random);
-    return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
+    return EllipseOp::Make(context, std::move(paint), aaType, viewMatrix, ellipse,
                            GrTest::TestStrokeRec(random));
 }
 
 GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
+    // Only test kNone and kCoverage aaTypes (kMSAA returns nullptr)
+    GrAAType aaType = random->nextBool() ? GrAAType::kNone : GrAAType::kCoverage;
     SkMatrix viewMatrix = GrTest::TestMatrix(random);
     SkRect ellipse = GrTest::TestSquare(random);
-    return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse,
+    return DIEllipseOp::Make(context, std::move(paint), aaType, viewMatrix, ellipse,
                              GrTest::TestStrokeRec(random));
 }
 
 GR_DRAW_OP_TEST_DEFINE(RRectOp) {
+    // Only test kNone and kCoverage aaTypes (kMSAA returns nullptr)
+    GrAAType aaType = random->nextBool() ? GrAAType::kNone : GrAAType::kCoverage;
     SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
     const SkRRect& rrect = GrTest::TestRRectSimple(random);
-    return make_rrect_op(context, std::move(paint), viewMatrix, rrect,
+    return make_rrect_op(context, std::move(paint), aaType, viewMatrix, rrect,
                          GrTest::TestStrokeRec(random));
 }
 
diff --git a/src/gpu/ops/GrOvalOpFactory.h b/src/gpu/ops/GrOvalOpFactory.h
index b0f0034..ff00212 100644
--- a/src/gpu/ops/GrOvalOpFactory.h
+++ b/src/gpu/ops/GrOvalOpFactory.h
@@ -20,6 +20,7 @@
 struct SkRect;
 class SkRRect;
 class SkStrokeRec;
+enum class GrAAType : unsigned int;
 
 /*
  * This namespace wraps helper functions that draw ovals, rrects, and arcs (filled & stroked)
@@ -28,6 +29,7 @@
 public:
     static std::unique_ptr<GrDrawOp> MakeOvalOp(GrRecordingContext*,
                                                 GrPaint&&,
+                                                GrAAType aaType,
                                                 const SkMatrix&,
                                                 const SkRect& oval,
                                                 const GrStyle& style,
@@ -35,6 +37,7 @@
 
     static std::unique_ptr<GrDrawOp> MakeRRectOp(GrRecordingContext*,
                                                  GrPaint&&,
+                                                 GrAAType aaType,
                                                  const SkMatrix&,
                                                  const SkRRect&,
                                                  const SkStrokeRec&,
@@ -42,6 +45,7 @@
 
     static std::unique_ptr<GrDrawOp> MakeArcOp(GrRecordingContext*,
                                                GrPaint&&,
+                                               GrAAType aaType,
                                                const SkMatrix&,
                                                const SkRect& oval,
                                                SkScalar startAngle,