Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c5c96fd authored by Michael Ludwig's avatar Michael Ludwig
Browse files

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
parent 7ac030ed
Loading
Loading
Loading
Loading
+58 −36
Original line number Diff line number Diff line
@@ -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
+60 −16
Original line number Diff line number Diff line
@@ -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 {