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
This commit is contained in:
csmartdalton 2016-07-22 08:59:08 -07:00 committed by Commit bot
parent 3011e33769
commit cbecb082d1
8 changed files with 503 additions and 287 deletions

View File

@ -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;
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;
}
friend class GrClipMaskManager;
/**
* 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;
}
};
/**

View File

@ -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 {

View File

@ -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);

View File

@ -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 (iior) {
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;
}
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 (stackBounds.isEmpty() || GrClip::IsOutsideClip(stackBounds, queryBounds)) {
bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
return insideOut ? kAllIn_InitialState : kAllOut_InitialState;
}
SkRect scalarBounds = SkRect::Make(*bounds);
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);
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;
}
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 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);
}

View File

@ -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

View File

@ -54,9 +54,7 @@ struct GrGLIRect {
}
fHeight = height;
SkASSERT(fLeft >= 0);
SkASSERT(fWidth >= 0);
SkASSERT(fBottom >= 0);
SkASSERT(fHeight >= 0);
}

View File

@ -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;

View File

@ -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))));
SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)),
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))};
SkPoint xy = {r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth),
r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight)};
SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, 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);
InitialState initial = GrReducedClip::ReduceClipStack(stack,
queryBounds,
&reducedClips,
&reducedGenID,
&clipIBounds,
&requiresAA);
REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID);
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
}