Add MSAA and non-aa modes to GrFillRRect Op

Adds a non-aa mode and an MSAA mode that uses the sample mask. Also
adds a new cap to decide whether we prefer this new sample mask Op for
large round rects, or whether it's faster to just continue drawing
them as paths like before.

Bug: skia:
Change-Id: Ic344ace26e7889c312c3040ad345b4d9a717f96d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/204135
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index ead28f6..12d4300 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -21,34 +21,41 @@
 
 // Hardware derivatives are not always accurate enough for highly elliptical corners. This method
 // checks to make sure the corners will still all look good if we use HW derivatives.
-static bool can_use_hw_derivatives(const GrShaderCaps&, const SkMatrix&, const SkRRect&);
+static bool can_use_hw_derivatives_with_coverage(
+        const GrShaderCaps&, const SkMatrix&, const SkRRect&);
 
 std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make(
-        GrRecordingContext* ctx, const SkMatrix& viewMatrix, const SkRRect& rrect,
+        GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect,
         const GrCaps& caps, GrPaint&& paint) {
     if (!caps.instanceAttribSupport()) {
         return nullptr;
     }
 
-    // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we already
-    // use HW derivatives. The only trick will be adjusting the AA outset to account for
-    // perspective.  (i.e., outset = 0.5 * z.)
-    if (viewMatrix.hasPerspective()) {
-        return nullptr;
-    }
-
-    GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
-    return pool->allocate<GrFillRRectOp>(*caps.shaderCaps(), viewMatrix, rrect, std::move(paint));
-}
-
-GrFillRRectOp::GrFillRRectOp(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
-                             const SkRRect& rrect, GrPaint&& paint)
-        : GrDrawOp(ClassID())
-        , fOriginalColor(paint.getColor4f())
-        , fLocalRect(rrect.rect())
-        , fProcessors(std::move(paint)) {
-    if (can_use_hw_derivatives(shaderCaps, viewMatrix, rrect)) {
-        fFlags |= Flags::kUseHWDerivatives;
+    Flags flags = Flags::kNone;
+    if (GrAAType::kCoverage == aaType) {
+        // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we
+        // already use HW derivatives. The only trick will be adjusting the AA outset to account for
+        // perspective. (i.e., outset = 0.5 * z.)
+        if (viewMatrix.hasPerspective()) {
+            return nullptr;
+        }
+        if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) {
+            // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms
+            // in coverage mode. We use them as long as the approximation will be accurate enough.
+            flags |= Flags::kUseHWDerivatives;
+        }
+    } else {
+        if (GrAAType::kMSAA == aaType) {
+            if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleVariablesSupport()) {
+                return nullptr;
+            }
+        }
+        if (viewMatrix.hasPerspective()) {
+            // HW derivatives are consistently slower on all platforms in sample mask mode. We
+            // therefore only use them when there is perspective, since then we can't interpolate
+            // the symbolic screen-space gradient.
+            flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective;
+        }
     }
 
     // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space.
@@ -60,23 +67,61 @@
     // Map to device space.
     m.postConcat(viewMatrix);
 
-    // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's
-    // device-space quad, it's quite simple to find the bounding rectangle:
-    SkASSERT(!m.hasPerspective());
-    SkRect bounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0);
-    bounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()),
-                  SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY()));
-    this->setBounds(bounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+    SkRect devBounds;
+    if (!(flags & Flags::kHasPerspective)) {
+        // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's
+        // device-space quad, it's quite simple to find the bounding rectangle:
+        devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0);
+        devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()),
+                         SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY()));
+    } else {
+        viewMatrix.mapRect(&devBounds, rrect.rect());
+    }
+
+    if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) {
+        // We are on a platform that prefers fine triangles instead of using the sample mask. See if
+        // the round rect is large enough that it will be faster for us to send it off to the
+        // default path renderer instead. The 200x200 threshold was arrived at using the
+        // "shapes_rrect" benchmark on an ARM Galaxy S9.
+        if (devBounds.height() * devBounds.width() > 200 * 200) {
+            return nullptr;
+        }
+    }
+
+    GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
+    return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds);
+}
+
+GrFillRRectOp::GrFillRRectOp(
+        GrAAType aaType, const SkRRect& rrect, Flags flags,
+        const SkMatrix& totalShapeMatrix, GrPaint&& paint, const SkRect& devBounds)
+        : GrDrawOp(ClassID())
+        , fAAType(aaType)
+        , fOriginalColor(paint.getColor4f())
+        , fLocalRect(rrect.rect())
+        , fFlags(flags)
+        , fProcessors(std::move(paint)) {
+    SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective());
+    this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
 
     // Write the matrix attribs.
-    this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
-    this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
+    const SkMatrix& m = totalShapeMatrix;
+    if (!(fFlags & Flags::kHasPerspective)) {
+        // Affine 2D transformation (float2x2 plus float2 translate).
+        SkASSERT(!m.hasPerspective());
+        this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
+        this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
+    } else {
+        // Perspective float3x3 transformation matrix.
+        SkASSERT(m.hasPerspective());
+        m.get9(this->appendInstanceData<float>(9));
+    }
 
     // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
     Sk4f radiiX, radiiY;
     Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY);
-    (radiiX * (2/(r - l))).store(this->appendInstanceData<float>(4));
-    (radiiY * (2/(b - t))).store(this->appendInstanceData<float>(4));
+    (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4));
+    (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4));
 
     // We will write the color and local rect attribs during finalize().
 }
@@ -132,13 +177,73 @@
     }
 }
 
-namespace {
+class GrFillRRectOp::Processor : public GrGeometryProcessor {
+public:
+    Processor(GrAAType aaType, Flags flags)
+            : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID)
+            , fAAType(aaType)
+            , fFlags(flags) {
+        int numVertexAttribs = (GrAAType::kCoverage == fAAType) ? 3 : 2;
+        this->setVertexAttributes(kVertexAttribs, numVertexAttribs);
 
-// Our round rect geometry consists of an inset octagon with solid coverage, surrounded by linear
+        if (!(flags & Flags::kHasPerspective)) {
+            // Affine 2D transformation (float2x2 plus float2 translate).
+            fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+            fInstanceAttribs.emplace_back(
+                    "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
+        } else {
+            // Perspective float3x3 transformation matrix.
+            fInstanceAttribs.emplace_back("persp_x", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+            fInstanceAttribs.emplace_back("persp_y", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+            fInstanceAttribs.emplace_back("persp_z", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+        }
+        fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        fColorAttrib = &fInstanceAttribs.push_back(
+                MakeColorAttribute("color", (flags & Flags::kWideColor)));
+        if (fFlags & Flags::kHasLocalCoords) {
+            fInstanceAttribs.emplace_back(
+                    "local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        }
+        this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count());
+
+        if (GrAAType::kMSAA == fAAType) {
+            this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
+        }
+    }
+
+    const char* name() const override { return "GrFillRRectOp::Processor"; }
+
+    void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
+        b->add32(((uint32_t)fFlags << 16) | (uint32_t)fAAType);
+    }
+
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
+
+private:
+    static constexpr Attribute kVertexAttribs[] = {
+            {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+            {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+            // Coverage only.
+            {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
+
+    const GrAAType fAAType;
+    const Flags fFlags;
+
+    SkSTArray<6, Attribute> fInstanceAttribs;
+    const Attribute* fColorAttrib;
+
+    class CoverageImpl;
+    class MSAAImpl;
+};
+
+constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[];
+
+// Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear
 // coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal
 // edges. The Vertex struct tells the shader where to place its vertex within a normalized
 // ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode.
-struct Vertex {
+struct CoverageVertex {
     std::array<float, 4> fRadiiSelector;
     std::array<float, 2> fCorner;
     std::array<float, 2> fRadiusOutset;
@@ -152,7 +257,7 @@
 // rectangles.
 static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2);
 
-static constexpr Vertex kVertexData[] = {
+static constexpr CoverageVertex kCoverageVertexData[] = {
         // Left inset edge.
         {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{+1,0}},  1,  1},
         {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{+1,0}},  1,  1},
@@ -219,9 +324,9 @@
         {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}},  {{-1,+1}},  0,  0},
         {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}},  {{-1,+1}},  0,  0}};
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
 
-static constexpr uint16_t kIndexData[] = {
+static constexpr uint16_t kCoverageIndexData[] = {
         // Inset octagon (solid coverage).
         0, 1, 7,
         1, 2, 7,
@@ -260,64 +365,18 @@
         39, 36, 38,
         36, 38, 37};
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
 
-}
-
-class GrFillRRectOp::Processor : public GrGeometryProcessor {
-public:
-    Processor(Flags flags)
-            : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID)
-            , fFlags(flags) {
-        this->setVertexAttributes(kVertexAttribs, 3);
-        fInSkew = { "skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType };
-        fInTranslate = { "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType };
-        fInRadiiX = { "radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType };
-        fInRadiiY = { "radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType };
-        fInColor = MakeColorAttribute("color", (flags & Flags::kWideColor));
-        fInLocalRect = {"local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-
-        this->setInstanceAttributes(&fInSkew, (flags & Flags::kHasLocalCoords) ? 6 : 5);
-        SkASSERT(this->vertexStride() == sizeof(Vertex));
-    }
-
-    const char* name() const override { return "GrFillRRectOp::Processor"; }
-
-    void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
-        b->add32(static_cast<uint32_t>(fFlags));
-    }
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
-
-private:
-    static constexpr Attribute kVertexAttribs[] = {
-            {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
-
-    Attribute fInSkew;
-    Attribute fInTranslate;
-    Attribute fInRadiiX;
-    Attribute fInRadiiY;
-    Attribute fInColor;
-    Attribute fInLocalRect;  // Conditional.
-
-    const Flags fFlags;
-
-    class Impl;
-};
-
-constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[];
-
-class GrFillRRectOp::Processor::Impl : public GrGLSLGeometryProcessor {
-public:
+class GrFillRRectOp::Processor::CoverageImpl : public GrGLSLGeometryProcessor {
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
         const auto& proc = args.fGP.cast<Processor>();
         bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
 
+        SkASSERT(proc.vertexStride() == sizeof(CoverageVertex));
+
         GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
         varyings->emitAttributes(proc);
-        varyings->addPassThroughAttribute(proc.fInColor, args.fOutputColor,
+        varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
                                           GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
 
         // Emit the vertex shader.
@@ -388,6 +447,7 @@
                              args.fFPCoordTransformHandler);
 
         // Transform to device space.
+        SkASSERT(!(proc.fFlags & Flags::kHasPerspective));
         v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
         v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
@@ -420,7 +480,7 @@
         f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn());
         f->codeAppendf("half coverage;");
         f->codeAppendf("if (0 == x_plus_1) {");
-        f->codeAppendf(    "coverage = half(y);");  // We are a non-arc pixel (i.e., linear coverage).
+        f->codeAppendf(    "coverage = half(y);");  // We are a non-arc pixel (linear coverage).
         f->codeAppendf("} else {");
         f->codeAppendf(    "float fn = x_plus_1 * (x_plus_1 - 2);");  // fn = (x+1)*(x-1) = x^2-1
         f->codeAppendf(    "fn = fma(y,y, fn);");  // fn = x^2 + y^2 - 1
@@ -443,9 +503,195 @@
     }
 };
 
+// Our MSAA geometry consists of an inset octagon with full sample mask coverage, circumscribed
+// by a larger octagon that modifies the sample mask for the arc at each corresponding corner.
+struct MSAAVertex {
+    std::array<float, 4> fRadiiSelector;
+    std::array<float, 2> fCorner;
+    std::array<float, 2> fRadiusOutset;
+};
+
+static constexpr MSAAVertex kMSAAVertexData[] = {
+        // Left edge. (Negative radii selector indicates this is not an arc section.)
+        {{{0,0,0,-1}},  {{-1,+1}},  {{0,-1}}},
+        {{{-1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
+
+        // Top edge.
+        {{{-1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
+        {{{0,-1,0,0}},  {{+1,-1}},  {{-1,0}}},
+
+        // Right edge.
+        {{{0,-1,0,0}},  {{+1,-1}},  {{0,+1}}},
+        {{{0,0,-1,0}},  {{+1,+1}},  {{0,-1}}},
+
+        // Bottom edge.
+        {{{0,0,-1,0}},  {{+1,+1}},  {{-1,0}}},
+        {{{0,0,0,-1}},  {{-1,+1}},  {{+1,0}}},
+
+        // Top-left corner.
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}}},
+
+        // Top-right corner.
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}}},
+
+        // Bottom-right corner.
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}}},
+
+        // Bottom-left corner.
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}}}};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
+
+static constexpr uint16_t kMSAAIndexData[] = {
+        // Inset octagon. (Full sample mask.)
+        0, 1, 2,
+        0, 2, 3,
+        0, 3, 6,
+        3, 4, 5,
+        3, 5, 6,
+        6, 7, 0,
+
+        // Top-left arc. (Sample mask is set to the arc.)
+         8,  9, 10,
+         9, 11, 10,
+
+        // Top-right arc.
+        12, 13, 14,
+        13, 15, 14,
+
+        // Bottom-right arc.
+        16, 17, 18,
+        17, 19, 18,
+
+        // Bottom-left arc.
+        20, 21, 22,
+        21, 23, 22};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
+
+class GrFillRRectOp::Processor::MSAAImpl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const auto& proc = args.fGP.cast<Processor>();
+        bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
+        bool hasPerspective = (proc.fFlags & Flags::kHasPerspective);
+        bool hasLocalCoords = (proc.fFlags & Flags::kHasLocalCoords);
+        SkASSERT(useHWDerivatives == hasPerspective);
+
+        SkASSERT(proc.vertexStride() == sizeof(MSAAVertex));
+
+        // Emit the vertex shader.
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+
+        GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
+        varyings->emitAttributes(proc);
+        varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
+                                          GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
+
+        // Unpack vertex attribs.
+        v->codeAppendf("float2 corner = corner_and_radius_outsets.xy;");
+        v->codeAppendf("float2 radius_outset = corner_and_radius_outsets.zw;");
+
+        // Identify our radii.
+        v->codeAppend("float2 radii;");
+        v->codeAppend("radii.x = dot(radii_selector, radii_x);");
+        v->codeAppend("radii.y = dot(radii_selector, radii_y);");
+        v->codeAppendf("bool is_arc_section = (radii.x > 0);");
+        v->codeAppendf("radii = abs(radii);");
+
+        // Find our vertex position, adjusted for radii. Our rect is drawn in normalized
+        // [-1,-1,+1,+1] space.
+        v->codeAppend("float2 vertexpos = corner + radius_outset * radii;");
+
+        // Emit transforms.
+        GrShaderVar localCoord("", kFloat2_GrSLType);
+        if (hasLocalCoords) {
+            v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
+                                               "local_rect.zw * (1 + vertexpos)) * .5;");
+            localCoord.set(kFloat2_GrSLType, "localcoord");
+        }
+        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
+                             args.fFPCoordTransformHandler);
+
+        // Transform to device space.
+        if (!hasPerspective) {
+            v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
+            v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
+            gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
+        } else {
+            v->codeAppend("float3x3 persp_matrix = float3x3(persp_x, persp_y, persp_z);");
+            v->codeAppend("float3 devcoord = float3(vertexpos, 1) * persp_matrix;");
+            gpArgs->fPositionVar.set(kFloat3_GrSLType, "devcoord");
+        }
+
+        // Determine normalized arc coordinates for the implicit function.
+        GrGLSLVarying arcCoord((useHWDerivatives) ? kFloat2_GrSLType : kFloat4_GrSLType);
+        varyings->addVarying("arccoord", &arcCoord);
+        v->codeAppendf("if (is_arc_section) {");
+        v->codeAppendf(    "%s.xy = 1 - abs(radius_outset);", arcCoord.vsOut());
+        if (!useHWDerivatives) {
+            // The gradient is order-1: Interpolate it across arccoord.zw.
+            // This doesn't work with perspective.
+            SkASSERT(!hasPerspective);
+            v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
+            v->codeAppendf("%s.zw = derivatives * (%s.xy/radii * corner * 2);",
+                           arcCoord.vsOut(), arcCoord.vsOut());
+        }
+        v->codeAppendf("} else {");
+        if (useHWDerivatives) {
+            v->codeAppendf("%s = float2(0);", arcCoord.vsOut());
+        } else {
+            v->codeAppendf("%s = float4(0);", arcCoord.vsOut());
+        }
+        v->codeAppendf("}");
+
+        // Emit the fragment shader.
+        GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
+
+        f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
+
+        // If x,y == 0, then we are drawing a triangle that does not track an arc.
+        f->codeAppendf("if (float2(0) != %s.xy) {", arcCoord.fsIn());
+        f->codeAppendf(    "float fn = dot(%s.xy, %s.xy) - 1;", arcCoord.fsIn(), arcCoord.fsIn());
+        if (GrAAType::kMSAA == proc.fAAType) {
+            using ScopeFlags = GrGLSLFPFragmentBuilder::ScopeFlags;
+            if (!useHWDerivatives) {
+                f->codeAppendf("float2 grad = %s.zw;", arcCoord.fsIn());
+                f->applyFnToMultisampleMask("fn", "grad", ScopeFlags::kInsidePerPrimitiveBranch);
+            } else {
+                f->applyFnToMultisampleMask("fn", nullptr, ScopeFlags::kInsidePerPrimitiveBranch);
+            }
+        } else {
+            f->codeAppendf("if (fn > 0) {");
+            f->codeAppendf(    "%s = half4(0);", args.fOutputCoverage);
+            f->codeAppendf("}");
+        }
+        f->codeAppendf("}");
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+};
+
 GrGLSLPrimitiveProcessor* GrFillRRectOp::Processor::createGLSLInstance(
         const GrShaderCaps&) const {
-    return new Impl();
+    if (GrAAType::kCoverage != fAAType) {
+        return new MSAAImpl();
+    }
+    return new CoverageImpl();
 }
 
 void GrFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
@@ -453,26 +699,50 @@
         return;  // Setup failed.
     }
 
-    GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
+    sk_sp<const GrBuffer> indexBuffer, vertexBuffer;
+    int indexCount;
 
-    sk_sp<const GrBuffer> indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
-            GrGpuBufferType::kIndex, sizeof(kIndexData), kIndexData, gIndexBufferKey);
-    if (!indexBuffer) {
+    if (GrAAType::kCoverage == fAAType) {
+        GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
+
+        indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kIndex, sizeof(kCoverageIndexData), kCoverageIndexData,
+                gCoverageIndexBufferKey);
+
+        GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
+
+        vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kVertex, sizeof(kCoverageVertexData), kCoverageVertexData,
+                gCoverageVertexBufferKey);
+
+        indexCount = SK_ARRAY_COUNT(kCoverageIndexData);
+    } else {
+        GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
+
+        indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kIndex, sizeof(kMSAAIndexData), kMSAAIndexData,
+                gMSAAIndexBufferKey);
+
+        GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
+
+        vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kVertex, sizeof(kMSAAVertexData), kMSAAVertexData,
+                gMSAAVertexBufferKey);
+
+        indexCount = SK_ARRAY_COUNT(kMSAAIndexData);
+    }
+
+    if (!indexBuffer || !vertexBuffer) {
         return;
     }
 
-    GR_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey);
-
-    sk_sp<const GrBuffer> vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
-            GrGpuBufferType::kVertex, sizeof(kVertexData), kVertexData, gVertexBufferKey);
-    if (!vertexBuffer) {
-        return;
-    }
-
-    Processor proc(fFlags);
+    Processor proc(fAAType, fFlags);
     SkASSERT(proc.instanceStride() == (size_t)fInstanceStride);
 
     GrPipeline::InitArgs initArgs;
+    if (GrAAType::kMSAA == fAAType) {
+        initArgs.fFlags = GrPipeline::kHWAntialias_Flag;
+    }
     initArgs.fCaps = &flushState->caps();
     initArgs.fResourceProvider = flushState->resourceProvider();
     initArgs.fDstProxy = flushState->drawOpArgs().fDstProxy;
@@ -481,15 +751,16 @@
     GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
 
     GrMesh mesh(GrPrimitiveType::kTriangles);
-    mesh.setIndexedInstanced(std::move(indexBuffer), SK_ARRAY_COUNT(kIndexData), fInstanceBuffer,
-                             fInstanceCount, fBaseInstance, GrPrimitiveRestart::kNo);
+    mesh.setIndexedInstanced(
+            std::move(indexBuffer), indexCount, fInstanceBuffer, fInstanceCount, fBaseInstance,
+            GrPrimitiveRestart::kNo);
     mesh.setVertexData(std::move(vertexBuffer));
-    flushState->rtCommandBuffer()->draw(proc, pipeline, &fixedDynamicState, nullptr, &mesh, 1,
-                                        this->bounds());
+    flushState->rtCommandBuffer()->draw(
+            proc, pipeline, &fixedDynamicState, nullptr, &mesh, 1, this->bounds());
 }
 
 // Will the given corner look good if we use HW derivatives?
-static bool can_use_hw_derivatives(const Sk2f& devScale, const Sk2f& cornerRadii) {
+static bool can_use_hw_derivatives_with_coverage(const Sk2f& devScale, const Sk2f& cornerRadii) {
     Sk2f devRadii = devScale * cornerRadii;
     if (devRadii[1] < devRadii[0]) {
         devRadii = SkNx_shuffle<1,0>(devRadii);
@@ -500,13 +771,14 @@
     return minDevRadius * minDevRadius * 5 > devRadii[1];
 }
 
-static bool can_use_hw_derivatives(const Sk2f& devScale, const SkVector& cornerRadii) {
-    return can_use_hw_derivatives(devScale, Sk2f::Load(&cornerRadii));
+static bool can_use_hw_derivatives_with_coverage(
+        const Sk2f& devScale, const SkVector& cornerRadii) {
+    return can_use_hw_derivatives_with_coverage(devScale, Sk2f::Load(&cornerRadii));
 }
 
 // Will the given round rect look good if we use HW derivatives?
-static bool can_use_hw_derivatives(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
-                                   const SkRRect& rrect) {
+static bool can_use_hw_derivatives_with_coverage(
+        const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) {
     if (!shaderCaps.shaderDerivativeSupport()) {
         return false;
     }
@@ -521,27 +793,27 @@
 
         case SkRRect::kOval_Type:
         case SkRRect::kSimple_Type:
-            return can_use_hw_derivatives(devScale, rrect.getSimpleRadii());
+            return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii());
 
         case SkRRect::kNinePatch_Type: {
             Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect));
             Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2);
             Sk2f minRadii = Sk2f::Min(r0, r1);
             Sk2f maxRadii = Sk2f::Max(r0, r1);
-            return can_use_hw_derivatives(devScale, Sk2f(minRadii[0], maxRadii[1])) &&
-                   can_use_hw_derivatives(devScale, Sk2f(maxRadii[0], minRadii[1]));
+            return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) &&
+                   can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1]));
         }
 
         case SkRRect::kComplex_Type: {
             for (int i = 0; i < 4; ++i) {
                 auto corner = static_cast<SkRRect::Corner>(i);
-                if (!can_use_hw_derivatives(devScale, rrect.radii(corner))) {
+                if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) {
                     return false;
                 }
             }
             return true;
         }
     }
-    SK_ABORT("Unreachable code.");
+    SK_ABORT("Invalid round rect type.");
     return false;  // Add this return to keep GCC happy.
 }