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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,24 +91,48 @@ 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];
|
||||
}
|
||||
|
||||
SkConservativeClip& clip() {
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
void popEntry(FatVector<DeferredEntry<T>, N>* stack) {
|
||||
if (!(stack->back().deferredSaveCount--)) {
|
||||
stack->pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user