Loading libs/hwui/canvas/CanvasFrontend.cpp +58 −36 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -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 libs/hwui/canvas/CanvasFrontend.h +60 −16 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ #include "CanvasOpBuffer.h" #include <SaveFlags.h> #include <SkRasterClip.h> #include <ui/FatVector.h> #include <optional> Loading @@ -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, Loading Loading @@ -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(); } Loading @@ -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 { Loading Loading
libs/hwui/canvas/CanvasFrontend.cpp +58 −36 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -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
libs/hwui/canvas/CanvasFrontend.h +60 −16 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ #include "CanvasOpBuffer.h" #include <SaveFlags.h> #include <SkRasterClip.h> #include <ui/FatVector.h> #include <optional> Loading @@ -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, Loading Loading @@ -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(); } Loading @@ -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 { Loading