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

Commit dc95f101 authored by John Reck's avatar John Reck
Browse files

Introduce CanvasFrontend

Add a front-end to convert between "stateful" canvas
and "semi-statelss" CanvasOps. Handles tracking
save/restores, clips, and transforms, and injects
the transform on all draw ops.

Test: CanvasFrontend unit tests

Change-Id: Ifbcd14600690f10717047c50954ab54b4fc27aee
parent be41f05d
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -429,6 +429,7 @@ cc_defaults {
    whole_static_libs: ["libskia"],
    whole_static_libs: ["libskia"],


    srcs: [
    srcs: [
        "canvas/CanvasFrontend.cpp",
        "canvas/CanvasOpBuffer.cpp",
        "canvas/CanvasOpBuffer.cpp",
        "canvas/CanvasOpRasterizer.cpp",
        "canvas/CanvasOpRasterizer.cpp",
        "pipeline/skia/SkiaDisplayList.cpp",
        "pipeline/skia/SkiaDisplayList.cpp",
@@ -607,6 +608,7 @@ cc_test {
        "tests/unit/CacheManagerTests.cpp",
        "tests/unit/CacheManagerTests.cpp",
        "tests/unit/CanvasContextTests.cpp",
        "tests/unit/CanvasContextTests.cpp",
        "tests/unit/CanvasOpTests.cpp",
        "tests/unit/CanvasOpTests.cpp",
        "tests/unit/CanvasFrontendTests.cpp",
        "tests/unit/CommonPoolTests.cpp",
        "tests/unit/CommonPoolTests.cpp",
        "tests/unit/DamageAccumulatorTests.cpp",
        "tests/unit/DamageAccumulatorTests.cpp",
        "tests/unit/DeferredLayerUpdaterTests.cpp",
        "tests/unit/DeferredLayerUpdaterTests.cpp",

libs/hwui/SaveFlags.h

0 → 100644
+36 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <inttypes.h>

// TODO: Move this to an enum class
namespace android::SaveFlags {

// These must match the corresponding Canvas API constants.
enum {
    Matrix = 0x01,
    Clip = 0x02,
    HasAlphaLayer = 0x04,
    ClipToLayer = 0x10,

    // Helper constant
    MatrixClip = Matrix | Clip,
};
typedef uint32_t Flags;

}  // namespace android::SaveFlags
+117 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CanvasFrontend.h"
#include "CanvasOps.h"
#include "CanvasOpBuffer.h"

namespace android::uirenderer {

CanvasStateHelper::CanvasStateHelper(int width, int height) {
    mInitialBounds = SkIRect::MakeWH(width, height);
    mSaveStack.emplace_back();
    mClipStack.emplace_back().setRect(mInitialBounds);
    mTransformStack.emplace_back();
    mCurrentClipIndex = 0;
    mCurrentTransformIndex = 0;
}

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;
    }
    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;
        return true;
    }
    return false;
}

// Assert that the cast from SkClipOp to SkRegion::Op is valid
static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);

void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
    clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
}

void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
    clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
}

bool CanvasStateHelper::internalRestore() {
    // Prevent underflows
    if (saveCount() <= 1) {
        return false;
    }

    SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
    mSaveStack.pop_back();
    bool needsRestorePropagation = entry.layer;
    if (entry.matrix) {
        mTransformStack.pop_back();
        mCurrentTransformIndex -= 1;
    }
    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;
        needsRestorePropagation = true;
    }
    return needsRestorePropagation;
}

SkRect CanvasStateHelper::getClipBounds() const {
    SkIRect ibounds = clip().getBounds();

    if (ibounds.isEmpty()) {
        return SkRect::MakeEmpty();
    }

    SkMatrix inverse;
    // if we can't invert the CTM, we can't return local clip bounds
    if (!transform().invert(&inverse)) {
        return SkRect::MakeEmpty();
    }

    SkRect ret = SkRect::MakeEmpty();
    inverse.mapRect(&ret, SkRect::Make(ibounds));
    return ret;
}

bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
    // TODO: Implement
    return false;
}

bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
    // TODO: Implement
    return false;
}

} // namespace android::uirenderer
+200 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

// TODO: Can we get the dependencies scoped down more?
#include "CanvasOps.h"
#include "CanvasOpBuffer.h"
#include <SaveFlags.h>

#include <SkRasterClip.h>
#include <ui/FatVector.h>

#include <optional>

namespace android::uirenderer {

// Exists to avoid forcing all this common logic into the templated class
class CanvasStateHelper {
protected:
    CanvasStateHelper(int width, int height);
    ~CanvasStateHelper() = default;

    struct SaveEntry {
        bool clip : 1 = false;
        bool matrix : 1 = false;
        bool layer : 1 = false;
    };

    constexpr SaveEntry saveEntryForLayer() {
        return {
            .clip = true,
            .matrix = true,
            .layer = true,
        };
    }

    constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) {
        return SaveEntry {
            .clip = static_cast<bool>(flags & SaveFlags::Clip),
            .matrix = static_cast<bool>(flags & SaveFlags::Matrix),
            .layer = false
        };
    }

    bool internalSave(SaveEntry saveEntry);
    bool internalSave(SaveFlags::Flags flags) {
        return internalSave(flagsToSaveEntry(flags));
    }
    void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) {
        internalSave({
            .clip = true,
            .matrix = true,
            .layer = true
        });
        internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect);
    }

    bool internalRestore();

    void internalClipRect(const SkRect& rect, SkClipOp op);
    void internalClipPath(const SkPath& path, SkClipOp op);

    SkIRect mInitialBounds;
    FatVector<SaveEntry, 6> mSaveStack;
    FatVector<SkMatrix, 6> mTransformStack;
    FatVector<SkConservativeClip, 6> mClipStack;

    size_t mCurrentTransformIndex;
    size_t mCurrentClipIndex;

    const SkConservativeClip& clip() const {
        return mClipStack[mCurrentClipIndex];
    }

    SkConservativeClip& clip() {
        return mClipStack[mCurrentClipIndex];
    }

public:
    int saveCount() const { return mSaveStack.size(); }

    SkRect getClipBounds() const;
    bool quickRejectRect(float left, float top, float right, float bottom) const;
    bool quickRejectPath(const SkPath& path) const;

    const SkMatrix& transform() const {
        return mTransformStack[mCurrentTransformIndex];
    }

    SkMatrix& transform() {
        return mTransformStack[mCurrentTransformIndex];
    }

    // For compat with existing HWUI Canvas interface
    void getMatrix(SkMatrix* outMatrix) const {
        *outMatrix = transform();
    }

    void setMatrix(const SkMatrix& matrix) {
        transform() = matrix;
    }

    void concat(const SkMatrix& matrix) {
        transform().preConcat(matrix);
    }

    void rotate(float degrees) {
        SkMatrix m;
        m.setRotate(degrees);
        concat(m);
    }

    void scale(float sx, float sy) {
        SkMatrix m;
        m.setScale(sx, sy);
        concat(m);
    }

    void skew(float sx, float sy) {
        SkMatrix m;
        m.setSkew(sx, sy);
        concat(m);
    }

    void translate(float dx, float dy) {
        transform().preTranslate(dx, dy);
    }
};

// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream
template <typename CanvasOpReceiver>
class CanvasFrontend final : public CanvasStateHelper {
public:
    template<class... Args>
    CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
            mReceiver(std::forward<Args>(args)...) { }
    ~CanvasFrontend() = default;

    void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
        if (internalSave(flagsToSaveEntry(flags))) {
            submit<CanvasOpType::Save>({});
        }
    }

    void restore() {
        if (internalRestore()) {
            submit<CanvasOpType::Restore>({});
        }
    }

    template <CanvasOpType T>
    void draw(CanvasOp<T>&& op) {
        // The front-end requires going through certain front-doors, which these aren't.
        static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead");
        static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead");

        if constexpr (T == CanvasOpType::SaveLayer) {
            internalSaveLayer(op.saveLayerRec);
        }
        if constexpr (T == CanvasOpType::SaveBehind) {
            // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save
            // But we do want to flag it as a layer, such that restore is Definitely Required
            internalSave(saveEntryForLayer());
        }
        if constexpr (T == CanvasOpType::ClipRect) {
            internalClipRect(op.rect, op.op);
        }
        if constexpr (T == CanvasOpType::ClipPath) {
            internalClipPath(op.path, op.op);
        }

        submit(std::move(op));
    }

    const CanvasOpReceiver& receiver() const { return mReceiver; }

private:
    CanvasOpReceiver mReceiver;

    template <CanvasOpType T>
    void submit(CanvasOp<T>&& op) {
        mReceiver.push_container(CanvasOpContainer(std::move(op), transform()));
    }
};

} // namespace android::uirenderer
+22 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CanvasOpRecorder.h"

#include "CanvasOpBuffer.h"
#include "CanvasOps.h"

namespace android::uirenderer {}  // namespace android::uirenderer
Loading