Move 2 path renderers to skgpu::v1 namespace

Bug: skia:11837
Change-Id: I71c8e5b7221e0eb84400e0ddc1a8126d820d24bb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/440538
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/ops/TriangulatingPathRenderer.cpp b/src/gpu/ops/TriangulatingPathRenderer.cpp
new file mode 100644
index 0000000..15e450f
--- /dev/null
+++ b/src/gpu/ops/TriangulatingPathRenderer.cpp
@@ -0,0 +1,622 @@
+/*
+ * 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 "src/gpu/ops/TriangulatingPathRenderer.h"
+
+#include "include/private/SkIDChangeListener.h"
+#include "src/core/SkGeometry.h"
+#include "src/gpu/GrAuditTrail.h"
+#include "src/gpu/GrCaps.h"
+#include "src/gpu/GrDefaultGeoProcFactory.h"
+#include "src/gpu/GrDrawOpTest.h"
+#include "src/gpu/GrEagerVertexAllocator.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrProgramInfo.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/GrResourceCache.h"
+#include "src/gpu/GrResourceProvider.h"
+#include "src/gpu/GrSimpleMesh.h"
+#include "src/gpu/GrStyle.h"
+#include "src/gpu/GrThreadSafeCache.h"
+#include "src/gpu/geometry/GrAATriangulator.h"
+#include "src/gpu/geometry/GrPathUtils.h"
+#include "src/gpu/geometry/GrStyledShape.h"
+#include "src/gpu/geometry/GrTriangulator.h"
+#include "src/gpu/ops/GrMeshDrawOp.h"
+#include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
+#include "src/gpu/v1/SurfaceDrawContext_v1.h"
+
+#include <cstdio>
+
+#ifndef GR_AA_TESSELLATOR_MAX_VERB_COUNT
+#define GR_AA_TESSELLATOR_MAX_VERB_COUNT 10
+#endif
+
+/*
+ * This path renderer linearizes and decomposes the path into triangles using GrTriangulator,
+ * uploads the triangles to a vertex buffer, and renders them with a single draw call. It can do
+ * screenspace antialiasing with a one-pixel coverage ramp.
+ */
+namespace {
+
+// The TessInfo struct contains ancillary data not specifically required for the triangle
+// data (which is stored in a GrThreadSafeCache::VertexData object).
+// The 'fNumVertices' field is a temporary exception. It is still needed to support the
+// AA triangulated path case - which doesn't use the GrThreadSafeCache nor the VertexData object).
+// When there is an associated VertexData, its numVertices should always match the TessInfo's
+// value.
+struct TessInfo {
+    int       fNumVertices;
+    bool      fIsLinear;
+    SkScalar  fTolerance;
+};
+
+static sk_sp<SkData> create_data(int numVertices, bool isLinear, SkScalar tol) {
+    TessInfo info { numVertices, isLinear, tol };
+    return SkData::MakeWithCopy(&info, sizeof(info));
+}
+
+bool cache_match(const SkData* data, SkScalar tol) {
+    SkASSERT(data);
+
+    const TessInfo* info = static_cast<const TessInfo*>(data->data());
+
+    return info->fIsLinear || info->fTolerance < 3.0f * tol;
+}
+
+// Should 'challenger' replace 'incumbent' in the cache if there is a collision?
+bool is_newer_better(SkData* incumbent, SkData* challenger) {
+    const TessInfo* i = static_cast<const TessInfo*>(incumbent->data());
+    const TessInfo* c = static_cast<const TessInfo*>(challenger->data());
+
+    if (i->fIsLinear || i->fTolerance <= c->fTolerance) {
+        return false;  // prefer the incumbent
+    }
+
+    return true;
+}
+
+// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key.
+class UniqueKeyInvalidator : public SkIDChangeListener {
+public:
+    UniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID)
+            : fMsg(key, contextUniqueID, /* inThreadSafeCache */ true) {}
+
+private:
+    GrUniqueKeyInvalidatedMessage fMsg;
+
+    void changed() override { SkMessageBus<GrUniqueKeyInvalidatedMessage, uint32_t>::Post(fMsg); }
+};
+
+class StaticVertexAllocator : public GrEagerVertexAllocator {
+public:
+    StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB)
+            : fResourceProvider(resourceProvider)
+            , fCanMapVB(canMapVB) {
+    }
+
+#ifdef SK_DEBUG
+    ~StaticVertexAllocator() override {
+        SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && !fVertexData);
+    }
+#endif
+
+    void* lock(size_t stride, int eagerCount) override {
+        SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && !fVertexData);
+        SkASSERT(stride && eagerCount);
+
+        size_t size = eagerCount * stride;
+        fVertexBuffer = fResourceProvider->createBuffer(size, GrGpuBufferType::kVertex,
+                                                        kStatic_GrAccessPattern);
+        if (!fVertexBuffer) {
+            return nullptr;
+        }
+        if (fCanMapVB) {
+            fVertices = fVertexBuffer->map();
+        }
+        if (!fVertices) {
+            fVertices = sk_malloc_throw(eagerCount * stride);
+            fCanMapVB = false;
+        }
+        fLockStride = stride;
+        return fVertices;
+    }
+
+    void unlock(int actualCount) override {
+        SkASSERT(fLockStride && fVertices && fVertexBuffer && !fVertexData);
+
+        if (fCanMapVB) {
+            fVertexBuffer->unmap();
+        } else {
+            fVertexBuffer->updateData(fVertices, actualCount * fLockStride);
+            sk_free(fVertices);
+        }
+
+        fVertexData = GrThreadSafeCache::MakeVertexData(std::move(fVertexBuffer),
+                                                        actualCount, fLockStride);
+
+        fVertices = nullptr;
+        fLockStride = 0;
+    }
+
+    sk_sp<GrThreadSafeCache::VertexData> detachVertexData() {
+        SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && fVertexData);
+
+        return std::move(fVertexData);
+    }
+
+private:
+    sk_sp<GrThreadSafeCache::VertexData> fVertexData;
+    sk_sp<GrGpuBuffer> fVertexBuffer;
+    GrResourceProvider* fResourceProvider;
+    bool fCanMapVB;
+    void* fVertices = nullptr;
+    size_t fLockStride = 0;
+};
+
+class TriangulatingPathOp final : public GrMeshDrawOp {
+private:
+    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
+
+public:
+    DEFINE_OP_CLASS_ID
+
+    static GrOp::Owner Make(GrRecordingContext* context,
+                            GrPaint&& paint,
+                            const GrStyledShape& shape,
+                            const SkMatrix& viewMatrix,
+                            SkIRect devClipBounds,
+                            GrAAType aaType,
+                            const GrUserStencilSettings* stencilSettings) {
+        return Helper::FactoryHelper<TriangulatingPathOp>(context, std::move(paint), shape,
+                                                          viewMatrix, devClipBounds, aaType,
+                                                          stencilSettings);
+    }
+
+    const char* name() const override { return "TriangulatingPathOp"; }
+
+    void visitProxies(const GrVisitProxyFunc& func) const override {
+        if (fProgramInfo) {
+            fProgramInfo->visitFPProxies(func);
+        } else {
+            fHelper.visitProxies(func);
+        }
+    }
+
+    TriangulatingPathOp(GrProcessorSet* processorSet,
+                        const SkPMColor4f& color,
+                        const GrStyledShape& shape,
+                        const SkMatrix& viewMatrix,
+                        const SkIRect& devClipBounds,
+                        GrAAType aaType,
+                        const GrUserStencilSettings* stencilSettings)
+            : INHERITED(ClassID())
+            , fHelper(processorSet, aaType, stencilSettings)
+            , fColor(color)
+            , fShape(shape)
+            , fViewMatrix(viewMatrix)
+            , fDevClipBounds(devClipBounds)
+            , fAntiAlias(GrAAType::kCoverage == aaType) {
+        SkRect devBounds;
+        viewMatrix.mapRect(&devBounds, shape.bounds());
+        if (shape.inverseFilled()) {
+            // Because the clip bounds are used to add a contour for inverse fills, they must also
+            // include the path bounds.
+            devBounds.join(SkRect::Make(fDevClipBounds));
+        }
+        this->setBounds(devBounds, HasAABloat(fAntiAlias), IsHairline::kNo);
+    }
+
+    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
+
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrClampType clampType) override {
+        GrProcessorAnalysisCoverage coverage = fAntiAlias
+                                                       ? GrProcessorAnalysisCoverage::kSingleChannel
+                                                       : GrProcessorAnalysisCoverage::kNone;
+        // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
+        return fHelper.finalizeProcessors(caps, clip, clampType, coverage, &fColor, nullptr);
+    }
+
+private:
+    SkPath getPath() const {
+        SkASSERT(!fShape.style().applies());
+        SkPath path;
+        fShape.asPath(&path);
+        return path;
+    }
+
+    static void CreateKey(GrUniqueKey* key,
+                          const GrStyledShape& shape,
+                          const SkIRect& devClipBounds) {
+        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+
+        bool inverseFill = shape.inverseFilled();
+
+        static constexpr int kClipBoundsCnt = sizeof(devClipBounds) / sizeof(uint32_t);
+        int shapeKeyDataCnt = shape.unstyledKeySize();
+        SkASSERT(shapeKeyDataCnt >= 0);
+        GrUniqueKey::Builder builder(key, kDomain, shapeKeyDataCnt + kClipBoundsCnt, "Path");
+        shape.writeUnstyledKey(&builder[0]);
+        // For inverse fills, the tessellation is dependent on clip bounds.
+        if (inverseFill) {
+            memcpy(&builder[shapeKeyDataCnt], &devClipBounds, sizeof(devClipBounds));
+        } else {
+            memset(&builder[shapeKeyDataCnt], 0, sizeof(devClipBounds));
+        }
+
+        builder.finish();
+    }
+
+    // Triangulate the provided 'shape' in the shape's coordinate space. 'tol' should already
+    // have been mapped back from device space.
+    static int Triangulate(GrEagerVertexAllocator* allocator,
+                           const SkMatrix& viewMatrix,
+                           const GrStyledShape& shape,
+                           const SkIRect& devClipBounds,
+                           SkScalar tol,
+                           bool* isLinear) {
+        SkRect clipBounds = SkRect::Make(devClipBounds);
+
+        SkMatrix vmi;
+        if (!viewMatrix.invert(&vmi)) {
+            return 0;
+        }
+        vmi.mapRect(&clipBounds);
+
+        SkASSERT(!shape.style().applies());
+        SkPath path;
+        shape.asPath(&path);
+
+        return GrTriangulator::PathToTriangles(path, tol, clipBounds, allocator, isLinear);
+    }
+
+    void createNonAAMesh(GrMeshDrawTarget* target) {
+        SkASSERT(!fAntiAlias);
+        GrResourceProvider* rp = target->resourceProvider();
+        auto threadSafeCache = target->threadSafeCache();
+
+        GrUniqueKey key;
+        CreateKey(&key, fShape, fDevClipBounds);
+
+        SkScalar tol = GrPathUtils::scaleToleranceToSrc(GrPathUtils::kDefaultTolerance,
+                                                        fViewMatrix, fShape.bounds());
+
+        if (!fVertexData) {
+            auto [cachedVerts, data] = threadSafeCache->findVertsWithData(key);
+            if (cachedVerts && cache_match(data.get(), tol)) {
+                fVertexData = std::move(cachedVerts);
+            }
+        }
+
+        if (fVertexData) {
+            if (!fVertexData->gpuBuffer()) {
+                sk_sp<GrGpuBuffer> buffer = rp->createBuffer(fVertexData->size(),
+                                                             GrGpuBufferType::kVertex,
+                                                             kStatic_GrAccessPattern,
+                                                             fVertexData->vertices());
+                if (!buffer) {
+                    return;
+                }
+
+                // Since we have a direct context and a ref on 'fVertexData' we need not worry
+                // about any threading issues in this call.
+                fVertexData->setGpuBuffer(std::move(buffer));
+            }
+
+            fMesh = CreateMesh(target, fVertexData->refGpuBuffer(), 0, fVertexData->numVertices());
+            return;
+        }
+
+        bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
+        StaticVertexAllocator allocator(rp, canMapVB);
+
+        bool isLinear;
+        int vertexCount = Triangulate(&allocator, fViewMatrix, fShape, fDevClipBounds, tol,
+                                      &isLinear);
+        if (vertexCount == 0) {
+            return;
+        }
+
+        fVertexData = allocator.detachVertexData();
+
+        key.setCustomData(create_data(vertexCount, isLinear, tol));
+
+        auto [tmpV, tmpD] = threadSafeCache->addVertsWithData(key, fVertexData, is_newer_better);
+        if (tmpV != fVertexData) {
+            SkASSERT(!tmpV->gpuBuffer());
+            // In this case, although the different triangulation found in the cache is better,
+            // we will continue on with the current triangulation since it is already on the gpu.
+        } else {
+            // This isn't perfect. The current triangulation is in the cache but it may have
+            // replaced a pre-existing one. A duplicated listener is unlikely and not that
+            // expensive so we just roll with it.
+            fShape.addGenIDChangeListener(
+                sk_make_sp<UniqueKeyInvalidator>(key, target->contextUniqueID()));
+        }
+
+        fMesh = CreateMesh(target, fVertexData->refGpuBuffer(), 0, fVertexData->numVertices());
+    }
+
+    void createAAMesh(GrMeshDrawTarget* target) {
+        SkASSERT(!fVertexData);
+        SkASSERT(fAntiAlias);
+        SkPath path = this->getPath();
+        if (path.isEmpty()) {
+            return;
+        }
+        SkRect clipBounds = SkRect::Make(fDevClipBounds);
+        path.transform(fViewMatrix);
+        SkScalar tol = GrPathUtils::kDefaultTolerance;
+        sk_sp<const GrBuffer> vertexBuffer;
+        int firstVertex;
+        GrEagerDynamicVertexAllocator allocator(target, &vertexBuffer, &firstVertex);
+        int vertexCount = GrAATriangulator::PathToAATriangles(path, tol, clipBounds, &allocator);
+        if (vertexCount == 0) {
+            return;
+        }
+        fMesh = CreateMesh(target, std::move(vertexBuffer), firstVertex, vertexCount);
+    }
+
+    GrProgramInfo* programInfo() override { return fProgramInfo; }
+
+    void onCreateProgramInfo(const GrCaps* caps,
+                             SkArenaAlloc* arena,
+                             const GrSurfaceProxyView& writeView,
+                             bool usesMSAASurface,
+                             GrAppliedClip&& appliedClip,
+                             const GrDstProxyView& dstProxyView,
+                             GrXferBarrierFlags renderPassXferBarriers,
+                             GrLoadOp colorLoadOp) override {
+        GrGeometryProcessor* gp;
+        {
+            using namespace GrDefaultGeoProcFactory;
+
+            Color color(fColor);
+            LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
+            Coverage::Type coverageType;
+            if (fAntiAlias) {
+                if (fHelper.compatibleWithCoverageAsAlpha()) {
+                    coverageType = Coverage::kAttributeTweakAlpha_Type;
+                } else {
+                    coverageType = Coverage::kAttribute_Type;
+                }
+            } else {
+                coverageType = Coverage::kSolid_Type;
+            }
+            if (fAntiAlias) {
+                gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(arena, color, coverageType,
+                                                                 localCoordsType, fViewMatrix);
+            } else {
+                gp = GrDefaultGeoProcFactory::Make(arena, color, coverageType, localCoordsType,
+                                                   fViewMatrix);
+            }
+        }
+        if (!gp) {
+            return;
+        }
+
+#ifdef SK_DEBUG
+        auto vertexStride = sizeof(SkPoint);
+        if (fAntiAlias) {
+            vertexStride += sizeof(float);
+        }
+        SkASSERT(vertexStride == gp->vertexStride());
+#endif
+
+        GrPrimitiveType primitiveType = TRIANGULATOR_WIREFRAME ? GrPrimitiveType::kLines
+                                                               : GrPrimitiveType::kTriangles;
+
+        fProgramInfo =  fHelper.createProgramInfoWithStencil(caps, arena, writeView,
+                                                             std::move(appliedClip), dstProxyView,
+                                                             gp, primitiveType,
+                                                             renderPassXferBarriers, colorLoadOp);
+    }
+
+    void onPrePrepareDraws(GrRecordingContext* rContext,
+                           const GrSurfaceProxyView& writeView,
+                           GrAppliedClip* clip,
+                           const GrDstProxyView& dstProxyView,
+                           GrXferBarrierFlags renderPassXferBarriers,
+                           GrLoadOp colorLoadOp) override {
+        TRACE_EVENT0("skia.gpu", TRACE_FUNC);
+
+        INHERITED::onPrePrepareDraws(rContext, writeView, clip, dstProxyView,
+                                     renderPassXferBarriers, colorLoadOp);
+
+        if (fAntiAlias) {
+            // TODO: pull the triangulation work forward to the recording thread for the AA case
+            // too.
+            return;
+        }
+
+        auto threadSafeViewCache = rContext->priv().threadSafeCache();
+
+        GrUniqueKey key;
+        CreateKey(&key, fShape, fDevClipBounds);
+
+        SkScalar tol = GrPathUtils::scaleToleranceToSrc(GrPathUtils::kDefaultTolerance,
+                                                        fViewMatrix, fShape.bounds());
+
+        auto [cachedVerts, data] = threadSafeViewCache->findVertsWithData(key);
+        if (cachedVerts && cache_match(data.get(), tol)) {
+            fVertexData = std::move(cachedVerts);
+            return;
+        }
+
+        GrCpuVertexAllocator allocator;
+
+        bool isLinear;
+        int vertexCount = Triangulate(&allocator, fViewMatrix, fShape, fDevClipBounds, tol,
+                                      &isLinear);
+        if (vertexCount == 0) {
+            return;
+        }
+
+        fVertexData = allocator.detachVertexData();
+
+        key.setCustomData(create_data(vertexCount, isLinear, tol));
+
+        // If some other thread created and cached its own triangulation, the 'is_newer_better'
+        // predicate will replace the version in the cache if 'fVertexData' is a more accurate
+        // triangulation. This will leave some other recording threads using a poorer triangulation
+        // but will result in a version with greater applicability being in the cache.
+        auto [tmpV, tmpD] = threadSafeViewCache->addVertsWithData(key, fVertexData,
+                                                                  is_newer_better);
+        if (tmpV != fVertexData) {
+            // Someone beat us to creating the triangulation (and it is better than ours) so
+            // just go ahead and use it.
+            SkASSERT(cache_match(tmpD.get(), tol));
+            fVertexData = std::move(tmpV);
+        } else {
+            // This isn't perfect. The current triangulation is in the cache but it may have
+            // replaced a pre-existing one. A duplicated listener is unlikely and not that
+            // expensive so we just roll with it.
+            fShape.addGenIDChangeListener(
+                    sk_make_sp<UniqueKeyInvalidator>(key, rContext->priv().contextID()));
+        }
+    }
+
+    void onPrepareDraws(GrMeshDrawTarget* target) override {
+        if (fAntiAlias) {
+            this->createAAMesh(target);
+        } else {
+            this->createNonAAMesh(target);
+        }
+    }
+
+    static GrSimpleMesh* CreateMesh(GrMeshDrawTarget* target,
+                                    sk_sp<const GrBuffer> vb,
+                                    int firstVertex,
+                                    int count) {
+        auto mesh = target->allocMesh();
+        mesh->set(std::move(vb), count, firstVertex);
+        return mesh;
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        if (!fProgramInfo) {
+            this->createProgramInfo(flushState);
+        }
+
+        if (!fProgramInfo || !fMesh) {
+            return;
+        }
+
+        flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
+        flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
+        flushState->drawMesh(*fMesh);
+    }
+
+#if GR_TEST_UTILS
+    SkString onDumpInfo() const override {
+        return SkStringPrintf("Color 0x%08x, aa: %d\n%s",
+                              fColor.toBytes_RGBA(), fAntiAlias, fHelper.dumpInfo().c_str());
+    }
+#endif
+
+    Helper         fHelper;
+    SkPMColor4f    fColor;
+    GrStyledShape  fShape;
+    SkMatrix       fViewMatrix;
+    SkIRect        fDevClipBounds;
+    bool           fAntiAlias;
+
+    GrSimpleMesh*  fMesh = nullptr;
+    GrProgramInfo* fProgramInfo = nullptr;
+
+    sk_sp<GrThreadSafeCache::VertexData> fVertexData;
+
+    using INHERITED = GrMeshDrawOp;
+};
+
+}  // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if GR_TEST_UTILS
+
+GR_DRAW_OP_TEST_DEFINE(TriangulatingPathOp) {
+    SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
+    const SkPath& path = GrTest::TestPath(random);
+    SkIRect devClipBounds = SkIRect::MakeLTRB(
+        random->nextU(), random->nextU(), random->nextU(), random->nextU());
+    devClipBounds.sort();
+    static constexpr GrAAType kAATypes[] = {GrAAType::kNone, GrAAType::kMSAA, GrAAType::kCoverage};
+    GrAAType aaType;
+    do {
+        aaType = kAATypes[random->nextULessThan(SK_ARRAY_COUNT(kAATypes))];
+    } while(GrAAType::kMSAA == aaType && numSamples <= 1);
+    GrStyle style;
+    do {
+        GrTest::TestStyle(random, &style);
+    } while (!style.isSimpleFill());
+    GrStyledShape shape(path, style);
+    return TriangulatingPathOp::Make(context, std::move(paint), shape, viewMatrix, devClipBounds,
+                                     aaType, GrGetRandomStencil(random, context));
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+namespace skgpu::v1 {
+
+TriangulatingPathRenderer::TriangulatingPathRenderer()
+    : fMaxVerbCount(GR_AA_TESSELLATOR_MAX_VERB_COUNT) {
+}
+
+GrPathRenderer::CanDrawPath
+TriangulatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
+    // Don't use this path renderer with dynamic MSAA. DMSAA tries to not rely on caching.
+    if (args.fSurfaceProps->flags() & SkSurfaceProps::kDynamicMSAA_Flag) {
+        return CanDrawPath::kNo;
+    }
+    // This path renderer can draw fill styles, and can do screenspace antialiasing via a
+    // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex
+    // ones to simpler algorithms. We pass on paths that have styles, though they may come back
+    // around after applying the styling information to the geometry to create a filled path.
+    if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) {
+        return CanDrawPath::kNo;
+    }
+    switch (args.fAAType) {
+        case GrAAType::kNone:
+        case GrAAType::kMSAA:
+            // Prefer MSAA, if any antialiasing. In the non-analytic-AA case, We skip paths that
+            // don't have a key since the real advantage of this path renderer comes from caching
+            // the tessellated geometry.
+            if (!args.fShape->hasUnstyledKey()) {
+                return CanDrawPath::kNo;
+            }
+            break;
+        case GrAAType::kCoverage:
+            // Use analytic AA if we don't have MSAA. In this case, we do not cache, so we accept
+            // paths without keys.
+            SkPath path;
+            args.fShape->asPath(&path);
+            if (path.countVerbs() > fMaxVerbCount) {
+                return CanDrawPath::kNo;
+            }
+            break;
+    }
+    return CanDrawPath::kYes;
+}
+
+bool TriangulatingPathRenderer::onDrawPath(const DrawPathArgs& args) {
+    GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
+                              "GrTriangulatingPathRenderer::onDrawPath");
+
+    GrOp::Owner op = TriangulatingPathOp::Make(
+            args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix,
+            *args.fClipConservativeBounds, args.fAAType, args.fUserStencilSettings);
+    args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+    return true;
+}
+
+} // namespace skgpu::v1