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: