Merge clip rects in GrReducedClip

Merges the intersection of all rect clips into a single clip rect
element. This is also the first step toward better handling of the
subtractive elements.

Bug: skia:
Change-Id: I36bd9c256874917adb68f43e8faddc609e65f37a
Reviewed-on: https://skia-review.googlesource.com/64380
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index 09f5bef..6a71e00 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -160,7 +160,7 @@
     SkMatrix translate;
     translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top()));
 
-    for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
+    for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) {
         const Element* element = iter.get();
 
         SkClipOp op = element->getOp();
@@ -168,7 +168,7 @@
         bool needsStencil = invert ||
                             kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op;
 
-        if (PathNeedsSWRenderer(context, reducedClip.ibounds(), hasUserStencilSettings,
+        if (PathNeedsSWRenderer(context, reducedClip.scissor(), hasUserStencilSettings,
                                 renderTargetContext, translate, element, nullptr, needsStencil)) {
             return true;
         }
@@ -268,8 +268,8 @@
     const GrReducedClip reducedClip(*fStack, devBounds,
                                     renderTargetContext->priv().maxWindowRectangles());
 
-    if (reducedClip.hasIBounds() && !GrClip::IsInsideClip(reducedClip.ibounds(), devBounds)) {
-        out->addScissor(reducedClip.ibounds(), bounds);
+    if (reducedClip.hasScissor() && !GrClip::IsInsideClip(reducedClip.scissor(), devBounds)) {
+        out->addScissor(reducedClip.scissor(), bounds);
     }
 
     if (!reducedClip.windowRectangles().empty()) {
@@ -277,16 +277,16 @@
                                  GrWindowRectsState::Mode::kExclusive);
     }
 
-    if (reducedClip.elements().isEmpty()) {
+    if (reducedClip.maskElements().isEmpty()) {
         return InitialState::kAllIn == reducedClip.initialState();
     }
 
 #ifdef SK_DEBUG
-    SkASSERT(reducedClip.hasIBounds());
+    SkASSERT(reducedClip.hasScissor());
     SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(),
                                         renderTargetContext->height());
-    const SkIRect& clipIBounds = reducedClip.ibounds();
-    SkASSERT(rtIBounds.contains(clipIBounds)); // Mask shouldn't be larger than the RT.
+    const SkIRect& scissor = reducedClip.scissor();
+    SkASSERT(rtIBounds.contains(scissor)); // Mask shouldn't be larger than the RT.
 #endif
 
     bool avoidStencilBuffers = context->caps()->avoidStencilBuffers();
@@ -299,7 +299,7 @@
     // when drawing rounded div borders. This could probably be tuned based on a
     // configuration's relative costs of switching RTs to generate a mask vs
     // longer shaders.
-    if (reducedClip.elements().count() <= kMaxAnalyticElements) {
+    if (reducedClip.maskElements().count() <= kMaxAnalyticElements) {
         // When there are multiple samples we want to do per-sample clipping, not compute a
         // fractional pixel coverage.
         bool disallowAnalyticAA =
@@ -311,8 +311,8 @@
             disallowAnalyticAA = useHWAA || hasUserStencilSettings;
         }
         std::unique_ptr<GrFragmentProcessor> clipFP;
-        if ((reducedClip.requiresAA() || avoidStencilBuffers) &&
-            get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA, devBounds,
+        if ((reducedClip.maskRequiresAA() || avoidStencilBuffers) &&
+            get_analytic_clip_processor(reducedClip.maskElements(), disallowAnalyticAA, devBounds,
                                         &clipFP)) {
             out->addCoverageFP(std::move(clipFP));
             return true;
@@ -320,7 +320,7 @@
     }
 
     // If the stencil buffer is multisampled we can use it to do everything.
-    if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.requiresAA()) ||
+    if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.maskRequiresAA()) ||
         avoidStencilBuffers) {
         sk_sp<GrTextureProxy> result;
         if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
@@ -334,7 +334,7 @@
         if (result) {
             // The mask's top left coord should be pinned to the rounded-out top left corner of
             // the clip's device space bounds.
-            out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.ibounds()));
+            out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.scissor()));
             return true;
         }
 
@@ -351,12 +351,12 @@
     // This relies on the property that a reduced sub-rect of the last clip will contain all the
     // relevant window rectangles that were in the last clip. This subtle requirement will go away
     // after clipping is overhauled.
-    if (renderTargetContext->priv().mustRenderClip(reducedClip.elementsGenID(),
-                                                   reducedClip.ibounds())) {
+    if (renderTargetContext->priv().mustRenderClip(reducedClip.maskGenID(),
+                                                   reducedClip.scissor())) {
         reducedClip.drawStencilClipMask(context, renderTargetContext);
-        renderTargetContext->priv().setLastClip(reducedClip.elementsGenID(), reducedClip.ibounds());
+        renderTargetContext->priv().setLastClip(reducedClip.maskGenID(), reducedClip.scissor());
     }
-    out->addStencilClip(reducedClip.elementsGenID());
+    out->addStencilClip(reducedClip.maskGenID());
     return true;
 }
 
@@ -391,7 +391,7 @@
                                                            const GrReducedClip& reducedClip) const {
     GrResourceProvider* resourceProvider = context->resourceProvider();
     GrUniqueKey key;
-    create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
+    create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(), &key);
 
     sk_sp<GrTextureProxy> proxy(resourceProvider->findOrCreateProxyByUniqueKey(
                                                                 key, kBottomLeft_GrSurfaceOrigin));
@@ -420,7 +420,7 @@
 
     SkASSERT(result->origin() == kBottomLeft_GrSurfaceOrigin);
     resourceProvider->assignUniqueKeyToProxy(key, result.get());
-    add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
+    add_invalidate_on_pop_message(*fStack, reducedClip.maskGenID(), key);
 
     return result;
 }
@@ -436,19 +436,19 @@
 class ClipMaskData {
 public:
     ClipMaskData(const GrReducedClip& reducedClip)
-            : fMaskBounds(reducedClip.ibounds())
+            : fScissor(reducedClip.scissor())
             , fInitialState(reducedClip.initialState()) {
-        for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
+        for (ElementList::Iter iter(reducedClip.maskElements()); iter.get(); iter.next()) {
             fElements.addToTail(*iter.get());
         }
     }
 
-    const SkIRect& ibounds() const { return fMaskBounds; }
+    const SkIRect& scissor() const { return fScissor; }
     InitialState initialState() const { return fInitialState; }
     const ElementList& elements() const { return fElements; }
 
 private:
-    SkIRect fMaskBounds;
+    SkIRect fScissor;
     InitialState fInitialState;
     ElementList fElements;
 };
@@ -456,10 +456,10 @@
 }
 
 static void draw_clip_elements_to_mask_helper(GrSWMaskHelper& helper, const ElementList& elements,
-                                              const SkIRect& ibounds, InitialState initialState) {
+                                              const SkIRect& scissor, InitialState initialState) {
     // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
     SkMatrix translate;
-    translate.setTranslate(SkIntToScalar(-ibounds.left()), SkIntToScalar(-ibounds.top()));
+    translate.setTranslate(SkIntToScalar(-scissor.left()), SkIntToScalar(-scissor.top()));
 
     helper.clear(InitialState::kAllIn == initialState ? 0xFF : 0x00);
 
@@ -474,7 +474,7 @@
             // but leave the pixels inside the geometry alone. For reverse difference we invert all
             // the pixels before clearing the ones outside the geometry.
             if (kReverseDifference_SkClipOp == op) {
-                SkRect temp = SkRect::Make(ibounds);
+                SkRect temp = SkRect::Make(scissor);
                 // invert the entire scene
                 helper.drawRect(temp, translate, SkRegion::kXOR_Op, GrAA::kNo, 0xFF);
             }
@@ -503,7 +503,7 @@
         GrContext* context, const GrReducedClip& reducedClip,
         GrRenderTargetContext* renderTargetContext) const {
     GrUniqueKey key;
-    create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
+    create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(), &key);
 
     sk_sp<GrTextureProxy> proxy(context->resourceProvider()->findOrCreateProxyByUniqueKey(
                                                                   key, kTopLeft_GrSurfaceOrigin));
@@ -536,7 +536,7 @@
             GrSWMaskHelper helper(uploaderRaw->getPixels());
             if (helper.init(maskSpaceIBounds)) {
                 draw_clip_elements_to_mask_helper(helper, uploaderRaw->data().elements(),
-                                                  uploaderRaw->data().ibounds(),
+                                                  uploaderRaw->data().scissor(),
                                                   uploaderRaw->data().initialState());
             } else {
                 SkDEBUGFAIL("Unable to allocate SW clip mask.");
@@ -552,7 +552,7 @@
             return nullptr;
         }
 
-        draw_clip_elements_to_mask_helper(helper, reducedClip.elements(), reducedClip.ibounds(),
+        draw_clip_elements_to_mask_helper(helper, reducedClip.maskElements(), reducedClip.scissor(),
                                           reducedClip.initialState());
 
         proxy = helper.toTextureProxy(context, SkBackingFit::kApprox);
@@ -560,6 +560,6 @@
 
     SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
     context->resourceProvider()->assignUniqueKeyToProxy(key, proxy.get());
-    add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
+    add_invalidate_on_pop_message(*fStack, reducedClip.maskGenID(), key);
     return proxy;
 }
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 22316c5..29f4830 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -21,8 +21,6 @@
 #include "GrUserStencilSettings.h"
 #include "SkClipOpPriv.h"
 
-typedef SkClipStack::Element Element;
-
 /**
  * There are plenty of optimizations that could be added here. Maybe flips could be folded into
  * earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
@@ -33,7 +31,8 @@
 GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds,
                              int maxWindowRectangles) {
     SkASSERT(!queryBounds.isEmpty());
-    fHasIBounds = false;
+    fHasScissor = false;
+    fAAClipRectGenID = SK_InvalidGenID;
 
     if (stack.isWideOpen()) {
         fInitialState = InitialState::kAllIn;
@@ -57,11 +56,10 @@
         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 fIBounds) rather than just telling the caller what it should be.
-            stackBounds.round(&fIBounds);
-            fHasIBounds = true;
-            fInitialState = fIBounds.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn;
+            // The clip is a non-aa rect. Here we just implement the entire thing using fScissor.
+            stackBounds.round(&fScissor);
+            fHasScissor = true;
+            fInitialState = fScissor.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn;
             return;
         }
         if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
@@ -71,42 +69,49 @@
 
         SkRect tightBounds;
         SkAssertResult(tightBounds.intersect(stackBounds, queryBounds));
-        fIBounds = GrClip::GetPixelIBounds(tightBounds);
-        if (fIBounds.isEmpty()) {
+        fScissor = GrClip::GetPixelIBounds(tightBounds);
+        if (fScissor.isEmpty()) {
             fInitialState = InitialState::kAllOut;
             return;
         }
-        fHasIBounds = true;
+        fHasScissor = true;
 
-        // Implement the clip with an AA rect element.
-        fElements.addToHead(stackBounds, SkMatrix::I(), kReplace_SkClipOp, true /*doAA*/);
-        fElementsGenID = stack.getTopmostGenID();
-        fRequiresAA = true;
+        fAAClipRect = stackBounds;
+        fAAClipRectGenID = stack.getTopmostGenID();
+        SkASSERT(SK_InvalidGenID != fAAClipRectGenID);
 
-        fInitialState = InitialState::kAllOut;
-        return;
+        fInitialState = InitialState::kAllIn;
+    } else {
+        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.)
+            SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
+        }
+
+        fScissor = GrClip::GetPixelIBounds(tighterQuery);
+        if (fScissor.isEmpty()) {
+            fInitialState = InitialState::kAllOut;
+            return;
+        }
+        fHasScissor = true;
+
+        // Now that we have determined the bounds to use and filtered out the trivial cases, call the
+        // helper that actually walks the stack.
+        this->walkStack(stack, tighterQuery, maxWindowRectangles);
     }
 
-    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 fIBounds.)
-        SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
-    }
-
-    fIBounds = GrClip::GetPixelIBounds(tighterQuery);
-    if (fIBounds.isEmpty()) {
-        fInitialState = InitialState::kAllOut;
-        return;
-    }
-    fHasIBounds = true;
-
-    // Now that we have determined the bounds to use and filtered out the trivial cases, call the
-    // helper that actually walks the stack.
-    this->walkStack(stack, tighterQuery, maxWindowRectangles);
-
-    if (fWindowRects.count() < maxWindowRectangles) {
-        this->addInteriorWindowRectangles(maxWindowRectangles);
+    if (SK_InvalidGenID != fAAClipRectGenID) { // Is there an AA clip rect?
+        if (fMaskElements.isEmpty()) {
+            // Use a replace since it is faster than intersect.
+            fMaskElements.addToHead(fAAClipRect, SkMatrix::I(), kReplace_SkClipOp, true /*doAA*/);
+            fInitialState = InitialState::kAllOut;
+        } else {
+            fMaskElements.addToTail(fAAClipRect, SkMatrix::I(), kIntersect_SkClipOp, true /*doAA*/);
+        }
+        fMaskRequiresAA = true;
+        fMaskGenID = fAAClipRectGenID;
+        fAAClipRectGenID = SK_InvalidGenID;
     }
 }
 
@@ -170,11 +175,12 @@
                         skippable = true;
                     } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
                         skippable = true;
-                    } else if (fWindowRects.count() < maxWindowRectangles && !embiggens &&
-                               !element->isAA() &&
-                               Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-                        this->addWindowRectangle(element->getDeviceSpaceRect(), false);
-                        skippable = true;
+                    } else if (!embiggens) {
+                        ClipResult result = this->clipOutsideElement(element, maxWindowRectangles);
+                        if (ClipResult::kMadeEmpty == result) {
+                            return;
+                        }
+                        skippable = (ClipResult::kClipped == result);
                     }
                 }
                 if (!skippable) {
@@ -198,16 +204,12 @@
                     } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
                         initialTriState = InitialTriState::kAllOut;
                         skippable = true;
-                    } else if (!embiggens && !element->isAA() &&
-                               Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-                        // fIBounds and queryBounds have already acccounted for this element via
-                        // clip stack bounds; here we just apply the non-aa rounding effect.
-                        SkIRect nonaaRect;
-                        element->getDeviceSpaceRect().round(&nonaaRect);
-                        if (!this->intersectIBounds(nonaaRect)) {
+                    } else if (!embiggens) {
+                        ClipResult result = this->clipInsideElement(element);
+                        if (ClipResult::kMadeEmpty == result) {
                             return;
                         }
-                        skippable = true;
+                        skippable = (ClipResult::kClipped == result);
                     }
                 }
                 if (!skippable) {
@@ -304,17 +306,15 @@
                     } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
                         initialTriState = InitialTriState::kAllOut;
                         skippable = true;
-                    } else if (!embiggens && !element->isAA() &&
-                               Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-                        // fIBounds and queryBounds have already acccounted for this element via
-                        // clip stack bounds; here we just apply the non-aa rounding effect.
-                        SkIRect nonaaRect;
-                        element->getDeviceSpaceRect().round(&nonaaRect);
-                        if (!this->intersectIBounds(nonaaRect)) {
+                    } else if (!embiggens) {
+                        ClipResult result = this->clipInsideElement(element);
+                        if (ClipResult::kMadeEmpty == result) {
                             return;
                         }
-                        initialTriState = InitialTriState::kAllIn;
-                        skippable = true;
+                        if (ClipResult::kClipped == result) {
+                            initialTriState = InitialTriState::kAllIn;
+                            skippable = true;
+                        }
                     }
                 }
                 if (!skippable) {
@@ -327,19 +327,19 @@
                 break;
         }
         if (!skippable) {
-            if (0 == fElements.count()) {
+            if (fMaskElements.isEmpty()) {
                 // This will be the last element. Record the stricter genID.
-                fElementsGenID = element->getGenID();
+                fMaskGenID = element->getGenID();
             }
 
             // if it is a flip, change it to a bounds-filling rect
             if (isFlip) {
                 SkASSERT(kXOR_SkClipOp == element->getOp() ||
                          kReverseDifference_SkClipOp == element->getOp());
-                fElements.addToHead(SkRect::Make(fIBounds), SkMatrix::I(),
-                                    kReverseDifference_SkClipOp, false);
+                fMaskElements.addToHead(SkRect::Make(fScissor), SkMatrix::I(),
+                                        kReverseDifference_SkClipOp, false);
             } else {
-                Element* newElement = fElements.addToHead(*element);
+                Element* newElement = fMaskElements.addToHead(*element);
                 if (newElement->isAA()) {
                     ++numAAElements;
                 }
@@ -362,10 +362,10 @@
 
     if ((InitialTriState::kAllOut == initialTriState && !embiggens) ||
         (InitialTriState::kAllIn == initialTriState && !emsmallens)) {
-        fElements.reset();
+        fMaskElements.reset();
         numAAElements = 0;
     } else {
-        Element* element = fElements.headIter().get();
+        Element* element = fMaskElements.headIter().get();
         while (element) {
             bool skippable = false;
             switch (element->getOp()) {
@@ -429,47 +429,80 @@
                 if (element->isAA()) {
                     --numAAElements;
                 }
-                fElements.popHead();
-                element = fElements.headIter().get();
+                fMaskElements.popHead();
+                element = fMaskElements.headIter().get();
             }
         }
     }
-    fRequiresAA = numAAElements > 0;
+    fMaskRequiresAA = numAAElements > 0;
 
     SkASSERT(InitialTriState::kUnknown != initialTriState);
     fInitialState = static_cast<GrReducedClip::InitialState>(initialTriState);
 }
 
-static bool element_is_pure_subtract(SkClipOp op) {
-    SkASSERT(static_cast<int>(op) >= 0);
-    return static_cast<int>(op) <= static_cast<int>(kIntersect_SkClipOp);
+GrReducedClip::ClipResult GrReducedClip::clipInsideElement(const Element* element) {
+    SkIRect elementIBounds;
+    if (!element->isAA()) {
+        element->getBounds().round(&elementIBounds);
+    } else {
+        elementIBounds = GrClip::GetPixelIBounds(element->getBounds());
+    }
+    SkASSERT(fHasScissor);
+    if (!fScissor.intersect(elementIBounds)) {
+        this->makeEmpty();
+        return ClipResult::kMadeEmpty;
+    }
 
-    GR_STATIC_ASSERT(0 == static_cast<int>(kDifference_SkClipOp));
-    GR_STATIC_ASSERT(1 == static_cast<int>(kIntersect_SkClipOp));
+    switch (element->getDeviceSpaceType()) {
+        case Element::DeviceSpaceType::kEmpty:
+            return ClipResult::kMadeEmpty;
+
+        case Element::DeviceSpaceType::kRect:
+            SkASSERT(element->getBounds() == element->getDeviceSpaceRect());
+            if (element->isAA()) {
+                if (SK_InvalidGenID == fAAClipRectGenID) { // No AA clip rect yet?
+                    fAAClipRect = element->getDeviceSpaceRect();
+                    // fAAClipRectGenID is the value we should use for fMaskGenID if we end up
+                    // moving the AA clip rect into the mask. The mask GenID is simply the topmost
+                    // element's GenID. And since we walk the stack backwards, this means it's just
+                    // the first element we don't skip during our walk.
+                    fAAClipRectGenID = fMaskElements.isEmpty() ? element->getGenID() : fMaskGenID;
+                    SkASSERT(SK_InvalidGenID != fAAClipRectGenID);
+                } else if (!fAAClipRect.intersect(element->getDeviceSpaceRect())) {
+                    this->makeEmpty();
+                    return ClipResult::kMadeEmpty;
+                }
+            }
+            return ClipResult::kClipped;
+
+        case Element::DeviceSpaceType::kRRect:
+        case Element::DeviceSpaceType::kPath:
+            return ClipResult::kNotClipped;
+    }
+
+    SK_ABORT("Unexpected DeviceSpaceType");
+    return ClipResult::kNotClipped;
 }
 
-void GrReducedClip::addInteriorWindowRectangles(int maxWindowRectangles) {
-    SkASSERT(fWindowRects.count() < maxWindowRectangles);
-    // Walk backwards through the element list and add window rectangles to the interiors of
-    // "difference" elements. Quit if we encounter an element that may grow the clip.
-    ElementList::Iter iter(fElements, ElementList::Iter::kTail_IterStart);
-    for (; iter.get() && element_is_pure_subtract(iter.get()->getOp()); iter.prev()) {
-        const Element* element = iter.get();
-        if (kDifference_SkClipOp != element->getOp()) {
-            continue;
-        }
+GrReducedClip::ClipResult GrReducedClip::clipOutsideElement(const Element* element,
+                                                            int maxWindowRectangles) {
+    if (fWindowRects.count() >= maxWindowRectangles) {
+        return ClipResult::kNotClipped;
+    }
 
-        if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-            SkASSERT(element->isAA());
-            this->addWindowRectangle(element->getDeviceSpaceRect(), true);
-            if (fWindowRects.count() >= maxWindowRectangles) {
-                return;
-            }
-            continue;
-        }
+    switch (element->getDeviceSpaceType()) {
+        case Element::DeviceSpaceType::kEmpty:
+            return ClipResult::kMadeEmpty;
 
-        if (Element::DeviceSpaceType::kRRect == element->getDeviceSpaceType()) {
-            // For round rects we add two overlapping windows in the shape of a plus.
+        case Element::DeviceSpaceType::kRect:
+            // Clip out the inside of every rect. We won't be able to entirely skip the AA ones, but
+            // it saves processing time.
+            this->addWindowRectangle(element->getDeviceSpaceRect(), element->isAA());
+            return !element->isAA() ? ClipResult::kClipped : ClipResult::kNotClipped;
+
+        case Element::DeviceSpaceType::kRRect: {
+            // Clip out the interiors of round rects with two window rectangles in the shape of a
+            // plus. It doesn't allow us to skip the clip element, but still saves processing time.
             const SkRRect& clipRRect = element->getDeviceSpaceRRect();
             SkVector insetTL = clipRRect.radii(SkRRect::kUpperLeft_Corner);
             SkVector insetBR = clipRRect.radii(SkRRect::kLowerRight_Corner);
@@ -484,25 +517,28 @@
             const SkRect& bounds = clipRRect.getBounds();
             if (insetTL.x() + insetBR.x() >= bounds.width() ||
                 insetTL.y() + insetBR.y() >= bounds.height()) {
-                continue; // The interior "plus" is empty.
+                return ClipResult::kNotClipped; // The interior "plus" is empty.
             }
 
             SkRect horzRect = SkRect::MakeLTRB(bounds.left(), bounds.top() + insetTL.y(),
                                                bounds.right(), bounds.bottom() - insetBR.y());
             this->addWindowRectangle(horzRect, element->isAA());
             if (fWindowRects.count() >= maxWindowRectangles) {
-                return;
+                return ClipResult::kNotClipped;
             }
 
             SkRect vertRect = SkRect::MakeLTRB(bounds.left() + insetTL.x(), bounds.top(),
                                                bounds.right() - insetBR.x(), bounds.bottom());
             this->addWindowRectangle(vertRect, element->isAA());
-            if (fWindowRects.count() >= maxWindowRectangles) {
-                return;
-            }
-            continue;
+            return ClipResult::kNotClipped;
         }
+
+        case Element::DeviceSpaceType::kPath:
+            return ClipResult::kNotClipped;
     }
+
+    SK_ABORT("Unexpected DeviceSpaceType");
+    return ClipResult::kNotClipped;
 }
 
 inline void GrReducedClip::addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA) {
@@ -517,17 +553,12 @@
     }
 }
 
-inline bool GrReducedClip::intersectIBounds(const SkIRect& irect) {
-    SkASSERT(fHasIBounds);
-    if (!fIBounds.intersect(irect)) {
-        fHasIBounds = false;
-        fWindowRects.reset();
-        fElements.reset();
-        fRequiresAA = false;
-        fInitialState = InitialState::kAllOut;
-        return false;
-    }
-    return true;
+void GrReducedClip::makeEmpty() {
+    fHasScissor = false;
+    fAAClipRectGenID = SK_InvalidGenID;
+    fWindowRects.reset();
+    fMaskElements.reset();
+    fInitialState = InitialState::kAllOut;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -540,10 +571,10 @@
                             const SkClipStack::Element* element) {
     GrAA aa = GrBoolToAA(element->isAA());
     switch (element->getDeviceSpaceType()) {
-        case Element::DeviceSpaceType::kEmpty:
+        case SkClipStack::Element::DeviceSpaceType::kEmpty:
             SkDEBUGFAIL("Should never get here with an empty element.");
             break;
-        case Element::DeviceSpaceType::kRect:
+        case SkClipStack::Element::DeviceSpaceType::kRect:
             return rtc->priv().drawAndStencilRect(clip, ss, (SkRegion::Op)element->getOp(),
                                                   element->isInverseFilled(), aa, viewMatrix,
                                                   element->getDeviceSpaceRect());
@@ -572,10 +603,10 @@
                          const SkClipStack::Element* element) {
     // TODO: Draw rrects directly here.
     switch (element->getDeviceSpaceType()) {
-        case Element::DeviceSpaceType::kEmpty:
+        case SkClipStack::Element::DeviceSpaceType::kEmpty:
             SkDEBUGFAIL("Should never get here with an empty element.");
             break;
-        case Element::DeviceSpaceType::kRect:
+        case SkClipStack::Element::DeviceSpaceType::kRect:
             rtc->drawRect(clip, std::move(paint), aa, viewMatrix, element->getDeviceSpaceRect());
             break;
         default: {
@@ -594,10 +625,10 @@
 bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const {
     // The texture may be larger than necessary, this rect represents the part of the texture
     // we populate with a rasterization of the clip.
-    GrFixedClip clip(SkIRect::MakeWH(fIBounds.width(), fIBounds.height()));
+    GrFixedClip clip(SkIRect::MakeWH(fScissor.width(), fScissor.height()));
 
     if (!fWindowRects.empty()) {
-        clip.setWindowRectangles(fWindowRects.makeOffset(-fIBounds.left(), -fIBounds.top()),
+        clip.setWindowRectangles(fWindowRects.makeOffset(-fScissor.left(), -fScissor.top()),
                                  GrWindowRectsState::Mode::kExclusive);
     }
 
@@ -608,10 +639,10 @@
 
     // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
     SkMatrix translate;
-    translate.setTranslate(SkIntToScalar(-fIBounds.left()), SkIntToScalar(-fIBounds.top()));
+    translate.setTranslate(SkIntToScalar(-fScissor.left()), SkIntToScalar(-fScissor.top()));
 
     // walk through each clip element and perform its set op
-    for (ElementList::Iter iter(fElements); iter.get(); iter.next()) {
+    for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) {
         const Element* element = iter.get();
         SkRegion::Op op = (SkRegion::Op)element->getOp();
         GrAA aa = GrBoolToAA(element->isAA());
@@ -643,7 +674,7 @@
                      0xffff>()
             );
             if (!rtc->priv().drawAndStencilRect(clip, &kDrawOutsideElement, op, !invert, GrAA::kNo,
-                                                translate, SkRect::Make(fIBounds))) {
+                                                translate, SkRect::Make(fScissor))) {
                 return false;
             }
         } else {
@@ -703,7 +734,7 @@
 bool GrReducedClip::drawStencilClipMask(GrContext* context,
                                         GrRenderTargetContext* renderTargetContext) const {
     // We set the current clip to the bounds so that our recursive draws are scissored to them.
-    StencilClip stencilClip(fIBounds, this->elementsGenID());
+    StencilClip stencilClip(fScissor, this->maskGenID());
 
     if (!fWindowRects.empty()) {
         stencilClip.setWindowRectangles(fWindowRects, GrWindowRectsState::Mode::kExclusive);
@@ -713,7 +744,7 @@
     renderTargetContext->priv().clearStencilClip(stencilClip.fixedClip(), initialState);
 
     // walk through each clip element and perform its set op with the existing clip.
-    for (ElementList::Iter iter(fElements); iter.get(); iter.next()) {
+    for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) {
         const Element* element = iter.get();
         GrAAType aaType = GrAAType::kNone;
         if (element->isAA() && GrFSAAType::kNone != renderTargetContext->fsaaType()) {
@@ -843,7 +874,7 @@
                 // The view matrix is setup to do clip space -> stencil space translation, so
                 // draw rect in clip space.
                 renderTargetContext->priv().stencilRect(stencilClip, *pass, aaType, SkMatrix::I(),
-                                                        SkRect::Make(fIBounds));
+                                                        SkRect::Make(fScissor));
             }
         }
     }
diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h
index c9cce36..0746439 100644
--- a/src/gpu/GrReducedClip.h
+++ b/src/gpu/GrReducedClip.h
@@ -21,47 +21,54 @@
  */
 class SK_API GrReducedClip {
 public:
+    using Element = SkClipStack::Element;
+    using ElementList = SkTLList<SkClipStack::Element, 16>;
+
     GrReducedClip(const SkClipStack&, const SkRect& queryBounds, int maxWindowRectangles = 0);
 
     /**
-     * If hasIBounds() is true, this is the bounding box within which the clip elements are valid.
-     * The caller must not modify any pixels outside this box. Undefined if hasIBounds() is false.
+     * If hasScissor() is true, the clip mask is not valid outside this rect and the caller must
+     * enforce this scissor during draw.
      */
-    const SkIRect& ibounds() const { SkASSERT(fHasIBounds); return fIBounds; }
-    int left() const { return this->ibounds().left(); }
-    int top() const { return this->ibounds().top(); }
-    int width() const { return this->ibounds().width(); }
-    int height() const { return this->ibounds().height(); }
+    const SkIRect& scissor() const { SkASSERT(fHasScissor); return fScissor; }
+    int left() const { return this->scissor().left(); }
+    int top() const { return this->scissor().top(); }
+    int width() const { return this->scissor().width(); }
+    int height() const { return this->scissor().height(); }
 
     /**
-     * Indicates whether ibounds() are defined. They will always be defined if the elements() are
+     * Indicates whether scissor() is defined. It will always be defined if the maskElements() are
      * nonempty.
      */
-    bool hasIBounds() const { return fHasIBounds; }
+    bool hasScissor() const { return fHasScissor; }
 
     /**
-     * If nonempty, this is a set of "exclusive" windows within which the clip elements are NOT
-     * valid. The caller must not modify any pixels inside these windows.
+     * If nonempty, the clip mask is not valid inside these windows and the caller must clip them
+     * out using the window rectangles GPU extension.
      */
     const GrWindowRectangles& windowRectangles() const { return fWindowRects; }
 
-    typedef SkTLList<SkClipStack::Element, 16> ElementList;
+    /**
+     * An ordered list of clip elements that could not be skipped or implemented by other means. If
+     * nonempty, the caller must create an alpha and/or stencil mask for these elements and apply it
+     * during draw.
+     */
+    const ElementList& maskElements() const { return fMaskElements; }
 
     /**
-     * Populated with a minimal list of elements required to fully implement the clip.
+     * If maskElements() are nonempty, uniquely identifies the region of the clip mask that falls
+     * inside of scissor().
+     * NOTE: since clip elements might fall outside the query bounds, different regions of the same
+     * clip stack might have more or less restrictive IDs.
+     * FIXME: this prevents us from reusing a sub-rect of a perfectly good mask when that rect has
+     * been assigned a less restrictive ID.
      */
-    const ElementList& elements() const { return fElements; }
+    uint32_t maskGenID() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskGenID; }
 
     /**
-     * If elements() are nonempty, uniquely identifies the list of elements within ibounds().
-     * Otherwise undefined.
+     * Indicates whether antialiasing is required to process any of the mask elements.
      */
-    uint32_t elementsGenID() const { SkASSERT(!fElements.isEmpty()); return fElementsGenID; }
-
-    /**
-     * Indicates whether antialiasing is required to process any of the clip elements.
-     */
-    bool requiresAA() const { return fRequiresAA; }
+    bool maskRequiresAA() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskRequiresAA; }
 
     enum class InitialState : bool {
         kAllIn,
@@ -75,16 +82,32 @@
 
 private:
     void walkStack(const SkClipStack&, const SkRect& queryBounds, int maxWindowRectangles);
-    void addInteriorWindowRectangles(int maxWindowRectangles);
-    void addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA);
-    bool intersectIBounds(const SkIRect&);
 
-    SkIRect              fIBounds;
-    bool                 fHasIBounds;
+    enum class ClipResult {
+        kNotClipped,
+        kClipped,
+        kMadeEmpty
+    };
+
+    // Clips the the given element's interior out of the final clip.
+    // NOTE: do not call for elements followed by ops that can grow the clip.
+    ClipResult clipInsideElement(const Element* element);
+
+    // Clips the the given element's exterior out of the final clip.
+    // NOTE: do not call for elements followed by ops that can grow the clip.
+    ClipResult clipOutsideElement(const Element* element, int maxWindowRectangles);
+
+    void addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA);
+    void makeEmpty();
+
+    SkIRect              fScissor;
+    bool                 fHasScissor;
+    SkRect               fAAClipRect;
+    uint32_t             fAAClipRectGenID; // GenID the mask will have if includes the AA clip rect.
     GrWindowRectangles   fWindowRects;
-    ElementList          fElements;
-    uint32_t             fElementsGenID;
-    bool                 fRequiresAA;
+    ElementList          fMaskElements;
+    uint32_t             fMaskGenID;
+    bool                 fMaskRequiresAA;
     InitialState         fInitialState;
 };
 
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index 8984027..5543623 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -1031,23 +1031,16 @@
         const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, queryBounds);
 
         REPORTER_ASSERT_MESSAGE(reporter,
-                                reduced->elements().isEmpty() ||
-                                SkClipStack::kInvalidGenID != reduced->elementsGenID(),
+                                reduced->maskElements().isEmpty() ||
+                                SkClipStack::kInvalidGenID != reduced->maskGenID(),
                                 testCase.c_str());
 
-        if (!reduced->elements().isEmpty()) {
-            REPORTER_ASSERT_MESSAGE(reporter, reduced->hasIBounds(), testCase.c_str());
+        if (!reduced->maskElements().isEmpty()) {
+            REPORTER_ASSERT_MESSAGE(reporter, reduced->hasScissor(), testCase.c_str());
             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(reduced->ibounds(), stackBounds),
-                                        testCase.c_str());
-            }
-            REPORTER_ASSERT_MESSAGE(reporter, reduced->requiresAA() == doAA, testCase.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, reduced->maskRequiresAA() == doAA, testCase.c_str());
         }
 
         // Build a new clip stack based on the reduced clip elements
@@ -1056,22 +1049,22 @@
             // whether the result is bounded or not, the whole plane should start outside the clip.
             reducedStack.clipEmpty();
         }
-        for (ElementList::Iter iter(reduced->elements()); iter.get(); iter.next()) {
+        for (ElementList::Iter iter(reduced->maskElements()); iter.get(); iter.next()) {
             add_elem_to_stack(*iter.get(), &reducedStack);
         }
 
-        SkIRect ibounds = reduced->hasIBounds() ? reduced->ibounds() : kIBounds;
+        SkIRect scissor = reduced->hasScissor() ? reduced->scissor() : kIBounds;
 
         // GrReducedClipStack assumes that the final result is clipped to the returned bounds
-        reducedStack.clipDevRect(ibounds, kIntersect_SkClipOp);
-        stack.clipDevRect(ibounds, kIntersect_SkClipOp);
+        reducedStack.clipDevRect(scissor, kIntersect_SkClipOp);
+        stack.clipDevRect(scissor, kIntersect_SkClipOp);
 
         // convert both the original stack and reduced stack to SkRegions and see if they're equal
         SkRegion region;
-        set_region_to_stack(stack, ibounds, &region);
+        set_region_to_stack(stack, scissor, &region);
 
         SkRegion reducedRegion;
-        set_region_to_stack(reducedStack, ibounds, &reducedRegion);
+        set_region_to_stack(reducedStack, scissor, &reducedRegion);
 
         REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str());
 
@@ -1099,9 +1092,9 @@
         GR_STATIC_ASSERT(0 == SkClipStack::kInvalidGenID);
         const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, bounds);
 
-        REPORTER_ASSERT(reporter, reduced->elements().count() == 1);
+        REPORTER_ASSERT(reporter, reduced->maskElements().count() == 1);
         // Clips will be cached based on the generation id. Make sure the gen id is valid.
-        REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reduced->elementsGenID());
+        REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reduced->maskGenID());
 
         reduced->~GrReducedClip();
     }
@@ -1151,7 +1144,7 @@
             // Rect A.
             { XYWH(0, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 25, 25) },
             { XYWH(0.1f, 0.1f, 25.1f, 25.1f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 26, 26) },
-            { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::InitialState::kAllOut, IXYWH(0, 0, 27, 27)},
+            { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::InitialState::kAllOut, IXYWH(0, 0, 26, 26)},
 
             // Rect B.
             { XYWH(50, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 25, 25) },
@@ -1183,18 +1176,19 @@
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) {
             const GrReducedClip reduced(stack, testCases[i].testBounds);
-            REPORTER_ASSERT(reporter, reduced.elements().count() == testCases[i].reducedClipCount);
-            SkASSERT(reduced.elements().count() == testCases[i].reducedClipCount);
-            if (reduced.elements().count()) {
-                REPORTER_ASSERT(reporter, reduced.elementsGenID() == testCases[i].reducedGenID);
-                SkASSERT(reduced.elementsGenID() == testCases[i].reducedGenID);
+            REPORTER_ASSERT(reporter, reduced.maskElements().count() ==
+                            testCases[i].reducedClipCount);
+            SkASSERT(reduced.maskElements().count() == testCases[i].reducedClipCount);
+            if (reduced.maskElements().count()) {
+                REPORTER_ASSERT(reporter, reduced.maskGenID() == testCases[i].reducedGenID);
+                SkASSERT(reduced.maskGenID() == testCases[i].reducedGenID);
             }
             REPORTER_ASSERT(reporter, reduced.initialState() == testCases[i].initialState);
             SkASSERT(reduced.initialState() == testCases[i].initialState);
-            REPORTER_ASSERT(reporter, reduced.hasIBounds());
-            SkASSERT(reduced.hasIBounds());
-            REPORTER_ASSERT(reporter, reduced.ibounds() == testCases[i].clipIRect);
-            SkASSERT(reduced.ibounds() == testCases[i].clipIRect);
+            REPORTER_ASSERT(reporter, reduced.hasScissor());
+            SkASSERT(reduced.hasScissor());
+            REPORTER_ASSERT(reporter, reduced.scissor() == testCases[i].clipIRect);
+            SkASSERT(reduced.scissor() == testCases[i].clipIRect);
         }
     }
 }
@@ -1207,7 +1201,7 @@
 
     // At the time, this would crash.
     const GrReducedClip reduced(stack, bounds);
-    REPORTER_ASSERT(reporter, reduced.elements().isEmpty());
+    REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty());
 }
 
 enum class ClipMethod {
@@ -1232,7 +1226,8 @@
     switch (expectedMethod) {
         case ClipMethod::kSkipDraw:
             SkASSERT(0 == numExpectedElems);
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter,
+                                    reduced.maskElements().isEmpty(), testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter,
                                     GrReducedClip::InitialState::kAllOut == reduced.initialState(),
                                     testName.c_str());
@@ -1240,10 +1235,10 @@
         case ClipMethod::kIgnoreClip:
             SkASSERT(0 == numExpectedElems);
             REPORTER_ASSERT_MESSAGE(reporter,
-                                    !reduced.hasIBounds() ||
-                                    GrClip::IsInsideClip(reduced.ibounds(), queryBounds),
+                                    !reduced.hasScissor() ||
+                                    GrClip::IsInsideClip(reduced.scissor(), queryBounds),
                                     testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.maskElements().isEmpty(), testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter,
                                     GrReducedClip::InitialState::kAllIn == reduced.initialState(),
                                     testName.c_str());
@@ -1253,9 +1248,9 @@
             SkASSERT(0 == numExpectedElems);
             SkIRect expectedScissor;
             stackBounds.round(&expectedScissor);
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.ibounds(),
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.maskElements().isEmpty(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasScissor(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.scissor(),
                                     testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter,
                                     GrReducedClip::InitialState::kAllIn == reduced.initialState(),
@@ -1267,12 +1262,13 @@
             if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
                 SkAssertResult(expectedClipIBounds.intersect(GrClip::GetPixelIBounds(stackBounds)));
             }
-            REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reduced.elements().count(),
+            REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reduced.maskElements().count(),
                                     testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.ibounds(),
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasScissor(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.scissor(),
                                     testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == !reduced.elements().isEmpty(),
+            REPORTER_ASSERT_MESSAGE(reporter,
+                                    reduced.maskElements().isEmpty() || reduced.maskRequiresAA(),
                                     testName.c_str());
             break;
         }
@@ -1387,8 +1383,8 @@
                                    SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance),
                                    SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance/2)}) {
             const GrReducedClip reduced(stack, queryBounds);
-            REPORTER_ASSERT(reporter, !reduced.hasIBounds());
-            REPORTER_ASSERT(reporter, reduced.elements().isEmpty());
+            REPORTER_ASSERT(reporter, !reduced.hasScissor());
+            REPORTER_ASSERT(reporter, reduced.maskElements().isEmpty());
             REPORTER_ASSERT(reporter,
                             GrReducedClip::InitialState::kAllOut == reduced.initialState());
         }