Implement a simple clip atlas with GrTessellationPathRenderer

Bug: b/188794626
Bug: chromium:928984
Change-Id: Ic4e0584cccafb1e9f60861a6fee1ff5e34e736d8
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/418218
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Adlai Holler <adlai@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrClipStack.cpp b/src/gpu/GrClipStack.cpp
index c8ef83b..b9a2662 100644
--- a/src/gpu/GrClipStack.cpp
+++ b/src/gpu/GrClipStack.cpp
@@ -19,7 +19,6 @@
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrSWMaskHelper.h"
 #include "src/gpu/GrStencilMaskHelper.h"
-#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h"
 #include "src/gpu/effects/GrBlendFragmentProcessor.h"
 #include "src/gpu/effects/GrConvexPolyEffect.h"
 #include "src/gpu/effects/GrRRectEffect.h"
@@ -27,6 +26,7 @@
 #include "src/gpu/effects/generated/GrAARectEffect.h"
 #include "src/gpu/effects/generated/GrDeviceSpaceEffect.h"
 #include "src/gpu/geometry/GrQuadUtils.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 
 namespace {
 
@@ -232,45 +232,23 @@
     return GrFPFailure(std::move(fp));
 }
 
-// TODO: Currently this only works with CCPR because CCPR owns and manages the clip atlas. The
-// high-level concept should be generalized to support any path renderer going into a shared atlas.
-static GrFPResult clip_atlas_fp(GrCoverageCountingPathRenderer* ccpr,
-                                uint32_t opsTaskID,
-                                const SkIRect& bounds,
+// TODO: Currently this only works with tessellation because the tessellation path renderer owns and
+// manages the atlas. The high-level concept could be generalized to support any path renderer going
+// into a shared atlas.
+static GrFPResult clip_atlas_fp(GrTessellationPathRenderer* tessellator,
+                                const SkIRect& scissorBounds,
                                 const GrClipStack::Element& e,
-                                SkPath* devicePath,
-                                const GrCaps& caps,
-                                std::unique_ptr<GrFragmentProcessor> fp) {
-    // TODO: Currently the atlas manages device-space paths, so we have to transform by the ctm.
-    // In the future, the atlas manager should see the local path and the ctm so that it can
-    // cache across integer-only translations (internally, it already does this, just not exposed).
-    if (devicePath->isEmpty()) {
-        e.fShape.asPath(devicePath);
-        devicePath->transform(e.fLocalToDevice);
-        SkASSERT(!devicePath->isEmpty());
+                                std::unique_ptr<GrFragmentProcessor> inputFP,
+                                const GrCaps& caps) {
+    SkPath path;
+    e.fShape.asPath(&path);
+    SkASSERT(!path.isInverseFillType());
+    if (e.fOp == SkClipOp::kDifference) {
+        // Toggling fill type does not affect the path's "generationID" key.
+        path.toggleInverseFillType();
     }
-
-    SkASSERT(!devicePath->isInverseFillType());
-    if (e.fOp == SkClipOp::kIntersect) {
-        return ccpr->makeClipProcessor(std::move(fp), opsTaskID, *devicePath, bounds, caps);
-    } else {
-        // Use kDstOut to convert the non-inverted mask alpha into (1-alpha), so the atlas only
-        // ever renders non-inverse filled paths.
-        //  - When the input FP is null, this turns into "(1-sample(ccpr, 1).a) * input"
-        //  - When not null, it works out to
-        //       (1-sample(ccpr, input.rgb1).a) * sample(fp, input.rgb1) * input.a
-        //  - Since clips only care about the alpha channel, these are both equivalent to the
-        //    desired product of (1-ccpr) * fp * input.a.
-        auto [success, atlasFP] = ccpr->makeClipProcessor(nullptr, opsTaskID, *devicePath, bounds,
-                                                          caps);
-        if (!success) {
-            // "Difference" draws that don't intersect the clip need to be drawn "wide open".
-            return GrFPSuccess(nullptr);
-        }
-        return GrFPSuccess(GrBlendFragmentProcessor::Make(std::move(atlasFP),  // src
-                                                          std::move(fp),       // dst
-                                                          SkBlendMode::kDstOut));
-    }
+    return tessellator->makeAtlasClipFP(scissorBounds, e.fLocalToDevice, path, e.fAA,
+                                        std::move(inputFP), caps);
 }
 
 static void draw_to_sw_mask(GrSWMaskHelper* helper, const GrClipStack::Element& e, bool clearMask) {
@@ -1353,12 +1331,11 @@
     GrWindowRectangles windowRects;
 
     // Elements not represented as an analytic FP or skipped will be collected here and later
-    // applied by using the stencil buffer, CCPR clip atlas, or a cached SW mask.
+    // applied by using the stencil buffer or a cached SW mask.
     SkSTArray<kNumStackMasks, const Element*> elementsForMask;
-    SkSTArray<kNumStackMasks, const RawElement*> elementsForAtlas;
 
     bool maskRequiresAA = false;
-    auto* ccpr = context->priv().drawingManager()->getCoverageCountingPathRenderer();
+    auto* tessellator = context->priv().drawingManager()->getTessellationPathRenderer();
 
     int i = fElements.count();
     for (const RawElement& e : fElements.ritems()) {
@@ -1410,23 +1387,13 @@
                     std::tie(fullyApplied, clipFP) = analytic_clip_fp(e.asElement(),
                                                                       *caps->shaderCaps(),
                                                                       std::move(clipFP));
+                    if (!fullyApplied && tessellator) {
+                        std::tie(fullyApplied, clipFP) = clip_atlas_fp(tessellator, scissorBounds,
+                                                                       e.asElement(),
+                                                                       std::move(clipFP), *caps);
+                    }
                     if (fullyApplied) {
                         remainingAnalyticFPs--;
-                    } else if (ccpr && e.aa() == GrAA::kYes) {
-                        constexpr static int64_t kMaxClipPathArea =
-                                GrCoverageCountingPathRenderer::kMaxClipPathArea;
-                        SkIRect maskBounds;
-                        if (maskBounds.intersect(e.outerBounds(), draw.outerBounds()) &&
-                            maskBounds.height64() * maskBounds.width64() < kMaxClipPathArea) {
-                            // While technically the element is turned into a mask, each atlas entry
-                            // counts towards the FP complexity of the clip.
-                            // TODO - CCPR needs a stable ops task ID so we can't create FPs until
-                            // we know any other mask generation is finished. It also only works
-                            // with AA shapes, future atlas systems can improve on this.
-                            elementsForAtlas.push_back(&e);
-                            remainingAnalyticFPs--;
-                            fullyApplied = true;
-                        }
                     }
                 }
 
@@ -1442,13 +1409,12 @@
 
     if (!scissorIsNeeded) {
         // More detailed analysis of the element shapes determined no clip is needed
-        SkASSERT(elementsForMask.empty() && elementsForAtlas.empty() && !clipFP);
+        SkASSERT(elementsForMask.empty() && !clipFP);
         return Effect::kUnclipped;
     }
 
     // Fill out the GrAppliedClip with what we know so far, possibly with a tightened scissor
-    if (cs.op() == SkClipOp::kIntersect &&
-        (!elementsForMask.empty() || !elementsForAtlas.empty())) {
+    if (cs.op() == SkClipOp::kIntersect && !elementsForMask.empty()) {
         SkAssertResult(scissorBounds.intersect(draw.outerBounds()));
     }
     if (!GrClip::IsInsideClip(scissorBounds, *bounds)) {
@@ -1486,22 +1452,6 @@
         }
     }
 
-    // Finish CCPR paths now that the render target's ops task is stable.
-    if (!elementsForAtlas.empty()) {
-        uint32_t opsTaskID = rtc->getOpsTask()->uniqueID();
-        for (int i = 0; i < elementsForAtlas.count(); ++i) {
-            SkASSERT(elementsForAtlas[i]->aa() == GrAA::kYes);
-            bool success;
-            std::tie(success, clipFP) = clip_atlas_fp(ccpr, opsTaskID, scissorBounds,
-                                                      elementsForAtlas[i]->asElement(),
-                                                      elementsForAtlas[i]->devicePath(), *caps,
-                                                      std::move(clipFP));
-            if (!success) {
-                return Effect::kClippedOut;
-            }
-        }
-    }
-
     if (clipFP) {
         // This will include all analytic FPs, all CCPR atlas FPs, and a SW mask FP.
         out->addCoverageFP(std::move(clipFP));
diff --git a/src/gpu/GrClipStack.h b/src/gpu/GrClipStack.h
index 662b959..0562318 100644
--- a/src/gpu/GrClipStack.h
+++ b/src/gpu/GrClipStack.h
@@ -116,8 +116,6 @@
         const SkIRect&  innerBounds() const { return fInnerBounds; }
         GrAA            aa() const { return fAA; }
 
-        SkPath*         devicePath() const { return &fDevicePath; }
-
         ClipState       clipType() const;
 
         // As new elements are pushed on to the stack, they may make older elements redundant.
@@ -147,9 +145,6 @@
         bool combine(const RawElement& other, const SaveRecord& current);
 
         SkMatrix fDeviceToLocal; // cached inverse of fLocalToDevice for contains() optimization
-        // TODO: This is only needed because CCPR tracks clip paths in device space; if we didn't
-        // cache this, every use of the path would be re-transformed and get its own atlas entry.
-        mutable SkPath fDevicePath;    // lazily initialized the first time it's needed
 
         // Device space bounds, rounded in or out to pixel boundaries and accounting for any
         // uncertainty around anti-aliasing and rasterization snapping.
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 4ce8c21..2c2d3e3 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -928,7 +928,7 @@
     return fPathRendererChain->getCoverageCountingPathRenderer();
 }
 
-GrPathRenderer* GrDrawingManager::getTessellationPathRenderer() {
+GrTessellationPathRenderer* GrDrawingManager::getTessellationPathRenderer() {
     if (!fPathRendererChain) {
         fPathRendererChain = std::make_unique<GrPathRendererChain>(fContext,
                                                                    fOptionsForPathRendererChain);
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 7ce9aa7..6e5e077 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -38,6 +38,7 @@
 class GrSurfaceContext;
 class GrSurfaceDrawContext;
 class GrSurfaceProxyView;
+class GrTessellationPathRenderer;
 class GrTextureResolveRenderTask;
 class SkDeferredDisplayList;
 
@@ -115,7 +116,7 @@
 
     // Returns a direct pointer to the tessellation path renderer, or null if it is not supported
     // and turned on.
-    GrPathRenderer* getTessellationPathRenderer();
+    GrTessellationPathRenderer* getTessellationPathRenderer();
 
     void flushIfNecessary();
 
diff --git a/src/gpu/GrPathRendererChain.h b/src/gpu/GrPathRendererChain.h
index 58787c2..11e97bb 100644
--- a/src/gpu/GrPathRendererChain.h
+++ b/src/gpu/GrPathRendererChain.h
@@ -16,6 +16,7 @@
 #include "include/private/SkTArray.h"
 
 class GrCoverageCountingPathRenderer;
+class GrTessellationPathRenderer;
 
 /**
  * Keeps track of an ordered list of path renderers. When a path needs to be
@@ -55,7 +56,7 @@
 
     /** Returns a direct pointer to the tessellation path renderer, or null if it is not in the
         chain. */
-    GrPathRenderer* getTessellationPathRenderer() {
+    GrTessellationPathRenderer* getTessellationPathRenderer() {
         return fTessellationPathRenderer;
     }
 
@@ -65,7 +66,7 @@
     };
     SkSTArray<kPreAllocCount, sk_sp<GrPathRenderer>>    fChain;
     std::unique_ptr<GrCoverageCountingPathRenderer>     fCoverageCountingPathRenderer;
-    GrPathRenderer*                                     fTessellationPathRenderer = nullptr;
+    GrTessellationPathRenderer*                         fTessellationPathRenderer = nullptr;
 };
 
 #endif
diff --git a/src/gpu/GrSurfaceDrawContext.cpp b/src/gpu/GrSurfaceDrawContext.cpp
index be4b444..5a58dac 100644
--- a/src/gpu/GrSurfaceDrawContext.cpp
+++ b/src/gpu/GrSurfaceDrawContext.cpp
@@ -69,6 +69,7 @@
 #include "src/gpu/ops/GrShadowRRectOp.h"
 #include "src/gpu/ops/GrStrokeRectOp.h"
 #include "src/gpu/ops/GrTextureOp.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 #include "src/gpu/text/GrSDFTControl.h"
 #include "src/gpu/text/GrTextBlobCache.h"
 
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index c8c06f5..9854ff0 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -15,6 +15,8 @@
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrSurfaceDrawContext.h"
 #include "src/gpu/GrVx.h"
+#include "src/gpu/effects/GrBlendFragmentProcessor.h"
+#include "src/gpu/effects/generated/GrDeviceSpaceEffect.h"
 #include "src/gpu/geometry/GrStyledShape.h"
 #include "src/gpu/geometry/GrWangsFormula.h"
 #include "src/gpu/ops/GrFillRectOp.h"
@@ -208,6 +210,79 @@
     surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
 }
 
+GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(
+        const SkIRect& drawBounds, const SkMatrix& viewMatrix, const SkPath& path, GrAA aa,
+        std::unique_ptr<GrFragmentProcessor> inputCoverage, const GrCaps& caps) {
+    if (viewMatrix.hasPerspective()) {
+        return GrFPFailure(std::move(inputCoverage));
+    }
+    SkIRect devIBounds;
+    SkIPoint16 locationInAtlas;
+    bool transposedInAtlas;
+    // tryAddPathToAtlas() ignores inverseness of the fill. See getAtlasUberPath().
+    if (!this->tryAddPathToAtlas(caps, viewMatrix, path, viewMatrix.mapRect(path.getBounds()),
+                                 aa != GrAA::kNo, &devIBounds, &locationInAtlas,
+                                 &transposedInAtlas)) {
+        // The path is too big, or the atlas ran out of room.
+        return GrFPFailure(std::move(inputCoverage));
+    }
+    GrSurfaceProxyView atlasView(sk_ref_sp(fAtlas.textureProxy()), GrDynamicAtlas::kTextureOrigin,
+                                 caps.getReadSwizzle(fAtlas.textureProxy()->backendFormat(),
+                                                     GrColorType::kAlpha_8));
+    SkMatrix atlasMatrix;
+    SkRect atlasSubset, atlasDomain;
+    auto [atlasX, atlasY] = locationInAtlas;
+    if (!transposedInAtlas) {
+        auto atlasOffset = SkVector::Make(atlasX - devIBounds.left(), atlasY - devIBounds.top());
+        atlasMatrix = SkMatrix::Translate(atlasOffset);
+        atlasSubset = SkRect::Make(devIBounds).makeOffset(atlasOffset);
+        atlasDomain = SkRect::Make(drawBounds).makeOffset(atlasOffset);
+    } else {
+        atlasMatrix.setAll(0, 1, atlasX - devIBounds.top(),
+                           1, 0, atlasY - devIBounds.left(),
+                           0, 0, 1);
+        atlasSubset = SkRect::MakeXYWH(atlasX, atlasY, devIBounds.height(), devIBounds.width());
+        atlasDomain = atlasMatrix.mapRect(SkRect::Make(drawBounds));
+    }
+#ifdef SK_DEBUG
+    if (!path.isInverseFillType()) {
+        // At this point in time we expect callers to tighten the scissor for "kIntersect" clips, as
+        // opposed to us having to enforce the texture subset. Feel free to remove this assert if
+        // that ever changes.
+        SkASSERT(atlasDomain.isEmpty() || atlasSubset.contains(atlasDomain));
+    }
+#endif
+    // Inset the domain because if it is equal to the subset, then it falls on an exact boundary
+    // between pixels, the "nearest" filter becomes undefined, and GrTextureEffect is forced to
+    // manually enforce the subset. This inset is justifiable because textures are sampled at pixel
+    // center, unless sample shading is enabled, in which case we assume standard sample locations
+    // (https://www.khronos.org/registry/vulkan/specs/1.2/html/chap25.html).
+    // NOTE: At MSAA16, standard sample locations begin falling on actual pixel boundaries. If this
+    // happens then we simply have to rely on the fact that the atlas has a 1px padding between
+    // entries.
+    constexpr static float kMinInsetOfStandardMSAA8Locations = 1/16.f;
+    atlasDomain.inset(kMinInsetOfStandardMSAA8Locations, kMinInsetOfStandardMSAA8Locations);
+    // Look up clip coverage in the atlas.
+    GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
+                                GrSamplerState::Filter::kNearest);
+    auto fp = GrTextureEffect::MakeSubset(std::move(atlasView), kPremul_SkAlphaType, atlasMatrix,
+                                          samplerState, atlasSubset, atlasDomain, caps);
+    // Feed sk_FragCoord into the above texture lookup.
+    fp = GrDeviceSpaceEffect::Make(std::move(fp));
+    if (path.isInverseFillType()) {
+        // outputCoverage = inputCoverage * (1 - atlasAlpha)
+        fp = GrBlendFragmentProcessor::Make(
+                std::move(fp), std::move(inputCoverage), SkBlendMode::kDstOut,
+                GrBlendFragmentProcessor::BlendBehavior::kSkModeBehavior);
+    } else {
+        // outputCoverage = inputCoverage * atlasAlpha
+        fp = GrBlendFragmentProcessor::Make(
+                std::move(fp), std::move(inputCoverage), SkBlendMode::kDstIn,
+                GrBlendFragmentProcessor::BlendBehavior::kSkModeBehavior);
+    }
+    return GrFPSuccess(std::move(fp));
+}
+
 void GrTessellationPathRenderer::AtlasPathKey::set(const SkMatrix& m, bool antialias,
                                                    const SkPath& path) {
     using grvx::float2;
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.h b/src/gpu/tessellate/GrTessellationPathRenderer.h
index 4a605dd..c8d5ed8 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.h
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.h
@@ -38,6 +38,17 @@
     bool onDrawPath(const DrawPathArgs&) override;
     void onStencilPath(const StencilPathArgs&) override;
 
+    // Returns a fragment processor that modulates inputCoverage by the given deviceSpacePath's
+    // coverage, implemented using an internal atlas.
+    //
+    // Returns 'inputCoverage' wrapped in GrFPFailure() if the path was too big, or if the atlas was
+    // out of room. (Currently, "too big" means more than 128*128 total pixels, or larger than half
+    // the atlas size in either dimension.)
+    //
+    // Also return GrFPFailure() if the view matrix has perspective.
+    GrFPResult makeAtlasClipFP(const SkIRect& drawBounds, const SkMatrix&, const SkPath&, GrAA,
+                               std::unique_ptr<GrFragmentProcessor> inputCoverage, const GrCaps&);
+
     void preFlush(GrOnFlushResourceProvider*, SkSpan<const uint32_t> taskIDs) override;
 
 private: