Add a path renderer that uses GPU tessellation

Implements a simple first pass for a path renderer that uses the
classic Red Book "stencil then cover" method, and linearizes curves
with GPU tessellation shaders.

The new path renderer is disabled by default, and can only be enabled
in the viewer UI or by passing the "--pr gtess" flag.

Change-Id: Ic9354952e93c8b108577961760b4f0daa82d35aa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/261715
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index 71efe38..34d6931 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -24,12 +24,18 @@
 #include "src/gpu/ops/GrSmallPathRenderer.h"
 #include "src/gpu/ops/GrStencilAndCoverPathRenderer.h"
 #include "src/gpu/ops/GrTessellatingPathRenderer.h"
+#include "src/gpu/tessellate/GrGpuTessellationPathRenderer.h"
 
 GrPathRendererChain::GrPathRendererChain(GrRecordingContext* context, const Options& options) {
     const GrCaps& caps = *context->priv().caps();
     if (options.fGpuPathRenderers & GpuPathRenderers::kDashLine) {
         fChain.push_back(sk_make_sp<GrDashLinePathRenderer>());
     }
+    if (options.fGpuPathRenderers & GpuPathRenderers::kGpuTessellation) {
+        if (caps.shaderCaps()->tessellationSupport()) {
+            fChain.push_back(sk_make_sp<GrGpuTessellationPathRenderer>());
+        }
+    }
     if (options.fGpuPathRenderers & GpuPathRenderers::kAAConvex) {
         fChain.push_back(sk_make_sp<GrAAConvexPathRenderer>());
     }
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index a65f110..db7b656 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -15,34 +15,36 @@
 
 #include "src/gpu/ops/GrOp.h"
 
-GrPipeline::GrPipeline(const InitArgs& args,
-                       GrProcessorSet&& processors,
-                       GrAppliedClip&& appliedClip)
+GrPipeline::GrPipeline(const InitArgs& args, sk_sp<const GrXferProcessor> xferProcessor,
+                       const GrAppliedHardClip& hardClip)
         : fOutputSwizzle(args.fOutputSwizzle) {
-    SkASSERT(processors.isFinalized());
-
     fFlags = (Flags)args.fInputFlags;
-    if (appliedClip.hasStencilClip()) {
+    if (hardClip.hasStencilClip()) {
         fFlags |= Flags::kHasStencilClip;
     }
-    if (appliedClip.scissorState().enabled()) {
+    if (hardClip.scissorState().enabled()) {
         fFlags |= Flags::kScissorEnabled;
     }
 
-    fWindowRectsState = appliedClip.windowRectsState();
+    fWindowRectsState = hardClip.windowRectsState();
     if (!args.fUserStencil->isDisabled(fFlags & Flags::kHasStencilClip)) {
         fFlags |= Flags::kStencilEnabled;
     }
 
     fUserStencilSettings = args.fUserStencil;
 
-    fXferProcessor = processors.refXferProcessor();
+    fXferProcessor = std::move(xferProcessor);
 
     if (args.fDstProxyView.proxy()) {
         fDstProxyView = args.fDstProxyView.proxyView();
         fDstTextureOffset = args.fDstProxyView.offset();
     }
+}
 
+GrPipeline::GrPipeline(const InitArgs& args, GrProcessorSet&& processors,
+                       GrAppliedClip&& appliedClip)
+        : GrPipeline(args, processors.refXferProcessor(), appliedClip.hardClip()) {
+    SkASSERT(processors.isFinalized());
     // Copy GrFragmentProcessors from GrProcessorSet to Pipeline
     fNumColorProcessors = processors.numColorFragmentProcessors();
     int numTotalProcessors = fNumColorProcessors +
@@ -77,8 +79,6 @@
         , fUserStencilSettings(userStencil)
         , fFlags((Flags)inputFlags)
         , fXferProcessor(std::move(xp))
-        , fFragmentProcessors()
-        , fNumColorProcessors(0)
         , fOutputSwizzle(outputSwizzle) {
     if (GrScissorTest::kEnabled == scissorTest) {
         fFlags |= Flags::kScissorEnabled;
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 7bad0e5..c057051 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -25,6 +25,7 @@
 #include "src/gpu/geometry/GrRect.h"
 
 class GrAppliedClip;
+class GrAppliedHardClip;
 class GrOp;
 class GrRenderTargetContext;
 
@@ -117,6 +118,7 @@
                InputFlags = InputFlags::kNone,
                const GrUserStencilSettings* = &GrUserStencilSettings::kUnused);
 
+    GrPipeline(const InitArgs& args, sk_sp<const GrXferProcessor>, const GrAppliedHardClip&);
     GrPipeline(const InitArgs&, GrProcessorSet&&, GrAppliedClip&&);
 
     GrPipeline(const GrPipeline&) = delete;
@@ -253,7 +255,7 @@
     FragmentProcessorArray fFragmentProcessors;
 
     // This value is also the index in fFragmentProcessors where coverage processors begin.
-    int fNumColorProcessors;
+    int fNumColorProcessors = 0;
 
     GrSwizzle fOutputSwizzle;
 };
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 3fffc8a..8a9fa42 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -99,6 +99,7 @@
         kGrConicEffect_ClassID,
         kGrConstColorProcessor_ClassID,
         kGrConvexPolyEffect_ClassID,
+        kGrCoverShader_ClassID,
         kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
         kGrDiffuseLightingEffect_ClassID,
         kGrDisplacementMapEffect_ClassID,
@@ -138,6 +139,7 @@
         kGrSampleMaskProcessor_ClassID,
         kGrSaturateProcessor_ClassID,
         kGrSweepGradientLayout_ClassID,
+        kGrTessellateWedgeShader_ClassID,
         kGrTextureEffect_ClassID,
         kGrTextureGradientColorizer_ClassID,
         kGrTiledGradientEffect_ClassID,
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index a8b1996..58e2aae 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -541,6 +541,7 @@
     friend class GrCCPerFlushResources;              // for access to addDrawOp
     friend class GrCoverageCountingPathRenderer;     // for access to addDrawOp
     friend class GrFillRectOp;                       // for access to addDrawOp
+    friend class GrGpuTessellationPathRenderer;      // for access to addDrawOp
     friend class GrTextureOp;                        // for access to addDrawOp
 
     SkDEBUGCODE(void onValidate() const override;)
diff --git a/src/gpu/tessellate/GrCenterWedgePatchGen.cpp b/src/gpu/tessellate/GrCenterWedgePatchGen.cpp
new file mode 100644
index 0000000..882854d
--- /dev/null
+++ b/src/gpu/tessellate/GrCenterWedgePatchGen.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrCenterWedgePatchGen.h"
+
+#include "include/core/SkPath.h"
+#include "src/core/SkPathPriv.h"
+
+static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
+    return a * (1 - T) + b * T;
+}
+
+int GrCenterWedgePatchGen::walkPath(std::array<SkPoint, 5>* wedgeData,
+                                    SkTArray<SkPoint, true>* contourMidpoints) {
+    fWedgeData = (std::array<SkPoint, 5>*)wedgeData;
+    fContourMidpoints = contourMidpoints;
+
+    fLastPt = {0,0};
+    fCurrContourInitialPatchCount = 0;
+    if (fWedgeData) {
+        fCurrContourMidpoint = fContourMidpoints->begin();
+    } else {
+        SkASSERT(fContourMidpoints->empty());
+        fCurrContourFanPtsSum = {0,0};
+    }
+    fNumWedges = 0;
+
+    const SkPoint* pts = SkPathPriv::PointData(fPath);
+    SkPoint contourStartPt = {0,0};
+    int ptsIdx = 0;
+    for (SkPath::Verb verb : SkPathPriv::Verbs(fPath)) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                this->close(contourStartPt);
+                contourStartPt = fLastPt = pts[ptsIdx++];
+                continue;
+            case SkPath::kClose_Verb:
+                fLastPt = this->close(contourStartPt);
+                continue;
+            case SkPath::kLine_Verb:
+                fLastPt = this->lineTo(pts[ptsIdx++]);
+                continue;
+            case SkPath::kQuad_Verb:
+                fLastPt = this->quadraticTo(pts[ptsIdx], pts[ptsIdx + 1]);
+                ptsIdx += 2;
+                continue;
+            case SkPath::kCubic_Verb:
+                fLastPt = this->cubicTo(pts[ptsIdx], pts[ptsIdx + 1], pts[ptsIdx + 2]);
+                ptsIdx += 3;
+                continue;
+            case SkPath::kConic_Verb:
+                // TODO: Eventually we want to use rational cubic wedges in order to support
+                // perspective and conics.
+            default:
+                SK_ABORT("Unexpected path verb.");
+        }
+    }
+    this->close(contourStartPt);
+    SkASSERT(!fWedgeData || fCurrContourMidpoint == fContourMidpoints->end());
+    return fNumWedges;
+}
+
+inline SkPoint GrCenterWedgePatchGen::lineTo(const SkPoint& p1) {
+    if (fWedgeData) {
+        fWedgeData[fNumWedges] = {
+                fLastPt, lerp(fLastPt, p1, 1/3.f), lerp(fLastPt, p1, 2/3.f), p1,
+                *fCurrContourMidpoint};
+    } else {
+        fCurrContourFanPtsSum += p1;
+    }
+    ++fNumWedges;
+    return p1;
+}
+
+inline SkPoint GrCenterWedgePatchGen::quadraticTo(const SkPoint& p1, const SkPoint& p2) {
+    if (fWedgeData) {
+        fWedgeData[fNumWedges] = {
+                fLastPt, lerp(fLastPt, p1, 2/3.f), lerp(p1, p2, 1/3.f), p2, *fCurrContourMidpoint};
+    } else {
+        fCurrContourFanPtsSum += p2;
+    }
+    ++fNumWedges;
+    return p2;
+}
+
+inline SkPoint GrCenterWedgePatchGen::cubicTo(const SkPoint& p1, const SkPoint& p2,
+                                              const SkPoint& p3) {
+    if (fWedgeData) {
+        fWedgeData[fNumWedges] = {fLastPt, p1, p2, p3, *fCurrContourMidpoint};
+    } else {
+        fCurrContourFanPtsSum += p3;
+    }
+    ++fNumWedges;
+    return p3;
+}
+
+inline SkPoint GrCenterWedgePatchGen::close(const SkPoint& startPt) {
+    if (fLastPt != startPt) {
+        this->lineTo(startPt);
+    }
+    if (fWedgeData) {
+        ++fCurrContourMidpoint;
+    } else {
+        int numPatchesInContour = fNumWedges - fCurrContourInitialPatchCount;
+        fContourMidpoints->push_back(fCurrContourFanPtsSum * (1.f/numPatchesInContour));
+        fCurrContourInitialPatchCount = fNumWedges;
+        fCurrContourFanPtsSum = {0,0};
+    }
+    return startPt;
+}
diff --git a/src/gpu/tessellate/GrCenterWedgePatchGen.h b/src/gpu/tessellate/GrCenterWedgePatchGen.h
new file mode 100644
index 0000000..ac7f770
--- /dev/null
+++ b/src/gpu/tessellate/GrCenterWedgePatchGen.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCenterWedgePatchGen_DEFINED
+#define GrCenterWedgePatchGen_DEFINED
+
+#include "include/core/SkPoint.h"
+#include "include/private/SkTArray.h"
+#include "src/gpu/tessellate/GrTessellateWedgeShader.h"
+#include <array>
+
+class SkPath;
+
+// Generates an array of cubic "wedges" from an SkPath, converting any lines and quadratics to
+// cubics. These wedges can then be fed into GrTessellateWedgeShader to stencil the path. A wedge is
+// a 5-point tessellation patch consisting of 4 cubic control points, plus an anchor point fanning
+// from the center of the curve's resident contour.
+// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
+class GrCenterWedgePatchGen {
+public:
+    GrCenterWedgePatchGen(const SkPath& path) : fPath(path) {}
+
+    // Iterates over the path and returns the number of wedges.
+    //
+    // If vertexData is null, then contourMidpoints must be empty and the center point of each
+    // contour, in order, is appended at the end.
+    //
+    // If vertexData is non-null, then wedges are written out to vertexData using the in-order
+    // values from contourMidpoints as the fan points for each contour.
+    int walkPath(std::array<SkPoint, 5>* vertexData, SkTArray<SkPoint, true>* contourMidpoints);
+
+private:
+    SkPoint lineTo(const SkPoint& p1);
+    SkPoint quadraticTo(const SkPoint& p1, const SkPoint& p2);
+    SkPoint cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3);
+    SkPoint close(const SkPoint& startPt);
+
+    const SkPath& fPath;
+
+    // Stateful data for path iteration.
+    std::array<SkPoint, 5>* fWedgeData;
+    SkTArray<SkPoint, true>* fContourMidpoints;
+    SkPoint fLastPt;
+    int fCurrContourInitialPatchCount;
+    union {
+        SkPoint* fCurrContourMidpoint;  // When fPatchData != null
+        SkPoint fCurrContourFanPtsSum;  // When fPatchData == null
+    };
+    int fNumWedges;
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrCoverShader.cpp b/src/gpu/tessellate/GrCoverShader.cpp
new file mode 100644
index 0000000..936fea7
--- /dev/null
+++ b/src/gpu/tessellate/GrCoverShader.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrCoverShader.h"
+
+#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
+#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
+#include "src/gpu/glsl/GrGLSLVarying.h"
+#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
+
+class GrCoverShader::Impl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
+        const char* viewMatrix;
+        fViewMatrixUniform = args.fUniformHandler->addUniform(
+                kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
+
+        const char* pathBounds;
+        fPathBoundsUniform = args.fUniformHandler->addUniform(
+                kVertex_GrShaderFlag, kFloat4_GrSLType, "path_bounds", &pathBounds);
+
+        const char* color;
+        fColorUniform = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kHalf4_GrSLType, "color", &color);
+
+        args.fVaryingHandler->emitAttributes(args.fGP.cast<GrCoverShader>());
+
+        args.fVertBuilder->codeAppendf(R"(
+                // Use sk_VertexID and uniforms (instead of vertex data) to find vertex positions.
+                float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
+                float2 localcoord = mix(%s.xy, %s.zw, T);
+                float2 vertexpos = (%s * float3(localcoord, 1)).xy;
+
+                // Outset to avoid possible T-junctions with extreme edges of the path.
+                float2x2 M2 = float2x2(%s);
+                float2 devoutset = .25 * sign(M2 * (T - .5));
+                vertexpos += devoutset;
+                localcoord += inverse(M2) * devoutset;)",
+                pathBounds, pathBounds, viewMatrix, viewMatrix);
+
+        this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
+                             GrShaderVar("localcoord", kFloat2_GrSLType),
+                             args.fFPCoordTransformHandler);
+
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
+
+        args.fFragBuilder->codeAppendf(R"(
+                %s = %s;
+                %s = half4(1);)",
+                args.fOutputColor, color, args.fOutputCoverage);
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
+                 const CoordTransformRange& transformRange) override {
+        const GrCoverShader& shader = primProc.cast<GrCoverShader>();
+        const SkRect& b = shader.fPathBounds;
+        const SkPMColor4f& color = shader.fColor;
+        pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix);
+        pdman.set4f(fPathBoundsUniform, b.left(), b.top(), b.right(), b.bottom());
+        pdman.set4f(fColorUniform, color.fR, color.fG, color.fB, color.fA);
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+    }
+
+    GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
+    GrGLSLUniformHandler::UniformHandle fPathBoundsUniform;
+    GrGLSLUniformHandler::UniformHandle fColorUniform;
+};
+
+GrGLSLPrimitiveProcessor* GrCoverShader::createGLSLInstance(const GrShaderCaps&) const {
+    return new Impl;
+}
diff --git a/src/gpu/tessellate/GrCoverShader.h b/src/gpu/tessellate/GrCoverShader.h
new file mode 100644
index 0000000..c39cf9e
--- /dev/null
+++ b/src/gpu/tessellate/GrCoverShader.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCoverShader_DEFINED
+#define GrCoverShader_DEFINED
+
+#include "src/gpu/GrGeometryProcessor.h"
+
+// Draws a path's bounding box, with a subpixel outset to avoid possible T-junctions with extreme
+// edges of the path. This class is used for the "cover" pass of stencil-then-cover path rendering.
+// NOTE: The emitted geometry may not be axis-aligned, depending on the view matrix.
+class GrCoverShader : public GrGeometryProcessor {
+public:
+    GrCoverShader(const SkMatrix& viewMatrix, const SkRect& pathBounds, const SkPMColor4f& color)
+            : GrGeometryProcessor(kGrCoverShader_ClassID)
+            , fViewMatrix(viewMatrix)
+            , fPathBounds(pathBounds)
+            , fColor(color) {}
+
+    const char* name() const override { return "GrCoverShader"; }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+private:
+    const SkMatrix fViewMatrix;
+    const SkRect fPathBounds;
+    const SkPMColor4f fColor;
+
+    class Impl;
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
new file mode 100644
index 0000000..7c8a29c
--- /dev/null
+++ b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrGpuTessellationPathRenderer.h"
+
+#include "src/core/SkPathPriv.h"
+#include "src/gpu/GrClip.h"
+#include "src/gpu/GrMemoryPool.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/GrRenderTargetContext.h"
+#include "src/gpu/geometry/GrShape.h"
+#include "src/gpu/tessellate/GrTessellatePathOp.h"
+
+GrPathRenderer::CanDrawPath GrGpuTessellationPathRenderer::onCanDrawPath(
+        const CanDrawPathArgs& args) const {
+    // This class should not have been added to the chain without tessellation support.
+    SkASSERT(args.fCaps->shaderCaps()->tessellationSupport());
+    if (!args.fShape->style().isSimpleFill() || args.fShape->inverseFilled()) {
+        return CanDrawPath::kNo;
+    }
+    if (GrAAType::kCoverage == args.fAAType) {
+        SkASSERT(1 == args.fProxy->numSamples());
+        if (!args.fProxy->canUseMixedSamples(*args.fCaps)) {
+            return CanDrawPath::kNo;
+        }
+    }
+    SkPath path;
+    args.fShape->asPath(&path);
+    if (SkPathPriv::ConicWeightCnt(path)) {
+        return CanDrawPath::kNo;
+    }
+    return CanDrawPath::kYes;
+}
+
+bool GrGpuTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
+    SkPath path;
+    args.fShape->asPath(&path);
+
+    GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool();
+    args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate<GrTessellatePathOp>(
+            *args.fViewMatrix, path, std::move(args.fPaint), args.fAAType));
+
+    return true;
+}
+
+void GrGpuTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
+    SkPath path;
+    args.fShape->asPath(&path);
+
+    GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
+
+    GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool();
+    args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate<GrTessellatePathOp>(
+            *args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly));
+}
diff --git a/src/gpu/tessellate/GrGpuTessellationPathRenderer.h b/src/gpu/tessellate/GrGpuTessellationPathRenderer.h
new file mode 100644
index 0000000..74bc6ee
--- /dev/null
+++ b/src/gpu/tessellate/GrGpuTessellationPathRenderer.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGpuTessellationPathRenderer_DEFINED
+#define GrGpuTessellationPathRenderer_DEFINED
+
+#include "src/gpu/GrPathRenderer.h"
+
+// This is the tie-in point for path rendering via GrTessellatePathOp.
+class GrGpuTessellationPathRenderer : public GrPathRenderer {
+    StencilSupport getStencilSupport(const GrShape& shape) const {
+        // TODO: Single-pass (e.g., convex) paths can have full support.
+        return kStencilOnly_StencilSupport;
+    }
+
+    CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override;
+    bool onDrawPath(const DrawPathArgs&) override;
+    void onStencilPath(const StencilPathArgs&) override;
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrTessellatePathOp.cpp b/src/gpu/tessellate/GrTessellatePathOp.cpp
new file mode 100644
index 0000000..88c8fce
--- /dev/null
+++ b/src/gpu/tessellate/GrTessellatePathOp.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrTessellatePathOp.h"
+
+#include "src/gpu/GrGpu.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
+#include "src/gpu/tessellate/GrCenterWedgePatchGen.h"
+#include "src/gpu/tessellate/GrCoverShader.h"
+#include "src/gpu/tessellate/GrTessellateWedgeShader.h"
+
+GrTessellatePathOp::FixedFunctionFlags GrTessellatePathOp::fixedFunctionFlags() const {
+    auto flags = FixedFunctionFlags::kUsesStencil;
+    if (GrAAType::kNone != fAAType) {
+        flags |= FixedFunctionFlags::kUsesHWAA;
+    }
+    return flags;
+}
+
+void GrTessellatePathOp::onPrepare(GrOpFlushState* state) {
+    SkSTArray<16, SkPoint, true> contourMidpoints;
+    GrCenterWedgePatchGen patchGen(fPath);
+    int numPatches = patchGen.walkPath(nullptr, &contourMidpoints);
+    if (!numPatches) {
+        return;
+    }
+    if (auto* wedgeData = (std::array<SkPoint, 5>*)state->makeVertexSpace(
+            sizeof(SkPoint), numPatches * 5, &fWedgeBuffer, &fBaseWedgeVertex)) {
+        fNumWedges = patchGen.walkPath(wedgeData, &contourMidpoints);
+        SkASSERT(fNumWedges == numPatches);
+    }
+}
+
+void GrTessellatePathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
+    if (!fWedgeBuffer) {
+        return;
+    }
+
+    GrAppliedClip clip = state->detachAppliedClip();
+    GrPipeline::FixedDynamicState fixedDynamicState;
+    if (clip.scissorState().enabled()) {
+        fixedDynamicState.fScissorRect = clip.scissorState().rect();
+    }
+
+    this->drawStencilPass(state, clip.hardClip(), &fixedDynamicState);
+    if (!(Flags::kStencilOnly & fFlags)) {
+        this->drawCoverPass(state, std::move(clip), &fixedDynamicState);
+    }
+}
+
+void GrTessellatePathOp::drawStencilPass(GrOpFlushState* state, const GrAppliedHardClip& hardClip,
+                                         const GrPipeline::FixedDynamicState* fixedDynamicState) {
+    // Increments clockwise triangles and decrements counterclockwise. Used for "winding" fill.
+    constexpr static GrUserStencilSettings kIncrDecrStencil(
+        GrUserStencilSettings::StaticInitSeparate<
+            0x0000,                                0x0000,
+            GrUserStencilTest::kAlwaysIfInClip,    GrUserStencilTest::kAlwaysIfInClip,
+            0xffff,                                0xffff,
+            GrUserStencilOp::kIncWrap,             GrUserStencilOp::kDecWrap,
+            GrUserStencilOp::kKeep,                GrUserStencilOp::kKeep,
+            0xffff,                                0xffff>());
+
+    // Inverts the bottom stencil bit. Used for "even/odd" fill.
+    constexpr static GrUserStencilSettings kInvertStencil(
+        GrUserStencilSettings::StaticInit<
+            0x0000,
+            GrUserStencilTest::kAlwaysIfInClip,
+            0xffff,
+            GrUserStencilOp::kInvert,
+            GrUserStencilOp::kKeep,
+            0x0001>());
+
+    GrPipeline::InitArgs initArgs;
+    if (GrAAType::kNone != fAAType) {
+        initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
+    }
+    if (state->caps().wireframeSupport() && (Flags::kWireframe & fFlags)) {
+        initArgs.fInputFlags |= GrPipeline::InputFlags::kWireframe;
+    }
+    SkASSERT(SkPathFillType::kWinding == fPath.getFillType() ||
+             SkPathFillType::kEvenOdd == fPath.getFillType());
+    initArgs.fUserStencil = (SkPathFillType::kWinding == fPath.getFillType()) ?
+            &kIncrDecrStencil : &kInvertStencil;
+    initArgs.fCaps = &state->caps();
+
+    GrPipeline pipeline(initArgs, GrDisableColorXPFactory::MakeXferProcessor(), hardClip);
+    GrTessellateWedgeShader shader(fViewMatrix);
+    GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
+                              state->proxy()->backendFormat(), state->view()->origin(), &pipeline,
+                              &shader, fixedDynamicState, nullptr, 0,
+                              GrPrimitiveType::kPatches, 5);
+
+    GrMesh mesh(GrPrimitiveType::kPatches, 5);
+    mesh.setNonIndexedNonInstanced(fNumWedges * 5);
+    mesh.setVertexData(fWedgeBuffer, fBaseWedgeVertex);
+
+    state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
+
+    // http://skbug.com/9739
+    if (state->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
+        state->gpu()->insertManualFramebufferBarrier();
+    }
+}
+
+void GrTessellatePathOp::drawCoverPass(GrOpFlushState* state, GrAppliedClip&& clip,
+                                       const GrPipeline::FixedDynamicState* fixedDynamicState) {
+    // Allows non-zero stencil values to pass and write a color, and resets the stencil value back
+    // to zero; discards immediately on stencil values of zero.
+    // NOTE: It's ok to not check the clip here because the previous stencil pass only wrote to
+    // samples already inside the clip.
+    constexpr static GrUserStencilSettings kTestAndResetStencil(
+        GrUserStencilSettings::StaticInit<
+            0x0000,
+            GrUserStencilTest::kNotEqual,
+            0xffff,
+            GrUserStencilOp::kZero,
+            GrUserStencilOp::kKeep,
+            0xffff>());
+
+    GrPipeline::InitArgs initArgs;
+    if (GrAAType::kNone != fAAType) {
+        initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
+        if (1 == state->proxy()->numSamples()) {
+            SkASSERT(GrAAType::kCoverage == fAAType);
+            // We are mixed sampled. Use conservative raster to make the sample coverage mask 100%
+            // at every fragment. This way we will still get a double hit on shared edges, but
+            // whichever side comes first will cover every sample and will clear the stencil. The
+            // other side will then be discarded and not cause a double blend.
+            initArgs.fInputFlags |= GrPipeline::InputFlags::kConservativeRaster;
+        }
+    }
+    initArgs.fUserStencil = &kTestAndResetStencil;
+    initArgs.fCaps = &state->caps();
+    initArgs.fDstProxyView = state->drawOpArgs().dstProxyView();
+    initArgs.fOutputSwizzle = state->drawOpArgs().outputSwizzle();
+
+    GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
+    GrCoverShader shader(fViewMatrix, fPath.getBounds(), fColor);
+    GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
+                              state->proxy()->backendFormat(), state->view()->origin(), &pipeline,
+                              &shader, fixedDynamicState, nullptr, 0,
+                              GrPrimitiveType::kTriangleStrip);
+
+    GrMesh mesh(GrPrimitiveType::kTriangleStrip);
+    mesh.setNonIndexedNonInstanced(4);
+
+    state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
+}
diff --git a/src/gpu/tessellate/GrTessellatePathOp.h b/src/gpu/tessellate/GrTessellatePathOp.h
new file mode 100644
index 0000000..c0648df
--- /dev/null
+++ b/src/gpu/tessellate/GrTessellatePathOp.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTessellatePathOp_DEFINED
+#define GrTessellatePathOp_DEFINED
+
+#include "src/gpu/ops/GrDrawOp.h"
+
+class GrAppliedHardClip;
+
+// Renders paths using the classic Red Book "stencil, then cover" method. Curves get linearized by
+// GPU tessellation shaders. This Op doesn't apply analytic AA, so it requires a render target that
+// supports either MSAA or mixed samples if AA is desired.
+class GrTessellatePathOp : public GrDrawOp {
+public:
+    enum class Flags {
+        kNone = 0,
+        kStencilOnly = (1 << 0),
+        kWireframe = (1 << 1)
+    };
+
+private:
+    DEFINE_OP_CLASS_ID
+
+    GrTessellatePathOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
+                       GrAAType aaType, Flags flags = Flags::kNone)
+            : GrDrawOp(ClassID())
+            , fFlags(flags)
+            , fViewMatrix(viewMatrix)
+            , fPath(path)
+            , fAAType(aaType)
+            , fColor(paint.getColor4f())
+            , fProcessors(std::move(paint)) {
+        SkRect devBounds;
+        fViewMatrix.mapRect(&devBounds, path.getBounds());
+        this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType),
+                        GrOp::IsHairline::kNo);
+    }
+
+    const char* name() const override { return "GrTessellatePathOp"; }
+    void visitProxies(const VisitProxyFunc& fn) const override { fProcessors.visitProxies(fn); }
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      bool hasMixedSampledCoverage,
+                                      GrClampType clampType) override {
+        return fProcessors.finalize(
+                fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
+                hasMixedSampledCoverage, caps, clampType, &fColor);
+    }
+
+    FixedFunctionFlags fixedFunctionFlags() const override;
+    void onPrepare(GrOpFlushState* state) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    void drawStencilPass(GrOpFlushState*, const GrAppliedHardClip&,
+                         const GrPipeline::FixedDynamicState*);
+    void drawCoverPass(GrOpFlushState*, GrAppliedClip&&, const GrPipeline::FixedDynamicState*);
+
+    const Flags fFlags;
+    const SkMatrix fViewMatrix;
+    const SkPath fPath;
+    const GrAAType fAAType;
+    SkPMColor4f fColor;
+    GrProcessorSet fProcessors;
+
+    sk_sp<const GrBuffer> fWedgeBuffer;
+    int fBaseWedgeVertex;
+    int fNumWedges;
+
+    friend class GrOpMemoryPool;  // For ctor.
+};
+
+GR_MAKE_BITFIELD_CLASS_OPS(GrTessellatePathOp::Flags);
+
+#endif
diff --git a/src/gpu/tessellate/GrTessellateWedgeShader.cpp b/src/gpu/tessellate/GrTessellateWedgeShader.cpp
new file mode 100644
index 0000000..9fc1ad1
--- /dev/null
+++ b/src/gpu/tessellate/GrTessellateWedgeShader.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/tessellate/GrTessellateWedgeShader.h"
+
+#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
+#include "src/gpu/glsl/GrGLSLVarying.h"
+#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
+
+class GrTessellateWedgeShader::Impl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs&, GrGPArgs*) override;
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
+                 const CoordTransformRange& transformRange) override;
+
+    GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
+};
+
+void GrTessellateWedgeShader::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
+    const char* viewMatrix;
+    fViewMatrixUniform = args.fUniformHandler->addUniform(
+            kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
+
+    args.fVaryingHandler->emitAttributes(args.fGP.cast<GrTessellateWedgeShader>());
+
+    args.fVertBuilder->declareGlobal(GrShaderVar(
+            "P_", kFloat2_GrSLType, GrShaderVar::kOut_TypeModifier));
+    args.fVertBuilder->codeAppendf("P_ = (%s * float3(P, 1)).xy;", viewMatrix);
+
+    // No fragment shader.
+};
+
+SkString GrTessellateWedgeShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls,
+                                                           const GrShaderCaps&) const {
+    SkString code(versionAndExtensionDecls);
+    code.append(R"(
+            layout(vertices = 1) out;
+
+            in vec2 P_[];
+
+            out mat4x2 P[];
+            out vec2 fanpoint[];
+
+            // Wang's formula for cubics (1985) gives us the number of evenly spaced (in the
+            // parametric sense) line segments that are guaranteed to be within a distance of
+            // "MAX_LINEARIZATION_ERROR" from the actual curve.
+            #define MAX_LINEARIZATION_ERROR 0.25  // 1/4 pixel
+            float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
+                float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR);
+                float f = sqrt(k * length(max(abs(p2 - p1*2.0 + p0),
+                                              abs(p3 - p2*2.0 + p1))));
+                return max(1.0, ceil(f));
+            }
+
+            void main() {
+                // Calculate how many triangles we need to linearize the curve.
+                float num_segments = wangs_formula_cubic(P_[0], P_[1], P_[2], P_[3]);
+
+                // Tessellate the first side of the patch into num_segments triangles.
+                gl_TessLevelOuter[0] = num_segments;
+
+                // Leave the other two sides of the patch as single segments.
+                gl_TessLevelOuter[1] = 1.0;
+                gl_TessLevelOuter[2] = 1.0;
+
+                // Changing the inner level to 1 when num_segments == 1 collapses the entire
+                // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
+                // triangles have an interior point to originate from.
+                gl_TessLevelInner[0] = min(num_segments, 2.0);
+
+                P[gl_InvocationID /*== 0*/] = mat4x2(P_[0], P_[1], P_[2], P_[3]);
+                fanpoint[gl_InvocationID /*== 0*/] = P_[4];
+            })");
+
+    return code;
+}
+
+
+SkString GrTessellateWedgeShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls,
+                                                              const GrShaderCaps&) const {
+    SkString code(versionAndExtensionDecls);
+    code.append(R"(
+            layout(triangles, equal_spacing, cw) in;
+
+            uniform vec4 sk_RTAdjust;
+
+            in mat4x2 P[];
+            in vec2 fanpoint[];
+
+            void main() {
+                vec2 p0 = P[0][0], p1 = P[0][1], p2 = P[0][2], p3 = P[0][3];
+
+                // Locate our parametric point of interest. It is equal to the barycentric
+                // y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
+                // 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
+                // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
+                float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
+
+                // Evaluate our point of interest using numerically stable mix() operations.
+                vec2 ab = mix(p0, p1, T);
+                vec2 bc = mix(p1, p2, T);
+                vec2 cd = mix(p2, p3, T);
+                vec2 abc = mix(ab, bc, T);
+                vec2 bcd = mix(bc, cd, T);
+                vec2 vertexpos = mix(abc, bcd, T);
+
+                if (gl_TessCoord.x == 1.0) {
+                    // We are the anchor point that fans from the center of the curve's contour.
+                    vertexpos = fanpoint[0];
+                } else if (gl_TessCoord.x != 0.0) {
+                    // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
+                    vertexpos = (p0 + vertexpos + p3) / 3.0;
+                }
+
+                gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
+            })");
+
+    return code;
+}
+
+void GrTessellateWedgeShader::Impl::setData(const GrGLSLProgramDataManager& pdman,
+                                            const GrPrimitiveProcessor& primProc,
+                                            const CoordTransformRange& transformRange) {
+    const GrTessellateWedgeShader& shader = primProc.cast<GrTessellateWedgeShader>();
+    pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix);
+}
+
+GrGLSLPrimitiveProcessor* GrTessellateWedgeShader::createGLSLInstance(const GrShaderCaps&) const {
+    return new Impl;
+}
diff --git a/src/gpu/tessellate/GrTessellateWedgeShader.h b/src/gpu/tessellate/GrTessellateWedgeShader.h
new file mode 100644
index 0000000..67d7285
--- /dev/null
+++ b/src/gpu/tessellate/GrTessellateWedgeShader.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTessellateWedgeShader_DEFINED
+#define GrTessellateWedgeShader_DEFINED
+
+#include "src/gpu/GrGeometryProcessor.h"
+
+// Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
+// wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning from
+// the center of the curve's resident contour. We stencil paths by converting lines and quadratics
+// to cubics, then rendering a cubic wedge for each verb.
+// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
+class GrTessellateWedgeShader : public GrGeometryProcessor {
+public:
+    GrTessellateWedgeShader(const SkMatrix& viewMatrix)
+            : GrGeometryProcessor(kGrTessellateWedgeShader_ClassID)
+            , fViewMatrix(viewMatrix) {
+        static constexpr Attribute kPtAttrib = {"P", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
+        this->setVertexAttributes(&kPtAttrib, 1);
+        this->setWillUseTessellationShaders();
+    }
+
+    const char* name() const override { return "GrTessellateWedgeShader"; }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+    SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls,
+                                      const GrShaderCaps&) const override;
+    SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls,
+                                         const GrShaderCaps&) const override;
+
+private:
+    const SkMatrix fViewMatrix;
+
+    class Impl;
+};
+
+#endif