Move CanvasFrontend off of SkConservativeClip
Implements clip bounds tracking for intersect and difference
directly. Also adds implementations for quickReject. Switches
the separate clip/transform stack entries to use deferred
saves so that they are only allocated when modified.
Test: existing tests still pass
Change-Id: Ic2d5b87d6f686e95bc6579750ecd63cde4a13143
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
index dd01a5b..112ac12 100644
--- a/libs/hwui/canvas/CanvasFrontend.cpp
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -30,41 +30,61 @@
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 @@
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