diff options
-rw-r--r-- | libs/hwui/canvas/CanvasFrontend.cpp | 94 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasFrontend.h | 76 |
2 files changed, 118 insertions, 52 deletions
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp index dd01a5b15da5..112ac124ffa1 100644 --- a/libs/hwui/canvas/CanvasFrontend.cpp +++ b/libs/hwui/canvas/CanvasFrontend.cpp @@ -30,41 +30,61 @@ void CanvasStateHelper::resetState(int width, int height) { mClipStack.clear(); mTransformStack.clear(); mSaveStack.emplace_back(); - mClipStack.emplace_back().setRect(mInitialBounds); + mClipStack.emplace_back(); mTransformStack.emplace_back(); - mCurrentClipIndex = 0; - mCurrentTransformIndex = 0; + + clip().bounds = mInitialBounds; } bool CanvasStateHelper::internalSave(SaveEntry saveEntry) { mSaveStack.push_back(saveEntry); if (saveEntry.matrix) { - // We need to push before accessing transform() to ensure the reference doesn't move - // across vector resizes - mTransformStack.emplace_back() = transform(); - mCurrentTransformIndex += 1; + pushEntry(&mTransformStack); } if (saveEntry.clip) { - // We need to push before accessing clip() to ensure the reference doesn't move - // across vector resizes - mClipStack.emplace_back() = clip(); - mCurrentClipIndex += 1; + pushEntry(&mClipStack); return true; } return false; } -// Assert that the cast from SkClipOp to SkRegion::Op is valid. -// SkClipOp is a subset of SkRegion::Op and only supports difference and intersect. -static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op); -static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op); +void CanvasStateHelper::ConservativeClip::apply(SkClipOp op, const SkMatrix& matrix, + const SkRect& bounds, bool aa, bool fillsBounds) { + this->aa |= aa; + + if (op == SkClipOp::kIntersect) { + SkRect devBounds; + bool rect = matrix.mapRect(&devBounds, bounds) && fillsBounds; + if (!this->bounds.intersect(aa ? devBounds.roundOut() : devBounds.round())) { + this->bounds.setEmpty(); + } + this->rect &= rect; + } else { + // Difference operations subtracts a region from the clip, so conservatively + // the bounds remain unchanged and the shape is unlikely to remain a rect. + this->rect = false; + } +} void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) { - clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false); + clip().apply(op, transform(), rect, /*aa=*/false, /*fillsBounds=*/true); } void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) { - clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true); + SkRect bounds = path.getBounds(); + if (path.isInverseFillType()) { + // Toggle op type if the path is inverse filled + op = (op == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect); + } + clip().apply(op, transform(), bounds, /*aa=*/true, /*fillsBounds=*/false); +} + +CanvasStateHelper::ConservativeClip& CanvasStateHelper::clip() { + return writableEntry(&mClipStack); +} + +SkMatrix& CanvasStateHelper::transform() { + return writableEntry(&mTransformStack); } bool CanvasStateHelper::internalRestore() { @@ -77,45 +97,47 @@ bool CanvasStateHelper::internalRestore() { mSaveStack.pop_back(); bool needsRestorePropagation = entry.layer; if (entry.matrix) { - mTransformStack.pop_back(); - mCurrentTransformIndex -= 1; + popEntry(&mTransformStack); } if (entry.clip) { - // We need to push before accessing clip() to ensure the reference doesn't move - // across vector resizes - mClipStack.pop_back(); - mCurrentClipIndex -= 1; + popEntry(&mClipStack); needsRestorePropagation = true; } return needsRestorePropagation; } SkRect CanvasStateHelper::getClipBounds() const { - SkIRect ibounds = clip().getBounds(); - - if (ibounds.isEmpty()) { - return SkRect::MakeEmpty(); - } + SkIRect bounds = clip().bounds; SkMatrix inverse; // if we can't invert the CTM, we can't return local clip bounds - if (!transform().invert(&inverse)) { + if (bounds.isEmpty() || !transform().invert(&inverse)) { return SkRect::MakeEmpty(); } - SkRect ret = SkRect::MakeEmpty(); - inverse.mapRect(&ret, SkRect::Make(ibounds)); - return ret; + return inverse.mapRect(SkRect::Make(bounds)); +} + +bool CanvasStateHelper::ConservativeClip::quickReject(const SkMatrix& matrix, + const SkRect& bounds) const { + SkRect devRect = matrix.mapRect(bounds); + return devRect.isFinite() && + SkIRect::Intersects(this->bounds, aa ? devRect.roundOut() : devRect.round()); } bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const { - // TODO: Implement - return false; + return clip().quickReject(transform(), SkRect::MakeLTRB(left, top, right, bottom)); } bool CanvasStateHelper::quickRejectPath(const SkPath& path) const { - // TODO: Implement - return false; + if (this->isClipEmpty()) { + // reject everything (prioritized above path inverse fill type). + return true; + } else { + // Don't reject inverse-filled paths, since even if they are "empty" of points/verbs, + // they fill out the entire clip. + return !path.isInverseFillType() && clip().quickReject(transform(), path.getBounds()); + } } } // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h index f9a610129d3a..9f22b900e4ab 100644 --- a/libs/hwui/canvas/CanvasFrontend.h +++ b/libs/hwui/canvas/CanvasFrontend.h @@ -21,7 +21,6 @@ #include "CanvasOpBuffer.h" #include <SaveFlags.h> -#include <SkRasterClip.h> #include <ui/FatVector.h> #include <optional> @@ -40,6 +39,26 @@ protected: bool layer : 1 = false; }; + template <typename T> + struct DeferredEntry { + T entry; + int deferredSaveCount = 0; + + DeferredEntry() = default; + DeferredEntry(const T& t) : entry(t) {} + }; + + struct ConservativeClip { + SkIRect bounds = SkIRect::MakeEmpty(); + bool rect = true; + bool aa = false; + + bool quickReject(const SkMatrix& matrix, const SkRect& bounds) const; + + void apply(SkClipOp op, const SkMatrix& matrix, const SkRect& bounds, bool aa, + bool fillsBounds); + }; + constexpr SaveEntry saveEntryForLayer() { return { .clip = true, @@ -72,23 +91,47 @@ protected: void internalClipRect(const SkRect& rect, SkClipOp op); void internalClipPath(const SkPath& path, SkClipOp op); + // The canvas' clip will never expand beyond these bounds since intersect + // and difference operations only subtract pixels. SkIRect mInitialBounds; + // Every save() gets a SaveEntry to track what needs to be restored. FatVector<SaveEntry, 6> mSaveStack; - FatVector<SkMatrix, 6> mTransformStack; - FatVector<SkConservativeClip, 6> mClipStack; + // Transform and clip entries record a deferred save count and do not + // make a new entry until that particular state is modified. + FatVector<DeferredEntry<SkMatrix>, 6> mTransformStack; + FatVector<DeferredEntry<ConservativeClip>, 6> mClipStack; - size_t mCurrentTransformIndex; - size_t mCurrentClipIndex; + const ConservativeClip& clip() const { return mClipStack.back().entry; } - const SkConservativeClip& clip() const { - return mClipStack[mCurrentClipIndex]; + ConservativeClip& clip(); + + void resetState(int width, int height); + + // Stack manipulation for transform and clip stacks + template <typename T, size_t N> + void pushEntry(FatVector<DeferredEntry<T>, N>* stack) { + stack->back().deferredSaveCount += 1; } - SkConservativeClip& clip() { - return mClipStack[mCurrentClipIndex]; + template <typename T, size_t N> + void popEntry(FatVector<DeferredEntry<T>, N>* stack) { + if (!(stack->back().deferredSaveCount--)) { + stack->pop_back(); + } } - void resetState(int width, int height); + template <typename T, size_t N> + T& writableEntry(FatVector<DeferredEntry<T>, N>* stack) { + DeferredEntry<T>& back = stack->back(); + if (back.deferredSaveCount == 0) { + return back.entry; + } else { + back.deferredSaveCount -= 1; + // saved in case references move when re-allocating vector storage + T state = back.entry; + return stack->emplace_back(state).entry; + } + } public: int saveCount() const { return mSaveStack.size(); } @@ -97,13 +140,14 @@ public: bool quickRejectRect(float left, float top, float right, float bottom) const; bool quickRejectPath(const SkPath& path) const; - const SkMatrix& transform() const { - return mTransformStack[mCurrentTransformIndex]; - } + bool isClipAA() const { return clip().aa; } + bool isClipEmpty() const { return clip().bounds.isEmpty(); } + bool isClipRect() const { return clip().rect; } + bool isClipComplex() const { return !isClipEmpty() && (isClipAA() || !isClipRect()); } - SkMatrix& transform() { - return mTransformStack[mCurrentTransformIndex]; - } + const SkMatrix& transform() const { return mTransformStack.back().entry; } + + SkMatrix& transform(); // For compat with existing HWUI Canvas interface void getMatrix(SkMatrix* outMatrix) const { |