Reland "Simplify GrClip API"
This is a reland of 9716414e93f7b279e547595fab08b68235c4b2be
Original change's description:
> Simplify GrClip API
>
> Removes quickContains(SkRect), quickContains(SkRRect), and isRRect().
> Replaces these three functions with preApply() that conservatively
> determines the clip effect up to a single rrect intersection. The major
> motivation for this is the new GrClipStack implementation. preApply()
> and apply() will be able to reuse much more code compared to separating
> the preApply functionality across the older three functions that were
> removed. Additionally, preApply is able to convey more information for
> less work, since it can usually determine being skipped or unclipped while
> determining if the clip is a single rrect.
>
> As part of using this API, the attemptQuadOptimiziation and the equivalent
> rrect optimization are overhauled. Hopefully legibility is improved, and
> the rrect case is now applied outside of the android framework (but with
> tighter AA requirements).
>
> Bug: skia:10205
> Change-Id: I33249dd75a28a611495f87b211cb7ec74ebb7ba4
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/298506
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Chris Dalton <csmartdalton@google.com>
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Bug: skia:10205, 10456
Change-Id: I500eeda36ea50e95eb8cb658b36aa2373d5166c0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/298823
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/windowrectangles.cpp b/gm/windowrectangles.cpp
index d9e4ae9..5b7f268 100644
--- a/gm/windowrectangles.cpp
+++ b/gm/windowrectangles.cpp
@@ -159,18 +159,9 @@
};
/**
- * Base class for GrClips that visualize a clip mask.
- */
-class MaskOnlyClipBase : public GrClip {
-private:
- bool quickContains(const SkRect&) const final { return false; }
- bool isRRect(SkRRect* rr, GrAA*) const final { return false; }
-};
-
-/**
* This class clips a cover by an alpha mask. We use it to visualize the alpha clip mask.
*/
-class AlphaOnlyClip final : public MaskOnlyClipBase {
+class AlphaOnlyClip final : public GrClip {
public:
AlphaOnlyClip(GrSurfaceProxyView mask, int x, int y) : fMask(std::move(mask)), fX(x), fY(y) {}
@@ -178,9 +169,8 @@
SkIRect getConservativeBounds() const final {
return SkIRect::MakeXYWH(fX, fY, fMask.width(), fMask.height());
}
-
- bool apply(GrRecordingContext* ctx, GrRenderTargetContext*, bool, bool, GrAppliedClip* out,
- SkRect* bounds) const override {
+ Effect apply(GrRecordingContext* ctx, GrRenderTargetContext*, bool, bool,
+ GrAppliedClip* out, SkRect* bounds) const override {
GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
GrSamplerState::Filter::kNearest);
auto m = SkMatrix::Translate(-fX, -fY);
@@ -190,7 +180,7 @@
domain, *ctx->priv().caps());
fp = GrDeviceSpaceEffect::Make(std::move(fp));
out->addCoverageFP(std::move(fp));
- return true;
+ return Effect::kClipped;
}
GrSurfaceProxyView fMask;
int fX;
diff --git a/src/gpu/GrClip.h b/src/gpu/GrClip.h
index ee6935d..f9008ae 100644
--- a/src/gpu/GrClip.h
+++ b/src/gpu/GrClip.h
@@ -21,12 +21,33 @@
*/
class GrClip {
public:
+ enum class Effect {
+ // The clip conservatively modifies the draw's coverage but doesn't eliminate the draw
+ kClipped,
+ // The clip definitely does not modify the draw's coverage and the draw can be performed
+ // without clipping (beyond the automatic device bounds clip).
+ kUnclipped,
+ // The clip definitely eliminates all of the draw's coverage and the draw can be skipped
+ kClippedOut
+ };
+
+ struct PreClipResult {
+ Effect fEffect;
+ SkRRect fRRect; // Ignore if 'isRRect' is false
+ GrAA fAA; // Ignore if 'isRRect' is false
+ bool fIsRRect;
+
+ PreClipResult(Effect effect) : fEffect(effect), fIsRRect(false) {}
+ PreClipResult(SkRect rect, GrAA aa) : PreClipResult(SkRRect::MakeRect(rect), aa) {}
+ PreClipResult(SkRRect rrect, GrAA aa)
+ : fEffect(Effect::kClipped)
+ , fRRect(rrect)
+ , fAA(aa)
+ , fIsRRect(true) {}
+ };
+
virtual ~GrClip() {}
- virtual bool quickContains(const SkRect&) const = 0;
- virtual bool quickContains(const SkRRect& rrect) const {
- return this->quickContains(rrect.getBounds());
- }
/**
* Compute a conservative pixel bounds restricted to the given render target dimensions.
* The returned bounds represent the limits of pixels that can be drawn; anything outside of the
@@ -38,25 +59,34 @@
* This computes a GrAppliedClip from the clip which in turn can be used to build a GrPipeline.
* To determine the appropriate clipping implementation the GrClip subclass must know whether
* the draw will enable HW AA or uses the stencil buffer. On input 'bounds' is a conservative
- * bounds of the draw that is to be clipped. After return 'bounds' has been intersected with a
- * conservative bounds of the clip. A return value of false indicates that the draw can be
- * skipped as it is fully clipped out.
+ * bounds of the draw that is to be clipped. If kClipped or kUnclipped is returned, the 'bounds'
+ * will have been updated to be contained within the clip bounds (or the device's, for wide-open
+ * clips). If kNoDraw is returned, 'bounds' and the applied clip are in an undetermined state
+ * and should be ignored (and the draw should be skipped).
*/
- virtual bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
- bool hasUserStencilSettings, GrAppliedClip*, SkRect* bounds) const = 0;
+ virtual Effect apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
+ bool hasUserStencilSettings, GrAppliedClip*, SkRect* bounds) const = 0;
/**
- * This method quickly and conservatively determines whether the entire clip is equivalent to
- * intersection with a rrect. Moreover, the returned rrect need not be contained by the render
- * target bounds. We assume all draws will be implicitly clipped by the render target bounds.
+ * Perform preliminary, conservative analysis on the draw bounds as if it were provided to
+ * apply(). The results of this are returned the PreClipResults struct, where 'result.fEffect'
+ * corresponds to what 'apply' would return. If this value is kUnclipped or kNoDraw, then it
+ * can be assumed that apply() would also always result in the same Effect.
*
- * @param rrect If return is true rrect will contain the rrect equivalent to the clip within
- * rtBounds.
- * @param aa If return is true aa will indicate whether the rrect clip is antialiased.
- * @return true if the clip is equivalent to a single rrect, false otherwise.
+ * If kClipped is returned, apply() may further refine the effect to kUnclipped or kNoDraw,
+ * with one exception. When 'result.fIsRRect' is true, preApply() reports the single round rect
+ * and anti-aliased state that would act as an intersection on the draw geometry. If no further
+ * action is taken to modify the draw, apply() will represent this round rect in the applied
+ * clip.
*
+ * When set, 'result.fRRect' will intersect with the render target bounds but may extend
+ * beyond it. If the render target bounds are the only clip effect on the draw, this is reported
+ * as kUnclipped and not as a degenerate rrect that matches the bounds.
*/
- virtual bool isRRect(SkRRect* rrect, GrAA* aa) const = 0;
+ virtual PreClipResult preApply(const SkRect& drawBounds) const {
+ bool outside = !drawBounds.intersects(SkRect::Make(this->getConservativeBounds()));
+ return outside ? Effect::kClippedOut : Effect::kClipped;
+ }
/**
* This is the maximum distance that a draw may extend beyond a clip's boundary and still count
@@ -147,11 +177,11 @@
* return 'bounds' has been intersected with a conservative bounds of the clip. A return value
* of false indicates that the draw can be skipped as it is fully clipped out.
*/
- virtual bool apply(GrAppliedHardClip* out, SkRect* bounds) const = 0;
+ virtual Effect apply(GrAppliedHardClip* out, SkRect* bounds) const = 0;
private:
- bool apply(GrRecordingContext*, GrRenderTargetContext* rtc, bool useHWAA,
- bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final {
+ Effect apply(GrRecordingContext*, GrRenderTargetContext* rtc, bool useHWAA,
+ bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final {
return this->apply(&out->hardClip(), bounds);
}
};
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index 6c5bbff..5b96643 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -34,32 +34,22 @@
const char GrClipStackClip::kMaskTestTag[] = "clip_mask";
-bool GrClipStackClip::quickContains(const SkRect& rect) const {
- if (!fStack || fStack->isWideOpen()) {
- return true;
- }
- return fStack->quickContains(rect);
-}
-
-bool GrClipStackClip::quickContains(const SkRRect& rrect) const {
- if (!fStack || fStack->isWideOpen()) {
- return true;
- }
- return fStack->quickContains(rrect);
-}
-
-bool GrClipStackClip::isRRect(SkRRect* rr, GrAA* aa) const {
- if (!fStack) {
- return false;
+GrClip::PreClipResult GrClipStackClip::preApply(const SkRect& drawBounds) const {
+ SkIRect deviceRect = SkIRect::MakeSize(fDeviceSize);
+ SkRect rect = SkRect::Make(deviceRect);
+ if (!rect.intersect(drawBounds) || (fStack && fStack->isEmpty(deviceRect))) {
+ return Effect::kClippedOut;
+ } else if (!fStack || fStack->isWideOpen()) {
+ return Effect::kUnclipped;
}
- SkRect rtBounds = SkRect::MakeIWH(fDeviceSize.fWidth, fDeviceSize.fHeight);
+ PreClipResult result(Effect::kClipped);
bool isAA;
- if (fStack->isRRect(rtBounds, rr, &isAA)) {
- *aa = GrAA(isAA);
- return true;
+ if (fStack->isRRect(rect, &result.fRRect, &isAA)) {
+ result.fIsRRect = true;
+ result.fAA = GrAA(isAA);
}
- return false;
+ return result;
}
SkIRect GrClipStackClip::getConservativeBounds() const {
@@ -197,18 +187,19 @@
////////////////////////////////////////////////////////////////////////////////
// sort out what kind of clip mask needs to be created: alpha, stencil,
// scissor, or entirely software
-bool GrClipStackClip::apply(GrRecordingContext* context, GrRenderTargetContext* renderTargetContext,
- bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out,
- SkRect* bounds) const {
+GrClip::Effect GrClipStackClip::apply(GrRecordingContext* context,
+ GrRenderTargetContext* renderTargetContext,
+ bool useHWAA, bool hasUserStencilSettings,
+ GrAppliedClip* out, SkRect* bounds) const {
SkASSERT(renderTargetContext->width() == fDeviceSize.fWidth &&
renderTargetContext->height() == fDeviceSize.fHeight);
SkRect devBounds = SkRect::MakeIWH(fDeviceSize.fWidth, fDeviceSize.fHeight);
if (!devBounds.intersect(*bounds)) {
- return false;
+ return Effect::kClippedOut;
}
if (!fStack || fStack->isWideOpen()) {
- return true;
+ return Effect::kUnclipped;
}
// An default count of 4 was chosen because of the common pattern in Blink of:
@@ -233,23 +224,27 @@
maxWindowRectangles, maxAnalyticFPs, ccpr ? maxAnalyticFPs : 0);
if (InitialState::kAllOut == reducedClip.initialState() &&
reducedClip.maskElements().isEmpty()) {
- return false;
+ return Effect::kClippedOut;
}
+ Effect effect = Effect::kUnclipped;
if (reducedClip.hasScissor() && !GrClip::IsInsideClip(reducedClip.scissor(), devBounds)) {
out->hardClip().addScissor(reducedClip.scissor(), bounds);
+ effect = Effect::kClipped;
}
if (!reducedClip.windowRectangles().empty()) {
out->hardClip().addWindowRectangles(reducedClip.windowRectangles(),
GrWindowRectsState::Mode::kExclusive);
+ effect = Effect::kClipped;
}
if (!reducedClip.maskElements().isEmpty()) {
if (!this->applyClipMask(context, renderTargetContext, reducedClip, hasUserStencilSettings,
out)) {
- return false;
+ return Effect::kClippedOut;
}
+ effect = Effect::kClipped;
}
// The opsTask ID must not be looked up until AFTER producing the clip mask (if any). That step
@@ -258,9 +253,10 @@
if (auto clipFPs = reducedClip.finishAndDetachAnalyticFPs(context, *fMatrixProvider, ccpr,
opsTaskID)) {
out->addCoverageFP(std::move(clipFPs));
+ effect = Effect::kClipped;
}
- return true;
+ return effect;
}
bool GrClipStackClip::applyClipMask(GrRecordingContext* context,
diff --git a/src/gpu/GrClipStackClip.h b/src/gpu/GrClipStackClip.h
index ffbd001..f1d4796 100644
--- a/src/gpu/GrClipStackClip.h
+++ b/src/gpu/GrClipStackClip.h
@@ -27,13 +27,10 @@
, fStack(stack)
, fMatrixProvider(matrixProvider) {}
- bool quickContains(const SkRect&) const final;
- bool quickContains(const SkRRect&) const final;
SkIRect getConservativeBounds() const final;
- bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
- bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final;
-
- bool isRRect(SkRRect* rr, GrAA* aa) const override;
+ Effect apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
+ bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final;
+ PreClipResult preApply(const SkRect& drawBounds) const final;
sk_sp<GrTextureProxy> testingOnly_createClipMask(GrContext*) const;
static const char kMaskTestTag[];
diff --git a/src/gpu/GrFixedClip.cpp b/src/gpu/GrFixedClip.cpp
index 2f75bc9..928b680 100644
--- a/src/gpu/GrFixedClip.cpp
+++ b/src/gpu/GrFixedClip.cpp
@@ -10,42 +10,46 @@
#include "src/gpu/GrAppliedClip.h"
#include "src/gpu/GrRenderTargetContext.h"
-bool GrFixedClip::quickContains(const SkRect& rect) const {
- if (fWindowRectsState.enabled()) {
- return false;
- }
-
- return !fScissorState.enabled() || GrClip::IsInsideClip(fScissorState.rect(), rect);
-}
-
SkIRect GrFixedClip::getConservativeBounds() const {
return fScissorState.rect();
}
-bool GrFixedClip::isRRect(SkRRect* rr, GrAA* aa) const {
- if (fWindowRectsState.enabled()) {
- return false;
+GrClip::PreClipResult GrFixedClip::preApply(const SkRect& drawBounds) const {
+ if (IsOutsideClip(fScissorState.rect(), drawBounds)) {
+ return Effect::kClippedOut;
}
- // Whether or not the scissor test is enabled, the remaining clip is a rectangle described
- // by scissorState.rect() (either the scissor or the rt bounds).
- rr->setRect(SkRect::Make(fScissorState.rect()));
- *aa = GrAA::kNo;
- return true;
-};
-bool GrFixedClip::apply(GrAppliedHardClip* out, SkRect* bounds) const {
- if (IsOutsideClip(fScissorState.rect(), *bounds)) {
- return false;
+ if (fWindowRectsState.enabled()) {
+ return Effect::kClipped;
}
- if (!IsInsideClip(fScissorState.rect(), *bounds)) {
+
+ if (!fScissorState.enabled() || IsInsideClip(fScissorState.rect(), drawBounds)) {
+ // Either no scissor or the scissor doesn't clip the draw
+ return Effect::kUnclipped;
+ }
+ // Report the scissor as a degenerate round rect
+ return {SkRect::Make(fScissorState.rect()), GrAA::kNo};
+}
+
+GrClip::Effect GrFixedClip::apply(GrAppliedHardClip* out, SkRect* bounds) const {
+ if (IsOutsideClip(fScissorState.rect(), *bounds)) {
+ return Effect::kClippedOut;
+ }
+
+ Effect effect = Effect::kUnclipped;
+ if (fScissorState.enabled() && !IsInsideClip(fScissorState.rect(), *bounds)) {
SkIRect tightScissor = bounds->roundOut();
SkAssertResult(tightScissor.intersect(fScissorState.rect()));
out->addScissor(tightScissor, bounds);
+ effect = Effect::kClipped;
}
if (fWindowRectsState.enabled()) {
out->addWindowRectangles(fWindowRectsState);
+ // We could iterate each window rectangle to check for intersection, but be conservative
+ // and report that it's clipped
+ effect = Effect::kClipped;
}
- return true;
+ return effect;
}
diff --git a/src/gpu/GrFixedClip.h b/src/gpu/GrFixedClip.h
index 5abc310..383c388 100644
--- a/src/gpu/GrFixedClip.h
+++ b/src/gpu/GrFixedClip.h
@@ -46,10 +46,9 @@
fWindowRectsState.set(windows, mode);
}
- bool quickContains(const SkRect&) const override;
- SkIRect getConservativeBounds() const override;
- bool isRRect(SkRRect* rr, GrAA*) const override;
- bool apply(GrAppliedHardClip*, SkRect*) const override;
+ SkIRect getConservativeBounds() const final;
+ Effect apply(GrAppliedHardClip*, SkRect*) const final;
+ PreClipResult preApply(const SkRect& drawBounds) const final;
private:
GrScissorState fScissorState;
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index bf6906f..4e2a438 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -682,25 +682,6 @@
kCropped
};
-static bool make_vertex_finite(float* value) {
- if (SkScalarIsNaN(*value)) {
- return false;
- }
-
- if (!SkScalarIsFinite(*value)) {
- // +/- infinity at this point. Don't use exactly SK_ScalarMax so that we have some precision
- // left when calculating crops.
- static constexpr float kNearInfinity = SK_ScalarMax / 4.f;
- *value = *value < 0.f ? -kNearInfinity : kNearInfinity;
- }
-
- return true;
-}
-
-static SkIRect get_clip_bounds(const GrRenderTargetContext* rtc, const GrClip* clip) {
- return clip ? clip->getConservativeBounds() : SkIRect::MakeWH(rtc->width(), rtc->height());
-}
-
GrRenderTargetContext::QuadOptimization GrRenderTargetContext::attemptQuadOptimization(
const GrClip* clip, const SkPMColor4f* constColor,
const GrUserStencilSettings* stencilSettings, GrAA* aa, DrawQuad* quad) {
@@ -721,132 +702,110 @@
SkRect rtRect = this->asSurfaceProxy()->getBoundsRect();
SkRect drawBounds = quad->fDevice.bounds();
- if (constColor) {
- // If the device quad is not finite, coerce into a finite quad. This is acceptable since it
- // will be cropped to the finite 'clip' or render target and there is no local space mapping
- if (!quad->fDevice.isFinite()) {
- for (int i = 0; i < 4; ++i) {
- if (!make_vertex_finite(quad->fDevice.xs() + i) ||
- !make_vertex_finite(quad->fDevice.ys() + i) ||
- !make_vertex_finite(quad->fDevice.ws() + i)) {
- // Discard if we see a nan
- return QuadOptimization::kDiscarded;
- }
- }
- SkASSERT(quad->fDevice.isFinite());
- }
- } else {
- // CropToRect requires the quads to be finite. If they are not finite and we have local
- // coordinates, the mapping from local space to device space is poorly defined so drop it
- if (!quad->fDevice.isFinite()) {
- return QuadOptimization::kDiscarded;
- }
- }
-
- // If the quad is entirely off screen, it doesn't matter what the clip does
- if (!rtRect.intersects(drawBounds)) {
+ if (!quad->fDevice.isFinite() || drawBounds.isEmpty() ||
+ GrClip::IsOutsideClip(rtRect, drawBounds)) {
return QuadOptimization::kDiscarded;
}
-
- // Check if clip can be represented as a rounded rect (initialize as if clip fully contained
- // the render target).
- SkRRect clipRRect = SkRRect::MakeRect(rtRect);
- // We initialize clipAA to *aa when there are stencil settings so that we don't artificially
- // encounter mixed-aa edges (not allowed for stencil), but we want to start as non-AA for
- // regular draws so that if we fully cover the render target, that can stop being anti-aliased.
- GrAA clipAA = stencilSettings ? *aa : GrAA::kNo;
- bool axisAlignedClip = true;
- if (clip && !clip->quickContains(rtRect)) {
- if (!clip->isRRect(&clipRRect, &clipAA)) {
- axisAlignedClip = false;
+ auto conservativeCrop = [&]() {
+ static constexpr int kLargeDrawLimit = 15000;
+ // Crop the quad to the render target. This doesn't change the visual results of drawing but
+ // is meant to help numerical stability for excessively large draws.
+ if (drawBounds.width() > kLargeDrawLimit || drawBounds.height() > kLargeDrawLimit) {
+ GrQuadUtils::CropToRect(rtRect, *aa, quad, /* compute local */ !constColor);
+ SkASSERT(quad->fEdgeFlags == oldFlags);
}
- }
+ };
- // If the clip rrect is valid (i.e. axis-aligned), we can potentially combine it with the
- // draw geometry so that no clip is needed when drawing.
- if (axisAlignedClip && (!stencilSettings || clipAA == *aa)) {
- // Tighten clip bounds (if clipRRect.isRect() is true, clipBounds now holds the intersection
- // of the render target and the clip rect)
- SkRect clipBounds = rtRect;
- if (!clipBounds.intersect(clipRRect.rect()) || !clipBounds.intersects(drawBounds)) {
+ bool simpleColor = !stencilSettings && constColor;
+ GrClip::PreClipResult result = clip ? clip->preApply(drawBounds)
+ : GrClip::PreClipResult(GrClip::Effect::kUnclipped);
+ switch(result.fEffect) {
+ case GrClip::Effect::kClippedOut:
return QuadOptimization::kDiscarded;
- }
-
- if (clipRRect.isRect()) {
- // No rounded corners, so the kClear and kExplicitClip optimizations are possible
- if (GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /*compute local*/ !constColor)) {
- if (!stencilSettings && constColor &&
- quad->fDevice.quadType() == GrQuad::Type::kAxisAligned) {
- // Clear optimization is possible
- drawBounds = quad->fDevice.bounds();
- if (drawBounds.contains(rtRect)) {
- // Fullscreen clear
- this->clear(*constColor);
- return QuadOptimization::kSubmitted;
- } else if (GrClip::IsPixelAligned(drawBounds) &&
- drawBounds.width() > 256 && drawBounds.height() > 256) {
- // Scissor + clear (round shouldn't do anything since we are pixel aligned)
- SkIRect scissorRect;
- drawBounds.round(&scissorRect);
- this->clear(scissorRect, *constColor);
- return QuadOptimization::kSubmitted;
- }
- }
-
- // Update overall AA setting.
- if (*aa == GrAA::kNo && clipAA == GrAA::kYes &&
- quad->fEdgeFlags != GrQuadAAFlags::kNone) {
- // The clip was anti-aliased and now the draw needs to be upgraded to AA to
- // properly reflect the smooth edge of the clip.
- *aa = GrAA::kYes;
- }
- // We intentionally do not downgrade AA here because we don't know if we need to
- // preserve MSAA (see GrQuadAAFlags docs). But later in the pipeline, the ops can
- // use GrResolveAATypeForQuad() to turn off coverage AA when all flags are off.
-
- // deviceQuad is exactly the intersection of original quad and clip, so it can be
- // drawn with no clip (submitted by caller)
+ case GrClip::Effect::kUnclipped:
+ if (!simpleColor) {
+ conservativeCrop();
return QuadOptimization::kClipApplied;
} else {
- // The quads have been updated to better fit the clip bounds, but can't get rid of
- // the clip entirely
- quad->fEdgeFlags = oldFlags;
- return QuadOptimization::kCropped;
+ // Update result to store the render target bounds in order and then fall
+ // through to attempt the draw->native clear optimization
+ result = GrClip::PreClipResult(SkRRect::MakeRect(rtRect), *aa);
}
- } else if (!stencilSettings && constColor) {
- // Rounded corners and constant filled color (limit ourselves to solid colors because
- // there is no way to use custom local coordinates with drawRRect).
- if (GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /* compute local */ false) &&
- quad->fDevice.quadType() == GrQuad::Type::kAxisAligned &&
- quad->fDevice.bounds().contains(clipBounds)) {
- // Since the cropped quad became a rectangle which covered the bounds of the rrect,
- // we can draw the rrect directly and ignore the edge flags
- GrPaint paint;
- clear_to_grpaint(*constColor, &paint);
- this->drawRRect(nullptr, std::move(paint), clipAA, SkMatrix::I(),
- clipRRect, GrStyle::SimpleFill());
- return QuadOptimization::kSubmitted;
- } else {
- // The quad has been updated to better fit clip bounds, but can't remove the clip
- quad->fEdgeFlags = oldFlags;
+ break;
+ case GrClip::Effect::kClipped:
+ if (!result.fIsRRect || (stencilSettings && result.fAA != *aa) ||
+ (!result.fRRect.isRect() && !simpleColor)) {
+ // The clip and draw state are too complicated to try and reduce
+ conservativeCrop();
return QuadOptimization::kCropped;
+ } // Else fall through to attempt to combine the draw and clip geometry together
+ break;
+ default:
+ SkUNREACHABLE;
+ }
+
+ // If we reached here, we know we're an axis-aligned clip that is either a rect or a round rect,
+ // so we can potentially combine it with the draw geometry so that no clipping is needed.
+ SkASSERT(result.fEffect == GrClip::Effect::kClipped && result.fIsRRect);
+ SkRect clippedBounds = result.fRRect.getBounds();
+ clippedBounds.intersect(rtRect);
+ if (result.fRRect.isRect()) {
+ // No rounded corners, so we might be able to become a native clear or we might be able to
+ // modify geometry and edge flags to represent intersected shape of clip and draw.
+ if (GrQuadUtils::CropToRect(clippedBounds, result.fAA, quad,
+ /*compute local*/ !constColor)) {
+ if (simpleColor && quad->fDevice.quadType() == GrQuad::Type::kAxisAligned) {
+ // Clear optimization is possible
+ drawBounds = quad->fDevice.bounds();
+ if (drawBounds.contains(rtRect)) {
+ // Fullscreen clear
+ this->clear(*constColor);
+ return QuadOptimization::kSubmitted;
+ } else if (GrClip::IsPixelAligned(drawBounds) &&
+ drawBounds.width() > 256 && drawBounds.height() > 256) {
+ // Scissor + clear (round shouldn't do anything since we are pixel aligned)
+ SkIRect scissorRect;
+ drawBounds.round(&scissorRect);
+ this->clear(scissorRect, *constColor);
+ return QuadOptimization::kSubmitted;
+ }
}
+
+ // else the draw and clip were combined so just update the AA to reflect combination
+ if (*aa == GrAA::kNo && result.fAA == GrAA::kYes &&
+ quad->fEdgeFlags != GrQuadAAFlags::kNone) {
+ // The clip was anti-aliased and now the draw needs to be upgraded to AA to
+ // properly reflect the smooth edge of the clip.
+ *aa = GrAA::kYes;
+ }
+ // We intentionally do not downgrade AA here because we don't know if we need to
+ // preserve MSAA (see GrQuadAAFlags docs). But later in the pipeline, the ops can
+ // use GrResolveAATypeForQuad() to turn off coverage AA when all flags are off.
+ // deviceQuad is exactly the intersection of original quad and clip, so it can be
+ // drawn with no clip (submitted by caller)
+ return QuadOptimization::kClipApplied;
+ }
+ } else {
+ // Rounded corners and constant filled color (limit ourselves to solid colors because
+ // there is no way to use custom local coordinates with drawRRect).
+ SkASSERT(simpleColor);
+ if (GrQuadUtils::CropToRect(clippedBounds, result.fAA, quad,
+ /* compute local */ false) &&
+ quad->fDevice.quadType() == GrQuad::Type::kAxisAligned &&
+ quad->fDevice.bounds().contains(clippedBounds)) {
+ // Since the cropped quad became a rectangle which covered the bounds of the rrect,
+ // we can draw the rrect directly and ignore the edge flags
+ GrPaint paint;
+ clear_to_grpaint(*constColor, &paint);
+ this->drawRRect(nullptr, std::move(paint), result.fAA, SkMatrix::I(), result.fRRect,
+ GrStyle::SimpleFill());
+ return QuadOptimization::kSubmitted;
}
}
- // Crop the quad to the conservative bounds of the clip.
- SkRect clipBounds = SkRect::Make(get_clip_bounds(this, clip));
-
- // One final check for discarding, since we may have gone here directly due to a complex clip
- if (!clipBounds.intersects(drawBounds)) {
- return QuadOptimization::kDiscarded;
- }
-
- // Even if this were to return true, the crop rect does not exactly match the clip, so can not
- // report explicit-clip. Since these edges aren't visible, don't update the final edge flags.
- GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /* compute local */ !constColor);
+ // The quads have been updated to better fit the clip bounds, but can't get rid of
+ // the clip entirely
quad->fEdgeFlags = oldFlags;
-
return QuadOptimization::kCropped;
}
@@ -1074,7 +1033,7 @@
GrAppliedHardClip appliedClip(fRenderTargetContext->dimensions(),
fRenderTargetContext->asSurfaceProxy()->backingStoreDimensions());
- if (clip && !clip->apply(&appliedClip, &bounds)) {
+ if (clip && GrClip::Effect::kClippedOut == clip->apply(&appliedClip, &bounds)) {
return;
}
// else see FIXME above; we'd normally want to check path bounds with render target bounds,
@@ -1175,26 +1134,49 @@
SkDEBUGCODE(this->validate();)
GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawRRect", fContext);
+ SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
+
const SkStrokeRec& stroke = style.strokeRec();
if (stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.isEmpty()) {
return;
}
const GrClip* clip = origClip;
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
- // The Android framework frequently clips rrects to themselves where the clip is non-aa and the
- // draw is aa. Since our lower level clip code works from op bounds, which are SkRects, it
- // doesn't detect that the clip can be ignored (modulo antialiasing). The following test
- // attempts to mitigate the stencil clip cost but will only help when the entire clip stack
- // can be ignored. We'd prefer to fix this in the framework by removing the clips calls. This
- // only works for filled rrects since the stroke width outsets beyond the rrect itself.
+ // It is not uncommon to clip to a round rect and then draw that same round rect. Since our
+ // lower level clip code works from op bounds, which are SkRects, it doesn't detect that the
+ // clip can be ignored. The following test attempts to mitigate the stencil clip cost but only
+ // works for axis-aligned round rects. This also only works for filled rrects since the stroke
+ // width outsets beyond the rrect itself.
SkRRect devRRect;
if (clip && stroke.getStyle() == SkStrokeRec::kFill_Style &&
- rrect.transform(viewMatrix, &devRRect) && clip->quickContains(devRRect)) {
- clip = nullptr;
- }
+ rrect.transform(viewMatrix, &devRRect)) {
+ GrClip::PreClipResult result = clip->preApply(devRRect.getBounds());
+ switch(result.fEffect) {
+ case GrClip::Effect::kClippedOut:
+ return;
+ case GrClip::Effect::kUnclipped:
+ clip = nullptr;
+ break;
+ case GrClip::Effect::kClipped:
+ // Currently there's no general-purpose rrect-to-rrect contains function, and if we
+ // got here, we know the devRRect's bounds aren't fully contained by the clip.
+ // Testing for equality between the two is a reasonable stop-gap for now.
+ if (result.fIsRRect && result.fRRect == devRRect) {
+ // NOTE: On the android framework, we allow this optimization even when the clip
+ // is non-AA and the draw is AA.
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+ if (result.fAA == aa || (result.fAA == GrAA::kNo && aa == GrAA::kYes)) {
+#else
+ if (result.fAA == aa) {
#endif
- SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
+ clip = nullptr;
+ }
+ }
+ break;
+ default:
+ SkUNREACHABLE;
+ }
+ }
AutoCheckFlush acf(this->drawingManager());
@@ -1823,6 +1805,10 @@
/* attempt fallback */ false);
}
+static SkIRect get_clip_bounds(const GrRenderTargetContext* rtc, const GrClip* clip) {
+ return clip ? clip->getConservativeBounds() : SkIRect::MakeWH(rtc->width(), rtc->height());
+}
+
bool GrRenderTargetContextPriv::drawAndStencilPath(const GrHardClip* clip,
const GrUserStencilSettings* ss,
SkRegion::Op op,
@@ -2059,14 +2045,11 @@
bool skipDraw = false;
if (clip) {
// Have a complex clip, so defer to its early clip culling
- if (!clip->apply(fContext, this, usesHWAA, usesUserStencilBits, &appliedClip, &bounds)) {
- skipDraw = true;
- }
+ skipDraw = clip->apply(fContext, this, usesHWAA, usesUserStencilBits,
+ &appliedClip, &bounds) == GrClip::Effect::kClippedOut;
} else {
// No clipping, so just clip the bounds against the logical render target dimensions
- if (!bounds.intersect(this->asSurfaceProxy()->getBoundsRect())) {
- skipDraw = true;
- }
+ skipDraw = !bounds.intersect(this->asSurfaceProxy()->getBoundsRect());
}
if (skipDraw) {
diff --git a/src/gpu/GrStencilClip.h b/src/gpu/GrStencilClip.h
index 4ab8a82..a55c463 100644
--- a/src/gpu/GrStencilClip.h
+++ b/src/gpu/GrStencilClip.h
@@ -32,23 +32,29 @@
bool hasStencilClip() const { return SK_InvalidGenID != fStencilStackID; }
void setStencilClip(uint32_t stencilStackID) { fStencilStackID = stencilStackID; }
- bool quickContains(const SkRect& rect) const override {
- return !this->hasStencilClip() && fFixedClip.quickContains(rect);
- }
- SkIRect getConservativeBounds() const override {
+ SkIRect getConservativeBounds() const final {
return fFixedClip.getConservativeBounds();
}
- bool isRRect(SkRRect* rr, GrAA* aa) const override {
- return !this->hasStencilClip() && fFixedClip.isRRect(rr, aa);
- }
- bool apply(GrAppliedHardClip* out, SkRect* bounds) const override {
- if (!fFixedClip.apply(out, bounds)) {
- return false;
+
+ Effect apply(GrAppliedHardClip* out, SkRect* bounds) const final {
+ Effect effect = fFixedClip.apply(out, bounds);
+ if (effect == Effect::kClippedOut) {
+ // Stencil won't bring back coverage
+ return Effect::kClippedOut;
}
if (this->hasStencilClip()) {
out->addStencilClip(fStencilStackID);
+ effect = Effect::kClipped;
}
- return true;
+ return effect;
+ }
+
+ PreClipResult preApply(const SkRect& drawBounds) const final {
+ if (this->hasStencilClip()) {
+ return this->INHERITED::preApply(drawBounds);
+ } else {
+ return fFixedClip.preApply(drawBounds);
+ }
}
private:
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index fcff78c..5ea3738 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -125,9 +125,9 @@
// fake inverse with a stencil and cover
GrAppliedClip appliedClip(args.fRenderTargetContext->dimensions());
- if (args.fClip && !args.fClip->apply(
+ if (args.fClip && args.fClip->apply(
args.fContext, args.fRenderTargetContext, doStencilMSAA, true, &appliedClip,
- &devBounds)) {
+ &devBounds) == GrClip::Effect::kClippedOut) {
return true;
}
GrStencilClip stencilClip(args.fRenderTargetContext->dimensions(),
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index 875d939..a390864 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -442,25 +442,29 @@
bool skipClip = false;
SkIRect clipRect = SkIRect::MakeEmpty();
SkRect rtBounds = SkRect::MakeWH(target->width(), target->height());
- SkRRect clipRRect = SkRRect::MakeRect(rtBounds);
- GrAA aa;
// We can clip geometrically if we're not using SDFs or transformed glyphs,
// and we have an axis-aligned rectangular non-AA clip
- if (!this->drawAsDistanceFields() &&
- !this->needsTransform() &&
- (!clip || (clip->isRRect(&clipRRect, &aa) &&
- clipRRect.isRect() && GrAA::kNo == aa))) {
+ if (!this->drawAsDistanceFields() && !this->needsTransform()) {
// We only need to do clipping work if the subrun isn't contained by the clip
+ skipClip = true;
SkRect subRunBounds = this->deviceRect(deviceMatrix.localToDevice(), drawOrigin);
- if (!clipRRect.getBounds().contains(subRunBounds)) {
+ if (!clip && !rtBounds.intersects(subRunBounds)) {
// If the subrun is completely outside, don't add an op for it
- if (!clipRRect.getBounds().intersects(subRunBounds)) {
+ return;
+ } else if (clip) {
+ GrClip::PreClipResult result = clip->preApply(subRunBounds);
+ if (result.fEffect == GrClip::Effect::kClipped) {
+ if (result.fIsRRect && result.fRRect.isRect() && result.fAA == GrAA::kNo) {
+ // Embed non-AA axis-aligned clip into the draw
+ result.fRRect.getBounds().round(&clipRect);
+ } else {
+ // Can't actually skip the regular clipping
+ skipClip = false;
+ }
+ } else if (result.fEffect == GrClip::Effect::kClippedOut) {
return;
- } else {
- clipRRect.getBounds().round(&clipRect);
}
}
- skipClip = true;
}
auto op = this->makeOp(deviceMatrix, drawOrigin, clipRect, paint, props, target);
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index 4f1b6d8..7561981 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -40,16 +40,15 @@
private:
SkIRect getConservativeBounds() const final { return fPath.getBounds().roundOut(); }
- bool apply(GrRecordingContext* context, GrRenderTargetContext* rtc, bool useHWAA,
- bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const override {
+ Effect apply(GrRecordingContext* context, GrRenderTargetContext* rtc, bool useHWAA,
+ bool hasUserStencilSettings, GrAppliedClip* out,
+ SkRect* bounds) const override {
out->addCoverageFP(fCCPR->makeClipProcessor(/*inputFP=*/nullptr,
rtc->priv().testingOnly_getOpsTaskID(), fPath,
SkIRect::MakeWH(rtc->width(), rtc->height()),
*context->priv().caps()));
- return true;
+ return Effect::kClipped;
}
- bool quickContains(const SkRect&) const final { return false; }
- bool isRRect(SkRRect* rr, GrAA*) const final { return false; }
GrCoverageCountingPathRenderer* const fCCPR;
const SkPath fPath;
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index 9bb4bff..77965a4 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -178,15 +178,13 @@
SkIRect getConservativeBounds() const final {
return SkIRect::MakeSize(fAtlas->dimensions());
}
-
- bool apply(GrRecordingContext* context, GrRenderTargetContext*, bool useHWAA,
- bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const override {
+ Effect apply(GrRecordingContext* context, GrRenderTargetContext*, bool useHWAA,
+ bool hasUserStencilSettings, GrAppliedClip* out,
+ SkRect* bounds) const override {
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
out->addCoverageFP(std::make_unique<ClipFP>(context, proxyProvider, fTest, fAtlas));
- return true;
+ return Effect::kClipped;
}
- bool quickContains(const SkRect&) const final { return false; }
- bool isRRect(SkRRect* rr, GrAA*) const final { return false; }
LazyProxyTest* const fTest;
GrTextureProxy* fAtlas;