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

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

Add MultiDisplayList + memory leak fixes

MultiDisplayList can contain either a SkiaDisplayList
or a CanvasOpBuffer. However DisplayList itself
still points to the SkiaDisplayList-only wrapper
to avoid any std::variant or std::visit overhead
just yet.

Also fixes a memory leak in CanvasFrontend from an
uninitialized std::optional and a few minor leaks
in unit tests.

Test: build & hwui_unit passes

Change-Id: Ifa6b723b6456f5d3eeac1201e76f337250103d6f
parent f78234f1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -615,6 +615,7 @@ cc_test {
        "tests/unit/CommonPoolTests.cpp",
        "tests/unit/DamageAccumulatorTests.cpp",
        "tests/unit/DeferredLayerUpdaterTests.cpp",
        "tests/unit/EglManagerTests.cpp",
        "tests/unit/FatVectorTests.cpp",
        "tests/unit/GraphicsStatsServiceTests.cpp",
        "tests/unit/LayerUpdateQueueTests.cpp",
+177 −12
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
#pragma once

#include "pipeline/skia/SkiaDisplayList.h"
#include "canvas/CanvasOpBuffer.h"

#include <memory>
#include <variant>

namespace android {
namespace uirenderer {
@@ -28,29 +30,25 @@ class Tree;
};
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;

/**
 * Data structure that holds the list of commands used in display list stream
 */
//using DisplayList = skiapipeline::SkiaDisplayList;
class DisplayList {
class SkiaDisplayListWrapper {
public:
    // Constructs an empty (invalid) DisplayList
    explicit DisplayList() {}
    explicit SkiaDisplayListWrapper() {}

    // Constructs a DisplayList from a SkiaDisplayList
    explicit DisplayList(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
    explicit SkiaDisplayListWrapper(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
        : mImpl(std::move(impl)) {}

    // Move support
    DisplayList(DisplayList&& other) : mImpl(std::move(other.mImpl)) {}
    DisplayList& operator=(DisplayList&& other) {
    SkiaDisplayListWrapper(SkiaDisplayListWrapper&& other) : mImpl(std::move(other.mImpl)) {}
    SkiaDisplayListWrapper& operator=(SkiaDisplayListWrapper&& other) {
        mImpl = std::move(other.mImpl);
        return *this;
    }

    // No copy support
    DisplayList(const DisplayList& other) = delete;
    DisplayList& operator=(const DisplayList&) = delete;
    SkiaDisplayListWrapper(const SkiaDisplayListWrapper& other) = delete;
    SkiaDisplayListWrapper& operator=(const SkiaDisplayListWrapper&) = delete;

    void updateChildren(std::function<void(RenderNode*)> updateFn) {
        mImpl->updateChildren(std::move(updateFn));
@@ -137,7 +135,7 @@ public:

    void applyColorTransform(ColorTransform transform) {
        if (mImpl) {
            mImpl->mDisplayList.applyColorTransform(transform);
            mImpl->applyColorTransform(transform);
        }
    }

@@ -145,5 +143,172 @@ private:
    std::unique_ptr<skiapipeline::SkiaDisplayList> mImpl;
};


/**
 * Data structure that holds the list of commands used in display list stream
 */
//using DisplayList = skiapipeline::SkiaDisplayList;
class MultiDisplayList {
private:
    using SkiaDisplayList = skiapipeline::SkiaDisplayList;

    struct EmptyList {
        bool hasText() const { return false; }
        void updateChildren(std::function<void(RenderNode*)> updateFn) {}
        bool isEmpty() const { return true; }
        bool containsProjectionReceiver() const { return false; }
        bool hasVectorDrawables() const { return false; }
        size_t getUsedSize() const { return 0; }
        size_t getAllocatedSize() const { return 0; }
        void output(std::ostream& output, uint32_t level) const { }
        bool hasFunctor() const { return false; }
        bool prepareListAndChildren(
                TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
                std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
            return false;
        }
        void syncContents(const WebViewSyncData& data) { }
        void applyColorTransform(ColorTransform transform) { }
    };

    std::variant<EmptyList, std::unique_ptr<SkiaDisplayList>, CanvasOpBuffer> mImpls;

    template <typename T>
    static constexpr T& get(T& t) { return t; }
    template <typename T>
    static constexpr const T& get(const T& t) { return t; }

    template <typename T>
    static constexpr T& get(std::unique_ptr<T>& t) { return *t; }
    template <typename T>
    static constexpr const T& get(const std::unique_ptr<T>& t) { return *t; }

    template <typename T>
    auto apply(T&& t) {
        return std::visit([&t](auto& it) -> auto {
            return t(get(it));
        }, mImpls);
    }

    template <typename T>
    auto apply(T&& t) const {
        return std::visit([&t](const auto& it) -> auto {
            return t(get(it));
        }, mImpls);
    }

public:
    // Constructs an empty (invalid) DisplayList
    explicit MultiDisplayList() {}

    // Constructs a DisplayList from a SkiaDisplayList
    explicit MultiDisplayList(std::unique_ptr<SkiaDisplayList> impl)
        : mImpls(std::move(impl)) {}

    explicit MultiDisplayList(CanvasOpBuffer&& opBuffer) : mImpls(std::move(opBuffer)) {}

    // Move support
    MultiDisplayList(MultiDisplayList&& other) : mImpls(std::move(other.mImpls)) {}
    MultiDisplayList& operator=(MultiDisplayList&& other) {
        mImpls = std::move(other.mImpls);
        return *this;
    }

    // No copy support
    MultiDisplayList(const MultiDisplayList& other) = delete;
    MultiDisplayList& operator=(const MultiDisplayList&) = delete;

    void updateChildren(std::function<void(RenderNode*)> updateFn) {
        apply([&](auto& it) { it.updateChildren(std::move(updateFn)); });
    }

    [[nodiscard]] explicit operator bool() const {
        return isValid();
    }

    // If true this DisplayList contains a backing content, even if that content is empty
    // If false, there this DisplayList is in an "empty" state
    [[nodiscard]] bool isValid() const {
        return mImpls.index() != 0;
    }

    [[nodiscard]] bool isEmpty() const {
        return apply([](const auto& it) -> auto { return it.isEmpty(); });
    }

    [[nodiscard]] bool hasContent() const {
        return !isEmpty();
    }

    [[nodiscard]] bool containsProjectionReceiver() const {
        return apply([](const auto& it) -> auto { return it.containsProjectionReceiver(); });
    }

    [[nodiscard]] SkiaDisplayList* asSkiaDl() {
        return std::get<1>(mImpls).get();
    }

    [[nodiscard]] const SkiaDisplayList* asSkiaDl() const {
        return std::get<1>(mImpls).get();
    }

    [[nodiscard]] bool hasVectorDrawables() const {
        return apply([](const auto& it) -> auto { return it.hasVectorDrawables(); });
    }

    void clear(RenderNode* owningNode = nullptr) {
        if (owningNode && mImpls.index() == 1) {
            auto& skiaDl = std::get<1>(mImpls);
            if (skiaDl->reuseDisplayList(owningNode)) {
                skiaDl.release();
            }
        }
        mImpls = EmptyList{};
    }

    [[nodiscard]] size_t getUsedSize() const {
        return apply([](const auto& it) -> auto { return it.getUsedSize(); });
    }

    [[nodiscard]] size_t getAllocatedSize() const {
        return apply([](const auto& it) -> auto { return it.getAllocatedSize(); });
    }

    void output(std::ostream& output, uint32_t level) const {
        apply([&](const auto& it) { it.output(output, level); });
    }

    [[nodiscard]] bool hasFunctor() const {
        return apply([](const auto& it) -> auto { return it.hasFunctor(); });
    }

    bool prepareListAndChildren(
            TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
            std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
        return apply([&](auto& it) -> auto {
            return it.prepareListAndChildren(observer, info, functorsNeedLayer, std::move(childFn));
        });
    }

    void syncContents(const WebViewSyncData& data) {
        apply([&](auto& it) { it.syncContents(data); });
    }

    [[nodiscard]] bool hasText() const {
        return apply([](const auto& it) -> auto { return it.hasText(); });
    }

    void applyColorTransform(ColorTransform transform) {
        apply([=](auto& it) { it.applyColorTransform(transform); });
    }

    [[nodiscard]] CanvasOpBuffer& asOpBuffer() {
        return std::get<CanvasOpBuffer>(mImpls);
    }
};

// For now stick to the original single-type container to avoid any regressions
using DisplayList = SkiaDisplayListWrapper;

}  // namespace uirenderer
}  // namespace android
+6 −3
Original line number Diff line number Diff line
@@ -147,8 +147,7 @@ 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;
            mReceiver(std::in_place, std::forward<Args>(args)...) { }

    void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
        if (internalSave(flagsToSaveEntry(flags))) {
@@ -186,7 +185,10 @@ public:
        submit(std::move(op));
    }

    const CanvasOpReceiver& receiver() const { return *mReceiver; }
    const CanvasOpReceiver& receiver() const {
        LOG_ALWAYS_FATAL_IF(!mReceiver.has_value());
        return *mReceiver;
    }

    CanvasOpReceiver finish() {
        auto ret = std::move(mReceiver.value());
@@ -205,6 +207,7 @@ private:

    template <CanvasOpType T>
    void submit(CanvasOp<T>&& op) {
        LOG_ALWAYS_FATAL_IF(!mReceiver.has_value());
        mReceiver->push_container(CanvasOpContainer(std::move(op), transform()));
    }
};
+28 −0
Original line number Diff line number Diff line
@@ -22,4 +22,32 @@ namespace android::uirenderer {

template class OpBuffer<CanvasOpType, CanvasOpContainer>;

void CanvasOpBuffer::updateChildren(std::function<void(RenderNode*)> updateFn) {
    // TODO: Do we need a fast-path for finding children?
    if (mHas.children) {
        for (auto& iter : filter<CanvasOpType::DrawRenderNode>()) {
            updateFn(iter->renderNode.get());
        }
    }
}

void CanvasOpBuffer::output(std::ostream& output, uint32_t level) const {
    LOG_ALWAYS_FATAL("TODO");
}

bool CanvasOpBuffer::prepareListAndChildren(
            TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
            std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
    LOG_ALWAYS_FATAL("TODO");
    return false;
}

void CanvasOpBuffer::syncContents(const WebViewSyncData& data) {
    LOG_ALWAYS_FATAL("TODO");
}

void CanvasOpBuffer::applyColorTransform(ColorTransform transform) {
    LOG_ALWAYS_FATAL("TODO");
}

}  // namespace android::uirenderer
+70 −1
Original line number Diff line number Diff line
@@ -19,10 +19,17 @@
#include <SkMatrix.h>

#include "CanvasOpTypes.h"
#include "CanvasTransform.h"
#include "OpBuffer.h"
#include "TreeInfo.h"
#include "private/hwui/WebViewFunctor.h"

#include <functional>

namespace android::uirenderer {

class RenderNode;

template <CanvasOpType T>
struct CanvasOp;

@@ -53,12 +60,74 @@ public:
};

extern template class OpBuffer<CanvasOpType, CanvasOpContainer>;
class CanvasOpBuffer final : public OpBuffer<CanvasOpType, CanvasOpContainer> {
class CanvasOpBuffer final : private OpBuffer<CanvasOpType, CanvasOpContainer> {
private:
    using SUPER = OpBuffer<CanvasOpType, CanvasOpContainer>;

public:
    // Expose select superclass methods publicly
    using SUPER::for_each;
    using SUPER::size;
    using SUPER::resize;

    template <CanvasOpType T>
    void push(CanvasOp<T>&& op) {
        push_container(CanvasOpContainer<T>(std::move(op)));
    }

    template <CanvasOpType T>
    void push_container(CanvasOpContainer<T>&& op) {
        if constexpr (IsDrawOp(T)) {
            mHas.content = true;
        }
        if constexpr (T == CanvasOpType::DrawRenderNode) {
            mHas.children = true;
            // use staging property, since recording on UI thread
            if (op->renderNode->stagingProperties().isProjectionReceiver()) {
                mHas.projectionReceiver = true;
            }
        }
        SUPER::push_container(std::move(op));
    }

    void clear() {
        mHas = Contains{};
        SUPER::clear();
    }

    void updateChildren(std::function<void(RenderNode*)> updateFn);
    bool prepareListAndChildren(
            TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
            std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn);
    void syncContents(const WebViewSyncData& data);
    void applyColorTransform(ColorTransform transform);

    [[nodiscard]] bool isEmpty() const { return !mHas.content; }
    [[nodiscard]] bool hasText() const { return mHas.text; }
    [[nodiscard]] bool hasVectorDrawables() const { return mHas.vectorDrawable; }
    [[nodiscard]] bool containsProjectionReceiver() const { return mHas.projectionReceiver; }
    [[nodiscard]] bool hasFunctor() const { return mHas.functor; }

    [[nodiscard]] size_t getUsedSize() const {
        return size();
    }

    [[nodiscard]] size_t getAllocatedSize() const {
        return capacity();
    }

    void output(std::ostream& output, uint32_t level) const;

private:
    struct Contains {
        bool content : 1 = false;
        bool children : 1 = false;
        bool projectionReceiver : 1 = false;
        bool text : 1 = false;
        bool vectorDrawable : 1 = false;
        bool functor : 1 = false;
    };
    Contains mHas;
};

}  // namespace android::uirenderer
Loading