Allow GrReducedClip to take non-integer query bounds
authorcsmartdalton <csmartdalton@google.com>
Fri, 22 Jul 2016 15:59:08 +0000 (08:59 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 22 Jul 2016 15:59:08 +0000 (08:59 -0700)
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

include/gpu/GrClip.h
src/gpu/GrClip.cpp
src/gpu/GrClipMaskManager.cpp
src/gpu/GrReducedClip.cpp
src/gpu/GrReducedClip.h
src/gpu/gl/GrGLIRect.h
src/utils/SkLua.cpp
tests/ClipStackTest.cpp

index ab83441f41e28692aca17183ceae27528fb89699..1cb1a2bcf60a9399da4a356ad5941cdb8a82febc 100644 (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;
-    }
-
-    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;
+    }
 };
 
 /**
index dc2208ad4389c883bc8d9d557304f65541be11df..b0c8db3744ba48234326758d49dbb6245f725b20 100644 (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 {
index 6e2a30384415d8d2a8a1769b9c841d2cf3038e8d..67c6f6eac30a547f3acc5967293792e622f5fdd8 100644 (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);
index 26b89369524f0cfe40610a6b7ad8a56efb0b83ea..2940f6a682a6db215667f0361dc18d34c9b1be02 100644 (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 (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);
 }
index da0bae6bfcd95340f2374d85577f14520cd7bbfb..780f6f1ff54ad631c55ff764da770fe77eed59ca 100644 (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
index 44f5280fd69fe370729b7720a382b605dc802356..41ac13b753eecfc996d9110f4c90969593836989 100644 (file)
@@ -54,9 +54,7 @@ struct GrGLIRect {
         }
         fHeight = height;
 
-        SkASSERT(fLeft >= 0);
         SkASSERT(fWidth >= 0);
-        SkASSERT(fBottom >= 0);
         SkASSERT(fHeight >= 0);
     }
 
index d2ff5551af1854095c8cc654014bb03131134f18..a3a1685eae897625dc22b0f722ca081ffe98374d 100644 (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;
index 4d375ee8bcc41fd8a6a344d8088b3f8033147afd..f6edb8ce5b1f773610a9b7e5e827c2cae0c1a873 100644 (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))));
-
-            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
 }