diff options
author | Chris Dalton <csmartdalton@google.com> | 2017-10-27 01:50:57 -0600 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-10-27 19:16:44 +0000 |
commit | 7947193c319bced2ef557971550e196adbe60966 (patch) | |
tree | 82f94198c9ce7a0c74363bbc478a3920c0fc50c0 | |
parent | 75e98c14e91cd94a2ac5751c39ff9b7e1dff1c20 (diff) |
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>
-rw-r--r-- | src/gpu/GrClipStackClip.cpp | 60 | ||||
-rw-r--r-- | src/gpu/GrReducedClip.cpp | 273 | ||||
-rw-r--r-- | src/gpu/GrReducedClip.h | 77 | ||||
-rw-r--r-- | tests/ClipStackTest.cpp | 84 |
4 files changed, 272 insertions, 222 deletions
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp index 09f5bef323..6a71e00a1c 100644 --- a/src/gpu/GrClipStackClip.cpp +++ b/src/gpu/GrClipStackClip.cpp @@ -160,7 +160,7 @@ bool GrClipStackClip::UseSWOnlyPath(GrContext* context, 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 GrClipStackClip::UseSWOnlyPath(GrContext* context, 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar // 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar } // 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar 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 @@ bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTar // 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context, 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context, 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 @@ namespace { 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 @@ private: } 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 @@ static void draw_clip_elements_to_mask_helper(GrSWMaskHelper& helper, const Elem // 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask( 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask( 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask( 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 @@ sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask( 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 22316c5c0a..29f483022b 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 @@ typedef SkClipStack::Element Element; 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 @@ GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds 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 @@ GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds 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))); + } - 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))); - } + fScissor = GrClip::GetPixelIBounds(tighterQuery); + if (fScissor.isEmpty()) { + fInitialState = InitialState::kAllOut; + return; + } + fHasScissor = true; - fIBounds = GrClip::GetPixelIBounds(tighterQuery); - if (fIBounds.isEmpty()) { - fInitialState = InitialState::kAllOut; - return; + // 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); } - 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound } 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound } 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound 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 @@ void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBound 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; + } + + 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; + } - GR_STATIC_ASSERT(0 == static_cast<int>(kDifference_SkClipOp)); - GR_STATIC_ASSERT(1 == static_cast<int>(kIntersect_SkClipOp)); + 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 @@ void GrReducedClip::addInteriorWindowRectangles(int maxWindowRectangles) { 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 void GrReducedClip::addWindowRectangle(const SkRect& elementInteriorRect, } } -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 @@ static bool stencil_element(GrRenderTargetContext* rtc, 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 @@ static void draw_element(GrRenderTargetContext* rtc, 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 @@ static void draw_element(GrRenderTargetContext* rtc, 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 @@ bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const { // 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 @@ bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const { 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 @@ private: 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 @@ bool GrReducedClip::drawStencilClipMask(GrContext* context, 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 @@ bool GrReducedClip::drawStencilClipMask(GrContext* context, // 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 c9cce361ec..0746439ba3 100644 --- a/src/gpu/GrReducedClip.h +++ b/src/gpu/GrReducedClip.h @@ -21,47 +21,54 @@ class GrRenderTargetContext; */ 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; - /** - * Populated with a minimal list of elements required to fully implement the clip. + * 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& elements() const { return fElements; } + const ElementList& maskElements() const { return fMaskElements; } /** - * If elements() are nonempty, uniquely identifies the list of elements within ibounds(). - * Otherwise undefined. + * 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. */ - uint32_t elementsGenID() const { SkASSERT(!fElements.isEmpty()); return fElementsGenID; } + uint32_t maskGenID() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskGenID; } /** - * Indicates whether antialiasing is required to process any of the clip elements. + * Indicates whether antialiasing is required to process any of the mask elements. */ - bool requiresAA() const { return fRequiresAA; } + bool maskRequiresAA() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskRequiresAA; } enum class InitialState : bool { kAllIn, @@ -75,16 +82,32 @@ public: private: void walkStack(const SkClipStack&, const SkRect& queryBounds, int maxWindowRectangles); - void addInteriorWindowRectangles(int maxWindowRectangles); + + 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); - bool intersectIBounds(const SkIRect&); + void makeEmpty(); - SkIRect fIBounds; - bool fHasIBounds; + 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 8984027664..554362372d 100644 --- a/tests/ClipStackTest.cpp +++ b/tests/ClipStackTest.cpp @@ -1031,23 +1031,16 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) { 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 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) { // 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, ®ion); + set_region_to_stack(stack, scissor, ®ion); 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 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) { 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 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) { // 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 @@ static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) { 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 @@ static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) { // 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 @@ static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName 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 @@ static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName 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 @@ static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName 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 @@ static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName 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 @@ static void test_tiny_query_bounds_assertion_bug(skiatest::Reporter* reporter) { 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()); } |