Consolidate stroke-rect ops and use GrFillRectOp instead of GrNonAA/AARectOp

Bug: skia:
Change-Id: Iee57bc970a026de2ad5a0758153e9cbb20753fa1
Reviewed-on: https://skia-review.googlesource.com/c/173105
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/ops/GrAAFillRectOp.cpp b/src/gpu/ops/GrAAFillRectOp.cpp
deleted file mode 100644
index 3aa05bb..0000000
--- a/src/gpu/ops/GrAAFillRectOp.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
-#include "GrResourceKey.h"
-#include "GrResourceProvider.h"
-#include "GrTypes.h"
-#include "SkMatrix.h"
-#include "SkMatrixPriv.h"
-#include "SkRect.h"
-#include "SkPointPriv.h"
-#include "ops/GrSimpleMeshDrawOpHelper.h"
-#include <new>
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
-
-static inline bool view_matrix_ok_for_aa_fill_rect(const SkMatrix& viewMatrix) {
-    return viewMatrix.preservesRightAngles();
-}
-
-static inline void set_inset_fan(SkPoint* pts, size_t stride, const SkRect& r, SkScalar dx,
-                                 SkScalar dy) {
-    SkPointPriv::SetRectFan(pts, r.fLeft + dx, r.fTop + dy, r.fRight - dx, r.fBottom - dy, stride);
-}
-
-static const int kNumAAFillRectsInIndexBuffer = 256;
-static const int kVertsPerAAFillRect = 8;
-static const int kIndicesPerAAFillRect = 30;
-
-static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
-    GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
-
-    // clang-format off
-    static const uint16_t gFillAARectIdx[] = {
-        0, 1, 5, 5, 4, 0,
-        1, 2, 6, 6, 5, 1,
-        2, 3, 7, 7, 6, 2,
-        3, 0, 4, 4, 7, 3,
-        4, 5, 6, 6, 7, 4,
-    };
-    // clang-format on
-
-    GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
-    return resourceProvider->findOrCreatePatternedIndexBuffer(
-            gFillAARectIdx, kIndicesPerAAFillRect, kNumAAFillRectsInIndexBuffer,
-            kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
-}
-
-static void generate_aa_fill_rect_geometry(intptr_t verts,
-                                           size_t vertexStride,
-                                           GrColor color,
-                                           const SkMatrix& viewMatrix,
-                                           const SkRect& rect,
-                                           const SkRect& devRect,
-                                           bool tweakAlphaForCoverage,
-                                           const SkMatrix* localMatrix) {
-    SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
-    SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vertexStride);
-
-    SkScalar inset;
-
-    if (viewMatrix.rectStaysRect()) {
-        inset = SkMinScalar(devRect.width(), SK_Scalar1);
-        inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height());
-
-        set_inset_fan(fan0Pos, vertexStride, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
-        set_inset_fan(fan1Pos, vertexStride, devRect, inset, inset);
-    } else {
-        // compute transformed (1, 0) and (0, 1) vectors
-        SkVector vec[2] = {{viewMatrix[SkMatrix::kMScaleX], viewMatrix[SkMatrix::kMSkewY]},
-                           {viewMatrix[SkMatrix::kMSkewX], viewMatrix[SkMatrix::kMScaleY]}};
-
-        SkScalar len1 = SkPoint::Normalize(&vec[0]);
-        vec[0].scale(SK_ScalarHalf);
-        SkScalar len2 = SkPoint::Normalize(&vec[1]);
-        vec[1].scale(SK_ScalarHalf);
-
-        inset = SkMinScalar(len1 * rect.width(), SK_Scalar1);
-        inset = SK_ScalarHalf * SkMinScalar(inset, len2 * rect.height());
-
-        // create the rotated rect
-        SkPointPriv::SetRectFan(fan0Pos, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
-                vertexStride);
-        SkMatrixPriv::MapPointsWithStride(viewMatrix, fan0Pos, vertexStride, 4);
-
-        // Now create the inset points and then outset the original
-        // rotated points
-
-        // TL
-        *((SkPoint*)((intptr_t)fan1Pos + 0 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) + vec[0] + vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) -= vec[0] + vec[1];
-        // BL
-        *((SkPoint*)((intptr_t)fan1Pos + 1 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) + vec[0] - vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) -= vec[0] - vec[1];
-        // BR
-        *((SkPoint*)((intptr_t)fan1Pos + 2 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) - vec[0] - vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) += vec[0] + vec[1];
-        // TR
-        *((SkPoint*)((intptr_t)fan1Pos + 3 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) - vec[0] + vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) += vec[0] - vec[1];
-    }
-
-    if (localMatrix) {
-        SkMatrix invViewMatrix;
-        if (!viewMatrix.invert(&invViewMatrix)) {
-            SkDebugf("View matrix is non-invertible, local coords will be wrong.");
-            invViewMatrix = SkMatrix::I();
-        }
-        SkMatrix localCoordMatrix;
-        localCoordMatrix.setConcat(*localMatrix, invViewMatrix);
-        SkPoint* fan0Loc = reinterpret_cast<SkPoint*>(verts + sizeof(SkPoint) + sizeof(GrColor));
-        SkMatrixPriv::MapPointsWithStride(localCoordMatrix, fan0Loc, vertexStride, fan0Pos,
-                                          vertexStride, 8);
-    }
-
-    // Make verts point to vertex color and then set all the color and coverage vertex attrs
-    // values.
-    verts += sizeof(SkPoint);
-
-    // The coverage offset is always the last vertex attribute
-    intptr_t coverageOffset = vertexStride - sizeof(GrColor) - sizeof(SkPoint);
-    for (int i = 0; i < 4; ++i) {
-        if (tweakAlphaForCoverage) {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = 0;
-        } else {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
-            *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = 0;
-        }
-    }
-
-    int scale;
-    if (inset < SK_ScalarHalf) {
-        scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
-        SkASSERT(scale >= 0 && scale <= 255);
-    } else {
-        scale = 0xff;
-    }
-
-    verts += 4 * vertexStride;
-
-    float innerCoverage = GrNormalizeByteToFloat(scale);
-    GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
-
-    for (int i = 0; i < 4; ++i) {
-        if (tweakAlphaForCoverage) {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = scaledColor;
-        } else {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
-            *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = innerCoverage;
-        }
-    }
-}
-
-namespace {
-
-class AAFillRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect& devRect,
-                                          const SkMatrix* localMatrix,
-                                          const GrUserStencilSettings* stencil) {
-        SkASSERT(view_matrix_ok_for_aa_fill_rect(viewMatrix));
-        return Helper::FactoryHelper<AAFillRectOp>(context, std::move(paint), viewMatrix, rect,
-                                                   devRect, localMatrix, stencil);
-    }
-
-    AAFillRectOp(const Helper::MakeArgs& helperArgs,
-                 const SkPMColor4f& color,
-                 const SkMatrix& viewMatrix,
-                 const SkRect& rect,
-                 const SkRect& devRect,
-                 const SkMatrix* localMatrix,
-                 const GrUserStencilSettings* stencil)
-            : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencil) {
-        if (localMatrix) {
-            void* mem = fRectData.push_back_n(sizeof(RectWithLocalMatrixInfo));
-            new (mem) RectWithLocalMatrixInfo(color, viewMatrix, rect, devRect, *localMatrix);
-        } else {
-            void* mem = fRectData.push_back_n(sizeof(RectInfo));
-            new (mem) RectInfo(color, viewMatrix, rect, devRect);
-        }
-        IsZeroArea zeroArea =
-                (!rect.width() || !rect.height()) ? IsZeroArea::kYes : IsZeroArea::kNo;
-        this->setBounds(devRect, HasAABloat::kYes, zeroArea);
-        fRectCnt = 1;
-    }
-
-    const char* name() const override { return "AAFillRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.append(INHERITED::dumpInfo());
-        str.appendf("# combined: %d\n", fRectCnt);
-        const RectInfo* info = this->first();
-        for (int i = 0; i < fRectCnt; ++i) {
-            const SkRect& rect = info->rect();
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        info->color().toBytes_RGBA(), rect.fLeft, rect.fTop, rect.fRight,
-                        rect.fBottom);
-            info = this->next(info);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f color = this->first()->color();
-        auto result = fHelper.xpRequiresDstTexture(
-                caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, &color);
-        this->first()->setColor(color);
-        return result;
-    }
-
-private:
-    void onPrepareDraws(Target* target) override {
-        using namespace GrDefaultGeoProcFactory;
-
-        Color color(Color::kPremulGrColorAttribute_Type);
-        Coverage::Type coverageType = Coverage::kSolid_Type;
-        if (!fHelper.compatibleWithAlphaAsCoverage()) {
-            coverageType = Coverage::kAttribute_Type;
-        }
-        LocalCoords lc = LocalCoords::kUnused_Type;
-        if (fHelper.usesLocalCoords()) {
-            lc = LocalCoords::kHasExplicit_Type;
-        }
-
-        sk_sp<GrGeometryProcessor> gp =
-                GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color, coverageType,
-                                              lc, SkMatrix::I());
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-
-        size_t vertexStride = gp->vertexStride();
-
-        sk_sp<const GrBuffer> indexBuffer = get_index_buffer(target->resourceProvider());
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, vertexStride, indexBuffer.get(),
-                             kVertsPerAAFillRect, kIndicesPerAAFillRect, fRectCnt);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        const RectInfo* info = this->first();
-        const SkMatrix* localMatrix = nullptr;
-        for (int i = 0; i < fRectCnt; i++) {
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerAAFillRect * vertexStride;
-            if (fHelper.usesLocalCoords()) {
-                if (info->hasLocalMatrix()) {
-                    localMatrix = &static_cast<const RectWithLocalMatrixInfo*>(info)->localMatrix();
-                } else {
-                    localMatrix = &SkMatrix::I();
-                }
-            }
-            // TODO4F: Preserve float colors
-            generate_aa_fill_rect_geometry(verts, vertexStride, info->color().toBytes_RGBA(),
-                                           info->viewMatrix(), info->rect(), info->devRect(),
-                                           fHelper.compatibleWithAlphaAsCoverage(), localMatrix);
-            info = this->next(info);
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        AAFillRectOp* that = t->cast<AAFillRectOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-
-        fRectData.push_back_n(that->fRectData.count(), that->fRectData.begin());
-        fRectCnt += that->fRectCnt;
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-    public:
-        RectInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix, const SkRect& rect,
-                 const SkRect& devRect)
-                : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kNo) {}
-        bool hasLocalMatrix() const { return HasLocalMatrix::kYes == fHasLocalMatrix; }
-        const SkPMColor4f& color() const { return fColor; }
-        const SkMatrix& viewMatrix() const { return fViewMatrix; }
-        const SkRect& rect() const { return fRect; }
-        const SkRect& devRect() const { return fDevRect; }
-
-        void setColor(const SkPMColor4f& color) { fColor = color; }
-
-    protected:
-        enum class HasLocalMatrix : uint32_t { kNo, kYes };
-
-        RectInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix, const SkRect& rect,
-                 const SkRect& devRect, HasLocalMatrix hasLM)
-                : fHasLocalMatrix(hasLM)
-                , fColor(color)
-                , fViewMatrix(viewMatrix)
-                , fRect(rect)
-                , fDevRect(devRect) {}
-
-        HasLocalMatrix fHasLocalMatrix;
-        SkPMColor4f fColor;
-        SkMatrix fViewMatrix;
-        SkRect fRect;
-        SkRect fDevRect;
-    };
-
-    struct RectWithLocalMatrixInfo : public RectInfo {
-    public:
-        RectWithLocalMatrixInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix,
-                                const SkRect& rect, const SkRect& devRect,
-                                const SkMatrix& localMatrix)
-                : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kYes)
-                , fLocalMatrix(localMatrix) {}
-        const SkMatrix& localMatrix() const { return fLocalMatrix; }
-
-    private:
-        SkMatrix fLocalMatrix;
-    };
-
-    RectInfo* first() { return reinterpret_cast<RectInfo*>(fRectData.begin()); }
-    const RectInfo* first() const { return reinterpret_cast<const RectInfo*>(fRectData.begin()); }
-    const RectInfo* next(const RectInfo* prev) const {
-        intptr_t next =
-                reinterpret_cast<intptr_t>(prev) +
-                (prev->hasLocalMatrix() ? sizeof(RectWithLocalMatrixInfo) : sizeof(RectInfo));
-        return reinterpret_cast<const RectInfo*>(next);
-    }
-
-    SkSTArray<4 * sizeof(RectWithLocalMatrixInfo), uint8_t, true> fRectData;
-    Helper fHelper;
-    int fRectCnt;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-
-std::unique_ptr<GrDrawOp> MakeAAFill(GrContext* context,
-                                     GrPaint&& paint,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& rect,
-                                     const GrUserStencilSettings* stencil) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              nullptr, stencil);
-}
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrContext* context,
-                                                    GrPaint&& paint,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect& rect) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              &localMatrix, nullptr);
-}
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrContext* context,
-                                                  GrPaint&& paint,
-                                                  const SkMatrix& viewMatrix,
-                                                  const SkRect& rect,
-                                                  const SkRect& localRect) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    SkMatrix localMatrix;
-    if (!localMatrix.setRectToRect(rect, localRect, SkMatrix::kFill_ScaleToFit)) {
-        return nullptr;
-    }
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              &localMatrix, nullptr);
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#if GR_TEST_UTILS
-
-#include "GrDrawOpTest.h"
-
-GR_DRAW_OP_TEST_DEFINE(AAFillRectOp) {
-    SkMatrix viewMatrix;
-    do {
-        viewMatrix = GrTest::TestMatrixInvertible(random);
-    } while (!view_matrix_ok_for_aa_fill_rect(viewMatrix));
-    SkRect rect = GrTest::TestRect(random);
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    const SkMatrix* localMatrix = nullptr;
-    SkMatrix m;
-    if (random->nextBool()) {
-        m = GrTest::TestMatrix(random);
-    }
-    const GrUserStencilSettings* stencil = random->nextBool() ? nullptr
-                                                              : GrGetRandomStencil(random, context);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect,
-                              devRect, localMatrix, stencil);
-}
-
-#endif
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index a30fe3b..1cb2d14 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -9,6 +9,7 @@
 #include "GrContext.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
+#include "GrFillRectOp.h"
 #include "GrFixedClip.h"
 #include "GrMesh.h"
 #include "GrOpFlushState.h"
@@ -23,7 +24,6 @@
 #include "SkTLazy.h"
 #include "SkTraceEvent.h"
 #include "ops/GrMeshDrawOp.h"
-#include "ops/GrRectOpFactory.h"
 
 GrDefaultPathRenderer::GrDefaultPathRenderer() {
 }
@@ -611,11 +611,11 @@
             }
             const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                                viewMatrix;
+            // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start
             renderTargetContext->addDrawOp(
                     clip,
-                    GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
-                            context, std::move(paint), viewM, localMatrix,
-                            bounds, aaType, passes[p]));
+                    GrFillRectOp::MakeWithLocalMatrix(context, std::move(paint), aaType, viewM,
+                                                      localMatrix, bounds, passes[p]));
         } else {
             bool stencilPass = stencilOnly || passCount > 1;
             std::unique_ptr<GrDrawOp> op;
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index 15baf8c..5e4d8933 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -174,8 +174,13 @@
                 break;
             }
         }
-        auto result = fHelper.xpRequiresDstTexture(
-                caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, &quadColors);
+
+        // If the AA type is coverage, it will be a single value per pixel; if it's not coverage AA
+        // then the coverage is always 1.0, so specify kNone for more optimal blending.
+        GrProcessorAnalysisCoverage coverage = fHelper.aaType() == GrAAType::kCoverage ?
+                GrProcessorAnalysisCoverage::kSingleChannel :
+                GrProcessorAnalysisCoverage::kNone;
+        auto result = fHelper.xpRequiresDstTexture(caps, clip, coverage, &quadColors);
         // If there is a constant color after analysis, that means all of the quads should be set
         // to the same color (even if they started out with different colors).
         SkPMColor4f colorOverride;
@@ -352,40 +357,40 @@
 
 namespace GrFillRectOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                               GrPaint&& paint,
-                               GrAAType aaType,
-                               GrQuadAAFlags edgeAA,
-                               const SkMatrix& viewMatrix,
-                               const SkRect& rect,
-                               const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+                                      GrPaint&& paint,
+                                      GrAAType aaType,
+                                      GrQuadAAFlags edgeAA,
+                                      const SkMatrix& viewMatrix,
+                                      const SkRect& rect,
+                                      const GrUserStencilSettings* stencilSettings) {
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(rect, SkMatrix::I()), GrQuadType::kRect);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
-                                              GrPaint&& paint,
-                                              GrAAType aaType,
-                                              GrQuadAAFlags edgeAA,
-                                              const SkMatrix& viewMatrix,
-                                              const SkMatrix& localMatrix,
-                                              const SkRect& rect,
-                                              const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+                                                     GrPaint&& paint,
+                                                     GrAAType aaType,
+                                                     GrQuadAAFlags edgeAA,
+                                                     const SkMatrix& viewMatrix,
+                                                     const SkMatrix& localMatrix,
+                                                     const SkRect& rect,
+                                                     const GrUserStencilSettings* stencilSettings) {
     GrQuadType localQuadType = GrQuadTypeForTransformedRect(localMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(rect, localMatrix), localQuadType);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
-                                            GrPaint&& paint,
-                                            GrAAType aaType,
-                                            GrQuadAAFlags edgeAA,
-                                            const SkMatrix& viewMatrix,
-                                            const SkRect& rect,
-                                            const SkRect& localRect,
-                                            const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+                                                   GrPaint&& paint,
+                                                   GrAAType aaType,
+                                                   GrQuadAAFlags edgeAA,
+                                                   const SkMatrix& viewMatrix,
+                                                   const SkRect& rect,
+                                                   const SkRect& localRect,
+                                                   const GrUserStencilSettings* stencilSettings) {
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(localRect, SkMatrix::I()), GrQuadType::kRect);
@@ -426,6 +431,41 @@
     return op;
 }
 
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const GrUserStencilSettings* stencil) {
+    return MakePerEdge(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, rect, stencil);
+}
+
+std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
+                                              GrPaint&& paint,
+                                              GrAAType aaType,
+                                              const SkMatrix& viewMatrix,
+                                              const SkMatrix& localMatrix,
+                                              const SkRect& rect,
+                                              const GrUserStencilSettings* stencil) {
+    return MakePerEdgeWithLocalMatrix(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, localMatrix, rect, stencil);
+}
+
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
+                                            GrPaint&& paint,
+                                            GrAAType aaType,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& localRect,
+                                            const GrUserStencilSettings* stencil) {
+    return MakePerEdgeWithLocalRect(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, rect, localRect, stencil);
+}
+
 } // namespace GrFillRectOp
 
 #if GR_TEST_UTILS
@@ -477,19 +517,21 @@
             } else {
                 // Single local matrix
                 SkMatrix localMatrix = GrTest::TestMatrixInvertible(random);
-                return GrFillRectOp::MakeWithLocalMatrix(context, std::move(paint), aaType, aaFlags,
-                                                         viewMatrix, localMatrix, rect, stencil);
+                return GrFillRectOp::MakePerEdgeWithLocalMatrix(context, std::move(paint), aaType,
+                                                                aaFlags, viewMatrix, localMatrix,
+                                                                rect, stencil);
             }
         } else {
             // Pass local rect directly
             SkRect localRect = GrTest::TestRect(random);
-            return GrFillRectOp::MakeWithLocalRect(context, std::move(paint), aaType, aaFlags,
-                                                   viewMatrix, rect, localRect, stencil);
+            return GrFillRectOp::MakePerEdgeWithLocalRect(context, std::move(paint), aaType,
+                                                          aaFlags, viewMatrix, rect, localRect,
+                                                          stencil);
         }
     } else {
         // The simplest constructor
-        return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, viewMatrix, rect,
-                                  stencil);
+        return GrFillRectOp::MakePerEdge(context, std::move(paint), aaType, aaFlags, viewMatrix,
+                                         rect, stencil);
     }
 }
 
diff --git a/src/gpu/ops/GrFillRectOp.h b/src/gpu/ops/GrFillRectOp.h
index 8d287bb..175c9f3 100644
--- a/src/gpu/ops/GrFillRectOp.h
+++ b/src/gpu/ops/GrFillRectOp.h
@@ -17,12 +17,55 @@
 class SkMatrix;
 struct SkRect;
 
+/**
+ * A set of factory functions for drawing filled rectangles either coverage-antialiased, or
+ * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories,
+ * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then
+ * the paint is unmodified and may still be used.
+ */
 namespace GrFillRectOp {
 
+// General purpose factory functions that handle per-edge anti-aliasing
+std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+                                      GrPaint&& paint,
+                                      GrAAType aaType,
+                                      GrQuadAAFlags edgeAA,
+                                      const SkMatrix& viewMatrix,
+                                      const SkRect& rect,
+                                      const GrUserStencilSettings* stencil = nullptr);
+
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+                                                     GrPaint&& paint,
+                                                     GrAAType aaType,
+                                                     GrQuadAAFlags edgeAA,
+                                                     const SkMatrix& viewMatrix,
+                                                     const SkMatrix& localMatrix,
+                                                     const SkRect& rect,
+                                                     const GrUserStencilSettings* stl = nullptr);
+
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+                                                   GrPaint&& paint,
+                                                   GrAAType aaType,
+                                                   GrQuadAAFlags edgeAA,
+                                                   const SkMatrix& viewMatrix,
+                                                   const SkRect& rect,
+                                                   const SkRect& localRect,
+                                                   const GrUserStencilSettings* stencil = nullptr);
+
+// Bulk API for drawing quads with a single op
+std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
+                                  GrPaint&& paint,
+                                  GrAAType aaType,
+                                  const SkMatrix& viewMatrix,
+                                  const GrRenderTargetContext::QuadSetEntry quads[],
+                                  int quadCount,
+                                  const GrUserStencilSettings* stencil = nullptr);
+
+// Specializations where all edges are treated the same. If the aa type is coverage, then the
+// edges will be anti-aliased, otherwise per-edge AA will be disabled.
 std::unique_ptr<GrDrawOp> Make(GrContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
-                               GrQuadAAFlags edgeAA,
                                const SkMatrix& viewMatrix,
                                const SkRect& rect,
                                const GrUserStencilSettings* stencil = nullptr);
@@ -30,7 +73,6 @@
 std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
                                               GrPaint&& paint,
                                               GrAAType aaType,
-                                              GrQuadAAFlags edgeAA,
                                               const SkMatrix& viewMatrix,
                                               const SkMatrix& localMatrix,
                                               const SkRect& rect,
@@ -39,19 +81,11 @@
 std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
                                             GrPaint&& paint,
                                             GrAAType aaType,
-                                            GrQuadAAFlags edgeAA,
                                             const SkMatrix& viewMatrix,
                                             const SkRect& rect,
                                             const SkRect& localRect,
                                             const GrUserStencilSettings* stencil = nullptr);
 
-std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
-                                  GrPaint&& paint,
-                                  GrAAType aaType,
-                                  const SkMatrix& viewMatrix,
-                                  const GrRenderTargetContext::QuadSetEntry quads[],
-                                  int quadCount,
-                                  const GrUserStencilSettings* stencil = nullptr);
-}
+} // namespace GrFillRectOp
 
 #endif // GrFillRectOp_DEFINED
diff --git a/src/gpu/ops/GrNonAAFillRectOp.cpp b/src/gpu/ops/GrNonAAFillRectOp.cpp
deleted file mode 100644
index ae0c9b7..0000000
--- a/src/gpu/ops/GrNonAAFillRectOp.cpp
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrAppliedClip.h"
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrDrawOpTest.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrPrimitiveProcessor.h"
-#include "GrQuad.h"
-#include "GrRectOpFactory.h"
-#include "GrResourceProvider.h"
-#include "GrSimpleMeshDrawOpHelper.h"
-#include "SkMatrixPriv.h"
-
-static const int kVertsPerRect = 4;
-static const int kIndicesPerRect = 6;
-
-/** We always use per-vertex colors so that rects can be combined across color changes. Sometimes
-    we  have explicit local coords and sometimes not. We *could* always provide explicit local
-    coords and just duplicate the positions when the caller hasn't provided a local coord rect,
-    but we haven't seen a use case which frequently switches between local rect and no local
-    rect draws.
-
-    The vertex attrib order is always pos, color, [local coords].
- */
-static sk_sp<GrGeometryProcessor> make_gp(const GrShaderCaps* shaderCaps) {
-    using namespace GrDefaultGeoProcFactory;
-    return GrDefaultGeoProcFactory::Make(shaderCaps,
-                                         Color::kPremulGrColorAttribute_Type,
-                                         Coverage::kSolid_Type,
-                                         LocalCoords::kHasExplicit_Type,
-                                         SkMatrix::I());
-}
-
-static sk_sp<GrGeometryProcessor> make_perspective_gp(const GrShaderCaps* shaderCaps,
-                                                      const SkMatrix& viewMatrix,
-                                                      bool hasExplicitLocalCoords,
-                                                      const SkMatrix* localMatrix) {
-    SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
-
-    using namespace GrDefaultGeoProcFactory;
-
-    // If we have perspective on the viewMatrix then we won't map on the CPU, nor will we map
-    // the local rect on the cpu (in case the localMatrix also has perspective).
-    // Otherwise, if we have a local rect, then we apply the localMatrix directly to the localRect
-    // to generate vertex local coords
-    if (viewMatrix.hasPerspective()) {
-        LocalCoords localCoords(hasExplicitLocalCoords ? LocalCoords::kHasExplicit_Type
-                                                       : LocalCoords::kUsePosition_Type,
-                                localMatrix);
-        return GrDefaultGeoProcFactory::Make(shaderCaps, Color::kPremulGrColorAttribute_Type,
-                                             Coverage::kSolid_Type, localCoords, viewMatrix);
-    } else if (hasExplicitLocalCoords) {
-        LocalCoords localCoords(LocalCoords::kHasExplicit_Type, localMatrix);
-        return GrDefaultGeoProcFactory::Make(shaderCaps, Color::kPremulGrColorAttribute_Type,
-                                             Coverage::kSolid_Type, localCoords, SkMatrix::I());
-    } else {
-        LocalCoords localCoords(LocalCoords::kUsePosition_Type, localMatrix);
-        return GrDefaultGeoProcFactory::MakeForDeviceSpace(shaderCaps,
-                                                           Color::kPremulGrColorAttribute_Type,
-                                                           Coverage::kSolid_Type, localCoords,
-                                                           viewMatrix);
-    }
-}
-
-static void tesselate(intptr_t vertices,
-                      size_t vertexStride,
-                      GrColor color,
-                      const SkMatrix* viewMatrix,
-                      const SkRect& rect,
-                      const GrQuad* localQuad) {
-    SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
-
-    SkPointPriv::SetRectTriStrip(positions, rect, vertexStride);
-
-    if (viewMatrix) {
-        SkMatrixPriv::MapPointsWithStride(*viewMatrix, positions, vertexStride, kVertsPerRect);
-    }
-
-    // Setup local coords
-    // TODO we should only do this if local coords are being read
-    if (localQuad) {
-        static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
-        for (int i = 0; i < kVertsPerRect; i++) {
-            SkPoint* coords =
-                    reinterpret_cast<SkPoint*>(vertices + kLocalOffset + i * vertexStride);
-            *coords = localQuad->point(i);
-        }
-    }
-
-    static const int kColorOffset = sizeof(SkPoint);
-    GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset);
-    for (int j = 0; j < 4; ++j) {
-        *vertColor = color;
-        vertColor = (GrColor*)((intptr_t)vertColor + vertexStride);
-    }
-}
-
-namespace {
-
-class NonAAFillRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect* localRect,
-                                          const SkMatrix* localMatrix,
-                                          GrAAType aaType,
-                                          const GrUserStencilSettings* stencilSettings) {
-        SkASSERT(GrAAType::kCoverage != aaType);
-        return Helper::FactoryHelper<NonAAFillRectOp>(context, std::move(paint), viewMatrix, rect,
-                                                      localRect, localMatrix, aaType,
-                                                      stencilSettings);
-    }
-
-    NonAAFillRectOp() = delete;
-
-    NonAAFillRectOp(const Helper::MakeArgs& args, const SkPMColor4f& color,
-                    const SkMatrix& viewMatrix, const SkRect& rect, const SkRect* localRect,
-                    const SkMatrix* localMatrix, GrAAType aaType,
-                    const GrUserStencilSettings* stencilSettings)
-            : INHERITED(ClassID()), fHelper(args, aaType, stencilSettings) {
-
-        SkASSERT(!viewMatrix.hasPerspective() && (!localMatrix || !localMatrix->hasPerspective()));
-        RectInfo& info = fRects.push_back();
-        info.fColor = color;
-        info.fViewMatrix = viewMatrix;
-        info.fRect = rect;
-        if (localRect && localMatrix) {
-            info.fLocalQuad = GrQuad(*localRect, *localMatrix);
-        } else if (localRect) {
-            info.fLocalQuad = GrQuad(*localRect);
-        } else if (localMatrix) {
-            info.fLocalQuad = GrQuad(rect, *localMatrix);
-        } else {
-            info.fLocalQuad = GrQuad(rect);
-        }
-        this->setTransformedBounds(fRects[0].fRect, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-    }
-
-    const char* name() const override { return "NonAAFillRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.append(GrMeshDrawOp::dumpInfo());
-        str.appendf("# combined: %d\n", fRects.count());
-        for (int i = 0; i < fRects.count(); ++i) {
-            const RectInfo& info = fRects[i];
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        info.fColor.toBytes_RGBA(), info.fRect.fLeft, info.fRect.fTop,
-                        info.fRect.fRight, info.fRect.fBottom);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f* color = &fRects.front().fColor;
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone, color);
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    DEFINE_OP_CLASS_ID
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp = make_gp(target->caps().shaderCaps());
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-
-        size_t kVertexStride = gp->vertexStride();
-        int rectCount = fRects.count();
-
-        sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, kVertexStride, indexBuffer.get(),
-                             kVertsPerRect, kIndicesPerRect, rectCount);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        for (int i = 0; i < rectCount; i++) {
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * kVertexStride;
-            // TODO4F: Preserve float colors
-            tesselate(verts, kVertexStride, fRects[i].fColor.toBytes_RGBA(), &fRects[i].fViewMatrix,
-                      fRects[i].fRect, &fRects[i].fLocalQuad);
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        NonAAFillRectOp* that = t->cast<NonAAFillRectOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-        fRects.push_back_n(that->fRects.count(), that->fRects.begin());
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-        SkPMColor4f fColor;
-        SkMatrix fViewMatrix;
-        SkRect fRect;
-        GrQuad fLocalQuad;
-    };
-
-    Helper fHelper;
-    SkSTArray<1, RectInfo, true> fRects;
-    typedef GrMeshDrawOp INHERITED;
-};
-
-// We handle perspective in the local matrix or viewmatrix with special ops.
-class NonAAFillRectPerspectiveOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect* localRect,
-                                          const SkMatrix* localMatrix,
-                                          GrAAType aaType,
-                                          const GrUserStencilSettings* stencilSettings) {
-        SkASSERT(GrAAType::kCoverage != aaType);
-        return Helper::FactoryHelper<NonAAFillRectPerspectiveOp>(context, std::move(paint),
-                                                                 viewMatrix, rect,
-                                                                 localRect, localMatrix, aaType,
-                                                                 stencilSettings);
-    }
-
-    NonAAFillRectPerspectiveOp() = delete;
-
-    NonAAFillRectPerspectiveOp(const Helper::MakeArgs& args, const SkPMColor4f& color,
-                               const SkMatrix& viewMatrix, const SkRect& rect,
-                               const SkRect* localRect, const SkMatrix* localMatrix,
-                               GrAAType aaType, const GrUserStencilSettings* stencilSettings)
-            : INHERITED(ClassID())
-            , fHelper(args, aaType, stencilSettings)
-            , fViewMatrix(viewMatrix) {
-        SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
-        RectInfo& info = fRects.push_back();
-        info.fColor = color;
-        info.fRect = rect;
-        fHasLocalRect = SkToBool(localRect);
-        fHasLocalMatrix = SkToBool(localMatrix);
-        if (fHasLocalMatrix) {
-            fLocalMatrix = *localMatrix;
-        }
-        if (fHasLocalRect) {
-            info.fLocalRect = *localRect;
-        }
-        this->setTransformedBounds(rect, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-    }
-
-    const char* name() const override { return "NonAAFillRectPerspectiveOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.appendf("# combined: %d\n", fRects.count());
-        for (int i = 0; i < fRects.count(); ++i) {
-            const RectInfo& geo = fRects[i];
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        geo.fColor.toBytes_RGBA(), geo.fRect.fLeft, geo.fRect.fTop,
-                        geo.fRect.fRight, geo.fRect.fBottom);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f* color = &fRects.front().fColor;
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone, color);
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    DEFINE_OP_CLASS_ID
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp = make_perspective_gp(
-                target->caps().shaderCaps(),
-                fViewMatrix,
-                fHasLocalRect,
-                fHasLocalMatrix ? &fLocalMatrix : nullptr);
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-        size_t vertexStride = gp->vertexStride();
-        int rectCount = fRects.count();
-
-        sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, vertexStride, indexBuffer.get(),
-                             kVertsPerRect, kIndicesPerRect, rectCount);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        for (int i = 0; i < rectCount; i++) {
-            const RectInfo& info = fRects[i];
-            // TODO4F: Preserve float colors
-            GrColor color = info.fColor.toBytes_RGBA();
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * vertexStride;
-            if (fHasLocalRect) {
-                GrQuad quad(info.fLocalRect);
-                tesselate(verts, vertexStride, color, nullptr, info.fRect, &quad);
-            } else {
-                tesselate(verts, vertexStride, color, nullptr, info.fRect, nullptr);
-            }
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        NonAAFillRectPerspectiveOp* that = t->cast<NonAAFillRectPerspectiveOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-
-        // We could combine across perspective vm changes if we really wanted to.
-        if (!fViewMatrix.cheapEqualTo(that->fViewMatrix)) {
-            return CombineResult::kCannotCombine;
-        }
-        if (fHasLocalRect != that->fHasLocalRect) {
-            return CombineResult::kCannotCombine;
-        }
-        if (fHasLocalMatrix && !fLocalMatrix.cheapEqualTo(that->fLocalMatrix)) {
-            return CombineResult::kCannotCombine;
-        }
-
-        fRects.push_back_n(that->fRects.count(), that->fRects.begin());
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-        SkRect fRect;
-        SkPMColor4f fColor;
-        SkRect fLocalRect;
-    };
-
-    SkSTArray<1, RectInfo, true> fRects;
-    Helper fHelper;
-    bool fHasLocalMatrix;
-    bool fHasLocalRect;
-    SkMatrix fLocalMatrix;
-    SkMatrix fViewMatrix;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-
-std::unique_ptr<GrDrawOp> MakeNonAAFill(GrContext* context,
-                                        GrPaint&& paint,
-                                        const SkMatrix& viewMatrix,
-                                        const SkRect& rect,
-                                        GrAAType aaType,
-                                        const GrUserStencilSettings* stencilSettings) {
-    if (viewMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                nullptr, nullptr, aaType, stencilSettings);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, nullptr, nullptr,
-                                     aaType, stencilSettings);
-    }
-}
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalMatrix(
-                                                    GrContext* context,
-                                                    GrPaint&& paint,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect& rect,
-                                                    GrAAType aaType,
-                                                    const GrUserStencilSettings* stencilSettings) {
-    if (viewMatrix.hasPerspective() || localMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                nullptr, &localMatrix, aaType, stencilSettings);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, nullptr,
-                                     &localMatrix, aaType, stencilSettings);
-    }
-}
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalRect(GrContext* context,
-                                                     GrPaint&& paint,
-                                                     const SkMatrix& viewMatrix,
-                                                     const SkRect& rect,
-                                                     const SkRect& localRect,
-                                                     GrAAType aaType) {
-    if (viewMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                &localRect, nullptr, aaType, nullptr);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, &localRect,
-                                     nullptr, aaType, nullptr);
-    }
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#if GR_TEST_UTILS
-
-GR_DRAW_OP_TEST_DEFINE(NonAAFillRectOp) {
-    SkRect rect = GrTest::TestRect(random);
-    SkRect localRect = GrTest::TestRect(random);
-    SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
-    SkMatrix localMatrix = GrTest::TestMatrix(random);
-    const GrUserStencilSettings* stencil = GrGetRandomStencil(random, context);
-    GrAAType aaType = GrAAType::kNone;
-    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
-        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
-    }
-    const SkRect* lr = random->nextBool() ? &localRect : nullptr;
-    const SkMatrix* lm = random->nextBool() ? &localMatrix : nullptr;
-    if (viewMatrix.hasPerspective() || (lm && lm->hasPerspective())) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                lr, lm, aaType, stencil);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect,
-                                     lr, lm, aaType, stencil);
-    }
-}
-
-#endif
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
deleted file mode 100644
index c2786ef..0000000
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrDrawOpTest.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
-#include "GrSimpleMeshDrawOpHelper.h"
-#include "SkRandom.h"
-#include "SkStrokeRec.h"
-
-/*  create a triangle strip that strokes the specified rect. There are 8
-    unique vertices, but we repeat the last 2 to close up. Alternatively we
-    could use an indices array, and then only send 8 verts, but not sure that
-    would be faster.
-    */
-static void init_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
-    const SkScalar rad = SkScalarHalf(width);
-
-    verts[0].set(rect.fLeft + rad, rect.fTop + rad);
-    verts[1].set(rect.fLeft - rad, rect.fTop - rad);
-    verts[2].set(rect.fRight - rad, rect.fTop + rad);
-    verts[3].set(rect.fRight + rad, rect.fTop - rad);
-    verts[4].set(rect.fRight - rad, rect.fBottom - rad);
-    verts[5].set(rect.fRight + rad, rect.fBottom + rad);
-    verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
-    verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
-    verts[8] = verts[0];
-    verts[9] = verts[1];
-
-    // TODO: we should be catching this higher up the call stack and just draw a single
-    // non-AA rect
-    if (2*rad >= rect.width()) {
-        verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
-    }
-    if (2*rad >= rect.height()) {
-        verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
-    }
-}
-
-// Allow all hairlines and all miters, so long as the miter limit doesn't produce beveled corners.
-inline static bool allowed_stroke(const SkStrokeRec& stroke) {
-    SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
-             stroke.getStyle() == SkStrokeRec::kHairline_Style);
-    return !stroke.getWidth() ||
-           (stroke.getJoin() == SkPaint::kMiter_Join && stroke.getMiter() > SK_ScalarSqrt2);
-}
-
-namespace {
-
-class NonAAStrokeRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelper;
-
-public:
-    DEFINE_OP_CLASS_ID
-
-    const char* name() const override { return "NonAAStrokeRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString string;
-        string.appendf(
-                "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
-                "StrokeWidth: %.2f\n",
-                fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
-                fStrokeWidth);
-        string += fHelper.dumpInfo();
-        string += INHERITED::dumpInfo();
-        return string;
-    }
-#endif
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkStrokeRec& stroke,
-                                          GrAAType aaType) {
-        if (!allowed_stroke(stroke)) {
-            return nullptr;
-        }
-        Helper::Flags flags = Helper::Flags::kNone;
-        // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
-        // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
-        // when MSAA is enabled because it can cause ugly artifacts.
-        if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
-            flags |= Helper::Flags::kSnapVerticesToPixelCenters;
-        }
-        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
-                                                        viewMatrix, rect,
-                                                        stroke, aaType);
-    }
-
-    NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                      Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
-                      const SkStrokeRec& stroke, GrAAType aaType)
-            : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
-        fColor = color;
-        fViewMatrix = viewMatrix;
-        fRect = rect;
-        // Sort the rect for hairlines
-        fRect.sort();
-        fStrokeWidth = stroke.getWidth();
-
-        SkScalar rad = SkScalarHalf(fStrokeWidth);
-        SkRect bounds = rect;
-        bounds.outset(rad, rad);
-
-        // If our caller snaps to pixel centers then we have to round out the bounds
-        if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
-            viewMatrix.mapRect(&bounds);
-            // We want to be consistent with how we snap non-aa lines. To match what we do in
-            // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
-            // pixel to force us to pixel centers.
-            bounds.set(SkScalarFloorToScalar(bounds.fLeft),
-                       SkScalarFloorToScalar(bounds.fTop),
-                       SkScalarFloorToScalar(bounds.fRight),
-                       SkScalarFloorToScalar(bounds.fBottom));
-            bounds.offset(0.5f, 0.5f);
-            this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
-        } else {
-            this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-        }
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone,
-                                            &fColor);
-    }
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp;
-        {
-            using namespace GrDefaultGeoProcFactory;
-            Color color(fColor);
-            LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
-                                                        ? LocalCoords::kUsePosition_Type
-                                                        : LocalCoords::kUnused_Type;
-            gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
-                                               Coverage::kSolid_Type, localCoordsType,
-                                               fViewMatrix);
-        }
-
-        size_t kVertexStride = gp->vertexStride();
-        int vertexCount = kVertsPerHairlineRect;
-        if (fStrokeWidth > 0) {
-            vertexCount = kVertsPerStrokeRect;
-        }
-
-        const GrBuffer* vertexBuffer;
-        int firstVertex;
-
-        void* verts =
-                target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
-
-        if (!verts) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
-
-        GrPrimitiveType primType;
-        if (fStrokeWidth > 0) {
-            primType = GrPrimitiveType::kTriangleStrip;
-            init_stroke_rect_strip(vertex, fRect, fStrokeWidth);
-        } else {
-            // hairline
-            primType = GrPrimitiveType::kLineStrip;
-            vertex[0].set(fRect.fLeft, fRect.fTop);
-            vertex[1].set(fRect.fRight, fRect.fTop);
-            vertex[2].set(fRect.fRight, fRect.fBottom);
-            vertex[3].set(fRect.fLeft, fRect.fBottom);
-            vertex[4].set(fRect.fLeft, fRect.fTop);
-        }
-
-        GrMesh* mesh = target->allocMesh(primType);
-        mesh->setNonIndexedNonInstanced(vertexCount);
-        mesh->setVertexData(vertexBuffer, firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
-    }
-
-    // TODO: override onCombineIfPossible
-
-    Helper fHelper;
-    SkPMColor4f fColor;
-    SkMatrix fViewMatrix;
-    SkRect fRect;
-    SkScalar fStrokeWidth;
-
-    const static int kVertsPerHairlineRect = 5;
-    const static int kVertsPerStrokeRect = 10;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-std::unique_ptr<GrDrawOp> MakeNonAAStroke(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkStrokeRec& stroke,
-                                          GrAAType aaType) {
-    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
-}
-}  // namespace GrRectOpFactory
-
-#if GR_TEST_UTILS
-
-GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
-    SkMatrix viewMatrix = GrTest::TestMatrix(random);
-    SkRect rect = GrTest::TestRect(random);
-    SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
-    SkPaint strokePaint;
-    strokePaint.setStrokeWidth(strokeWidth);
-    strokePaint.setStyle(SkPaint::kStroke_Style);
-    strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
-    SkStrokeRec strokeRec(strokePaint);
-    GrAAType aaType = GrAAType::kNone;
-    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
-        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
-    }
-    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
-}
-
-#endif
diff --git a/src/gpu/ops/GrRectOpFactory.h b/src/gpu/ops/GrRectOpFactory.h
deleted file mode 100644
index 29ac10e..0000000
--- a/src/gpu/ops/GrRectOpFactory.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrRectOpFactory_DEFINED
-#define GrRectOpFactory_DEFINED
-
-#include <memory>
-#include "GrTypes.h"
-
-enum class GrAAType : unsigned;
-class GrDrawOp;
-class GrPaint;
-struct GrUserStencilSettings;
-class SkMatrix;
-struct SkRect;
-class SkStrokeRec;
-
-/**
- * A set of factory functions for drawing rectangles including fills, strokes, coverage-antialiased,
- * and non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp
- * factories, the GrPaint is only consumed by these methods if a valid op is returned. If null is
- * returned then the paint is unmodified and may still be used.
- */
-namespace GrRectOpFactory {
-/** AA Fill */
-
-std::unique_ptr<GrDrawOp> MakeAAFill(GrContext*,
-                                     GrPaint&&,
-                                     const SkMatrix&,
-                                     const SkRect&,
-                                     const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrContext*,
-                                                    GrPaint&&,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect&);
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrContext*,
-                                                  GrPaint&&,
-                                                  const SkMatrix&,
-                                                  const SkRect& rect,
-                                                  const SkRect& localRect);
-
-/** Non-AA Fill - GrAAType must be either kNone or kMSAA. */
-
-std::unique_ptr<GrDrawOp> MakeNonAAFill(GrContext*,
-                                        GrPaint&&,
-                                        const SkMatrix& viewMatrix,
-                                        const SkRect& rect,
-                                        GrAAType,
-                                        const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalMatrix(GrContext*,
-                                                       GrPaint&&,
-                                                       const SkMatrix& viewMatrix,
-                                                       const SkMatrix& localMatrix,
-                                                       const SkRect&,
-                                                       GrAAType,
-                                                       const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalRect(GrContext*,
-                                                     GrPaint&&,
-                                                     const SkMatrix&,
-                                                     const SkRect& rect,
-                                                     const SkRect& localRect,
-                                                     GrAAType);
-
-/** AA Stroke */
-
-std::unique_ptr<GrDrawOp> MakeAAStroke(GrContext*,
-                                       GrPaint&&,
-                                       const SkMatrix&,
-                                       const SkRect&,
-                                       const SkStrokeRec&);
-
-// rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to
-// draw rather than failure.
-std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrContext*,
-                                                GrPaint&&,
-                                                const SkMatrix&,
-                                                const SkRect rects[2]);
-
-/** Non-AA Stroke - GrAAType must be either kNone or kMSAA. */
-
-std::unique_ptr<GrDrawOp> MakeNonAAStroke(GrContext*,
-                                          GrPaint&&,
-                                          const SkMatrix&,
-                                          const SkRect&,
-                                          const SkStrokeRec&,
-                                          GrAAType);
-
-}  // namespace GrRectOpFactory
-
-#endif
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index a0ef219..9a54dfe 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -18,7 +18,7 @@
 #include "GrStencilClip.h"
 #include "GrStencilPathOp.h"
 #include "GrStyle.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider,
                                                       const GrCaps& caps) {
@@ -156,10 +156,12 @@
             if (GrAAType::kMixedSamples == coverAAType) {
                 coverAAType = GrAAType::kNone;
             }
-            std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
+            // This is a non-coverage aa rect operation
+            SkASSERT(coverAAType == GrAAType::kNone || coverAAType == GrAAType::kMSAA);
+            std::unique_ptr<GrDrawOp> op = GrFillRectOp::MakeWithLocalMatrix(
                                                          args.fContext, std::move(args.fPaint),
-                                                         coverMatrix, localMatrix, coverBounds,
-                                                         coverAAType, &kInvertedCoverPass);
+                                                         coverAAType, coverMatrix, localMatrix,
+                                                         coverBounds, &kInvertedCoverPass);
 
             args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
         }
diff --git a/src/gpu/ops/GrAAStrokeRectOp.cpp b/src/gpu/ops/GrStrokeRectOp.cpp
similarity index 65%
rename from src/gpu/ops/GrAAStrokeRectOp.cpp
rename to src/gpu/ops/GrStrokeRectOp.cpp
index d6c4d97..98b3b67 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrStrokeRectOp.cpp
@@ -1,25 +1,31 @@
 /*
- * Copyright 2015 Google Inc.
+ * Copyright 2018 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
+#include "GrStrokeRectOp.h"
+
+#include "GrColor.h"
 #include "GrDefaultGeoProcFactory.h"
+#include "GrDrawOpTest.h"
+#include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
 #include "GrResourceKey.h"
 #include "GrResourceProvider.h"
 #include "GrSimpleMeshDrawOpHelper.h"
 #include "GrVertexWriter.h"
+#include "ops/GrFillRectOp.h"
+#include "SkRandom.h"
 #include "SkStrokeRec.h"
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
-GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
+namespace {
 
 // We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter
-// limit makes a miter join effectively beveled.
-inline static bool allowed_stroke(const SkStrokeRec& stroke, bool* isMiter) {
+// limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only
+// supported when using an AA stroke.
+inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) {
     SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
              stroke.getStyle() == SkStrokeRec::kHairline_Style);
     // For hairlines, make bevel and round joins appear the same as mitered ones.
@@ -29,18 +35,216 @@
     }
     if (stroke.getJoin() == SkPaint::kBevel_Join) {
         *isMiter = false;
-        return true;
+        return aa == GrAA::kYes; // bevel only supported with AA
     }
     if (stroke.getJoin() == SkPaint::kMiter_Join) {
         *isMiter = stroke.getMiter() >= SK_ScalarSqrt2;
-        return true;
+        // Supported under non-AA only if it remains mitered
+        return aa == GrAA::kYes || *isMiter;
     }
     return false;
 }
 
-static void compute_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
-                          bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
-                          SkScalar strokeWidth, bool miterStroke) {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Non-AA Stroking
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*  create a triangle strip that strokes the specified rect. There are 8
+    unique vertices, but we repeat the last 2 to close up. Alternatively we
+    could use an indices array, and then only send 8 verts, but not sure that
+    would be faster.
+    */
+static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
+    const SkScalar rad = SkScalarHalf(width);
+
+    verts[0].set(rect.fLeft + rad, rect.fTop + rad);
+    verts[1].set(rect.fLeft - rad, rect.fTop - rad);
+    verts[2].set(rect.fRight - rad, rect.fTop + rad);
+    verts[3].set(rect.fRight + rad, rect.fTop - rad);
+    verts[4].set(rect.fRight - rad, rect.fBottom - rad);
+    verts[5].set(rect.fRight + rad, rect.fBottom + rad);
+    verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
+    verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
+    verts[8] = verts[0];
+    verts[9] = verts[1];
+
+    // TODO: we should be catching this higher up the call stack and just draw a single
+    // non-AA rect
+    if (2*rad >= rect.width()) {
+        verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
+    }
+    if (2*rad >= rect.height()) {
+        verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
+    }
+}
+
+class NonAAStrokeRectOp final : public GrMeshDrawOp {
+private:
+    using Helper = GrSimpleMeshDrawOpHelper;
+
+public:
+    DEFINE_OP_CLASS_ID
+
+    const char* name() const override { return "NonAAStrokeRectOp"; }
+
+    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
+        fHelper.visitProxies(func);
+    }
+
+#ifdef SK_DEBUG
+    SkString dumpInfo() const override {
+        SkString string;
+        string.appendf(
+                "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
+                "StrokeWidth: %.2f\n",
+                fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
+                fStrokeWidth);
+        string += fHelper.dumpInfo();
+        string += INHERITED::dumpInfo();
+        return string;
+    }
+#endif
+
+    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                                          GrPaint&& paint,
+                                          const SkMatrix& viewMatrix,
+                                          const SkRect& rect,
+                                          const SkStrokeRec& stroke,
+                                          GrAAType aaType) {
+        bool isMiter;
+        if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) {
+            return nullptr;
+        }
+        Helper::Flags flags = Helper::Flags::kNone;
+        // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
+        // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
+        // when MSAA is enabled because it can cause ugly artifacts.
+        if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
+            flags |= Helper::Flags::kSnapVerticesToPixelCenters;
+        }
+        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
+                                                        viewMatrix, rect,
+                                                        stroke, aaType);
+    }
+
+    NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+                      Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
+                      const SkStrokeRec& stroke, GrAAType aaType)
+            : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
+        fColor = color;
+        fViewMatrix = viewMatrix;
+        fRect = rect;
+        // Sort the rect for hairlines
+        fRect.sort();
+        fStrokeWidth = stroke.getWidth();
+
+        SkScalar rad = SkScalarHalf(fStrokeWidth);
+        SkRect bounds = rect;
+        bounds.outset(rad, rad);
+
+        // If our caller snaps to pixel centers then we have to round out the bounds
+        if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
+            viewMatrix.mapRect(&bounds);
+            // We want to be consistent with how we snap non-aa lines. To match what we do in
+            // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
+            // pixel to force us to pixel centers.
+            bounds.set(SkScalarFloorToScalar(bounds.fLeft),
+                       SkScalarFloorToScalar(bounds.fTop),
+                       SkScalarFloorToScalar(bounds.fRight),
+                       SkScalarFloorToScalar(bounds.fBottom));
+            bounds.offset(0.5f, 0.5f);
+            this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
+        } else {
+            this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
+        }
+    }
+
+    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
+
+    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone,
+                                            &fColor);
+    }
+
+private:
+    void onPrepareDraws(Target* target) override {
+        sk_sp<GrGeometryProcessor> gp;
+        {
+            using namespace GrDefaultGeoProcFactory;
+            Color color(fColor);
+            LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
+            gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
+                                               Coverage::kSolid_Type, localCoordsType,
+                                               fViewMatrix);
+        }
+
+        size_t kVertexStride = gp->vertexStride();
+        int vertexCount = kVertsPerHairlineRect;
+        if (fStrokeWidth > 0) {
+            vertexCount = kVertsPerStrokeRect;
+        }
+
+        const GrBuffer* vertexBuffer;
+        int firstVertex;
+
+        void* verts =
+                target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
+
+        if (!verts) {
+            SkDebugf("Could not allocate vertices\n");
+            return;
+        }
+
+        SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
+
+        GrPrimitiveType primType;
+        if (fStrokeWidth > 0) {
+            primType = GrPrimitiveType::kTriangleStrip;
+            init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth);
+        } else {
+            // hairline
+            primType = GrPrimitiveType::kLineStrip;
+            vertex[0].set(fRect.fLeft, fRect.fTop);
+            vertex[1].set(fRect.fRight, fRect.fTop);
+            vertex[2].set(fRect.fRight, fRect.fBottom);
+            vertex[3].set(fRect.fLeft, fRect.fBottom);
+            vertex[4].set(fRect.fLeft, fRect.fTop);
+        }
+
+        GrMesh* mesh = target->allocMesh(primType);
+        mesh->setNonIndexedNonInstanced(vertexCount);
+        mesh->setVertexData(vertexBuffer, firstVertex);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+    }
+
+    // TODO: override onCombineIfPossible
+
+    Helper fHelper;
+    SkPMColor4f fColor;
+    SkMatrix fViewMatrix;
+    SkRect fRect;
+    SkScalar fStrokeWidth;
+
+    const static int kVertsPerHairlineRect = 5;
+    const static int kVertsPerStrokeRect = 10;
+
+    typedef GrMeshDrawOp INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// AA Stroking
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
+
+static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
+                             bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
+                             SkScalar strokeWidth, bool miterStroke) {
     SkRect devRect;
     viewMatrix.mapRect(&devRect, rect);
 
@@ -90,10 +294,10 @@
     }
 }
 
-static sk_sp<GrGeometryProcessor> create_stroke_rect_gp(const GrShaderCaps* shaderCaps,
-                                                        bool tweakAlphaForCoverage,
-                                                        const SkMatrix& viewMatrix,
-                                                        bool usesLocalCoords) {
+static sk_sp<GrGeometryProcessor> create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps,
+                                                           bool tweakAlphaForCoverage,
+                                                           const SkMatrix& viewMatrix,
+                                                           bool usesLocalCoords) {
     using namespace GrDefaultGeoProcFactory;
 
     Coverage::Type coverageType;
@@ -111,8 +315,6 @@
                               viewMatrix);
 }
 
-namespace {
-
 class AAStrokeRectOp final : public GrMeshDrawOp {
 private:
     using Helper = GrSimpleMeshDrawOpHelper;
@@ -148,7 +350,7 @@
                                           const SkRect& rect,
                                           const SkStrokeRec& stroke) {
         bool isMiter;
-        if (!allowed_stroke(stroke, &isMiter)) {
+        if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) {
             return nullptr;
         }
         return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, rect,
@@ -163,8 +365,8 @@
             , fViewMatrix(viewMatrix) {
         fMiterStroke = isMiter;
         RectInfo& info = fRects.push_back();
-        compute_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
-                      &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
+        compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
+                         &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
         info.fColor = color;
         if (isMiter) {
             this->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo);
@@ -254,13 +456,11 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
-}  // anonymous namespace
-
 void AAStrokeRectOp::onPrepareDraws(Target* target) {
-    sk_sp<GrGeometryProcessor> gp(create_stroke_rect_gp(target->caps().shaderCaps(),
-                                                        fHelper.compatibleWithAlphaAsCoverage(),
-                                                        this->viewMatrix(),
-                                                        fHelper.usesLocalCoords()));
+    sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(),
+                                                           fHelper.compatibleWithAlphaAsCoverage(),
+                                                           this->viewMatrix(),
+                                                           fHelper.usesLocalCoords()));
     if (!gp) {
         SkDebugf("Couldn't create GrGeometryProcessor\n");
         return;
@@ -520,12 +720,31 @@
     }
 }
 
-namespace GrRectOpFactory {
+}  // anonymous namespace
 
-std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrContext* context,
-                                                GrPaint&& paint,
-                                                const SkMatrix& viewMatrix,
-                                                const SkRect rects[2]) {
+namespace GrStrokeRectOp {
+
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke) {
+    if (aaType == GrAAType::kCoverage) {
+        // The AA op only supports axis-aligned rectangles
+        if (!viewMatrix.rectStaysRect()) {
+            return nullptr;
+        }
+        return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
+    } else {
+        return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
+    }
+}
+
+std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+                                     GrPaint&& paint,
+                                     const SkMatrix& viewMatrix,
+                                     const SkRect rects[2]) {
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
 
@@ -536,28 +755,35 @@
         if (devOutside.isEmpty()) {
             return nullptr;
         }
-        return MakeAAFill(context, std::move(paint), viewMatrix, rects[0]);
+        return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, viewMatrix,
+                                  rects[0]);
     }
 
     return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside);
 }
 
-std::unique_ptr<GrDrawOp> MakeAAStroke(GrContext* context,
-                                       GrPaint&& paint,
-                                       const SkMatrix& viewMatrix,
-                                       const SkRect& rect,
-                                       const SkStrokeRec& stroke) {
-    return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+}  // namespace GrStrokeRectOp
 
 #if GR_TEST_UTILS
 
 #include "GrDrawOpTest.h"
 
+GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
+    SkMatrix viewMatrix = GrTest::TestMatrix(random);
+    SkRect rect = GrTest::TestRect(random);
+    SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
+    SkPaint strokePaint;
+    strokePaint.setStrokeWidth(strokeWidth);
+    strokePaint.setStyle(SkPaint::kStroke_Style);
+    strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
+    SkStrokeRec strokeRec(strokePaint);
+    GrAAType aaType = GrAAType::kNone;
+    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
+        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
+    }
+    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
+}
+
 GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) {
     bool miterStroke = random->nextBool();
 
@@ -572,7 +798,7 @@
     rec.setStrokeParams(SkPaint::kButt_Cap,
                         miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f);
     SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random);
-    return GrRectOpFactory::MakeAAStroke(context, std::move(paint), matrix, rect, rec);
+    return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec);
 }
 
 #endif
diff --git a/src/gpu/ops/GrStrokeRectOp.h b/src/gpu/ops/GrStrokeRectOp.h
new file mode 100644
index 0000000..97ea865a
--- /dev/null
+++ b/src/gpu/ops/GrStrokeRectOp.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrStrokeRectOp_DEFINED
+#define GrStrokeRectOp_DEFINED
+
+#include "GrTypesPriv.h"
+
+class GrDrawOp;
+class GrPaint;
+class SkMatrix;
+struct SkRect;
+class SkStrokeRec;
+
+/**
+ * A set of factory functions for drawing stroked rectangles either coverage-antialiased, or
+ * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories,
+ * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then
+ * the paint is unmodified and may still be used.
+ */
+namespace GrStrokeRectOp {
+
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke);
+
+// rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to
+// draw rather than failure. The area between the rectangles will be filled by the paint, and it
+// will be anti-aliased with coverage AA. viewMatrix.rectStaysRect() must be true.
+std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+                                     GrPaint&& paint,
+                                     const SkMatrix& viewMatrix,
+                                     const SkRect rects[2]);
+
+}  // namespace GrStrokeRectOp
+
+#endif // GrStrokeRectOp_DEFINED