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
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
index d749d2f..f9a6101 100644
--- a/libs/hwui/canvas/CanvasFrontend.h
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -147,8 +147,7 @@
 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 @@
         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 @@
 
     template <CanvasOpType T>
     void submit(CanvasOp<T>&& op) {
+        LOG_ALWAYS_FATAL_IF(!mReceiver.has_value());
         mReceiver->push_container(CanvasOpContainer(std::move(op), transform()));
     }
 };
diff --git a/libs/hwui/canvas/CanvasOpBuffer.cpp b/libs/hwui/canvas/CanvasOpBuffer.cpp
index 7054e47e..6089c572 100644
--- a/libs/hwui/canvas/CanvasOpBuffer.cpp
+++ b/libs/hwui/canvas/CanvasOpBuffer.cpp
@@ -22,4 +22,32 @@
 
 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
diff --git a/libs/hwui/canvas/CanvasOpBuffer.h b/libs/hwui/canvas/CanvasOpBuffer.h
index 07e079a..af797ca 100644
--- a/libs/hwui/canvas/CanvasOpBuffer.h
+++ b/libs/hwui/canvas/CanvasOpBuffer.h
@@ -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 @@
 };
 
 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
diff --git a/libs/hwui/canvas/CanvasOpRasterizer.cpp b/libs/hwui/canvas/CanvasOpRasterizer.cpp
index 0093c38..9297604 100644
--- a/libs/hwui/canvas/CanvasOpRasterizer.cpp
+++ b/libs/hwui/canvas/CanvasOpRasterizer.cpp
@@ -33,21 +33,15 @@
     SkMatrix& currentGlobalTransform = globalMatrixStack.emplace_back(SkMatrix::I());
 
     source.for_each([&]<CanvasOpType T>(const CanvasOpContainer<T> * op) {
-        if constexpr (
-            T == CanvasOpType::BeginZ ||
-            T == CanvasOpType::EndZ   ||
-            T == CanvasOpType::DrawLayer
-        ) {
-            // Do beginZ or endZ
-            LOG_ALWAYS_FATAL("TODO");
-            return;
-        } else {
+        if constexpr (CanvasOpTraits::can_draw<CanvasOp<T>>) {
             // Generic OP
             // First apply the current transformation
             destination->setMatrix(SkMatrix::Concat(currentGlobalTransform, op->transform()));
             // Now draw it
             (*op)->draw(destination);
+            return;
         }
+        LOG_ALWAYS_FATAL("TODO, unable to rasterize %d", static_cast<int>(T));
     });
 }
 
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index cde50bd..b55ef9d 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -35,7 +35,8 @@
     ClipPath,
 
     // Drawing ops
-    DrawColor,
+    DRAW_OP_BEGIN,
+    DrawColor = DRAW_OP_BEGIN,
     DrawRect,
     DrawRegion,
     DrawRoundRect,
@@ -59,10 +60,16 @@
     DrawImageLattice,
     DrawPicture,
     DrawLayer,
+    DrawRenderNode,
+    DRAW_OP_END = DrawRenderNode,
 
     // TODO: Rest
 
     COUNT  // must be last
 };
 
+static constexpr bool IsDrawOp(CanvasOpType t) {
+    return CanvasOpType::DRAW_OP_BEGIN <= t && t <= CanvasOpType::DRAW_OP_END;
+}
+
 }  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index fa0c45b..07d7dc0 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -24,13 +24,15 @@
 #include <SkImage.h>
 #include <SkPicture.h>
 #include <SkRuntimeEffect.h>
-#include <hwui/Bitmap.h>
-#include <log/log.h>
-#include "CanvasProperty.h"
-#include "Points.h"
 
+#include <log/log.h>
+
+#include "hwui/Bitmap.h"
+#include "CanvasProperty.h"
 #include "CanvasOpTypes.h"
 #include "Layer.h"
+#include "Points.h"
+#include "RenderNode.h"
 
 #include <experimental/type_traits>
 #include <utility>
@@ -440,6 +442,11 @@
     sp<Layer> layer;
 };
 
+template<>
+struct CanvasOp<CanvasOpType::DrawRenderNode> {
+    sp<RenderNode> renderNode;
+};
+
 // cleanup our macros
 #undef ASSERT_DRAWABLE
 
diff --git a/libs/hwui/canvas/OpBuffer.h b/libs/hwui/canvas/OpBuffer.h
index 1237d69..8b5cdbb 100644
--- a/libs/hwui/canvas/OpBuffer.h
+++ b/libs/hwui/canvas/OpBuffer.h
@@ -64,7 +64,7 @@
     static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
     using ItemHeader = OpBufferItemHeader<ItemTypes>;
 
-    OpBuffer() = default;
+    explicit OpBuffer() = default;
 
     // Prevent copying by default
     OpBuffer(const OpBuffer&) = delete;
@@ -135,7 +135,7 @@
 
     template <typename F>
     void for_each(F&& f) const {
-        for_each(std::forward<F>(f), ItemTypesSequence{});
+        do_for_each(std::forward<F>(f), ItemTypesSequence{});
     }
 
     void clear();
@@ -225,7 +225,7 @@
     }
 
     template <typename F, std::size_t... I>
-    void for_each(F&& f, std::index_sequence<I...>) const {
+    void do_for_each(F&& f, std::index_sequence<I...>) const {
         // Validate we're not empty
         if (isEmpty()) return;