aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar csmartdalton <csmartdalton@google.com>2016-07-22 08:59:08 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-07-22 08:59:08 -0700
commitcbecb082d15da3c9cc5ac7192b243fd26dc29a57 (patch)
tree1014412e236e2fc29d0aad5439aa17dc92efd871
parent3011e337693a9786f62d8de9ac4b239ad6dbdaca (diff)
Allow GrReducedClip to take non-integer query bounds
Fixes places where AA bloat was being conflated with geometric boundaries and updates GrReducedClip to work with non-integer query bounds. This allows for better clip reduction with AA shared edges. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2160093002 Review-Url: https://codereview.chromium.org/2160093002
-rw-r--r--include/gpu/GrClip.h85
-rw-r--r--src/gpu/GrClip.cpp4
-rw-r--r--src/gpu/GrClipMaskManager.cpp49
-rw-r--r--src/gpu/GrReducedClip.cpp253
-rw-r--r--src/gpu/GrReducedClip.h37
-rw-r--r--src/gpu/gl/GrGLIRect.h2
-rw-r--r--src/utils/SkLua.cpp7
-rw-r--r--tests/ClipStackTest.cpp353
8 files changed, 503 insertions, 287 deletions
diff --git a/include/gpu/GrClip.h b/include/gpu/GrClip.h
index ab83441f41..1cb1a2bcf6 100644
--- a/include/gpu/GrClip.h
+++ b/include/gpu/GrClip.h
@@ -113,25 +113,76 @@ public:
virtual ~GrClip() {}
-protected:
/**
- * Returns true if a clip can safely disable its scissor test for a particular draw.
+ * This is the maximum distance that a draw may extend beyond a clip's boundary and still count
+ * count as "on the other side". We leave some slack because floating point rounding error is
+ * likely to blame. The rationale for 1e-3 is that in the coverage case (and barring unexpected
+ * rounding), as long as coverage stays within 0.5 * 1/256 of its intended value it shouldn't
+ * have any effect on the final pixel values.
*/
- static bool CanIgnoreScissor(const SkIRect& scissorRect, const SkRect& drawBounds) {
- // This is the maximum distance that a draw may extend beyond a clip's scissor and still
- // count as inside. We use a sloppy compare because the draw may have chosen its bounds in a
- // different coord system. The rationale for 1e-3 is that in the coverage case (and barring
- // unexpected rounding), as long as coverage stays below 0.5 * 1/256 we ought to be OK.
- constexpr SkScalar fuzz = 1e-3f;
- SkASSERT(!scissorRect.isEmpty());
- SkASSERT(!drawBounds.isEmpty());
- return scissorRect.fLeft <= drawBounds.fLeft + fuzz &&
- scissorRect.fTop <= drawBounds.fTop + fuzz &&
- scissorRect.fRight >= drawBounds.fRight - fuzz &&
- scissorRect.fBottom >= drawBounds.fBottom - fuzz;
- }
-
- friend class GrClipMaskManager;
+ constexpr static SkScalar kBoundsTolerance = 1e-3f;
+
+ /**
+ * Returns true if the given query bounds count as entirely inside the clip.
+ *
+ * @param innerClipBounds device-space rect contained by the clip (SkRect or SkIRect).
+ * @param queryBounds device-space bounds of the query region.
+ */
+ template<typename TRect> constexpr static bool IsInsideClip(const TRect& innerClipBounds,
+ const SkRect& queryBounds) {
+ return innerClipBounds.fRight - innerClipBounds.fLeft >= kBoundsTolerance &&
+ innerClipBounds.fBottom - innerClipBounds.fTop >= kBoundsTolerance &&
+ innerClipBounds.fLeft <= queryBounds.fLeft + kBoundsTolerance &&
+ innerClipBounds.fTop <= queryBounds.fTop + kBoundsTolerance &&
+ innerClipBounds.fRight >= queryBounds.fRight - kBoundsTolerance &&
+ innerClipBounds.fBottom >= queryBounds.fBottom - kBoundsTolerance;
+ }
+
+ /**
+ * Returns true if the given query bounds count as entirely outside the clip.
+ *
+ * @param outerClipBounds device-space rect that contains the clip (SkRect or SkIRect).
+ * @param queryBounds device-space bounds of the query region.
+ */
+ template<typename TRect> constexpr static bool IsOutsideClip(const TRect& outerClipBounds,
+ const SkRect& queryBounds) {
+ return outerClipBounds.fRight - outerClipBounds.fLeft < kBoundsTolerance ||
+ outerClipBounds.fBottom - outerClipBounds.fTop < kBoundsTolerance ||
+ outerClipBounds.fLeft > queryBounds.fRight - kBoundsTolerance ||
+ outerClipBounds.fTop > queryBounds.fBottom - kBoundsTolerance ||
+ outerClipBounds.fRight < queryBounds.fLeft + kBoundsTolerance ||
+ outerClipBounds.fBottom < queryBounds.fTop + kBoundsTolerance;
+ }
+
+ /**
+ * Returns the minimal integer rect that counts as containing a given set of bounds.
+ */
+ static SkIRect GetPixelIBounds(const SkRect& bounds) {
+ return SkIRect::MakeLTRB(SkScalarFloorToInt(bounds.fLeft + kBoundsTolerance),
+ SkScalarFloorToInt(bounds.fTop + kBoundsTolerance),
+ SkScalarCeilToInt(bounds.fRight - kBoundsTolerance),
+ SkScalarCeilToInt(bounds.fBottom - kBoundsTolerance));
+ }
+
+ /**
+ * Returns the minimal pixel-aligned rect that counts as containing a given set of bounds.
+ */
+ static SkRect GetPixelBounds(const SkRect& bounds) {
+ return SkRect::MakeLTRB(SkScalarFloorToScalar(bounds.fLeft + kBoundsTolerance),
+ SkScalarFloorToScalar(bounds.fTop + kBoundsTolerance),
+ SkScalarCeilToScalar(bounds.fRight - kBoundsTolerance),
+ SkScalarCeilToScalar(bounds.fBottom - kBoundsTolerance));
+ }
+
+ /**
+ * Returns true if the given rect counts as aligned with pixel boundaries.
+ */
+ static bool IsPixelAligned(const SkRect& rect) {
+ return SkScalarAbs(SkScalarRoundToScalar(rect.fLeft) - rect.fLeft) <= kBoundsTolerance &&
+ SkScalarAbs(SkScalarRoundToScalar(rect.fTop) - rect.fTop) <= kBoundsTolerance &&
+ SkScalarAbs(SkScalarRoundToScalar(rect.fRight) - rect.fRight) <= kBoundsTolerance &&
+ SkScalarAbs(SkScalarRoundToScalar(rect.fBottom) - rect.fBottom) <= kBoundsTolerance;
+ }
};
/**
diff --git a/src/gpu/GrClip.cpp b/src/gpu/GrClip.cpp
index dc2208ad43..b0c8db3744 100644
--- a/src/gpu/GrClip.cpp
+++ b/src/gpu/GrClip.cpp
@@ -54,10 +54,10 @@ bool GrFixedClip::apply(GrContext*,
SkIRect::MakeWH(drawContext->width(), drawContext->height()))) {
return false;
}
- if (devBounds && !devBounds->intersects(SkRect::Make(tightScissor))) {
+ if (devBounds && IsOutsideClip(tightScissor, *devBounds)) {
return false;
}
- if (!devBounds || !CanIgnoreScissor(fScissorState.rect(), *devBounds)) {
+ if (!devBounds || !IsInsideClip(fScissorState.rect(), *devBounds)) {
if (fHasStencilClip) {
out->makeScissoredStencil(tightScissor, &fDeviceBounds);
} else {
diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp
index 6e2a303844..67c6f6eac3 100644
--- a/src/gpu/GrClipMaskManager.cpp
+++ b/src/gpu/GrClipMaskManager.cpp
@@ -26,6 +26,7 @@
#include "effects/GrTextureDomain.h"
typedef SkClipStack::Element Element;
+typedef GrReducedClip::InitialState InitialState;
static const int kMaxAnalyticElements = 4;
@@ -146,7 +147,7 @@ bool GrClipMaskManager::UseSWOnlyPath(GrContext* context,
static bool get_analytic_clip_processor(const GrReducedClip::ElementList& elements,
bool abortIfAA,
- SkVector& clipToRTOffset,
+ const SkVector& clipToRTOffset,
const SkRect& drawBounds,
sk_sp<GrFragmentProcessor>* resultFP) {
SkRect boundsInClipSpace;
@@ -237,40 +238,32 @@ bool GrClipMaskManager::SetupClipping(GrContext* context,
return true;
}
+ SkRect devBounds = SkRect::MakeIWH(drawContext->width(), drawContext->height());
+ if (origDevBounds && !devBounds.intersect(*origDevBounds)) {
+ return false;
+ }
+
+ const SkScalar clipX = SkIntToScalar(clip.origin().x()),
+ clipY = SkIntToScalar(clip.origin().y());
+
GrReducedClip::ElementList elements;
int32_t genID = 0;
- GrReducedClip::InitialState initialState = GrReducedClip::kAllIn_InitialState;
SkIRect clipSpaceIBounds;
bool requiresAA = false;
- SkIRect clipSpaceReduceQueryBounds;
- SkRect devBounds;
- if (origDevBounds) {
- if (!devBounds.intersect(SkRect::MakeIWH(drawContext->width(), drawContext->height()),
- *origDevBounds)) {
- return false;
- }
- devBounds.roundOut(&clipSpaceReduceQueryBounds);
- clipSpaceReduceQueryBounds.offset(clip.origin());
- } else {
- devBounds = SkRect::MakeIWH(drawContext->width(), drawContext->height());
- clipSpaceReduceQueryBounds.setXYWH(0, 0, drawContext->width(), drawContext->height());
- clipSpaceReduceQueryBounds.offset(clip.origin());
- }
- GrReducedClip::ReduceClipStack(*clip.clipStack(),
- clipSpaceReduceQueryBounds,
- &elements,
- &genID,
- &initialState,
- &clipSpaceIBounds,
- &requiresAA);
+ InitialState initialState = GrReducedClip::ReduceClipStack(*clip.clipStack(),
+ devBounds.makeOffset(clipX, clipY),
+ &elements,
+ &genID,
+ &clipSpaceIBounds,
+ &requiresAA);
if (elements.isEmpty()) {
if (GrReducedClip::kAllOut_InitialState == initialState) {
return false;
} else {
SkIRect scissorSpaceIBounds(clipSpaceIBounds);
scissorSpaceIBounds.offset(-clip.origin());
- if (!GrClip::CanIgnoreScissor(scissorSpaceIBounds, devBounds)) {
+ if (!GrClip::IsInsideClip(scissorSpaceIBounds, devBounds)) {
out->makeScissored(scissorSpaceIBounds);
}
return true;
@@ -286,8 +279,6 @@ bool GrClipMaskManager::SetupClipping(GrContext* context,
// configuration's relative costs of switching RTs to generate a mask vs
// longer shaders.
if (elements.count() <= kMaxAnalyticElements) {
- SkVector clipToRTOffset = { SkIntToScalar(-clip.origin().fX),
- SkIntToScalar(-clip.origin().fY) };
// When there are multiple samples we want to do per-sample clipping, not compute a
// fractional pixel coverage.
bool disallowAnalyticAA = drawContext->isStencilBufferMultisampled();
@@ -299,11 +290,11 @@ bool GrClipMaskManager::SetupClipping(GrContext* context,
}
sk_sp<GrFragmentProcessor> clipFP;
if (requiresAA &&
- get_analytic_clip_processor(elements, disallowAnalyticAA, clipToRTOffset, devBounds,
+ get_analytic_clip_processor(elements, disallowAnalyticAA, {-clipX, -clipY}, devBounds,
&clipFP)) {
SkIRect scissorSpaceIBounds(clipSpaceIBounds);
scissorSpaceIBounds.offset(-clip.origin());
- if (GrClip::CanIgnoreScissor(scissorSpaceIBounds, devBounds)) {
+ if (GrClip::IsInsideClip(scissorSpaceIBounds, devBounds)) {
out->makeFPBased(std::move(clipFP), SkRect::Make(scissorSpaceIBounds));
} else {
out->makeScissoredFPBased(std::move(clipFP), scissorSpaceIBounds);
@@ -370,7 +361,7 @@ bool GrClipMaskManager::SetupClipping(GrContext* context,
// use both stencil and scissor test to the bounds for the final draw.
SkIRect scissorSpaceIBounds(clipSpaceIBounds);
scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset);
- if (GrClip::CanIgnoreScissor(scissorSpaceIBounds, devBounds)) {
+ if (GrClip::IsInsideClip(scissorSpaceIBounds, devBounds)) {
out->makeStencil(true, devBounds);
} else {
out->makeScissoredStencil(scissorSpaceIBounds);
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 26b8936952..2940f6a682 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -7,14 +7,16 @@
#include "GrReducedClip.h"
+#include "GrClip.h"
+
typedef SkClipStack::Element Element;
-static void reduced_stack_walker(const SkClipStack& stack,
- const SkRect& queryBounds,
- GrReducedClip::ElementList* result,
- int32_t* resultGenID,
- GrReducedClip::InitialState* initialState,
- bool* requiresAA) {
+static GrReducedClip::InitialState reduced_stack_walker(const SkClipStack& stack,
+ const SkRect& queryBounds,
+ const SkIRect& clipIBounds,
+ GrReducedClip::ElementList* result,
+ int32_t* resultGenID,
+ bool* requiresAA) {
// walk backwards until we get to:
// a) the beginning
@@ -23,27 +25,32 @@ static void reduced_stack_walker(const SkClipStack& stack,
static const GrReducedClip::InitialState kUnknown_InitialState =
static_cast<GrReducedClip::InitialState>(-1);
- *initialState = kUnknown_InitialState;
+ GrReducedClip::InitialState initialState = kUnknown_InitialState;
// During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
// TODO: track these per saved clip so that we can consider them on the forward pass.
bool embiggens = false;
bool emsmallens = false;
+ // We use a slightly relaxed set of query bounds for element containment tests. This is to
+ // account for floating point rounding error that may have occurred during coord transforms.
+ SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance,
+ GrClip::kBoundsTolerance);
+
SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
int numAAElements = 0;
- while ((kUnknown_InitialState == *initialState)) {
+ while (kUnknown_InitialState == initialState) {
const Element* element = iter.prev();
if (nullptr == element) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ initialState = GrReducedClip::kAllIn_InitialState;
break;
}
if (SkClipStack::kEmptyGenID == element->getGenID()) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ initialState = GrReducedClip::kAllOut_InitialState;
break;
}
if (SkClipStack::kWideOpenGenID == element->getGenID()) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ initialState = GrReducedClip::kAllIn_InitialState;
break;
}
@@ -55,17 +62,17 @@ static void reduced_stack_walker(const SkClipStack& stack,
// check if the shape subtracted either contains the entire bounds (and makes
// the clip empty) or is outside the bounds and therefore can be skipped.
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
}
} else {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
skippable = true;
}
}
@@ -78,17 +85,17 @@ static void reduced_stack_walker(const SkClipStack& stack,
// be skipped or it is outside the entire bounds and therefore makes the clip
// empty.
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
skippable = true;
}
} else {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
}
}
@@ -101,17 +108,17 @@ static void reduced_stack_walker(const SkClipStack& stack,
// the bounds is entirely inside the clip. If the union-ed shape is outside the
// bounds then this op can be skipped.
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllIn_InitialState;
skippable = true;
}
} else {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllIn_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
skippable = true;
}
}
@@ -125,15 +132,15 @@ static void reduced_stack_walker(const SkClipStack& stack,
// able to take advantage of this in the forward pass. If the xor-ed shape
// doesn't intersect the bounds then it can be skipped.
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
isFlip = true;
}
} else {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
isFlip = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
skippable = true;
}
}
@@ -147,17 +154,17 @@ static void reduced_stack_walker(const SkClipStack& stack,
// the bounds then we know after this element is applied that the bounds will be
// all outside the current clip.B
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
isFlip = true;
}
} else {
- if (element->contains(queryBounds)) {
+ if (element->contains(relaxedQueryBounds)) {
isFlip = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
}
}
@@ -165,30 +172,31 @@ static void reduced_stack_walker(const SkClipStack& stack,
emsmallens = embiggens = true;
}
break;
+
case SkRegion::kReplace_Op:
// Replace will always terminate our walk. We will either begin the forward walk
// at the replace op or detect here than the shape is either completely inside
// or completely outside the bounds. In this latter case it can be skipped by
// setting the correct value for initialState.
if (element->isInverseFilled()) {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllIn_InitialState;
skippable = true;
}
} else {
- if (element->contains(queryBounds)) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ if (element->contains(relaxedQueryBounds)) {
+ initialState = GrReducedClip::kAllIn_InitialState;
skippable = true;
- } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
+ initialState = GrReducedClip::kAllOut_InitialState;
skippable = true;
}
}
if (!skippable) {
- *initialState = GrReducedClip::kAllOut_InitialState;
+ initialState = GrReducedClip::kAllOut_InitialState;
embiggens = emsmallens = true;
}
break;
@@ -206,7 +214,8 @@ static void reduced_stack_walker(const SkClipStack& stack,
if (isFlip) {
SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
SkRegion::kReverseDifference_Op == element->getOp());
- result->addToHead(queryBounds, SkRegion::kReverseDifference_Op, false);
+ result->addToHead(SkRect::Make(clipIBounds), SkRegion::kReverseDifference_Op,
+ false);
} else {
Element* newElement = result->addToHead(*element);
if (newElement->isAA()) {
@@ -221,17 +230,18 @@ static void reduced_stack_walker(const SkClipStack& stack,
newElement->invertShapeFillType();
newElement->setOp(SkRegion::kDifference_Op);
if (isReplace) {
- SkASSERT(GrReducedClip::kAllOut_InitialState == *initialState);
- *initialState = GrReducedClip::kAllIn_InitialState;
+ SkASSERT(GrReducedClip::kAllOut_InitialState == initialState);
+ initialState = GrReducedClip::kAllIn_InitialState;
}
}
}
}
}
- if ((GrReducedClip::kAllOut_InitialState == *initialState && !embiggens) ||
- (GrReducedClip::kAllIn_InitialState == *initialState && !emsmallens)) {
+ if ((GrReducedClip::kAllOut_InitialState == initialState && !embiggens) ||
+ (GrReducedClip::kAllIn_InitialState == initialState && !emsmallens)) {
result->reset();
+ numAAElements = 0;
} else {
Element* element = result->headIter().get();
while (element) {
@@ -239,20 +249,20 @@ static void reduced_stack_walker(const SkClipStack& stack,
switch (element->getOp()) {
case SkRegion::kDifference_Op:
// subtracting from the empty set yields the empty set.
- skippable = GrReducedClip::kAllOut_InitialState == *initialState;
+ skippable = GrReducedClip::kAllOut_InitialState == initialState;
break;
case SkRegion::kIntersect_Op:
// intersecting with the empty set yields the empty set
- if (GrReducedClip::kAllOut_InitialState == *initialState) {
+ if (GrReducedClip::kAllOut_InitialState == initialState) {
skippable = true;
} else {
// We can clear to zero and then simply draw the clip element.
- *initialState = GrReducedClip::kAllOut_InitialState;
+ initialState = GrReducedClip::kAllOut_InitialState;
element->setOp(SkRegion::kReplace_Op);
}
break;
case SkRegion::kUnion_Op:
- if (GrReducedClip::kAllIn_InitialState == *initialState) {
+ if (GrReducedClip::kAllIn_InitialState == initialState) {
// unioning the infinite plane with anything is a no-op.
skippable = true;
} else {
@@ -261,23 +271,23 @@ static void reduced_stack_walker(const SkClipStack& stack,
}
break;
case SkRegion::kXOR_Op:
- if (GrReducedClip::kAllOut_InitialState == *initialState) {
+ if (GrReducedClip::kAllOut_InitialState == initialState) {
// xor could be changed to diff in the kAllIn case, not sure it's a win.
element->setOp(SkRegion::kReplace_Op);
}
break;
case SkRegion::kReverseDifference_Op:
- if (GrReducedClip::kAllIn_InitialState == *initialState) {
+ if (GrReducedClip::kAllIn_InitialState == initialState) {
// subtracting the whole plane will yield the empty set.
skippable = true;
- *initialState = GrReducedClip::kAllOut_InitialState;
+ initialState = GrReducedClip::kAllOut_InitialState;
} else {
// this picks up flips inserted in the backwards pass.
skippable = element->isInverseFilled() ?
- !SkRect::Intersects(element->getBounds(), queryBounds) :
- element->contains(queryBounds);
+ GrClip::IsOutsideClip(element->getBounds(), queryBounds) :
+ element->contains(relaxedQueryBounds);
if (skippable) {
- *initialState = GrReducedClip::kAllIn_InitialState;
+ initialState = GrReducedClip::kAllIn_InitialState;
} else {
element->setOp(SkRegion::kReplace_Op);
}
@@ -305,12 +315,15 @@ static void reduced_stack_walker(const SkClipStack& stack,
*requiresAA = numAAElements > 0;
if (0 == result->count()) {
- if (*initialState == GrReducedClip::kAllIn_InitialState) {
+ if (initialState == GrReducedClip::kAllIn_InitialState) {
*resultGenID = SkClipStack::kWideOpenGenID;
} else {
*resultGenID = SkClipStack::kEmptyGenID;
}
}
+
+ SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
+ return initialState;
}
/*
@@ -320,15 +333,13 @@ for the case where the bounds are kInsideOut_BoundsType. We could restrict earli
based on later intersect operations, and perhaps remove intersect-rects. We could optionally
take a rect in case the caller knows a bound on what is to be drawn through this clip.
*/
-void GrReducedClip::ReduceClipStack(const SkClipStack& stack,
- const SkIRect& queryBounds,
- ElementList* result,
- int32_t* resultGenID,
- InitialState* initialState,
- SkIRect* tighterBounds,
- bool* requiresAA) {
- SkASSERT(tighterBounds);
- SkASSERT(requiresAA);
+GrReducedClip::InitialState GrReducedClip::ReduceClipStack(const SkClipStack& stack,
+ const SkRect& queryBounds,
+ ElementList* result,
+ int32_t* resultGenID,
+ SkIRect* clipIBounds,
+ bool* requiresAA) {
+ SkASSERT(!queryBounds.isEmpty());
result->reset();
// The clip established by the element list might be cached based on the last
@@ -336,85 +347,55 @@ void GrReducedClip::ReduceClipStack(const SkClipStack& stack,
// id that lead to the state. Make a conservative guess.
*resultGenID = stack.getTopmostGenID();
+ // TODO: instead devise a way of telling the caller to disregard some or all of the clip bounds.
+ *clipIBounds = GrClip::GetPixelIBounds(queryBounds);
+
if (stack.isWideOpen()) {
- *initialState = kAllIn_InitialState;
- return;
+ return kAllIn_InitialState;
}
-
- // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
- // attempt to compute the tighterBounds.
-
SkClipStack::BoundsType stackBoundsType;
SkRect stackBounds;
bool iior;
stack.getBounds(&stackBounds, &stackBoundsType, &iior);
- const SkIRect* bounds = &queryBounds;
-
- SkRect scalarQueryBounds = SkRect::Make(queryBounds);
+ if (stackBounds.isEmpty() || GrClip::IsOutsideClip(stackBounds, queryBounds)) {
+ bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
+ return insideOut ? kAllIn_InitialState : kAllOut_InitialState;
+ }
if (iior) {
+ // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds.
+ // This should only be true if aa/non-aa status matches among all elements.
SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
- SkRect isectRect;
- if (stackBounds.contains(scalarQueryBounds)) {
- *initialState = GrReducedClip::kAllIn_InitialState;
- *tighterBounds = queryBounds;
- *requiresAA = false;
- } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
- // If the caller asked for tighter integer bounds we may be able to
- // return kAllIn and give the bounds with no elements
- isectRect.roundOut(tighterBounds);
- SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
- if (scalarTighterBounds == isectRect) {
- // the round-out didn't add any area outside the clip rect.
- *requiresAA = false;
- *initialState = GrReducedClip::kAllIn_InitialState;
- return;
- }
- *initialState = kAllOut_InitialState;
- // iior should only be true if aa/non-aa status matches among all elements.
- SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
- bool doAA = iter.prev()->isAA();
- result->addToHead(isectRect, SkRegion::kReplace_Op, doAA);
- *requiresAA = doAA;
- } else {
- *initialState = kAllOut_InitialState;
- *requiresAA = false;
+ SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
+ if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) {
+ // The clip is a non-aa rect. This is the one spot where we can actually implement the
+ // clip (using clipIBounds) rather than just telling the caller what it should be.
+ stackBounds.round(clipIBounds);
+ return kAllIn_InitialState;
}
- return;
- } else {
- if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
- if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
- *initialState = kAllOut_InitialState;
- *requiresAA = false;
- return;
- }
- SkIRect stackIBounds;
- stackBounds.roundOut(&stackIBounds);
- if (!tighterBounds->intersect(queryBounds, stackIBounds)) {
- SkASSERT(0);
- tighterBounds->setEmpty();
- }
- bounds = tighterBounds;
- } else {
- if (stackBounds.contains(scalarQueryBounds)) {
- *initialState = kAllOut_InitialState;
- // We know that the bounding box contains all the pixels that are outside the clip,
- // but we don't know that *all* the pixels in the box are outside the clip. So
- // proceed to walking the stack.
- }
- *tighterBounds = queryBounds;
+ if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
+ return kAllIn_InitialState;
}
+
+ // Implement the clip with an AA rect element.
+ result->addToHead(stackBounds, SkRegion::kReplace_Op, true/*doAA*/);
+ *requiresAA = true;
+
+ SkAssertResult(clipIBounds->intersect(GrClip::GetPixelIBounds(stackBounds)));
+ return kAllOut_InitialState;
}
- SkRect scalarBounds = SkRect::Make(*bounds);
+ SkRect tighterQuery = queryBounds;
+ if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
+ // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This new
+ // clip will be enforced by the scissor through clipIBounds.)
+ SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
+ *clipIBounds = GrClip::GetPixelIBounds(tighterQuery);
+ }
// Now that we have determined the bounds to use and filtered out the trivial cases, call the
// helper that actually walks the stack.
- reduced_stack_walker(stack, scalarBounds, result, resultGenID, initialState, requiresAA);
-
- // The list that was computed in this function may be cached based on the gen id of the last
- // element.
- SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
+ return reduced_stack_walker(stack, tighterQuery, *clipIBounds, result, resultGenID, requiresAA);
}
diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h
index da0bae6bfc..780f6f1ff5 100644
--- a/src/gpu/GrReducedClip.h
+++ b/src/gpu/GrReducedClip.h
@@ -21,24 +21,27 @@ public:
};
/**
- * This function takes a clip stack and a query rectangle and it produces a
- * reduced set of SkClipStack::Elements that are equivalent to applying the
- * full stack to the rectangle. The clip stack generation id that represents
- * the list of elements is returned in resultGenID. The initial state of the
- * query rectangle before the first clip element is applied is returned via
- * initialState. The reducer output tighterBounds is a tighter bounds on the
- * clip. tighterBounds will always be contained by queryBounds after return.
- * It is assumed that the caller will not draw outside of tighterBounds.
- * The requiresAA output will indicate whether anti-aliasing is required to
- * process any of the elements in the element list result.
+ * This function produces a reduced set of SkClipStack::Elements that are equivalent to applying
+ * a full clip stack within a specified query rectangle.
+ *
+ * @param stack the clip stack to reduce.
+ * @param queryBounds bounding box of geometry the stack will clip.
+ * @param result populated with a minimal list of elements that implement the clip
+ * within the provided query bounds.
+ * @param resultGenID uniquely identifies the resulting reduced clip.
+ * @param clipIBounds bounding box within which the reduced clip is valid. The caller must
+ * not draw any pixels outside this box. NOTE: this box may be undefined
+ * if no pixels are valid (e.g. empty result, "all out" initial state.)
+ * @param requiresAA indicates whether anti-aliasing is required to process any of the
+ * elements in the element list result. Undefined if the result is empty.
+ * @return the initial clip state within clipIBounds ("all in" or "all out").
*/
- static void ReduceClipStack(const SkClipStack& stack,
- const SkIRect& queryBounds,
- ElementList* result,
- int32_t* resultGenID,
- InitialState* initialState,
- SkIRect* tighterBounds,
- bool* requiresAA);
+ static InitialState ReduceClipStack(const SkClipStack& stack,
+ const SkRect& queryBounds,
+ ElementList* result,
+ int32_t* resultGenID,
+ SkIRect* clipIBounds,
+ bool* requiresAA);
};
#endif
diff --git a/src/gpu/gl/GrGLIRect.h b/src/gpu/gl/GrGLIRect.h
index 44f5280fd6..41ac13b753 100644
--- a/src/gpu/gl/GrGLIRect.h
+++ b/src/gpu/gl/GrGLIRect.h
@@ -54,9 +54,7 @@ struct GrGLIRect {
}
fHeight = height;
- SkASSERT(fLeft >= 0);
SkASSERT(fWidth >= 0);
- SkASSERT(fBottom >= 0);
SkASSERT(fHeight >= 0);
}
diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp
index d2ff5551af..a3a1685eae 100644
--- a/src/utils/SkLua.cpp
+++ b/src/utils/SkLua.cpp
@@ -638,12 +638,12 @@ static int lcanvas_getClipStack(lua_State* L) {
int SkLua::lcanvas_getReducedClipStack(lua_State* L) {
#if SK_SUPPORT_GPU
const SkCanvas* canvas = get_ref<SkCanvas>(L, 1);
- SkIRect queryBounds = canvas->getTopLayerBounds();
+ SkRect queryBounds = SkRect::Make(canvas->getTopLayerBounds());
GrReducedClip::ElementList elements;
- GrReducedClip::InitialState initialState;
int32_t genID;
SkIRect resultBounds;
+ bool requiresAA;
const SkClipStack& stack = *canvas->getClipStack();
@@ -651,9 +651,8 @@ int SkLua::lcanvas_getReducedClipStack(lua_State* L) {
queryBounds,
&elements,
&genID,
- &initialState,
&resultBounds,
- nullptr);
+ &requiresAA);
GrReducedClip::ElementList::Iter iter(elements);
int i = 0;
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index 4d375ee8bc..f6edb8ce5b 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -6,15 +6,18 @@
*/
#include "Test.h"
-#if SK_SUPPORT_GPU
- #include "GrReducedClip.h"
-#endif
#include "SkClipStack.h"
#include "SkPath.h"
#include "SkRandom.h"
#include "SkRect.h"
#include "SkRegion.h"
+#if SK_SUPPORT_GPU
+#include "GrReducedClip.h"
+typedef GrReducedClip::ElementList ElementList;
+typedef GrReducedClip::InitialState InitialState;
+#endif
+
static void test_assign_and_comparison(skiatest::Reporter* reporter) {
SkClipStack s;
bool doAA = false;
@@ -849,41 +852,45 @@ static void test_invfill_diff_bug(skiatest::Reporter* reporter) {
typedef void (*AddElementFunc) (const SkRect& rect,
bool invert,
SkRegion::Op op,
- SkClipStack* stack);
+ SkClipStack* stack,
+ bool doAA);
-static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
+ bool doAA) {
SkScalar rx = rect.width() / 10;
SkScalar ry = rect.height() / 20;
if (invert) {
SkPath path;
path.addRoundRect(rect, rx, ry);
path.setFillType(SkPath::kInverseWinding_FillType);
- stack->clipDevPath(path, op, false);
+ stack->clipDevPath(path, op, doAA);
} else {
SkRRect rrect;
rrect.setRectXY(rect, rx, ry);
- stack->clipDevRRect(rrect, op, false);
+ stack->clipDevRRect(rrect, op, doAA);
}
};
-static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
+ bool doAA) {
if (invert) {
SkPath path;
path.addRect(rect);
path.setFillType(SkPath::kInverseWinding_FillType);
- stack->clipDevPath(path, op, false);
+ stack->clipDevPath(path, op, doAA);
} else {
- stack->clipDevRect(rect, op, false);
+ stack->clipDevRect(rect, op, doAA);
}
};
-static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
+static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack,
+ bool doAA) {
SkPath path;
path.addOval(rect);
if (invert) {
path.setFillType(SkPath::kInverseWinding_FillType);
}
- stack->clipDevPath(path, op, false);
+ stack->clipDevPath(path, op, doAA);
};
static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
@@ -912,7 +919,7 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
static const SkRect kBounds = SkRect::MakeWH(100, 100);
enum {
- kNumTests = 200,
+ kNumTests = 250,
kMinElemsPerTest = 1,
kMaxElemsPerTest = 50,
};
@@ -938,6 +945,8 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
// We want to test inverse fills. However, they are quite rare in practice so don't over do it.
static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
+ static const SkScalar kFractionAntialiased = 0.25;
+
static const AddElementFunc kElementFuncs[] = {
add_rect,
add_round_rect,
@@ -947,9 +956,13 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
SkRandom r;
for (int i = 0; i < kNumTests; ++i) {
+ SkString testCase;
+ testCase.printf("Iteration %d", i);
+
// Randomly generate a clip stack.
SkClipStack stack;
int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
+ bool doAA = r.nextBiasedBool(kFractionAntialiased);
for (int e = 0; e < numElems; ++e) {
SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
if (op == SkRegion::kReplace_Op) {
@@ -963,43 +976,66 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
bool doSave = r.nextBool();
SkSize size = SkSize::Make(
- SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))),
- SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))));
-
- SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)),
- SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))};
-
- SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
+ SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)),
+ SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)));
+
+ SkPoint xy = {r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth),
+ r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight)};
+
+ SkRect rect;
+ if (doAA) {
+ rect.setXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
+ if (GrClip::IsPixelAligned(rect)) {
+ // Don't create an element that may accidentally become not antialiased.
+ rect.outset(0.5f, 0.5f);
+ }
+ SkASSERT(!GrClip::IsPixelAligned(rect));
+ } else {
+ rect.setXYWH(SkScalarFloorToScalar(xy.fX),
+ SkScalarFloorToScalar(xy.fY),
+ SkScalarCeilToScalar(size.fWidth),
+ SkScalarCeilToScalar(size.fHeight));
+ }
bool invert = r.nextBiasedBool(kFractionInverted);
- kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
+ kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack,
+ doAA);
if (doSave) {
stack.save();
}
}
- SkRect inflatedBounds = kBounds;
- inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
- SkIRect inflatedIBounds;
- inflatedBounds.roundOut(&inflatedIBounds);
+ SkRect queryBounds = kBounds;
+ queryBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
- typedef GrReducedClip::ElementList ElementList;
// Get the reduced version of the stack.
ElementList reducedClips;
int32_t reducedGenID;
- GrReducedClip::InitialState initial;
- SkIRect tighterBounds;
+ SkIRect clipIBounds;
bool requiresAA;
- GrReducedClip::ReduceClipStack(stack,
- inflatedIBounds,
- &reducedClips,
- &reducedGenID,
- &initial,
- &tighterBounds,
- &requiresAA);
-
- REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID);
+ InitialState initial = GrReducedClip::ReduceClipStack(stack,
+ queryBounds,
+ &reducedClips,
+ &reducedGenID,
+ &clipIBounds,
+ &requiresAA);
+
+ REPORTER_ASSERT_MESSAGE(reporter, SkClipStack::kInvalidGenID != reducedGenID,
+ testCase.c_str());
+
+ if (!reducedClips.isEmpty()) {
+ SkRect stackBounds;
+ SkClipStack::BoundsType stackBoundsType;
+ stack.getBounds(&stackBounds, &stackBoundsType);
+ if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
+ // Unless GrReducedClip starts doing some heroic tightening of the clip bounds, this
+ // will be true since the stack bounds are completely contained inside the query.
+ REPORTER_ASSERT_MESSAGE(reporter, GrClip::IsInsideClip(clipIBounds, stackBounds),
+ testCase.c_str());
+ }
+ REPORTER_ASSERT_MESSAGE(reporter, requiresAA == doAA, testCase.c_str());
+ }
// Build a new clip stack based on the reduced clip elements
SkClipStack reducedStack;
@@ -1012,18 +1048,16 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
}
// GrReducedClipStack assumes that the final result is clipped to the returned bounds
- reducedStack.clipDevRect(tighterBounds, SkRegion::kIntersect_Op);
- stack.clipDevRect(tighterBounds, SkRegion::kIntersect_Op);
+ reducedStack.clipDevRect(clipIBounds, SkRegion::kIntersect_Op);
+ stack.clipDevRect(clipIBounds, SkRegion::kIntersect_Op);
// convert both the original stack and reduced stack to SkRegions and see if they're equal
SkRegion region;
- set_region_to_stack(stack, inflatedIBounds, &region);
+ set_region_to_stack(stack, clipIBounds, &region);
SkRegion reducedRegion;
- set_region_to_stack(reducedStack, inflatedIBounds, &reducedRegion);
+ set_region_to_stack(reducedStack, clipIBounds, &reducedRegion);
- SkString testCase;
- testCase.printf("Iteration %d", i);
REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str());
}
}
@@ -1039,19 +1073,17 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
SkClipStack stack;
stack.clipDevRect(SkRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op, true);
stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkRegion::kReplace_Op, true);
- SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100);
+ SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
- GrReducedClip::ElementList reducedClips;
+ ElementList reducedClips;
int32_t reducedGenID;
- GrReducedClip::InitialState initial;
SkIRect tightBounds;
bool requiresAA;
GrReducedClip::ReduceClipStack(stack,
- inflatedIBounds,
+ bounds,
&reducedClips,
&reducedGenID,
- &initial,
&tightBounds,
&requiresAA);
@@ -1076,9 +1108,10 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
int32_t genIDD = stack.getTopmostGenID();
-#define XYWH SkIRect::MakeXYWH
+#define IXYWH SkIRect::MakeXYWH
+#define XYWH SkRect::MakeXYWH
- SkIRect stackBounds = XYWH(0, 0, 76, 76);
+ SkIRect stackBounds = IXYWH(0, 0, 76, 76);
// The base test is to test each rect in two ways:
// 1) The box dimensions. (Should reduce to "all in", no elements).
@@ -1089,55 +1122,58 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
// Not passing in tighter bounds is tested for consistency.
static const struct SUPPRESS_VISIBILITY_WARNING {
- SkIRect testBounds;
+ SkRect testBounds;
int reducedClipCount;
int32_t reducedGenID;
- GrReducedClip::InitialState initialState;
- SkIRect tighterBounds; // If this is empty, the query will not pass tighter bounds
+ InitialState initialState;
+ SkIRect clipIRect;
// parameter.
} testCases[] = {
// Rect A.
- { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 0, 25, 25) },
- { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, XYWH(0, 0, 27, 27)},
+ { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(0, 0, 25, 25) },
+ { XYWH(0.1f, 0.1f, 25.1f, 25.1f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(0, 0, 26, 26) },
+ { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, IXYWH(0, 0, 27, 27)},
// Rect B.
- { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 0, 25, 25) },
- { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, XYWH(50, 0, 26, 27) },
+ { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(50, 0, 25, 25) },
+ { XYWH(50, 0, 25.3f, 25.3f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(50, 0, 26, 26) },
+ { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, IXYWH(50, 0, 26, 27) },
// Rect C.
- { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 50, 25, 25) },
- { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, XYWH(0, 50, 27, 26) },
+ { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(0, 50, 25, 25) },
+ { XYWH(0.2f, 50.1f, 25.1f, 25.2f), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(0, 50, 26, 26) },
+ { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, IXYWH(0, 50, 27, 26) },
// Rect D.
- { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 50, 25, 25)},
- { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState, XYWH(50, 50, 26, 26)},
+ { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(50, 50, 25, 25)},
+ { XYWH(50.3f, 50.3f, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, IXYWH(50, 50, 26, 26)},
+ { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState, IXYWH(50, 50, 26, 26)},
// Other tests:
{ XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::kAllOut_InitialState, stackBounds },
// Rect in the middle, touches none.
- { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, XYWH(26, 26, 24, 24) },
+ { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, IXYWH(26, 26, 24, 24) },
// Rect in the middle, touches all the rects. GenID is the last rect.
- { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, XYWH(24, 24, 27, 27) },
+ { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, IXYWH(24, 24, 27, 27) },
};
#undef XYWH
+#undef IXYWH
for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) {
- GrReducedClip::ElementList reducedClips;
+ ElementList reducedClips;
int32_t reducedGenID;
- GrReducedClip::InitialState initial;
- SkIRect tightBounds;
+ SkIRect clipIRect;
bool requiresAA;
- GrReducedClip::ReduceClipStack(stack,
- testCases[i].testBounds,
- &reducedClips,
- &reducedGenID,
- &initial,
- &tightBounds,
- &requiresAA);
+ InitialState initial = GrReducedClip::ReduceClipStack(stack,
+ testCases[i].testBounds,
+ &reducedClips,
+ &reducedGenID,
+ &clipIRect,
+ &requiresAA);
REPORTER_ASSERT(reporter, reducedClips.count() == testCases[i].reducedClipCount);
SkASSERT(reducedClips.count() == testCases[i].reducedClipCount);
@@ -1145,8 +1181,8 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
SkASSERT(reducedGenID == testCases[i].reducedGenID);
REPORTER_ASSERT(reporter, initial == testCases[i].initialState);
SkASSERT(initial == testCases[i].initialState);
- REPORTER_ASSERT(reporter, tightBounds == testCases[i].tighterBounds);
- SkASSERT(tightBounds == testCases[i].tighterBounds);
+ REPORTER_ASSERT(reporter, clipIRect == testCases[i].clipIRect);
+ SkASSERT(clipIRect == testCases[i].clipIRect);
}
}
}
@@ -1155,26 +1191,182 @@ static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) {
SkClipStack stack;
stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op);
stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), SkRegion::kReplace_Op);
- SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100);
+ SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100);
- GrReducedClip::ElementList reducedClips;
+ ElementList reducedClips;
int32_t reducedGenID;
- GrReducedClip::InitialState initial;
SkIRect tightBounds;
bool requiresAA;
// At the time, this would crash.
GrReducedClip::ReduceClipStack(stack,
- inflatedIBounds,
+ bounds,
&reducedClips,
&reducedGenID,
- &initial,
&tightBounds,
&requiresAA);
REPORTER_ASSERT(reporter, 0 == reducedClips.count());
}
+enum class ClipMethod {
+ kSkipDraw,
+ kIgnoreClip,
+ kScissor,
+ kAAElements
+};
+
+static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName,
+ const SkClipStack& stack, const SkMatrix& queryXform,
+ const SkRect& preXformQuery, ClipMethod expectedMethod,
+ int numExpectedElems = 0) {
+ ElementList reducedElems;
+ int32_t reducedGenID;
+ SkIRect clipIBounds;
+ bool requiresAA;
+
+ SkRect queryBounds;
+ queryXform.mapRect(&queryBounds, preXformQuery);
+
+ InitialState initialState = GrReducedClip::ReduceClipStack(stack,
+ queryBounds,
+ &reducedElems,
+ &reducedGenID,
+ &clipIBounds,
+ &requiresAA);
+
+ SkClipStack::BoundsType stackBoundsType;
+ SkRect stackBounds;
+ stack.getBounds(&stackBounds, &stackBoundsType);
+
+ switch (expectedMethod) {
+ case ClipMethod::kSkipDraw:
+ SkASSERT(0 == numExpectedElems);
+ REPORTER_ASSERT_MESSAGE(reporter, reducedElems.isEmpty(), testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, GrReducedClip::kAllOut_InitialState == initialState,
+ testName.c_str());
+ return;
+ case ClipMethod::kIgnoreClip:
+ SkASSERT(0 == numExpectedElems);
+ REPORTER_ASSERT_MESSAGE(reporter, reducedElems.isEmpty(), testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, GrClip::IsInsideClip(clipIBounds, queryBounds),
+ testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, GrReducedClip::kAllIn_InitialState == initialState,
+ testName.c_str());
+ return;
+ case ClipMethod::kScissor: {
+ SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
+ SkASSERT(0 == numExpectedElems);
+ SkIRect expectedScissor;
+ stackBounds.round(&expectedScissor);
+ REPORTER_ASSERT_MESSAGE(reporter, reducedElems.isEmpty(), testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == clipIBounds, testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, GrReducedClip::kAllIn_InitialState == initialState,
+ testName.c_str());
+ return;
+ }
+ case ClipMethod::kAAElements: {
+ SkIRect expectedClipIBounds = GrClip::GetPixelIBounds(queryBounds);
+ if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
+ SkAssertResult(expectedClipIBounds.intersect(GrClip::GetPixelIBounds(stackBounds)));
+ }
+ REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reducedElems.count(),
+ testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == clipIBounds, testName.c_str());
+ REPORTER_ASSERT_MESSAGE(reporter, requiresAA == !reducedElems.isEmpty(),
+ testName.c_str());
+ break;
+ }
+ }
+}
+
+static void test_reduced_clip_stack_aa(skiatest::Reporter* reporter) {
+ constexpr SkScalar IL = 2, IT = 1, IR = 6, IB = 7; // Pixel aligned rect.
+ constexpr SkScalar L = 2.2f, T = 1.7f, R = 5.8f, B = 7.3f; // Generic rect.
+ constexpr SkScalar l = 3.3f, t = 2.8f, r = 4.7f, b = 6.2f; // Small rect contained in R.
+
+ SkRect alignedRect = {IL, IT, IR, IB};
+ SkRect rect = {L, T, R, B};
+ SkRect innerRect = {l, t, r, b};
+
+ SkMatrix m;
+ m.setIdentity();
+
+ constexpr SkScalar kMinScale = 2.0001f;
+ constexpr SkScalar kMaxScale = 3;
+ constexpr int kNumIters = 8;
+
+ SkString name;
+ SkRandom rand;
+
+ for (int i = 0; i < kNumIters; ++i) {
+ // Pixel-aligned rect (iior=true).
+ name.printf("Pixel-aligned rect test, iter %i", i);
+ SkClipStack stack;
+ stack.clipDevRect(alignedRect, SkRegion::kIntersect_Op, true);
+ test_aa_query(reporter, name, stack, m, {IL, IT, IR, IB}, ClipMethod::kIgnoreClip);
+ test_aa_query(reporter, name, stack, m, {IL, IT-1, IR, IT}, ClipMethod::kSkipDraw);
+ test_aa_query(reporter, name, stack, m, {IL, IT, IR, IB}, ClipMethod::kScissor);
+ test_aa_query(reporter, name, stack, m, {IL, IT+2, IR, IB-3}, ClipMethod::kScissor);
+
+ // Rect (iior=true).
+ name.printf("Rect test, iter %i", i);
+ stack.reset();
+ stack.clipDevRect(rect, SkRegion::kIntersect_Op, true);
+ test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kIgnoreClip);
+ test_aa_query(reporter, name, stack, m, {L-.1f, T, L, B}, ClipMethod::kSkipDraw);
+ test_aa_query(reporter, name, stack, m, {L-.1f, T, L+.1f, B}, ClipMethod::kAAElements, 1);
+
+ // Difference rect (iior=false, inside-out bounds).
+ name.printf("Difference rect test, iter %i", i);
+ stack.reset();
+ stack.clipDevRect(rect, SkRegion::kDifference_Op, true);
+ test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kSkipDraw);
+ test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T}, ClipMethod::kIgnoreClip);
+ test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T+.1f}, ClipMethod::kAAElements, 1);
+
+ // Complex clip (iior=false, normal bounds).
+ name.printf("Complex clip test, iter %i", i);
+ stack.reset();
+ stack.clipDevRect(rect, SkRegion::kIntersect_Op, true);
+ stack.clipDevRect(innerRect, SkRegion::kXOR_Op, true);
+ test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
+ test_aa_query(reporter, name, stack, m, {r-.1f, t, R, b}, ClipMethod::kAAElements, 1);
+ test_aa_query(reporter, name, stack, m, {r-.1f, t, R+.1f, b}, ClipMethod::kAAElements, 2);
+ test_aa_query(reporter, name, stack, m, {r, t, R+.1f, b}, ClipMethod::kAAElements, 1);
+ test_aa_query(reporter, name, stack, m, {r, t, R, b}, ClipMethod::kIgnoreClip);
+ test_aa_query(reporter, name, stack, m, {R, T, R+.1f, B}, ClipMethod::kSkipDraw);
+
+ // Complex clip where outer rect is pixel aligned (iior=false, normal bounds).
+ name.printf("Aligned Complex clip test, iter %i", i);
+ stack.reset();
+ stack.clipDevRect(alignedRect, SkRegion::kIntersect_Op, true);
+ stack.clipDevRect(innerRect, SkRegion::kXOR_Op, true);
+ test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw);
+ test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB}, ClipMethod::kAAElements, 1);
+ test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB+.1f}, ClipMethod::kAAElements, 1);
+ test_aa_query(reporter, name, stack, m, {l, b, r, IB+.1f}, ClipMethod::kAAElements, 0);
+ test_aa_query(reporter, name, stack, m, {l, b, r, IB}, ClipMethod::kIgnoreClip);
+ test_aa_query(reporter, name, stack, m, {IL, IB, IR, IB+.1f}, ClipMethod::kSkipDraw);
+
+ // Apply random transforms and try again. This ensures the clip stack reduction is hardened
+ // against FP rounding error.
+ SkScalar sx = rand.nextRangeScalar(kMinScale, kMaxScale);
+ sx = SkScalarFloorToScalar(sx * alignedRect.width()) / alignedRect.width();
+ SkScalar sy = rand.nextRangeScalar(kMinScale, kMaxScale);
+ sy = SkScalarFloorToScalar(sy * alignedRect.height()) / alignedRect.height();
+ SkScalar tx = SkScalarRoundToScalar(sx * alignedRect.x()) - sx * alignedRect.x();
+ SkScalar ty = SkScalarRoundToScalar(sy * alignedRect.y()) - sy * alignedRect.y();
+
+ SkMatrix xform = SkMatrix::MakeScale(sx, sy);
+ xform.postTranslate(tx, ty);
+ xform.mapRect(&alignedRect);
+ xform.mapRect(&rect);
+ xform.mapRect(&innerRect);
+ m.postConcat(xform);
+ }
+}
+
#endif
DEF_TEST(ClipStack, reporter) {
@@ -1226,5 +1418,6 @@ DEF_TEST(ClipStack, reporter) {
test_reduced_clip_stack(reporter);
test_reduced_clip_stack_genid(reporter);
test_reduced_clip_stack_no_aa_crash(reporter);
+ test_reduced_clip_stack_aa(reporter);
#endif
}