Convert GrAppliedClip interface to builder style

GrAppliedClip was about at its limit for how many "make" functions it
could have. Window rectangles would push it over the edge. This change
makes it so GrDrawTarget supplies the original draw bounds to the
constructor, and then GrClip adds the various required clipping
techniques.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2246113002

Review-Url: https://codereview.chromium.org/2246113002
diff --git a/include/gpu/GrClip.h b/include/gpu/GrClip.h
index 5f0a881..9db76f0 100644
--- a/include/gpu/GrClip.h
+++ b/include/gpu/GrClip.h
@@ -19,78 +19,43 @@
  */
 class GrAppliedClip : public SkNoncopyable {
 public:
-    GrAppliedClip() : fHasStencilClip(false), fDeviceBounds(SkRect::MakeLargest()) {}
-    GrFragmentProcessor* getClipCoverageFragmentProcessor() const {
-        return fClipCoverageFP.get();
+    GrAppliedClip(const SkRect& drawBounds)
+        : fHasStencilClip(false)
+        , fClippedDrawBounds(drawBounds) {
     }
+
     const GrScissorState& scissorState() const { return fScissorState; }
+    GrFragmentProcessor* clipCoverageFragmentProcessor() const { return fClipCoverageFP.get(); }
     bool hasStencilClip() const { return fHasStencilClip; }
 
-    void makeStencil(bool hasStencil, const SkRect& deviceBounds) {
-        fClipCoverageFP = nullptr;
-        fScissorState.setDisabled();
-        fHasStencilClip = hasStencil;
-        fDeviceBounds = deviceBounds;
+    /**
+     * Intersects the applied clip with the provided rect. Returns false if the draw became empty.
+     */
+    bool addScissor(const SkIRect& irect) {
+        return fScissorState.intersect(irect) && fClippedDrawBounds.intersect(SkRect::Make(irect));
     }
 
-    /**
-     * The device bounds of the clip defaults to the scissor rect, but a tighter bounds (based
-     * on the known effect of the stencil values) can be provided.
-     */
-    void makeScissoredStencil(const SkIRect& scissor, const SkRect* deviceBounds = nullptr) {
-        fClipCoverageFP = nullptr;
-        fScissorState.set(scissor);
+    void addCoverageFP(sk_sp<GrFragmentProcessor> fp) {
+        SkASSERT(!fClipCoverageFP);
+        fClipCoverageFP = fp;
+    }
+
+    void addStencilClip() {
+        SkASSERT(!fHasStencilClip);
         fHasStencilClip = true;
-        if (deviceBounds) {
-            fDeviceBounds = *deviceBounds;
-            SkASSERT(scissor.contains(*deviceBounds));
-        } else {
-            fDeviceBounds = SkRect::Make(scissor);
-        }
-    }
-
-    void makeFPBased(sk_sp<GrFragmentProcessor> fp, const SkRect& deviceBounds) {
-        fClipCoverageFP = fp;
-        fScissorState.setDisabled();
-        fHasStencilClip = false;
-        fDeviceBounds = deviceBounds;
-    }
-
-    void makeScissored(SkIRect& scissor) {
-        fClipCoverageFP.reset();
-        fScissorState.set(scissor);
-        fHasStencilClip = false;
-        fDeviceBounds = SkRect::Make(scissor);
     }
 
     /**
-     * The device bounds of the clip defaults to the scissor rect, but a tighter bounds (based
-     * on the known effect of the fragment processor) can be provided.
+     * Returns the device bounds of the draw after clip has been applied. TODO: Ideally this would
+     * consider the combined effect of all clipping techniques in play (scissor, stencil, fp, etc.).
      */
-    void makeScissoredFPBased(sk_sp<GrFragmentProcessor> fp, const SkIRect& scissor,
-                              const SkRect* deviceBounds = nullptr) {
-        fClipCoverageFP = fp;
-        fScissorState.set(scissor);
-        fHasStencilClip = false;
-        if (deviceBounds) {
-            fDeviceBounds = *deviceBounds;
-            SkASSERT(scissor.contains(*deviceBounds));
-        } else {
-            fDeviceBounds = SkRect::Make(scissor);
-        }
-    }
-
-    /**
-     * Returns the device bounds of the applied clip. Ideally this considers the combined effect of
-     * all clipping techniques in play (scissor, stencil, and/or coverage fp).
-     */
-    const SkRect& deviceBounds() const { return fDeviceBounds; }
+    const SkRect& clippedDrawBounds() const { return fClippedDrawBounds; }
 
 private:
-    sk_sp<GrFragmentProcessor> fClipCoverageFP;
     GrScissorState             fScissorState;
+    sk_sp<GrFragmentProcessor> fClipCoverageFP;
     bool                       fHasStencilClip;
-    SkRect                     fDeviceBounds;
+    SkRect                     fClippedDrawBounds;
     typedef SkNoncopyable INHERITED;
 };
 
@@ -103,11 +68,7 @@
     virtual bool quickContains(const SkRect&) const = 0;
     virtual void getConservativeBounds(int width, int height, SkIRect* devResult,
                                        bool* isIntersectionOfRects = nullptr) const = 0;
-    virtual bool apply(GrContext*,
-                       GrDrawContext*,
-                       const SkRect* devBounds,
-                       bool useHWAA,
-                       bool hasUserStencilSettings,
+    virtual bool apply(GrContext*, GrDrawContext*, bool useHWAA, bool hasUserStencilSettings,
                        GrAppliedClip* out) const = 0;
 
     virtual ~GrClip() {}
@@ -192,12 +153,7 @@
     bool quickContains(const SkRect&) const final { return true; }
     void getConservativeBounds(int width, int height, SkIRect* devResult,
                                bool* isIntersectionOfRects) const final;
-    bool apply(GrContext*,
-               GrDrawContext*,
-               const SkRect* /* devBounds */,
-               bool /* useHWAA */,
-               bool /* hasUserStencilSettings */,
-               GrAppliedClip* /* out */) const final { return true; }
+    bool apply(GrContext*, GrDrawContext*, bool, bool, GrAppliedClip*) const final { return true; }
 };
 
 /**
@@ -206,66 +162,33 @@
  */
 class GrFixedClip final : public GrClip {
 public:
-    GrFixedClip() : fDeviceBounds(SkRect::MakeLargest()), fHasStencilClip(false) {}
+    GrFixedClip() : fHasStencilClip(false) {}
     GrFixedClip(const SkIRect& scissorRect)
         : fScissorState(scissorRect)
-        , fDeviceBounds(SkRect::Make(scissorRect))
         , fHasStencilClip(false) {}
 
     void reset() {
         fScissorState.setDisabled();
-        fDeviceBounds.setLargest();
         fHasStencilClip = false;
     }
 
     void reset(const SkIRect& scissorRect) {
         fScissorState.set(scissorRect);
-        fDeviceBounds = SkRect::Make(scissorRect);
         fHasStencilClip = false;
     }
 
-    /**
-     * Enables stenciling. The stencil bounds is the device space bounds where the stencil test
-     * may pass.
-     */
-    void enableStencilClip(const SkRect& stencilBounds) {
-        fHasStencilClip = true;
-        fDeviceBounds = stencilBounds;
-        if (fScissorState.enabled()) {
-            const SkIRect& s = fScissorState.rect();
-            fDeviceBounds.fLeft   = SkTMax(fDeviceBounds.fLeft,   SkIntToScalar(s.fLeft));
-            fDeviceBounds.fTop    = SkTMax(fDeviceBounds.fTop,    SkIntToScalar(s.fTop));
-            fDeviceBounds.fRight  = SkTMin(fDeviceBounds.fRight,  SkIntToScalar(s.fRight));
-            fDeviceBounds.fBottom = SkTMin(fDeviceBounds.fBottom, SkIntToScalar(s.fBottom));
-        }
-    }
-
-    void disableStencilClip() {
-        fHasStencilClip = false;
-        if (fScissorState.enabled()) {
-            fDeviceBounds = SkRect::Make(fScissorState.rect());
-        } else {
-            fDeviceBounds.setLargest();
-        }
-    }
-
-    const GrScissorState& scissorState() const { return fScissorState; }
-    bool hasStencilClip() const { return fHasStencilClip; }
+    void enableStencilClip() { fHasStencilClip = true; }
+    void disableStencilClip() { fHasStencilClip = false; }
 
     bool quickContains(const SkRect&) const final;
     void getConservativeBounds(int width, int height, SkIRect* devResult,
                                bool* isIntersectionOfRects) const final;
 
 private:
-    bool apply(GrContext*,
-               GrDrawContext*,
-               const SkRect* devBounds,
-               bool useHWAA,
-               bool hasUserStencilSettings,
+    bool apply(GrContext*, GrDrawContext*, bool useHWAA, bool hasUserStencilSettings,
                GrAppliedClip* out) const final;
 
     GrScissorState   fScissorState;
-    SkRect           fDeviceBounds;
     bool             fHasStencilClip;
 };
 
diff --git a/include/gpu/GrTypesPriv.h b/include/gpu/GrTypesPriv.h
index abbfdfc..8c42d64 100644
--- a/include/gpu/GrTypesPriv.h
+++ b/include/gpu/GrTypesPriv.h
@@ -425,6 +425,13 @@
     GrScissorState(const SkIRect& rect) : fEnabled(true), fRect(rect) {}
     void setDisabled() { fEnabled = false; }
     void set(const SkIRect& rect) { fRect = rect; fEnabled = true; }
+    bool SK_WARN_UNUSED_RESULT intersect(const SkIRect& rect) {
+        if (!fEnabled) {
+            this->set(rect);
+            return true;
+        }
+        return fRect.intersect(rect);
+    }
     bool operator==(const GrScissorState& other) const {
         return fEnabled == other.fEnabled &&
                 (false == fEnabled || fRect == other.fRect);
diff --git a/src/gpu/GrClip.cpp b/src/gpu/GrClip.cpp
index 3eccd80..ef9c9cd 100644
--- a/src/gpu/GrClip.cpp
+++ b/src/gpu/GrClip.cpp
@@ -21,10 +21,7 @@
     if (fHasStencilClip) {
         return false;
     }
-    if (!fScissorState.enabled()) {
-        return true;
-    }
-    return fScissorState.rect().contains(rect);
+    return !fScissorState.enabled() || GrClip::IsInsideClip(fScissorState.rect(), rect);
 }
 
 void GrFixedClip::getConservativeBounds(int width, int height, SkIRect* devResult,
@@ -40,32 +37,25 @@
     }
 }
 
-bool GrFixedClip::apply(GrContext*,
-                        GrDrawContext* drawContext,
-                        const SkRect* devBounds,
-                        bool isHWAntiAlias,
-                        bool hasUserStencilSettings,
-                        GrAppliedClip* out) const {
-    SkASSERT(!fDeviceBounds.isLargest());
+bool GrFixedClip::apply(GrContext*, GrDrawContext* drawContext, bool isHWAntiAlias,
+                        bool hasUserStencilSettings, GrAppliedClip* out) const {
     if (fScissorState.enabled()) {
         SkIRect tightScissor;
         if (!tightScissor.intersect(fScissorState.rect(),
                                     SkIRect::MakeWH(drawContext->width(), drawContext->height()))) {
             return false;
         }
-        if (devBounds && IsOutsideClip(tightScissor, *devBounds)) {
+        if (IsOutsideClip(tightScissor, out->clippedDrawBounds())) {
             return false;
         }
-        if (!devBounds || !IsInsideClip(fScissorState.rect(), *devBounds)) {
-            if (fHasStencilClip) {
-                out->makeScissoredStencil(tightScissor, &fDeviceBounds);
-            } else {
-                out->makeScissored(tightScissor);
-            }
-            return true;
+        if (!IsInsideClip(fScissorState.rect(), out->clippedDrawBounds())) {
+            out->addScissor(tightScissor);
         }
     }
 
-    out->makeStencil(fHasStencilClip, fDeviceBounds);
+    if (fHasStencilClip) {
+        out->addStencilClip();
+    }
+
     return true;
 }
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index e117144..6cb216e 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -242,18 +242,14 @@
 ////////////////////////////////////////////////////////////////////////////////
 // sort out what kind of clip mask needs to be created: alpha, stencil,
 // scissor, or entirely software
-bool GrClipStackClip::apply(GrContext* context,
-                            GrDrawContext* drawContext,
-                            const SkRect* origDevBounds,
-                            bool useHWAA,
-                            bool hasUserStencilSettings,
-                            GrAppliedClip* out) const {
+bool GrClipStackClip::apply(GrContext* context, GrDrawContext* drawContext, bool useHWAA,
+                            bool hasUserStencilSettings, GrAppliedClip* out) const {
     if (!fStack || fStack->isWideOpen()) {
         return true;
     }
 
     SkRect devBounds = SkRect::MakeIWH(drawContext->width(), drawContext->height());
-    if (origDevBounds && !devBounds.intersect(*origDevBounds)) {
+    if (!devBounds.intersect(out->clippedDrawBounds())) {
         return false;
     }
 
@@ -263,18 +259,19 @@
     SkRect clipSpaceDevBounds = devBounds.makeOffset(clipX, clipY);
     const GrReducedClip reducedClip(*fStack, clipSpaceDevBounds);
 
-    if (reducedClip.elements().isEmpty()) {
-        if (GrReducedClip::InitialState::kAllOut == reducedClip.initialState()) {
-            return false;
-        }
-        if (!GrClip::IsInsideClip(reducedClip.iBounds(), clipSpaceDevBounds)) {
-            SkIRect scissorSpaceIBounds(reducedClip.iBounds());
-            scissorSpaceIBounds.offset(-fOrigin);
-            out->makeScissored(scissorSpaceIBounds);
-        }
-        return true;
+    if (reducedClip.hasIBounds() &&
+        !GrClip::IsInsideClip(reducedClip.ibounds(), clipSpaceDevBounds)) {
+        SkIRect scissorSpaceIBounds(reducedClip.ibounds());
+        scissorSpaceIBounds.offset(-fOrigin);
+        out->addScissor(scissorSpaceIBounds);
     }
 
+    if (reducedClip.elements().isEmpty()) {
+        return InitialState::kAllIn == reducedClip.initialState();
+    }
+
+    SkASSERT(reducedClip.hasIBounds());
+
     // An element count of 4 was chosen because of the common pattern in Blink of:
     //   isect RR
     //   diff  RR
@@ -297,13 +294,7 @@
         if (reducedClip.requiresAA() &&
             get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA,
                                         {-clipX, -clipY}, devBounds, &clipFP)) {
-            SkIRect scissorSpaceIBounds(reducedClip.iBounds());
-            scissorSpaceIBounds.offset(-fOrigin);
-            if (GrClip::IsInsideClip(scissorSpaceIBounds, devBounds)) {
-                out->makeFPBased(std::move(clipFP), SkRect::Make(scissorSpaceIBounds));
-            } else {
-                out->makeScissoredFPBased(std::move(clipFP), scissorSpaceIBounds);
-            }
+            out->addCoverageFP(std::move(clipFP));
             return true;
         }
     }
@@ -333,10 +324,9 @@
         if (result) {
             // The mask's top left coord should be pinned to the rounded-out top left corner of
             // clipSpace bounds. We determine the mask's position WRT to the render target here.
-            SkIRect rtSpaceMaskBounds = reducedClip.iBounds();
+            SkIRect rtSpaceMaskBounds = reducedClip.ibounds();
             rtSpaceMaskBounds.offset(-fOrigin);
-            out->makeFPBased(create_fp_for_mask(result.get(), rtSpaceMaskBounds),
-                             SkRect::Make(rtSpaceMaskBounds));
+            out->addCoverageFP(create_fp_for_mask(result.get(), rtSpaceMaskBounds));
             return true;
         }
         // if alpha clip mask creation fails fall through to the non-AA code paths
@@ -345,17 +335,7 @@
     // use the stencil clip if we can't represent the clip as a rectangle.
     SkIPoint clipSpaceToStencilSpaceOffset = -fOrigin;
     CreateStencilClipMask(context, drawContext, reducedClip, clipSpaceToStencilSpaceOffset);
-
-    // This must occur after createStencilClipMask. That function may change the scissor. Also, it
-    // only guarantees that the stencil mask is correct within the bounds it was passed, so we must
-    // use both stencil and scissor test to the bounds for the final draw.
-    if (GrClip::IsInsideClip(reducedClip.iBounds(), clipSpaceDevBounds)) {
-        out->makeStencil(true, devBounds);
-    } else {
-        SkIRect scissorSpaceIBounds(reducedClip.iBounds());
-        scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset);
-        out->makeScissoredStencil(scissorSpaceIBounds);
-    }
+    out->addStencilClip();
     return true;
 }
 
@@ -438,7 +418,7 @@
                                                       const SkVector& clipToMaskOffset) {
     GrResourceProvider* resourceProvider = context->resourceProvider();
     GrUniqueKey key;
-    GetClipMaskKey(reducedClip.genID(), reducedClip.iBounds(), &key);
+    GetClipMaskKey(reducedClip.genID(), reducedClip.ibounds(), &key);
     if (GrTexture* texture = resourceProvider->findAndRefTextureByUniqueKey(key)) {
         return sk_sp<GrTexture>(texture);
     }
@@ -463,9 +443,7 @@
 
     // The scratch texture that we are drawing into can be substantially larger than the mask. Only
     // clear the part that we care about.
-    dc->clear(&maskSpaceIBounds,
-              GrReducedClip::InitialState::kAllIn == reducedClip.initialState() ? -1 : 0,
-              true);
+    dc->clear(&maskSpaceIBounds, InitialState::kAllIn == reducedClip.initialState() ? -1 : 0, true);
 
     // Set the matrix so that rendered clip elements are transformed to mask space from clip
     // space.
@@ -513,7 +491,7 @@
             if (!dc->drawContextPriv().drawAndStencilRect(clip, &kDrawOutsideElement,
                                                           op, !invert, false,
                                                           translate,
-                                                          SkRect::Make(reducedClip.iBounds()))) {
+                                                          SkRect::Make(reducedClip.ibounds()))) {
                 return nullptr;
             }
         } else {
@@ -548,9 +526,9 @@
     }
 
     // TODO: these need to be swapped over to using a StencilAttachmentProxy
-    if (stencilAttachment->mustRenderClip(reducedClip.genID(), reducedClip.iBounds(),
+    if (stencilAttachment->mustRenderClip(reducedClip.genID(), reducedClip.ibounds(),
                                           clipSpaceToStencilOffset)) {
-        stencilAttachment->setLastClip(reducedClip.genID(), reducedClip.iBounds(),
+        stencilAttachment->setLastClip(reducedClip.genID(), reducedClip.ibounds(),
                                        clipSpaceToStencilOffset);
         // Set the matrix so that rendered clip elements are transformed from clip to stencil space.
         SkVector translate = {
@@ -561,11 +539,11 @@
         viewMatrix.setTranslate(translate);
 
         // We set the current clip to the bounds so that our recursive draws are scissored to them.
-        SkIRect stencilSpaceIBounds(reducedClip.iBounds());
+        SkIRect stencilSpaceIBounds(reducedClip.ibounds());
         stencilSpaceIBounds.offset(clipSpaceToStencilOffset);
         GrFixedClip clip(stencilSpaceIBounds);
 
-        bool insideClip = GrReducedClip::InitialState::kAllIn == reducedClip.initialState();
+        bool insideClip = InitialState::kAllIn == reducedClip.initialState();
         drawContext->drawContextPriv().clearStencilClip(stencilSpaceIBounds, insideClip);
 
         // walk through each clip element and perform its set op
@@ -671,22 +649,19 @@
                 }
             }
 
+            // Just enable stencil clip. The passes choose whether or not they will actually use it.
+            clip.enableStencilClip();
+
             // now we modify the clip bit by rendering either the clip
             // element directly or a bounding rect of the entire clip.
             for (GrUserStencilSettings const* const* pass = stencilPasses; *pass; ++pass) {
-
                 if (drawDirectToClip) {
                     if (Element::kRect_Type == element->getType()) {
-                        clip.enableStencilClip(element->getRect().makeOffset(translate.fX,
-                                                                             translate.fY));
                         drawContext->drawContextPriv().stencilRect(clip, *pass, useHWAA, viewMatrix,
                                                                    element->getRect());
                     } else {
                         GrShape shape(clipPath, GrStyle::SimpleFill());
                         GrPaint paint;
-                        SkRect bounds = clipPath.getBounds();
-                        bounds.offset(translate.fX, translate.fY);
-                        clip.enableStencilClip(bounds);
                         paint.setXPFactory(GrDisableColorXPFactory::Make());
                         paint.setAntiAlias(element->isAA());
                         GrPathRenderer::DrawPathArgs args;
@@ -704,11 +679,8 @@
                 } else {
                     // The view matrix is setup to do clip space -> stencil space translation, so
                     // draw rect in clip space.
-                    SkRect bounds = SkRect::Make(reducedClip.iBounds());
-                    bounds.offset(translate.fX, translate.fY);
-                    clip.enableStencilClip(bounds);
                     drawContext->drawContextPriv().stencilRect(clip, *pass, false, viewMatrix,
-                                                               SkRect::Make(reducedClip.iBounds()));
+                                                               SkRect::Make(reducedClip.ibounds()));
                 }
             }
         }
@@ -721,7 +693,7 @@
                                                          const GrReducedClip& reducedClip,
                                                          const SkVector& clipToMaskOffset) {
     GrUniqueKey key;
-    GetClipMaskKey(reducedClip.genID(), reducedClip.iBounds(), &key);
+    GetClipMaskKey(reducedClip.genID(), reducedClip.ibounds(), &key);
     if (GrTexture* texture = texProvider->findAndRefTextureByUniqueKey(key)) {
         return sk_sp<GrTexture>(texture);
     }
@@ -738,7 +710,7 @@
     translate.setTranslate(clipToMaskOffset);
 
     helper.init(maskSpaceIBounds, &translate);
-    helper.clear(GrReducedClip::InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00);
+    helper.clear(InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00);
 
     for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) {
         const Element* element = iter.get();
@@ -750,7 +722,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 (SkRegion::kReverseDifference_Op == op) {
-                SkRect temp = SkRect::Make(reducedClip.iBounds());
+                SkRect temp = SkRect::Make(reducedClip.ibounds());
                 // invert the entire scene
                 helper.drawRect(temp, SkRegion::kXOR_Op, false, 0xFF);
             }
diff --git a/src/gpu/GrClipStackClip.h b/src/gpu/GrClipStackClip.h
index aaa2f90..98675e6 100644
--- a/src/gpu/GrClipStackClip.h
+++ b/src/gpu/GrClipStackClip.h
@@ -33,11 +33,7 @@
     bool quickContains(const SkRect&) const final;
     void getConservativeBounds(int width, int height, SkIRect* devResult,
                                bool* isIntersectionOfRects) const final;
-    bool apply(GrContext*,
-               GrDrawContext*,
-               const SkRect* devBounds,
-               bool useHWAA,
-               bool hasUserStencilSettings,
+    bool apply(GrContext*, GrDrawContext*, bool useHWAA, bool hasUserStencilSettings,
                GrAppliedClip* out) const final;
 
 private:
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index e143f90..b4142eb 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -321,51 +321,38 @@
     }
 }
 
-static inline bool intersect(SkRect* out, const SkRect& a, const SkRect& b) {
-    SkASSERT(a.fLeft <= a.fRight && a.fTop <= a.fBottom);
-    SkASSERT(b.fLeft <= b.fRight && b.fTop <= b.fBottom);
-    out->fLeft   = SkTMax(a.fLeft,   b.fLeft);
-    out->fTop    = SkTMax(a.fTop,    b.fTop);
-    out->fRight  = SkTMin(a.fRight,  b.fRight);
-    out->fBottom = SkTMin(a.fBottom, b.fBottom);
-    return (out->fLeft <= out->fRight && out->fTop <= out->fBottom);
-}
-
 void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder,
                              GrDrawContext* drawContext,
                              const GrClip& clip,
                              GrDrawBatch* batch) {
     // Setup clip
-    GrAppliedClip appliedClip;
     SkRect bounds;
     batch_bounds(&bounds, batch);
-    if (!clip.apply(fContext, drawContext, &bounds,
-                    pipelineBuilder.isHWAntialias(), pipelineBuilder.hasUserStencilSettings(),
-                    &appliedClip)) {
+    GrAppliedClip appliedClip(bounds);
+    if (!clip.apply(fContext, drawContext, pipelineBuilder.isHWAntialias(),
+                    pipelineBuilder.hasUserStencilSettings(), &appliedClip)) {
         return;
     }
 
     // TODO: this is the only remaining usage of the AutoRestoreFragmentProcessorState - remove it
     GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps;
-    if (appliedClip.getClipCoverageFragmentProcessor()) {
+    if (appliedClip.clipCoverageFragmentProcessor()) {
         arfps.set(&pipelineBuilder);
-        arfps.addCoverageFragmentProcessor(sk_ref_sp(appliedClip.getClipCoverageFragmentProcessor()));
+        arfps.addCoverageFragmentProcessor(sk_ref_sp(appliedClip.clipCoverageFragmentProcessor()));
     }
 
-    GrPipeline::CreateArgs args;
-    args.fPipelineBuilder = &pipelineBuilder;
-    args.fDrawContext = drawContext;
-    args.fCaps = this->caps();
-    args.fScissor = &appliedClip.scissorState();
-    args.fHasStencilClip = appliedClip.hasStencilClip();
     if (pipelineBuilder.hasUserStencilSettings() || appliedClip.hasStencilClip()) {
         if (!fResourceProvider->attachStencilAttachment(drawContext->accessRenderTarget())) {
             SkDebugf("ERROR creating stencil attachment. Draw skipped.\n");
             return;
         }
     }
+
+    GrPipeline::CreateArgs args;
+    args.fPipelineBuilder = &pipelineBuilder;
+    args.fDrawContext = drawContext;
+    args.fCaps = this->caps();
     batch->getPipelineOptimizations(&args.fOpts);
-    GrScissorState finalScissor;
     if (args.fOpts.fOverrides.fUsePLSDstRead || fClipBatchToBounds) {
         GrGLIRect viewport;
         viewport.fLeft = 0;
@@ -381,14 +368,9 @@
                                viewport.fWidth);
         ibounds.fBottom = SkTPin(SkScalarCeilToInt(batch->bounds().fBottom), viewport.fBottom,
                                 viewport.fHeight);
-        if (appliedClip.scissorState().enabled()) {
-            const SkIRect& scissorRect = appliedClip.scissorState().rect();
-            if (!ibounds.intersect(scissorRect)) {
-                return;
-            }
+        if (!appliedClip.addScissor(ibounds)) {
+            return;
         }
-        finalScissor.set(ibounds);
-        args.fScissor = &finalScissor;
     }
     args.fOpts.fColorPOI.completeCalculations(
         sk_sp_address_as_pointer_address(pipelineBuilder.fColorFragmentProcessors.begin()),
@@ -396,6 +378,8 @@
     args.fOpts.fCoveragePOI.completeCalculations(
         sk_sp_address_as_pointer_address(pipelineBuilder.fCoverageFragmentProcessors.begin()),
         pipelineBuilder.numCoverageFragmentProcessors());
+    args.fScissor = &appliedClip.scissorState();
+    args.fHasStencilClip = appliedClip.hasStencilClip();
     if (!this->setupDstReadIfNecessary(pipelineBuilder, drawContext->accessRenderTarget(),
                                        clip, args.fOpts,
                                        &args.fDstTexture, batch->bounds())) {
@@ -410,9 +394,7 @@
     SkASSERT(fRenderTarget);
     batch->pipeline()->addDependenciesTo(fRenderTarget);
 #endif
-    SkRect clippedBounds;
-    SkAssertResult(intersect(&clippedBounds, bounds, appliedClip.deviceBounds()));
-    this->recordBatch(batch, clippedBounds);
+    this->recordBatch(batch, appliedClip.clippedDrawBounds());
 }
 
 void GrDrawTarget::stencilPath(GrDrawContext* drawContext,
@@ -424,16 +406,20 @@
     SkASSERT(path);
     SkASSERT(this->caps()->shaderCaps()->pathRenderingSupport());
 
+    // FIXME: Use path bounds instead of this WAR once
+    // https://bugs.chromium.org/p/skia/issues/detail?id=5640 is resolved.
+    SkRect bounds = SkRect::MakeIWH(drawContext->width(), drawContext->height());
+
     // Setup clip
-    GrAppliedClip appliedClip;
-    if (!clip.apply(fContext, drawContext, nullptr, useHWAA, true, &appliedClip)) {
+    GrAppliedClip appliedClip(bounds);
+    if (!clip.apply(fContext, drawContext, useHWAA, true, &appliedClip)) {
         return;
     }
     // TODO: respect fClipBatchToBounds if we ever start computing bounds here.
 
     // Coverage AA does not make sense when rendering to the stencil buffer. The caller should never
     // attempt this in a situation that would require coverage AA.
-    SkASSERT(!appliedClip.getClipCoverageFragmentProcessor());
+    SkASSERT(!appliedClip.clipCoverageFragmentProcessor());
 
     GrStencilAttachment* stencilAttachment = fResourceProvider->attachStencilAttachment(
                                                 drawContext->accessRenderTarget());
@@ -450,7 +436,7 @@
                                                 appliedClip.scissorState(),
                                                 drawContext->accessRenderTarget(),
                                                 path);
-    this->recordBatch(batch, appliedClip.deviceBounds());
+    this->recordBatch(batch, appliedClip.clippedDrawBounds());
     batch->unref();
 }
 
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index f7bac4a..132936c 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -343,9 +343,7 @@
     // generation id. When we make early returns, we do not know what was the generation
     // id that lead to the state. Make a conservative guess.
     fGenID = stack.getTopmostGenID();
-
-    // TODO: instead devise a way of telling the caller to disregard some or all of the clip bounds.
-    fIBounds = GrClip::GetPixelIBounds(queryBounds);
+    fHasIBounds = false;
 
     if (stack.isWideOpen()) {
         fInitialState = InitialState::kAllIn;
@@ -372,6 +370,7 @@
             // 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;
             return;
         }
@@ -380,8 +379,11 @@
             return;
         }
 
-        SkAssertResult(fIBounds.intersect(GrClip::GetPixelIBounds(stackBounds)));
+        SkRect tightBounds;
+        SkAssertResult(tightBounds.intersect(stackBounds, queryBounds));
+        fIBounds = GrClip::GetPixelIBounds(tightBounds);
         SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
+        fHasIBounds = true;
 
         // Implement the clip with an AA rect element.
         fElements.addToHead(stackBounds, SkRegion::kReplace_Op, true/*doAA*/);
@@ -396,10 +398,11 @@
         // 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);
     }
 
+    fIBounds = GrClip::GetPixelIBounds(tighterQuery);
     SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
+    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.
diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h
index 07e0669..d7b7ea8 100644
--- a/src/gpu/GrReducedClip.h
+++ b/src/gpu/GrReducedClip.h
@@ -25,14 +25,20 @@
     int32_t genID() const { return fGenID; }
 
     /**
-     * Bounding box within which the reduced clip is valid. The caller must not draw any pixels
-     * outside this box.
+     * If hasIBounds() is true, this is the bounding box within which the reduced clip is valid, and
+     * the caller must not modify any pixels outside this box. Undefined if hasIBounds() is false.
      */
-    const SkIRect& iBounds() const { 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& 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(); }
+
+    /**
+     * Indicates whether ibounds() are defined. They will always be defined if the elements() are
+     * nonempty.
+     */
+    bool hasIBounds() const { return fHasIBounds; }
 
     typedef SkTLList<SkClipStack::Element, 16> ElementList;
 
@@ -51,14 +57,12 @@
         kAllOut
     };
 
-    /**
-     * The initial state of the clip within iBounds().
-     */
     InitialState initialState() const { return fInitialState; }
 
 private:
     int32_t        fGenID;
     SkIRect        fIBounds;
+    bool           fHasIBounds;
     ElementList    fElements;
     bool           fRequiresAA;
     InitialState   fInitialState;
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index a6b6362..5265ac2 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -916,7 +916,8 @@
     // they are equal.
 
     // All the clip elements will be contained within these bounds.
-    static const SkRect kBounds = SkRect::MakeWH(100, 100);
+    static const SkIRect kIBounds = SkIRect::MakeWH(100, 100);
+    static const SkRect kBounds = SkRect::Make(kIBounds);
 
     enum {
         kNumTests = 250,
@@ -1015,13 +1016,14 @@
                                 testCase.c_str());
 
         if (!reduced.elements().isEmpty()) {
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), 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),
+                REPORTER_ASSERT_MESSAGE(reporter, GrClip::IsInsideClip(reduced.ibounds(), stackBounds),
                                         testCase.c_str());
             }
             REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == doAA, testCase.c_str());
@@ -1037,16 +1039,18 @@
             add_elem_to_stack(*iter.get(), &reducedStack);
         }
 
+        SkIRect ibounds = reduced.hasIBounds() ? reduced.ibounds() : kIBounds;
+
         // GrReducedClipStack assumes that the final result is clipped to the returned bounds
-        reducedStack.clipDevRect(reduced.iBounds(), SkRegion::kIntersect_Op);
-        stack.clipDevRect(reduced.iBounds(), SkRegion::kIntersect_Op);
+        reducedStack.clipDevRect(ibounds, SkRegion::kIntersect_Op);
+        stack.clipDevRect(ibounds, 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, reduced.iBounds(), &region);
+        set_region_to_stack(stack, ibounds, &region);
 
         SkRegion reducedRegion;
-        set_region_to_stack(reducedStack, reduced.iBounds(), &reducedRegion);
+        set_region_to_stack(reducedStack, ibounds, &reducedRegion);
 
         REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str());
     }
@@ -1151,8 +1155,10 @@
             SkASSERT(reduced.genID() == testCases[i].reducedGenID);
             REPORTER_ASSERT(reporter, reduced.initialState() == testCases[i].initialState);
             SkASSERT(reduced.initialState() == testCases[i].initialState);
-            REPORTER_ASSERT(reporter, reduced.iBounds() == testCases[i].clipIRect);
-            SkASSERT(reduced.iBounds() == testCases[i].clipIRect);
+            REPORTER_ASSERT(reporter, reduced.hasIBounds());
+            SkASSERT(reduced.hasIBounds());
+            REPORTER_ASSERT(reporter, reduced.ibounds() == testCases[i].clipIRect);
+            SkASSERT(reduced.ibounds() == testCases[i].clipIRect);
         }
     }
 }
@@ -1197,7 +1203,9 @@
             return;
         case ClipMethod::kIgnoreClip:
             SkASSERT(0 == numExpectedElems);
-            REPORTER_ASSERT_MESSAGE(reporter, GrClip::IsInsideClip(reduced.iBounds(), queryBounds),
+            REPORTER_ASSERT_MESSAGE(reporter,
+                                    !reduced.hasIBounds() ||
+                                    GrClip::IsInsideClip(reduced.ibounds(), queryBounds),
                                     testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter,
@@ -1210,7 +1218,8 @@
             SkIRect expectedScissor;
             stackBounds.round(&expectedScissor);
             REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.iBounds(),
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.ibounds(),
                                     testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter,
                                     GrReducedClip::InitialState::kAllIn == reduced.initialState(),
@@ -1224,7 +1233,8 @@
             }
             REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reduced.elements().count(),
                                     testName.c_str());
-            REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.iBounds(),
+            REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str());
+            REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.ibounds(),
                                     testName.c_str());
             REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == !reduced.elements().isEmpty(),
                                     testName.c_str());