Add a single-pass tessellation Op for convex fills

Bug: skia:10419
Change-Id: I0592adc43c1e426795fce133c37844ea7530058b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416979
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 3337a09..7133664 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -458,6 +458,8 @@
   "$_src/gpu/tessellate/GrPathInnerTriangulateOp.h",
   "$_src/gpu/tessellate/GrPathStencilCoverOp.cpp",
   "$_src/gpu/tessellate/GrPathStencilCoverOp.h",
+  "$_src/gpu/tessellate/GrPathTessellateOp.cpp",
+  "$_src/gpu/tessellate/GrPathTessellateOp.h",
   "$_src/gpu/tessellate/GrPathTessellator.h",
   "$_src/gpu/tessellate/GrPathWedgeTessellator.cpp",
   "$_src/gpu/tessellate/GrPathWedgeTessellator.h",
diff --git a/src/gpu/tessellate/GrPathTessellateOp.cpp b/src/gpu/tessellate/GrPathTessellateOp.cpp
new file mode 100644
index 0000000..283706e
--- /dev/null
+++ b/src/gpu/tessellate/GrPathTessellateOp.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 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/GrPathTessellateOp.h"
+
+#include "src/gpu/tessellate/GrPathWedgeTessellator.h"
+#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
+
+void GrPathTessellateOp::visitProxies(const VisitProxyFunc& fn) const {
+    if (fTessellationProgram) {
+        fTessellationProgram->pipeline().visitProxies(fn);
+    } else {
+        fProcessors.visitProxies(fn);
+    }
+}
+
+GrProcessorSet::Analysis GrPathTessellateOp::finalize(const GrCaps& caps,
+                                                        const GrAppliedClip* clip,
+                                                        GrClampType clampType) {
+    return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr, caps,
+                                clampType, &fColor);
+}
+
+void GrPathTessellateOp::prepareTessellator(const GrTessellationShader::ProgramArgs& args,
+                                            GrAppliedClip&& appliedClip) {
+    SkASSERT(!fTessellator);
+    SkASSERT(!fTessellationProgram);
+    auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(appliedClip),
+                                                        std::move(fProcessors));
+    fTessellator = GrPathWedgeTessellator::Make(args.fArena, fViewMatrix, fColor,
+                                                fPath.countVerbs(), *pipeline, *args.fCaps);
+    fTessellationProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(), pipeline,
+                                                             fStencil);
+}
+
+void GrPathTessellateOp::onPrePrepare(GrRecordingContext* context,
+                                      const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
+                                      const GrDstProxyView& dstProxyView,
+                                      GrXferBarrierFlags renderPassXferBarriers,
+                                      GrLoadOp colorLoadOp) {
+    this->prepareTessellator({context->priv().recordTimeAllocator(), writeView, &dstProxyView,
+                             renderPassXferBarriers, colorLoadOp, context->priv().caps()},
+                             (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
+    SkASSERT(fTessellationProgram);
+    context->priv().recordProgramInfo(fTessellationProgram);
+}
+
+void GrPathTessellateOp::onPrepare(GrOpFlushState* flushState) {
+    if (!fTessellator) {
+        this->prepareTessellator({flushState->allocator(), flushState->writeView(),
+                                  &flushState->dstProxyView(), flushState->renderPassBarriers(),
+                                  flushState->colorLoadOp(), &flushState->caps()},
+                                  flushState->detachAppliedClip());
+        SkASSERT(fTessellator);
+    }
+    fTessellator->prepare(flushState, this->bounds(), fPath);
+}
+
+void GrPathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    SkASSERT(fTessellator);
+    SkASSERT(fTessellationProgram);
+    flushState->bindPipelineAndScissorClip(*fTessellationProgram, this->bounds());
+    flushState->bindTextures(fTessellationProgram->geomProc(), nullptr,
+                             fTessellationProgram->pipeline());
+    fTessellator->draw(flushState);
+}
diff --git a/src/gpu/tessellate/GrPathTessellateOp.h b/src/gpu/tessellate/GrPathTessellateOp.h
new file mode 100644
index 0000000..9520406
--- /dev/null
+++ b/src/gpu/tessellate/GrPathTessellateOp.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPathTessellateOp_DEFINED
+#define GrPathTessellateOp_DEFINED
+
+#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/tessellate/shaders/GrTessellationShader.h"
+
+class GrPathTessellator;
+
+// Tessellates a path directly to the color buffer, using one single render pass. This currently
+// only works for convex paths.
+class GrPathTessellateOp : public GrDrawOp {
+private:
+    DEFINE_OP_CLASS_ID
+
+    GrPathTessellateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
+                       GrAAType aaType, const GrUserStencilSettings* stencil,
+                       const SkRect& devBounds)
+            : GrDrawOp(ClassID())
+            , fViewMatrix(viewMatrix)
+            , fPath(path)
+            , fAAType(aaType)
+            , fStencil(stencil)
+            , fColor(paint.getColor4f())
+            , fProcessors(std::move(paint)) {
+        this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
+    }
+
+    const char* name() const override { return "GrPathTessellateOp"; }
+    bool usesMSAA() const override { return fAAType == GrAAType::kMSAA; }
+    void visitProxies(const VisitProxyFunc& fn) const override;
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override;
+    bool usesStencil() const override { return !fStencil->isUnused(); }
+
+    void prepareTessellator(const GrTessellationShader::ProgramArgs&, GrAppliedClip&& clip);
+
+    void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
+                      const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override;
+    void onPrepare(GrOpFlushState*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    const SkMatrix fViewMatrix;
+    const SkPath fPath;
+    const GrAAType fAAType;
+    const GrUserStencilSettings* const fStencil;
+    SkPMColor4f fColor;
+    GrProcessorSet fProcessors;
+
+    // Decided during prepareTessellator.
+    GrPathTessellator* fTessellator = nullptr;
+    const GrProgramInfo* fTessellationProgram = nullptr;
+
+    friend class GrOp;  // For ctor.
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index e0edb7c..84093d3 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -19,6 +19,7 @@
 #include "src/gpu/tessellate/GrDrawAtlasPathOp.h"
 #include "src/gpu/tessellate/GrPathInnerTriangulateOp.h"
 #include "src/gpu/tessellate/GrPathStencilCoverOp.h"
+#include "src/gpu/tessellate/GrPathTessellateOp.h"
 #include "src/gpu/tessellate/GrStrokeTessellateOp.h"
 
 constexpr static SkISize kAtlasInitialSize{512, 512};
@@ -53,6 +54,16 @@
     }
 }
 
+GrPathRenderer::StencilSupport GrTessellationPathRenderer::onGetStencilSupport(
+        const GrStyledShape& shape) const {
+    if (!shape.style().isSimpleFill()) {
+        // Don't bother with stroke stencilling yet. Skia probably shouldn't support this at all
+        // since you can't clip by a stroke.
+        return kNoSupport_StencilSupport;
+    }
+    return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport;
+}
+
 GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath(
         const CanDrawPathArgs& args) const {
     const GrStyledShape& shape = *args.fShape;
@@ -61,7 +72,6 @@
         args.fViewMatrix->hasPerspective() ||
         shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
         shape.inverseFilled() ||
-        args.fHasUserStencilSettings ||
         !args.fProxy->canUseStencil(*args.fCaps)) {
         return CanDrawPath::kNo;
     }
@@ -77,49 +87,61 @@
             return CanDrawPath::kNo;
         }
     }
+    if (args.fHasUserStencilSettings) {
+        // Non-convex paths and strokes use the stencil buffer internally, so they can't support
+        // draws with stencil settings.
+        if (!shape.style().isSimpleFill() || !shape.knownToBeConvex()) {
+            return CanDrawPath::kNo;
+        }
+    }
     return CanDrawPath::kYes;
 }
 
-static GrOp::Owner make_op(GrRecordingContext* rContext, const GrSurfaceContext* surfaceContext,
-                           GrTessellationPathRenderer::PathFlags pathFlags, GrAAType aaType,
-                           const SkRect& shapeDevBounds, const SkMatrix& viewMatrix,
-                           const GrStyledShape& shape, GrPaint&& paint) {
-    SkPath path;
-    shape.asPath(&path);
-    if (!shape.style().isSimpleFill()) {
-        const SkStrokeRec& stroke = shape.style().strokeRec();
-        SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
-        return GrOp::Make<GrStrokeTessellateOp>(rContext, aaType, viewMatrix, path, stroke,
-                                                std::move(paint));
-    } else {
-        SkRect devBounds;
-        viewMatrix.mapRect(&devBounds, path.getBounds());
-        int numVerbs = path.countVerbs();
-        if (numVerbs > 0) {
-            // Check if the path is large and/or simple enough that we can triangulate the inner fan
-            // on the CPU. This is our fastest approach. It allows us to stencil only the curves,
-            // and then fill the inner fan directly to the final render target, thus drawing the
-            // majority of pixels in a single render pass.
-            float gpuFragmentWork = devBounds.height() * devBounds.width();
-            float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs);  // N log N.
-            constexpr static float kCpuWeight = 512;
-            constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
-            if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
-                return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
-                                                            std::move(paint), aaType, pathFlags,
-                                                            devBounds);
-            }
+static GrOp::Owner make_non_convex_fill_op(GrRecordingContext* rContext,
+                                           GrTessellationPathRenderer::PathFlags pathFlags,
+                                           GrAAType aaType, const SkRect& pathDevBounds,
+                                           const SkMatrix& viewMatrix, const SkPath& path,
+                                           GrPaint&& paint) {
+    SkASSERT(!path.isConvex());
+    int numVerbs = path.countVerbs();
+    if (numVerbs > 0) {
+        // Check if the path is large and/or simple enough that we can triangulate the inner fan
+        // on the CPU. This is our fastest approach. It allows us to stencil only the curves,
+        // and then fill the inner fan directly to the final render target, thus drawing the
+        // majority of pixels in a single render pass.
+        float gpuFragmentWork = pathDevBounds.height() * pathDevBounds.width();
+        float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs);  // N log N.
+        constexpr static float kCpuWeight = 512;
+        constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
+        if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
+            return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
+                                                        std::move(paint), aaType, pathFlags,
+                                                        pathDevBounds);
         }
-        return GrOp::Make<GrPathStencilCoverOp>(rContext, viewMatrix, path, std::move(paint),
-                                                aaType, pathFlags, devBounds);
     }
+    return GrOp::Make<GrPathStencilCoverOp>(rContext, viewMatrix, path, std::move(paint), aaType,
+                                            pathFlags, pathDevBounds);
 }
 
 bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
     GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
 
-    SkRect devBounds;
-    args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
+    SkPath path;
+    args.fShape->asPath(&path);
+
+    // Handle strokes first.
+    if (!args.fShape->style().isSimpleFill()) {
+        SkASSERT(args.fUserStencilSettings->isUnused());
+        const SkStrokeRec& stroke = args.fShape->style().strokeRec();
+        SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
+        auto op = GrOp::Make<GrStrokeTessellateOp>(args.fContext, args.fAAType, *args.fViewMatrix,
+                                                   path, stroke, std::move(args.fPaint));
+        surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+        return true;
+    }
+
+    SkRect pathDevBounds;
+    args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
 
     // See if the path is small and simple enough to atlas instead of drawing directly.
     //
@@ -129,8 +151,9 @@
     SkIRect devIBounds;
     SkIPoint16 locationInAtlas;
     bool transposedInAtlas;
-    if (this->tryAddPathToAtlas(*args.fContext->priv().caps(), *args.fViewMatrix, *args.fShape,
-                                devBounds, args.fAAType, &devIBounds, &locationInAtlas,
+    if (args.fUserStencilSettings->isUnused() &&
+        this->tryAddPathToAtlas(*args.fContext->priv().caps(), *args.fViewMatrix, path,
+                                pathDevBounds, args.fAAType, &devIBounds, &locationInAtlas,
                                 &transposedInAtlas)) {
         // The atlas is not compatible with DDL. We should only be using it on direct contexts.
         SkASSERT(args.fContext->asDirectContext());
@@ -142,21 +165,64 @@
         return true;
     }
 
-    if (auto op = make_op(args.fContext, surfaceDrawContext, PathFlags::kNone, args.fAAType,
-                          devBounds, *args.fViewMatrix, *args.fShape, std::move(args.fPaint))) {
+    // Handle convex paths only if we couldn't fit them in the atlas. We give the atlas priority in
+    // an effort to reduce DMSAA triggers.
+    if (args.fShape->knownToBeConvex()) {
+        auto op = GrOp::Make<GrPathTessellateOp>(args.fContext, *args.fViewMatrix, path,
+                                                 std::move(args.fPaint), args.fAAType,
+                                                 args.fUserStencilSettings, pathDevBounds);
         surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+        return true;
     }
+
+    SkASSERT(args.fUserStencilSettings->isUnused());  // See onGetStencilSupport().
+    auto op = make_non_convex_fill_op(args.fContext, PathFlags::kNone, args.fAAType, pathDevBounds,
+                                      *args.fViewMatrix, path, std::move(args.fPaint));
+    surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
     return true;
 }
 
-bool GrTessellationPathRenderer::tryAddPathToAtlas(
-        const GrCaps& caps, const SkMatrix& viewMatrix, const GrStyledShape& shape,
-        const SkRect& devBounds, GrAAType aaType, SkIRect* devIBounds, SkIPoint16* locationInAtlas,
-        bool* transposedInAtlas) {
-    if (!shape.style().isSimpleFill()) {
-        return false;
+void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
+    SkASSERT(args.fShape->style().isSimpleFill());  // See onGetStencilSupport().
+
+    GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
+    GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
+
+    SkRect pathDevBounds;
+    args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
+
+    SkPath path;
+    args.fShape->asPath(&path);
+
+    if (args.fShape->knownToBeConvex()) {
+        constexpr static GrUserStencilSettings kMarkStencil(
+            GrUserStencilSettings::StaticInit<
+                0x0001,
+                GrUserStencilTest::kAlways,
+                0xffff,
+                GrUserStencilOp::kReplace,
+                GrUserStencilOp::kKeep,
+                0xffff>());
+
+        GrPaint stencilPaint;
+        stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
+        auto op = GrOp::Make<GrPathTessellateOp>(args.fContext, *args.fViewMatrix, path,
+                                                 std::move(stencilPaint), aaType, &kMarkStencil,
+                                                 pathDevBounds);
+        surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+        return;
     }
 
+    auto op = make_non_convex_fill_op(args.fContext, PathFlags::kStencilOnly, aaType, pathDevBounds,
+                                      *args.fViewMatrix, path, GrPaint());
+    surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
+}
+
+bool GrTessellationPathRenderer::tryAddPathToAtlas(const GrCaps& caps, const SkMatrix& viewMatrix,
+                                                   const SkPath& path, const SkRect& pathDevBounds,
+                                                   GrAAType aaType, SkIRect* devIBounds,
+                                                   SkIPoint16* locationInAtlas,
+                                                   bool* transposedInAtlas) {
     if (!fMaxAtlasPathWidth) {
         return false;
     }
@@ -168,7 +234,7 @@
     // Transpose tall paths in the atlas. Since we limit ourselves to small-area paths, this
     // guarantees that every atlas entry has a small height, which lends very well to efficient pow2
     // atlas packing.
-    devBounds.roundOut(devIBounds);
+    pathDevBounds.roundOut(devIBounds);
     int maxDimenstion = devIBounds->width();
     int minDimension = devIBounds->height();
     *transposedInAtlas = minDimension > maxDimenstion;
@@ -201,25 +267,12 @@
     }
 
     // Concatenate this path onto our uber path that matches its fill and AA types.
-    SkPath path;
-    shape.asPath(&path);
     SkPath* uberPath = this->getAtlasUberPath(path.getFillType(), GrAAType::kNone != aaType);
     uberPath->moveTo(locationInAtlas->x(), locationInAtlas->y());  // Implicit moveTo(0,0).
     uberPath->addPath(path, atlasMatrix);
     return true;
 }
 
-void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
-    GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
-    GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
-    SkRect devBounds;
-    args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
-    if (auto op = make_op(args.fContext, surfaceDrawContext, PathFlags::kStencilOnly, aaType,
-                          devBounds, *args.fViewMatrix, *args.fShape, GrPaint())) {
-        surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
-    }
-}
-
 void GrTessellationPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
                                           SkSpan<const uint32_t> /* taskIDs */) {
     if (!fAtlas.drawBounds().isEmpty()) {
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.h b/src/gpu/tessellate/GrTessellationPathRenderer.h
index d934e8a..6b6ff1e 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.h
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.h
@@ -33,13 +33,13 @@
 
     GrTessellationPathRenderer(GrRecordingContext*);
     const char* name() const final { return "GrTessellationPathRenderer"; }
-    StencilSupport onGetStencilSupport(const GrStyledShape& shape) const override {
-        // TODO: Single-pass (e.g., convex) paths can have full support.
-        return kStencilOnly_StencilSupport;
-    }
+
+    StencilSupport onGetStencilSupport(const GrStyledShape&) const override;
     CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override;
+
     bool onDrawPath(const DrawPathArgs&) override;
     void onStencilPath(const StencilPathArgs&) override;
+
     void preFlush(GrOnFlushResourceProvider*, SkSpan<const uint32_t> taskIDs) override;
 
 private:
@@ -48,9 +48,11 @@
         idx |= (int)fillType & 1;
         return &fAtlasUberPaths[idx];
     }
-    // Allocates space in fAtlas if the path is small and simple enough, and if there is room.
-    bool tryAddPathToAtlas(const GrCaps&, const SkMatrix&, const GrStyledShape&,
-                           const SkRect& devBounds, GrAAType, SkIRect* devIBounds,
+    // Adds the filled path to fAtlas if the path is small enough, and if the atlas isn't full.
+    // Currently, "small enough" means 128*128 total pixels or less, and no larger than half the
+    // atlas size in either dimension.
+    bool tryAddPathToAtlas(const GrCaps&, const SkMatrix&, const SkPath&,
+                           const SkRect& pathDevBounds, GrAAType, SkIRect* devIBounds,
                            SkIPoint16* locationInAtlas, bool* transposedInAtlas);
     void renderAtlas(GrOnFlushResourceProvider*);