diff --git a/Kernel/src/renderive/base/node/Directed_Acyclic_Node.hpp b/Kernel/src/renderive/base/node/Directed_Acyclic_Node.hpp deleted file mode 100644 index 5e8c688..0000000 --- a/Kernel/src/renderive/base/node/Directed_Acyclic_Node.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -template -class Directed_Acyclic_Node { -public: - explicit Directed_Acyclic_Node( - Owner* owner = nullptr, - std::pmr::memory_resource& memory_resource = *std::pmr::get_default_resource()) noexcept - : owner_(owner), parents_(&memory_resource), children_(&memory_resource) {} - Directed_Acyclic_Node(const Directed_Acyclic_Node&) = delete; - Directed_Acyclic_Node& operator=(const Directed_Acyclic_Node&) = delete; - Directed_Acyclic_Node(Directed_Acyclic_Node&&) = delete; - Directed_Acyclic_Node& operator=(Directed_Acyclic_Node&&) = delete; - ~Directed_Acyclic_Node() { - detach(); - while (!children_.empty()) { - children_.front()->remove_parent(*this); - } - } - - void append_child(Directed_Acyclic_Node& child) { - insert_child(children_.size(), child); - } - - void insert_child(std::size_t index, Directed_Acyclic_Node& child) { - if (index > children_.size()) { - throw std::out_of_range("directed acyclic node child index"); - } - if (&child == this || child.is_ancestor_of(*this)) { - throw std::invalid_argument("directed acyclic node cycle"); - } - const auto current = std::find(children_.begin(), children_.end(), &child); - if (current != children_.end()) { - const auto current_index = static_cast(current - children_.begin()); - if (current_index < index) { - --index; - } - if (current_index == index) { - return; - } - children_.erase(current); - children_.insert(children_.begin() + static_cast(index), &child); - return; - } - children_.insert(children_.begin() + static_cast(index), &child); - child.parents_.push_back(this); - } - - void replace_parent(Directed_Acyclic_Node* parent) { - detach(); - if (parent) { - parent->append_child(*this); - } - } - - void remove_parent(Directed_Acyclic_Node& parent) noexcept { - std::erase(parents_, &parent); - std::erase(parent.children_, this); - } - - void detach() noexcept { - while (!parents_.empty()) { - remove_parent(*parents_.front()); - } - } - - bool has_parent(const Directed_Acyclic_Node& parent) const noexcept { - return std::find(parents_.begin(), parents_.end(), &parent) != parents_.end(); - } - - bool is_ancestor_of(const Directed_Acyclic_Node& node) const noexcept { - for (Directed_Acyclic_Node* child : children_) { - if (child == &node || child->is_ancestor_of(node)) { - return true; - } - } - return false; - } - - Owner* owner() noexcept { return owner_; } - const Owner* owner() const noexcept { return owner_; } - Directed_Acyclic_Node* parent() noexcept { return parents_.empty() ? nullptr : parents_.front(); } - const Directed_Acyclic_Node* parent() const noexcept { return parents_.empty() ? nullptr : parents_.front(); } - std::size_t parent_count() const noexcept { return parents_.size(); } - Directed_Acyclic_Node& parent(std::size_t index) { return *parents_.at(index); } - const Directed_Acyclic_Node& parent(std::size_t index) const { return *parents_.at(index); } - std::size_t child_count() const noexcept { return children_.size(); } - bool children_empty() const noexcept { return children_.empty(); } - Directed_Acyclic_Node& child(std::size_t index) { return *children_.at(index); } - const Directed_Acyclic_Node& child(std::size_t index) const { return *children_.at(index); } - -private: - Owner* owner_{}; - std::pmr::vector parents_; - std::pmr::vector children_; -}; diff --git a/Kernel/src/renderive/capture/Capture.hpp b/Kernel/src/renderive/capture/Capture.hpp index f00b084..49393fd 100644 --- a/Kernel/src/renderive/capture/Capture.hpp +++ b/Kernel/src/renderive/capture/Capture.hpp @@ -6,23 +6,9 @@ #include #include #include +#include "Capture_Types.hpp" #include "renderive/render_graph/Frame_Analysis.hpp" -using Capture_Session_Id = std::uint64_t; - -struct Capture_Frame_Ticket { - Capture_Session_Id session_id{}; - bool capture{}; -}; - -struct Capture_Controller_State { - Capture_Session_Id session_id{}; - std::size_t remaining_frame_count{}; - [[nodiscard]] bool enabled() const noexcept { - return remaining_frame_count != 0; - } -}; - class Capture_Controller { public: Capture_Session_Id capture_next_frame(); diff --git a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp index 2d1d243..576e716 100644 --- a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp +++ b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp @@ -13,7 +13,7 @@ #include "concept/Real_Time_Data.hpp" #include "renderive/renderable/concept/Renderable.hpp" #include "renderive/scene/base/Scene_Base.hpp" -#include "renderive/scene/base/Scene_Render_Context.hpp" +#include "renderive/scene/base/Render_Contexts.hpp" template inline constexpr bool Unique_Real_Time_Data_Types_Value = true; template @@ -92,7 +92,7 @@ public: return state; } protected: - void prepare(const Scene_Render_Context& context) override { + void prepare(const Prepare_Render_Context& context) override { discard_real_time_data(); Renderable_Type::prepare(context); } diff --git a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp index 48aea97..5998038 100644 --- a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp +++ b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp @@ -10,13 +10,29 @@ Renderable_Graph_Builder::Renderable_Graph_Builder( } Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace( + std::string logical_key, std::string name, + Prepare_Render_Node_Function function) { + return emplace_function(std::move(logical_key), std::move(name), + Render_Node_Kind::prepare, + Render_Node_Function{std::move(function)}); +} + +Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace( + std::string logical_key, std::string name, + Paint_Render_Node_Function function) { + return emplace_function(std::move(logical_key), std::move(name), + Render_Node_Kind::paint, + Render_Node_Function{std::move(function)}); +} + +Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace_function( std::string logical_key, std::string name, Render_Node_Kind kind, Render_Node_Function function) { if (logical_key.empty()) throw std::invalid_argument("render node logical key must not be empty"); if (tasks_.contains(logical_key)) throw std::invalid_argument("duplicate render node logical key"); - if (!function) + if (std::visit([](const auto& value) { return !value; }, function)) throw std::invalid_argument("render node function must not be empty"); const Render_Node_Id id = resolve_node_id_(logical_key); const Task task = builder_.emplace(id, owner_id_, std::move(name), kind); diff --git a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.hpp b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.hpp index 9669692..b7f0d61 100644 --- a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.hpp +++ b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.hpp @@ -3,10 +3,14 @@ #include #include #include +#include #include "renderive/render_graph/Render_Plan.hpp" +#include "renderive/scene/base/Render_Contexts.hpp" -struct Scene_Render_Context; -using Render_Node_Function = std::function; +using Prepare_Render_Node_Function = std::function; +using Paint_Render_Node_Function = std::function; +using Render_Node_Function = std::variant; struct Renderable_Graph { Render_Graph graph; @@ -18,8 +22,10 @@ public: using Task = Render_Graph_Builder::Task; using Node_Id_Resolver = std::function; Renderable_Graph_Builder(std::uint64_t owner_id, Node_Id_Resolver resolve_node_id); - Task emplace(std::string logical_key, std::string name, Render_Node_Kind kind, - Render_Node_Function function); + Task emplace(std::string logical_key, std::string name, + Prepare_Render_Node_Function function); + Task emplace(std::string logical_key, std::string name, + Paint_Render_Node_Function function); void precede(Task from, Task to); [[nodiscard]] Task find(std::string_view logical_key) const; [[nodiscard]] Renderable_Graph finish() &&; @@ -29,4 +35,7 @@ private: Render_Graph_Builder builder_; std::unordered_map tasks_; std::unordered_map functions_; + + Task emplace_function(std::string logical_key, std::string name, + Render_Node_Kind kind, Render_Node_Function function); }; diff --git a/Kernel/src/renderive/renderable/Renderable_Id.hpp b/Kernel/src/renderive/renderable/Renderable_Id.hpp new file mode 100644 index 0000000..63023d2 --- /dev/null +++ b/Kernel/src/renderive/renderable/Renderable_Id.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +using Renderable_Id = std::uint64_t; diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index 6239b83..799c7c5 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -14,7 +14,7 @@ void advance_revision(std::atomic& revision) noexcept { } } Renderable_Base::Renderable_Base(Scene_Base& scene, Renderable_Configuration configuration) - : memory_domain_(scene.memory_domain_), real_time_data_state_(std::allocate_shared(Scene_Memory_Allocator{memory_domain_}, scene.scene_lifetime_)), discard_stale_frame_on_latest_data_update(real_time_data_state_->discard_stale_frame_on_latest_data_update), layer_node_(this, memory_domain_->resource()), dependency_node_(this, memory_domain_->resource()), renderable_id_(scene.allocate_renderable_id()), composite_node_id_(scene.allocate_node_id()), configuration_(configuration) {} + : memory_domain_(scene.memory_domain_), real_time_data_state_(std::allocate_shared(Scene_Memory_Allocator{memory_domain_}, scene.scene_lifetime_)), discard_stale_frame_on_latest_data_update(real_time_data_state_->discard_stale_frame_on_latest_data_update), renderable_id_(scene.allocate_renderable_id()), composite_node_id_(scene.allocate_node_id()), configuration_(configuration) {} Renderable_Base::~Renderable_Base() { real_time_data_state_->renderable_alive.store(false, std::memory_order_release); } @@ -51,13 +51,13 @@ bool Renderable_Base::paint_cache_valid() const noexcept { void Renderable_Base::rebuild_render_graph() noexcept { { std::lock_guard lock(render_graph_mutex_); - render_graph_dirty_ = true; + render_graph_.reset(); } invalidate_prepare(); } std::shared_ptr Renderable_Base::render_graph() { std::lock_guard lock(render_graph_mutex_); - if (render_graph_dirty_) { + if (!render_graph_) { std::unordered_map next_identities; Renderable_Graph_Builder builder(renderable_id_, [this, &next_identities]( std::string_view key) { @@ -76,11 +76,10 @@ std::shared_ptr Renderable_Base::render_graph() { auto graph = std::make_shared(std::move(builder).finish()); node_identities_ = std::move(next_identities); render_graph_ = std::move(graph); - render_graph_dirty_ = false; } return render_graph_; } -std::uint64_t Renderable_Base::renderable_id() const noexcept { +Renderable_Id Renderable_Base::renderable_id() const noexcept { return renderable_id_; } std::pmr::memory_resource& Renderable_Base::memory_resource() const noexcept { @@ -97,26 +96,34 @@ Renderable_Configuration Renderable_Base::configuration() const noexcept { std::lock_guard lock(configuration_mutex_); return configuration_; } +bool Renderable_Base::is_visible() const noexcept { + std::lock_guard lock(configuration_mutex_); + return visible_; +} +void Renderable_Base::set_visible(bool visible) { + { + std::lock_guard lock(configuration_mutex_); + if (visible_ == visible) + return; + visible_ = visible; + invalidate_prepare(); + } + scene().notify_model_dirty(); +} void Renderable_Base::build_prepare_graph(Renderable_Graph_Builder& builder) { - builder.emplace("prepare", "Prepare", Render_Node_Kind::prepare, - [this](const Scene_Render_Context& context) { + builder.emplace("prepare", "Prepare", + [this](const Prepare_Render_Context& context) { prepare(context); }); } void Renderable_Base::build_paint_graph(Renderable_Graph_Builder&) {} -void Renderable_Base::prepare(const Scene_Render_Context&) {} +void Renderable_Base::prepare(const Prepare_Render_Context&) {} Render_State_View Renderable_Base::render_state_view() noexcept { return {}; } Real_Time_Data_Binding Renderable_Base::bind_real_time_data(Real_Time_Data_Base& data) { return data.bind_renderable(*this); } -bool Renderable_Base::requires_prepare() const noexcept { - return !prepare_cache_valid(); -} -bool Renderable_Base::requires_paint() const noexcept { - return !paint_cache_valid(); -} void Renderable_Base::mark_prepared(std::uint64_t revision) noexcept { prepared_revision_.store(revision, std::memory_order_release); } @@ -125,10 +132,6 @@ void Renderable_Base::mark_painted(std::uint64_t paint_revision, painted_prepare_revision_.store(prepare_revision, std::memory_order_release); painted_revision_.store(paint_revision, std::memory_order_release); } -void Renderable_Base::set_configuration(Renderable_Configuration configuration) noexcept { - std::lock_guard lock(configuration_mutex_); - configuration_ = configuration; -} void Renderable_Base::register_real_time_data(Real_Time_Data_Base& data) { std::lock_guard lock(real_time_data_mutex_); if (std::find(real_time_data_.begin(), real_time_data_.end(), &data) == real_time_data_.end()) { diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp index e71f982..bcb5bb5 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp @@ -9,7 +9,8 @@ #include #include #include "renderive/base/memory/Memory_Resource.hpp" -#include "renderive/base/node/Directed_Acyclic_Node.hpp" +#include "renderive/renderable/Renderable_Configuration.hpp" +#include "renderive/renderable/Renderable_Id.hpp" #include "renderive/renderable/Renderable_Graph_Builder.hpp" #include "renderive/scene/base/Scene_Lifetime.hpp" #include "renderive/state/Render_State_View.hpp" @@ -17,12 +18,7 @@ class Frame_Strategy_Real_Time_Data_Observer; class Real_Time_Data_Base; class Real_Time_Data_Binding; class Scene_Base; -struct Renderable_Layer_Node_Tag {}; -struct Renderable_Dependency_Node_Tag {}; -struct Renderable_Configuration { - bool cache_enabled{false}; -}; -class Renderable_Base { +class Renderable_Base : public std::enable_shared_from_this { private: struct Real_Time_Data_State { explicit Real_Time_Data_State(std::shared_ptr scene_lifetime) : scene_lifetime(std::move(scene_lifetime)) {} @@ -33,8 +29,6 @@ private: std::shared_ptr memory_domain_; std::shared_ptr real_time_data_state_; public: - using Layer_Node = Directed_Acyclic_Node; - using Dependency_Node = Directed_Acyclic_Node; explicit Renderable_Base(Scene_Base& scene, Renderable_Configuration configuration = {}); virtual ~Renderable_Base(); void invalidate_prepare() noexcept; @@ -45,15 +39,17 @@ public: [[nodiscard]] std::uint64_t painted_revision() const noexcept; [[nodiscard]] bool prepare_cache_valid() const noexcept; [[nodiscard]] bool paint_cache_valid() const noexcept; - [[nodiscard]] std::uint64_t renderable_id() const noexcept; + [[nodiscard]] Renderable_Id renderable_id() const noexcept; std::pmr::memory_resource& memory_resource() const noexcept; Scene_Base& scene() const; Renderable_Configuration configuration() const noexcept; + [[nodiscard]] bool is_visible() const noexcept; + void set_visible(bool visible); std::atomic& discard_stale_frame_on_latest_data_update; protected: virtual void build_prepare_graph(Renderable_Graph_Builder& builder); virtual void build_paint_graph(Renderable_Graph_Builder& builder); - virtual void prepare(const Scene_Render_Context& context); + virtual void prepare(const Prepare_Render_Context& context); void rebuild_render_graph() noexcept; [[nodiscard]] std::shared_ptr render_graph(); [[nodiscard]] static Render_State_View render_state_view() noexcept; @@ -62,25 +58,20 @@ private: friend class Frame_Strategy_Real_Time_Data_Observer; friend class Real_Time_Data_Binding; friend class Scene_Base; - bool requires_prepare() const noexcept; - bool requires_paint() const noexcept; void mark_prepared(std::uint64_t revision) noexcept; void mark_painted(std::uint64_t paint_revision, std::uint64_t prepare_revision) noexcept; - void set_configuration(Renderable_Configuration configuration) noexcept; void register_real_time_data(Real_Time_Data_Base& data); void unregister_real_time_data(Real_Time_Data_Base& data) noexcept; void publish_real_time_data(); - Layer_Node layer_node_; - Dependency_Node dependency_node_; - const std::uint64_t renderable_id_; + const Renderable_Id renderable_id_; const Render_Node_Id composite_node_id_; mutable std::mutex configuration_mutex_; Renderable_Configuration configuration_; + bool visible_{true}; std::mutex render_graph_mutex_; std::shared_ptr render_graph_; std::unordered_map node_identities_; - bool render_graph_dirty_{true}; std::atomic prepare_revision_{1}; std::atomic prepared_revision_{}; std::atomic paint_revision_{1}; diff --git a/Kernel/src/renderive/scene/Scene.hpp b/Kernel/src/renderive/scene/Scene.hpp index 4aceaed..e0bba80 100644 --- a/Kernel/src/renderive/scene/Scene.hpp +++ b/Kernel/src/renderive/scene/Scene.hpp @@ -2,5 +2,5 @@ #include "Scene2D_Context.hpp" #include "Scene3D_Context.hpp" #include "base/Scene_Base.hpp" -#include "base/Scene_Render_Context.hpp" +#include "base/Render_Contexts.hpp" #include "concept/Scene.hpp" diff --git a/Kernel/src/renderive/scene/Scene2D_Context.hpp b/Kernel/src/renderive/scene/Scene2D_Context.hpp index 157e729..c20d1d8 100644 --- a/Kernel/src/renderive/scene/Scene2D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene2D_Context.hpp @@ -2,11 +2,9 @@ #include #include #include +#include #include -#include #include -#include -#include "renderive/base/memory/Memory_Resource.hpp" #include "renderive/base/observer/Observer.hpp" #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/renderable/color/Concepts.hpp" @@ -28,9 +26,6 @@ requires Frame_Control_Strategy_For && Scene2D_Sta class Scene2D_Context : public Double_State_Strategy, public Scene_Compositor { public: using Scene_State_Strategy = Double_State_Strategy; - using Render_Task = Scene_Base::Render_Task; - using Renderable = Scene_Base::Renderable; - using Renderable_List = Scene_Base::Renderable_List; using Frame_Control = Strategy; using Frame = typename Frame_Control::Frame; using Painter_Lease = typename Frame_Control::Painter_Lease; @@ -41,7 +36,7 @@ public: template requires Scene2D_Frame_Control_Constructible explicit Scene2D_Context(std::pmr::memory_resource& memory_resource, Args&&... args) - : Scene_State_Strategy(State{}, memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), display_root_(nullptr, this->memory_resource()), dependency_root_(nullptr, this->memory_resource()), color_caches_(&this->memory_resource()) {} + : Scene_State_Strategy(State{}, memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())) {} template requires Scene2D_Frame_Control_Constructible Scene2D_Context(With_Observer state_observer, With_Observer scene_observer, Args&&... args) @@ -49,7 +44,7 @@ public: template requires Scene2D_Frame_Control_Constructible Scene2D_Context(std::pmr::memory_resource& memory_resource, With_Observer state_observer, With_Observer scene_observer, Args&&... args) - : Scene_State_Strategy(State{}, std::move(state_observer), memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), display_root_(nullptr, this->memory_resource()), dependency_root_(nullptr, this->memory_resource()), scene_observer_(std::move(scene_observer.observer)), color_caches_(&this->memory_resource()) {} + : Scene_State_Strategy(State{}, std::move(state_observer), memory_resource), frame_control(make_frame_control(this->memory_resource(), std::forward(args)...)), final_color_cache_(make_cache(this->memory_resource())), scene_observer_(std::move(scene_observer.observer)) {} ~Scene2D_Context() override { this->shutdown(); } @@ -70,63 +65,18 @@ protected: const Frame_Control_Strategy_Base& frame_control_strategy_impl() const noexcept override { return frame_control; } - Renderable_Base::Layer_Node* display_root_node() noexcept override { - return &display_root_; + std::shared_ptr make_renderable_color_cache() override { + return make_cache_pointer(this->memory_resource()); } - Renderable_Base::Dependency_Node* dependency_root_node() noexcept override { - return &dependency_root_; + Frame_Viewport frame_viewport() const override { + return this->Scene_State_Strategy::render_state_value().viewport; } - void on_renderable_attached(Renderable_Base& renderable) override { - Cache_Pointer cache = make_cache_pointer(this->memory_resource()); - auto& display_node = Scene_Base::layer_node(renderable); - auto& dependency_node = Scene_Base::dependency_node(renderable); - const bool attach_display = display_node.parent_count() == 0; - const bool attach_dependency = dependency_node.parent_count() == 0; - try { - if (attach_display) { - display_root_.append_child(display_node); - } - if (attach_dependency) { - dependency_root_.append_child(dependency_node); - } - color_caches_.try_emplace(&renderable, std::move(cache)); - } catch (...) { - if (attach_dependency) { - dependency_node.detach(); - } - if (attach_display) { - display_node.detach(); - } - throw; - } - renderable.invalidate_prepare(); - } - void on_renderable_detached(Renderable_Base& renderable) override { - promote_graph_children(Scene_Base::layer_node(renderable), display_root_); - auto& dependency = Scene_Base::dependency_node(renderable); - for (std::size_t index = 0; index < dependency.child_count(); ++index) { - dependency.child(index).owner()->invalidate_prepare(); - } - promote_graph_children(dependency, dependency_root_); - color_caches_.erase(&renderable); - } - void prepare_render_task(Render_Task& task, const Renderable_List& renderables) override { - task.render_order = Scene_Base::dependency_order(renderables); - task.display_order = Scene_Base::display_order(renderables); - task.viewport = this->Scene_State_Strategy::render_state_value().viewport; - } - Color_Cache* prepare_renderable_cache(Renderable_Base& renderable, bool clear) override { - Cache* cache = color_caches_.at(&renderable).get(); - if (clear) { - cache->clear(); - } - return cache; - } - void begin_composite(const Scene_Render_Context&) override { + void begin_composite(const Composite_Render_Context&) override { final_color_cache_.clear(); } - void composite(const Scene_Render_Context&, Renderable_Base& renderable) override { - final_color_cache_.composite(*color_caches_.at(&renderable)); + void composite(const Composite_Render_Context& context) override { + if (context.renderable && context.renderable->visible && context.color_cache) + final_color_cache_.composite(*context.color_cache); } std::uint64_t acquire_scene_state() override { return this->Scene_State_Strategy::state_revision(); @@ -138,7 +88,6 @@ protected: return scene_observer_.now_ns(); } private: - using Cache_Pointer = Memory_Resource_Unique_Ptr; template static Frame_Control make_frame_control(std::pmr::memory_resource& memory_resource, Args&&... args) { if constexpr (std::constructible_from) { @@ -154,35 +103,14 @@ private: return Cache(); } } - static Cache_Pointer make_cache_pointer(std::pmr::memory_resource& memory_resource) { + static std::shared_ptr make_cache_pointer(std::pmr::memory_resource& memory_resource) { + std::pmr::polymorphic_allocator allocator(&memory_resource); if constexpr (std::constructible_from) { - return make_memory_resource_unique(memory_resource, memory_resource); + return std::allocate_shared(allocator, memory_resource); } else { - return make_memory_resource_unique(memory_resource); + return std::allocate_shared(allocator); } } - template - void promote_graph_children(Node& node, Node& root) { - std::pmr::vector parents(&this->memory_resource()); - parents.reserve(node.parent_count()); - for (std::size_t index = 0; index < node.parent_count(); ++index) { - parents.push_back(&node.parent(index)); - } - while (!node.children_empty()) { - auto& child = node.child(0); - child.remove_parent(node); - for (auto* parent : parents) { - parent->append_child(child); - } - if (child.parent_count() == 0) { - root.append_child(child); - } - } - node.detach(); - } Cache final_color_cache_; - Renderable_Base::Layer_Node display_root_; - Renderable_Base::Dependency_Node dependency_root_; Scene_Observer scene_observer_; - std::pmr::unordered_map color_caches_; }; diff --git a/Kernel/src/renderive/scene/Scene3D_Context.hpp b/Kernel/src/renderive/scene/Scene3D_Context.hpp index 9d46705..fe25493 100644 --- a/Kernel/src/renderive/scene/Scene3D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene3D_Context.hpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "renderive/base/observer/Observer.hpp" #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/state/Triple_State_Strategy.hpp" @@ -48,14 +47,6 @@ protected: const Frame_Control_Strategy_Base& frame_control_strategy_impl() const noexcept override { return frame_control; } - void on_renderable_detached(Renderable_Base& renderable) override { - promote_graph_children(Scene_Base::layer_node(renderable)); - auto& dependency = Scene_Base::dependency_node(renderable); - for (std::size_t index = 0; index < dependency.child_count(); ++index) { - dependency.child(index).owner()->invalidate_prepare(); - } - promote_graph_children(dependency); - } std::uint64_t acquire_scene_state() override { return this->Scene_State_Strategy::acquire_render_state(); } @@ -66,22 +57,6 @@ protected: return scene_observer_.now_ns(); } private: - template - static void promote_graph_children(Node& node) { - std::vector parents; - parents.reserve(node.parent_count()); - for (std::size_t index = 0; index < node.parent_count(); ++index) { - parents.push_back(&node.parent(index)); - } - while (!node.children_empty()) { - auto& child = node.child(0); - child.remove_parent(node); - for (auto* parent : parents) { - parent->append_child(child); - } - } - node.detach(); - } template static Frame_Control make_frame_control(std::pmr::memory_resource& memory_resource, Args&&... args) { if constexpr (std::constructible_from) { diff --git a/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp b/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp index ecd4a0c..f02bfaf 100644 --- a/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp +++ b/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp @@ -4,8 +4,8 @@ #include #include #include "renderive/capture/Capture_Types.hpp" -#include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp" #include "renderive/renderable/Renderable_Configuration.hpp" +#include "renderive/renderable/Renderable_Id.hpp" #include "renderive/state/Render_State_View.hpp" #include "Frame_Viewport.hpp" @@ -16,7 +16,7 @@ struct Renderable_Graph; class Renderable_Frame_State { public: - std::uint64_t renderable_id{}; + Renderable_Id renderable_id{}; bool visible{true}; Renderable_Configuration configuration; std::uint64_t prepare_revision{}; @@ -27,7 +27,7 @@ public: bool prepare_required{}; bool paint_required{}; std::shared_ptr render_graph; - std::vector dependency_parent_ids; + std::vector dependency_parent_ids; private: friend class Scene_Base; @@ -37,14 +37,15 @@ private: class Frame_Render_Snapshot { public: - std::uint64_t frame_id{}; std::uint64_t render_sequence{}; - std::uint64_t scene_state_revision{}; Frame_Viewport viewport; - Frame_Control_Strategy_Base::State frame_control_state; - Capture_Controller_State capture_state; - Capture_Frame_Ticket capture_ticket; Render_State_View render_state; std::vector renderables; - std::vector display_order; + std::vector display_order; + +private: + friend class Scene_Base; + std::uint64_t scene_state_revision_{}; + std::uint64_t next_refresh_interval_ns_{}; + Capture_Frame_Ticket capture_ticket_; }; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 2c413b2..94ccf90 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -1,25 +1,31 @@ #include "Scene_Base.hpp" + #include -#include #include -#include +#include +#include #include #include #include #include #include #include + #include + #include "renderive/renderable/Render_Frame_Completion.hpp" #include "renderive/renderable/color/Color_Cache.hpp" #include "renderive/state/base/State_Strategy_Base.hpp" + static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0"); + namespace { -std::uint64_t allocate_id(std::atomic& next, - const char* exhausted_message) { - std::uint64_t value = next.load(std::memory_order_relaxed); + +template +Id allocate_id(std::atomic& next, const char* exhausted_message) { + Id value = next.load(std::memory_order_relaxed); for (;;) { - if (value == 0 || value == std::numeric_limits::max()) + if (value == 0 || value == std::numeric_limits::max()) throw std::overflow_error(exhausted_message); if (next.compare_exchange_weak(value, value + 1, std::memory_order_relaxed, @@ -28,57 +34,250 @@ std::uint64_t allocate_id(std::atomic& next, } } -template -Scene_Base::Renderable_List topological_order( - const Scene_Base::Renderable_List& renderables, - std::pmr::memory_resource& memory_resource, - Node_Accessor node_of, - const char* graph_name) { - std::pmr::monotonic_buffer_resource scratch_resource(&memory_resource); - std::pmr::unordered_map attached(&scratch_resource); - std::pmr::unordered_map indegree(&scratch_resource); - attached.reserve(renderables.size()); - indegree.reserve(renderables.size()); - for (const auto& renderable : renderables) { - attached.emplace(renderable.get(), renderable); - indegree.emplace(renderable.get(), 0U); - } - for (const auto& renderable : renderables) { - auto& node = node_of(*renderable); - for (std::size_t index = 0; index < node.parent_count(); ++index) { - auto* parent = node.parent(index).owner(); - if (parent && attached.contains(parent)) { - ++indegree.at(renderable.get()); - } - } - } - std::pmr::vector ready(&scratch_resource); - ready.reserve(renderables.size()); - for (const auto& renderable : renderables) { - if (indegree.at(renderable.get()) == 0) { - ready.push_back(renderable.get()); - } - } - Scene_Base::Renderable_List order(&memory_resource); - order.reserve(renderables.size()); - for (std::size_t ready_index = 0; ready_index < ready.size(); ++ready_index) { - Renderable_Base* renderable = ready[ready_index]; - order.push_back(attached.at(renderable)); - auto& node = node_of(*renderable); - for (std::size_t child_index = 0; child_index < node.child_count(); ++child_index) { - Renderable_Base* child = node.child(child_index).owner(); - auto iterator = indegree.find(child); - if (iterator != indegree.end() && --iterator->second == 0) { - ready.push_back(child); - } - } - } - if (order.size() != renderables.size()) { - throw std::logic_error(std::string(graph_name) + " graph contains a cycle"); - } - return order; +bool contains_id(const std::vector& values, Renderable_Id id) { + return std::find(values.begin(), values.end(), id) != values.end(); } + +bool contains_id(const std::pmr::vector& values, + Renderable_Id id) { + return std::find(values.begin(), values.end(), id) != values.end(); } + +} // namespace + +void Scene_Base::Id_Topology::erase_id( + std::pmr::vector& values, Renderable_Id id) noexcept { + std::erase(values, id); +} + +void Scene_Base::Id_Topology::validate_parent(Renderable_Id child, + Renderable_Id parent) const { + if (!nodes_.contains(child) || !nodes_.contains(parent)) + throw std::invalid_argument("topology endpoint is not attached"); + if (child == parent || reaches(child, parent)) + throw std::invalid_argument("topology relationship would create a cycle"); +} + +bool Scene_Base::Id_Topology::reaches(Renderable_Id from, + Renderable_Id target) const { + std::vector stack{from}; + std::unordered_set visited; + visited.reserve(nodes_.size()); + while (!stack.empty()) { + const Renderable_Id current = stack.back(); + stack.pop_back(); + if (current == target) + return true; + if (!visited.insert(current).second) + continue; + const auto iterator = nodes_.find(current); + if (iterator == nodes_.end()) + continue; + stack.insert(stack.end(), iterator->second.children.begin(), + iterator->second.children.end()); + } + return false; +} + +void Scene_Base::Id_Topology::attach( + Renderable_Id id, const std::vector& parents, + const std::vector& children) { + if (id == 0 || nodes_.contains(id)) + throw std::invalid_argument("topology node is already attached"); + std::unordered_set unique_parents; + unique_parents.reserve(parents.size()); + for (const Renderable_Id parent : parents) { + if (parent == id || !nodes_.contains(parent)) + throw std::invalid_argument("topology parent is not attached"); + if (!unique_parents.insert(parent).second) + throw std::invalid_argument("duplicate topology parent"); + } + std::unordered_set unique_children; + unique_children.reserve(children.size()); + for (const Renderable_Id child : children) { + if (child == id || !nodes_.contains(child)) + throw std::invalid_argument("topology child is not attached"); + if (!unique_children.insert(child).second) + throw std::invalid_argument("duplicate topology child"); + for (const Renderable_Id parent : parents) { + if (reaches(child, parent)) + throw std::invalid_argument( + "topology relationships would create a cycle"); + } + } + for (const Renderable_Id parent : parents) { + auto& parent_children = nodes_.at(parent).children; + parent_children.reserve(parent_children.size() + 1); + } + for (const Renderable_Id child : children) { + auto& child_parents = nodes_.at(child).parents; + child_parents.reserve(child_parents.size() + 1); + } + const auto [iterator, inserted] = + nodes_.try_emplace(id, *nodes_.get_allocator().resource()); + if (!inserted) + throw std::invalid_argument("topology node is already attached"); + try { + iterator->second.parents.reserve(parents.size()); + iterator->second.parents.insert(iterator->second.parents.end(), + parents.begin(), parents.end()); + iterator->second.children.reserve(children.size()); + iterator->second.children.insert(iterator->second.children.end(), + children.begin(), children.end()); + } catch (...) { + nodes_.erase(iterator); + throw; + } + for (const Renderable_Id parent : parents) + nodes_.at(parent).children.push_back(id); + for (const Renderable_Id child : children) + nodes_.at(child).parents.push_back(id); +} + +void Scene_Base::Id_Topology::erase(Renderable_Id id) noexcept { + const auto iterator = nodes_.find(id); + if (iterator == nodes_.end()) + return; + for (const Renderable_Id parent : iterator->second.parents) { + if (const auto node = nodes_.find(parent); node != nodes_.end()) + erase_id(node->second.children, id); + } + for (const Renderable_Id child : iterator->second.children) { + if (const auto node = nodes_.find(child); node != nodes_.end()) + erase_id(node->second.parents, id); + } + nodes_.erase(iterator); +} + +std::vector Scene_Base::Id_Topology::detach_and_promote( + Renderable_Id id) { + const auto iterator = nodes_.find(id); + if (iterator == nodes_.end()) + return {}; + const std::vector parents(iterator->second.parents.begin(), + iterator->second.parents.end()); + const std::vector children(iterator->second.children.begin(), + iterator->second.children.end()); + for (const Renderable_Id parent : parents) { + auto& parent_children = nodes_.at(parent).children; + parent_children.reserve(parent_children.size() + children.size()); + } + for (const Renderable_Id child : children) { + auto& child_parents = nodes_.at(child).parents; + child_parents.reserve(child_parents.size() + parents.size()); + } + for (const Renderable_Id parent : parents) + erase_id(nodes_.at(parent).children, id); + for (const Renderable_Id child : children) + erase_id(nodes_.at(child).parents, id); + for (const Renderable_Id parent : parents) { + auto& parent_children = nodes_.at(parent).children; + for (const Renderable_Id child : children) { + auto& child_parents = nodes_.at(child).parents; + if (!contains_id(parent_children, child)) + parent_children.push_back(child); + if (!contains_id(child_parents, parent)) + child_parents.push_back(parent); + } + } + nodes_.erase(iterator); + return children; +} + +bool Scene_Base::Id_Topology::replace_parents( + Renderable_Id child, const std::vector& parents) { + auto& child_node = nodes_.at(child); + std::unordered_set unique; + unique.reserve(parents.size()); + for (const Renderable_Id parent : parents) { + validate_parent(child, parent); + if (!unique.insert(parent).second) + throw std::invalid_argument("duplicate topology parent"); + } + if (child_node.parents.size() == parents.size() && + std::all_of(parents.begin(), parents.end(), [&](Renderable_Id parent) { + return contains_id(child_node.parents, parent); + })) + return false; + child_node.parents.reserve(parents.size()); + for (const Renderable_Id parent : parents) { + auto& children = nodes_.at(parent).children; + if (!contains_id(children, child)) + children.reserve(children.size() + 1); + } + const std::vector previous(child_node.parents.begin(), + child_node.parents.end()); + for (const Renderable_Id parent : previous) + erase_id(nodes_.at(parent).children, child); + child_node.parents.clear(); + child_node.parents.insert(child_node.parents.end(), parents.begin(), + parents.end()); + for (const Renderable_Id parent : parents) { + auto& children = nodes_.at(parent).children; + if (!contains_id(children, child)) + children.push_back(child); + } + return true; +} + +bool Scene_Base::Id_Topology::add_parent(Renderable_Id child, + Renderable_Id parent) { + validate_parent(child, parent); + auto& child_node = nodes_.at(child); + if (contains_id(child_node.parents, parent)) + return false; + auto& parent_node = nodes_.at(parent); + child_node.parents.reserve(child_node.parents.size() + 1); + parent_node.children.reserve(parent_node.children.size() + 1); + child_node.parents.push_back(parent); + parent_node.children.push_back(child); + return true; +} + +bool Scene_Base::Id_Topology::clear_parents(Renderable_Id child) { + auto& child_node = nodes_.at(child); + if (child_node.parents.empty()) + return false; + const std::vector parents(child_node.parents.begin(), + child_node.parents.end()); + for (const Renderable_Id parent : parents) + erase_id(nodes_.at(parent).children, child); + child_node.parents.clear(); + return true; +} + +const Scene_Base::Topology_Node& Scene_Base::Id_Topology::node( + Renderable_Id id) const { + return nodes_.at(id); +} + +std::vector Scene_Base::Id_Topology::order() const { + std::unordered_map indegree; + indegree.reserve(nodes_.size()); + std::priority_queue, + std::greater<>> ready; + for (const auto& [id, node] : nodes_) { + indegree.emplace(id, node.parents.size()); + if (node.parents.empty()) + ready.push(id); + } + std::vector result; + result.reserve(nodes_.size()); + while (!ready.empty()) { + const Renderable_Id id = ready.top(); + ready.pop(); + result.push_back(id); + for (const Renderable_Id child : nodes_.at(id).children) { + auto& value = indegree.at(child); + if (--value == 0) + ready.push(child); + } + } + if (result.size() != nodes_.size()) + throw std::logic_error("topology contains a cycle"); + return result; +} + class Scene_Base::Execution_Context { public: static tf::Executor& executor() { @@ -86,31 +285,49 @@ public: return executor; } }; + class Scene_Base::Render_Execution_Scope { public: - explicit Render_Execution_Scope(Scene_Base& scene) noexcept : previous_(std::exchange(active_execution_scene_, &scene)) {} + explicit Render_Execution_Scope(Scene_Base& scene) noexcept + : previous_(std::exchange(active_execution_scene_, &scene)) {} + ~Render_Execution_Scope() { active_execution_scene_ = previous_; } + private: Scene_Base* previous_{}; }; + Scene_Base::Scene_Base() : Scene_Base(*std::pmr::get_default_resource()) {} + Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource) - : scene_lifetime_(std::make_shared(*this)), memory_domain_(std::allocate_shared(std::pmr::polymorphic_allocator(&upstream_memory_resource), upstream_memory_resource)), renderable_states_{Renderable_List(&memory_domain_->resource()), Renderable_List(&memory_domain_->resource())}, render_renderables_(&renderable_states_[0]), cache_renderables_(&renderable_states_[1]), task_(memory_domain_->resource()), composite_begin_node_id_(allocate_node_id()) { - worker_ = std::thread([this] { - render_loop(); - }); + : scene_lifetime_(std::make_shared(*this)), + memory_domain_(std::allocate_shared( + std::pmr::polymorphic_allocator( + &upstream_memory_resource), + upstream_memory_resource)), + renderables_(&memory_domain_->resource()), + display_topology_(memory_domain_->resource()), + dependency_topology_(memory_domain_->resource()), + color_caches_(&memory_domain_->resource()), + task_(memory_domain_->resource()), + composite_begin_node_id_(allocate_node_id()) { + worker_ = std::thread([this] { render_loop(); }); } + Scene_Base::~Scene_Base() { shutdown(); } + void Scene_Base::render() { submit_render(nullptr); } + void Scene_Base::render(Abstract_Frame& frame) { submit_render(&frame); } + void Scene_Base::submit_render(Abstract_Frame* frame) { if (active_submitted_observer_scene_ == this) { std::lock_guard lock(task_mutex_); @@ -122,135 +339,281 @@ void Scene_Base::submit_render(Abstract_Frame* frame) { pending_exception_ = nullptr; pending_exception_observed_ = false; } - if (!pending_exception_ && current_completion_ && current_completion_->completed && current_completion_->exception && !current_completion_->observed) { + if (!pending_exception_ && current_completion_ && + current_completion_->completed && current_completion_->exception && + !current_completion_->observed) { pending_exception_ = current_completion_->exception; } - Render_Task task(memory_resource()); - { - std::lock_guard renderable_lock(renderable_mutex_); - std::swap(render_renderables_, cache_renderables_); - *cache_renderables_ = *render_renderables_; - prepare_render_task(task, *render_renderables_); - } + auto& strategy = frame_control_strategy(); strategy.swap(); - task.frame_control_state = strategy.frame_control_state(); - task.scene_state_revision = acquire_scene_state(); + auto snapshot = snapshot_live_model(); + snapshot->next_refresh_interval_ns_ = + strategy.frame_control_state().next_refresh_interval_ns; + snapshot->scene_state_revision_ = acquire_scene_state(); if (render_sequence_ == std::numeric_limits::max()) throw std::overflow_error("render sequence exhausted"); - task.render_sequence = ++render_sequence_; - if (frame) { - task.frame = frame; - } else { - task.owned_frame = std::make_shared(); - task.frame = task.owned_frame.get(); - } - task.plan = compile_render_plan(task); + snapshot->render_sequence = ++render_sequence_; + snapshot->viewport = frame_viewport(); + + Render_Task task(memory_resource()); + task.frame = frame + ? Render_Task::Frame{std::ref(*frame)} + : Render_Task::Frame{std::make_shared()}; + task.plan = compile_render_plan(*snapshot, task); task.completion = std::make_shared(); + snapshot->capture_ticket_ = capture_controller_.begin_frame(); + task.snapshot = std::move(snapshot); current_completion_ = task.completion; - const Observation submitted_observation{Observation_Event::render_submitted, observer_now_ns(), task.render_sequence, task.scene_state_revision, task.render_order.size()}; + const Observation submitted_observation{ + Observation_Event::render_submitted, observer_now_ns(), + task.snapshot->render_sequence, task.snapshot->scene_state_revision_, + task.snapshot->renderables.size()}; task_ = std::move(task); task_pending_ = true; task_lock.unlock(); - Scene_Base* previous_observer_scene = std::exchange(active_submitted_observer_scene_, this); + + Scene_Base* previous_observer_scene = + std::exchange(active_submitted_observer_scene_, this); observe_scene(submitted_observation); active_submitted_observer_scene_ = previous_observer_scene; task_ready_.notify_one(); + std::size_t deferred_render_count{}; { std::lock_guard lock(task_mutex_); deferred_render_count = std::exchange(deferred_render_count_, 0); } - for (std::size_t index = 0; index < deferred_render_count; ++index) { + for (std::size_t index = 0; index < deferred_render_count; ++index) render(); - } } + void Scene_Base::wait_for_render() { - if (is_render_worker_thread() || active_submitted_observer_scene_ == this) { + if (is_render_worker_thread() || active_submitted_observer_scene_ == this) return; - } std::unique_lock lock(task_mutex_); const auto completion = current_completion_; - if (!completion) { + if (!completion) return; - } - render_completed_.wait(lock, [&completion] { - return completion->completed; - }); + render_completed_.wait(lock, [&completion] { return completion->completed; }); std::exception_ptr exception; if (pending_exception_) { exception = pending_exception_; pending_exception_observed_ = true; - if (!completion->exception) { + if (!completion->exception) completion->observed = true; - } } else { exception = completion->exception; completion->observed = true; } lock.unlock(); - if (exception) { + if (exception) std::rethrow_exception(exception); - } } -void Scene_Base::attach_renderable(Renderable renderable) { - if (!renderable) { + +std::vector Scene_Base::relationship_ids_locked( + const std::vector& renderables) const { + std::vector ids; + ids.reserve(renderables.size()); + for (const Renderable& renderable : renderables) { + if (!renderable) + throw std::invalid_argument("topology parent is null"); + validate_renderable_scene(*renderable); + validate_renderable_attached_locked(renderable); + ids.push_back(renderable->renderable_id()); + } + return ids; +} + +void Scene_Base::attach_renderable(Renderable renderable, + Attach_Relationships relationships) { + if (!renderable) throw std::invalid_argument("renderable is null"); - } - auto task_lock = lock_render_idle(); validate_renderable_scene(*renderable); - std::lock_guard lock(renderable_mutex_); - if (is_renderable_attached_locked(*renderable)) { - return; + { + std::lock_guard lock(model_mutex_); + if (is_renderable_attached_locked(renderable)) + return; + const auto display_parents = + relationship_ids_locked(relationships.display_parents); + const auto display_children = + relationship_ids_locked(relationships.display_children); + const auto dependency_parents = + relationship_ids_locked(relationships.dependency_parents); + auto cache = make_renderable_color_cache(); + const Renderable_Id id = renderable->renderable_id(); + bool display_attached{}; + bool dependency_attached{}; + bool renderable_inserted{}; + try { + dependency_topology_.attach(id, dependency_parents); + dependency_attached = true; + display_topology_.attach(id, display_parents, display_children); + display_attached = true; + if (!renderables_.try_emplace(id, renderable).second) + throw std::logic_error("renderable id is already attached"); + renderable_inserted = true; + if (cache && !color_caches_.try_emplace(id, std::move(cache)).second) + throw std::logic_error("renderable color cache is already attached"); + } catch (...) { + color_caches_.erase(id); + if (renderable_inserted) + renderables_.erase(id); + if (display_attached) + display_topology_.erase(id); + if (dependency_attached) + dependency_topology_.erase(id); + throw; + } + renderable->invalidate_prepare(); } - cache_renderables_->reserve(cache_renderables_->size() + 1); - on_renderable_attached(*renderable); - cache_renderables_->push_back(std::move(renderable)); + notify_model_dirty(); } -void Scene_Base::detach_renderable(Renderable_Base& renderable) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - std::lock_guard lock(renderable_mutex_); - if (!is_renderable_attached_locked(renderable)) { - return; + +void Scene_Base::detach_renderable(const Renderable& renderable) { + if (!renderable) + throw std::invalid_argument("renderable is null"); + validate_renderable_scene(*renderable); + bool detached{}; + { + std::lock_guard lock(model_mutex_); + if (!is_renderable_attached_locked(renderable)) + return; + const Renderable_Id id = renderable->renderable_id(); + display_topology_.detach_and_promote(id); + const auto dependency_children = + dependency_topology_.detach_and_promote(id); + for (const Renderable_Id child : dependency_children) { + if (const auto iterator = renderables_.find(child); + iterator != renderables_.end()) + iterator->second->invalidate_prepare(); + } + color_caches_.erase(id); + renderables_.erase(id); + detached = true; } - on_renderable_detached(renderable); - const auto detached = [&renderable](const Renderable& value) { - return value.get() == &renderable; - }; - std::erase_if(*cache_renderables_, detached); - std::erase_if(*render_renderables_, detached); + if (detached) + notify_model_dirty(); } -void Scene_Base::set_display_parent(Renderable_Base& renderable, Renderable_Base* parent) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - if (parent) { - validate_renderable_scene(*parent); - } - std::lock_guard lock(renderable_mutex_); - validate_renderable_attached_locked(renderable); - if (parent) { - validate_renderable_attached_locked(*parent); - } - auto& node = layer_node(renderable); - auto* target = parent ? &layer_node(*parent) : display_root_node(); - if (node.parent_count() == 1 && node.parent() == target) { - return; - } - if (target) { - node.replace_parent(target); - } else { - node.detach(); + +void Scene_Base::set_display_parent(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("display relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + validate_renderable_attached_locked(parent); + changed = display_topology_.replace_parents( + child->renderable_id(), {parent->renderable_id()}); } + if (changed) + notify_model_dirty(); } + +void Scene_Base::add_display_parent(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("display relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + validate_renderable_attached_locked(parent); + changed = display_topology_.add_parent(child->renderable_id(), + parent->renderable_id()); + } + if (changed) + notify_model_dirty(); +} + +void Scene_Base::clear_display_parent(const Renderable& child) { + if (!child) + throw std::invalid_argument("display child is null"); + validate_renderable_scene(*child); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + changed = display_topology_.clear_parents(child->renderable_id()); + } + if (changed) + notify_model_dirty(); +} + +void Scene_Base::set_dependency_parent(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("dependency relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + validate_renderable_attached_locked(parent); + changed = dependency_topology_.replace_parents( + child->renderable_id(), {parent->renderable_id()}); + if (changed) + child->invalidate_prepare(); + } + if (changed) + notify_model_dirty(); +} + +void Scene_Base::add_dependency_parent(const Renderable& child, + const Renderable& parent) { + if (!child || !parent) + throw std::invalid_argument("dependency relationship endpoint is null"); + validate_renderable_scene(*child); + validate_renderable_scene(*parent); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + validate_renderable_attached_locked(parent); + changed = dependency_topology_.add_parent(child->renderable_id(), + parent->renderable_id()); + if (changed) + child->invalidate_prepare(); + } + if (changed) + notify_model_dirty(); +} + +void Scene_Base::clear_dependency_parent(const Renderable& child) { + if (!child) + throw std::invalid_argument("dependency child is null"); + validate_renderable_scene(*child); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + validate_renderable_attached_locked(child); + changed = dependency_topology_.clear_parents(child->renderable_id()); + if (changed) + child->invalidate_prepare(); + } + if (changed) + notify_model_dirty(); +} + void Scene_Base::publish_frame_state() { auto task_lock = lock_render_idle(); if (auto* state = dynamic_cast(this)) state->publish(); - Renderable_List renderables(&memory_resource()); + std::vector renderables; { - std::lock_guard lock(renderable_mutex_); - renderables = *cache_renderables_; + std::lock_guard lock(model_mutex_); + renderables.reserve(renderables_.size()); + for (const auto& [id, renderable] : renderables_) { + static_cast(id); + renderables.push_back(renderable); + } } for (const Renderable& renderable : renderables) { if (auto* state = dynamic_cast(renderable.get())) @@ -258,141 +621,173 @@ void Scene_Base::publish_frame_state() { renderable->publish_real_time_data(); } } + void Scene_Base::notify_model_dirty() noexcept { model_dirty_.store(true, std::memory_order_release); } -void Scene_Base::add_display_parent(Renderable_Base& renderable, Renderable_Base& parent) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - validate_renderable_scene(parent); - std::lock_guard lock(renderable_mutex_); - validate_renderable_attached_locked(renderable); - validate_renderable_attached_locked(parent); - auto& node = layer_node(renderable); - auto& parent_node = layer_node(parent); - if (node.has_parent(parent_node)) { - return; + +void Scene_Base::set_renderable_configuration( + const Renderable& renderable, Renderable_Configuration configuration) { + if (!renderable) + throw std::invalid_argument("renderable is null"); + validate_renderable_scene(*renderable); + bool changed{}; + { + std::lock_guard lock(model_mutex_); + std::lock_guard configuration_lock(renderable->configuration_mutex_); + changed = renderable->configuration_ != configuration; + if (changed) + renderable->configuration_ = configuration; } - if (auto* root = display_root_node(); root && node.has_parent(*root)) { - node.remove_parent(*root); - } - parent_node.append_child(node); -} -void Scene_Base::set_dependency_parent(Renderable_Base& renderable, Renderable_Base* parent) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - if (parent) { - validate_renderable_scene(*parent); - } - std::lock_guard lock(renderable_mutex_); - validate_renderable_attached_locked(renderable); - if (parent) { - validate_renderable_attached_locked(*parent); - } - auto& node = dependency_node(renderable); - auto* target = parent ? &dependency_node(*parent) : dependency_root_node(); - if (node.parent_count() == 1 && node.parent() == target) { - return; - } - if (target) { - node.replace_parent(target); - } else { - node.detach(); - } - renderable.invalidate_prepare(); -} -void Scene_Base::add_dependency_parent(Renderable_Base& renderable, Renderable_Base& parent) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - validate_renderable_scene(parent); - std::lock_guard lock(renderable_mutex_); - validate_renderable_attached_locked(renderable); - validate_renderable_attached_locked(parent); - auto& node = dependency_node(renderable); - auto& parent_node = dependency_node(parent); - if (node.has_parent(parent_node)) { - return; - } - if (auto* root = dependency_root_node(); root && node.has_parent(*root)) { - node.remove_parent(*root); - } - parent_node.append_child(node); - renderable.invalidate_prepare(); -} -void Scene_Base::set_renderable_configuration(Renderable_Base& renderable, Renderable_Configuration configuration) { - auto task_lock = lock_render_idle(); - validate_renderable_scene(renderable); - std::lock_guard lock(renderable_mutex_); - renderable.set_configuration(configuration); + if (changed) + notify_model_dirty(); } + std::size_t Scene_Base::renderable_count() const { - std::lock_guard lock(renderable_mutex_); - return cache_renderables_->size(); + std::lock_guard lock(model_mutex_); + return renderables_.size(); } + Scene_Base::Topology_Snapshot Scene_Base::topology_snapshot() const { - std::lock_guard lock(renderable_mutex_); + std::lock_guard lock(model_mutex_); Topology_Snapshot snapshot; - std::unordered_map renderables; - renderables.reserve(cache_renderables_->size()); - snapshot.renderables.reserve(cache_renderables_->size()); - snapshot.display.reserve(cache_renderables_->size()); - snapshot.dependency.reserve(cache_renderables_->size()); - for (const Renderable& renderable : *cache_renderables_) { - Const_Renderable value = renderable; - renderables.emplace(renderable.get(), value); - snapshot.renderables.push_back(std::move(value)); - } - for (const Renderable& renderable : *cache_renderables_) { - if (renderable->layer_node_.parent_count() == 0) { - snapshot.display.push_back({renderables.at(renderable.get()), Const_Renderable{}}); - } else { - for (std::size_t parent_index = 0; parent_index < renderable->layer_node_.parent_count(); ++parent_index) { - const auto* display_parent = renderable->layer_node_.parent(parent_index).owner(); - snapshot.display.push_back({renderables.at(renderable.get()), display_parent ? renderables.at(display_parent) : Const_Renderable{}}); + const auto ids = display_topology_.order(); + snapshot.renderables.reserve(ids.size()); + snapshot.display.reserve(ids.size()); + snapshot.dependency.reserve(ids.size()); + for (const Renderable_Id id : ids) + snapshot.renderables.push_back(renderables_.at(id)); + const auto append_relationships = [&](const Id_Topology& topology, + auto& output) { + for (const Renderable_Id id : ids) { + const auto& parents = topology.node(id).parents; + if (parents.empty()) { + output.push_back({renderables_.at(id), {}}); + continue; } + for (const Renderable_Id parent : parents) + output.push_back({renderables_.at(id), renderables_.at(parent)}); } - if (renderable->dependency_node_.parent_count() == 0) { - snapshot.dependency.push_back({renderables.at(renderable.get()), Const_Renderable{}}); - continue; + }; + append_relationships(display_topology_, snapshot.display); + append_relationships(dependency_topology_, snapshot.dependency); + return snapshot; +} + +std::vector Scene_Base::paint_order_snapshot() const { + std::lock_guard lock(model_mutex_); + std::vector result; + for (const Renderable_Id id : display_topology_.order()) + result.push_back(renderables_.at(id)); + return result; +} + +std::shared_ptr Scene_Base::snapshot_live_model() { + auto snapshot = std::make_shared(); + std::lock_guard lock(model_mutex_); + const auto dependency_order = dependency_topology_.order(); + snapshot->display_order = display_topology_.order(); + snapshot->renderables.reserve(dependency_order.size()); + for (const Renderable_Id id : dependency_order) { + const Renderable& owner = renderables_.at(id); + Renderable_Frame_State state; + state.renderable_id = id; + { + std::lock_guard configuration_lock(owner->configuration_mutex_); + state.visible = owner->visible_; + state.configuration = owner->configuration_; + state.prepare_revision = owner->prepare_revision(); + state.prepared_revision = owner->prepared_revision(); + state.paint_revision = owner->paint_revision(); + state.painted_revision = owner->painted_revision(); + state.painted_prepare_revision = + owner->painted_prepare_revision_.load(std::memory_order_acquire); } - for (std::size_t parent_index = 0; parent_index < renderable->dependency_node_.parent_count(); ++parent_index) { - const auto* dependency_parent = renderable->dependency_node_.parent(parent_index).owner(); - snapshot.dependency.push_back({renderables.at(renderable.get()), dependency_parent ? renderables.at(dependency_parent) : Const_Renderable{}}); + const bool prepare_valid = + state.prepare_revision != std::numeric_limits::max() && + state.configuration.cache_enabled && + state.prepared_revision == state.prepare_revision; + const bool paint_valid = + state.paint_revision != std::numeric_limits::max() && + prepare_valid && state.painted_revision == state.paint_revision && + state.painted_prepare_revision == state.prepared_revision; + state.prepare_required = !prepare_valid; + state.paint_required = !paint_valid; + state.render_graph = owner->render_graph(); + const auto& parents = dependency_topology_.node(id).parents; + state.dependency_parent_ids.assign(parents.begin(), parents.end()); + state.owner_ = owner; + if (const auto cache = color_caches_.find(id); + cache != color_caches_.end()) + state.paint_buffer_ = cache->second; + snapshot->renderables.push_back(std::move(state)); + } + + std::unordered_map indices; + indices.reserve(snapshot->renderables.size()); + std::unordered_map> children; + children.reserve(snapshot->renderables.size()); + for (std::size_t index = 0; index < snapshot->renderables.size(); ++index) { + const auto& state = snapshot->renderables[index]; + indices.emplace(state.renderable_id, index); + children.try_emplace(state.renderable_id); + } + for (const auto& state : snapshot->renderables) { + for (const Renderable_Id parent : state.dependency_parent_ids) + children.at(parent).push_back(state.renderable_id); + } + std::vector dirty; + dirty.reserve(snapshot->renderables.size()); + for (const auto& state : snapshot->renderables) { + if (state.prepare_required) + dirty.push_back(state.renderable_id); + } + for (std::size_t cursor = 0; cursor < dirty.size(); ++cursor) { + for (const Renderable_Id child : children.at(dirty[cursor])) { + auto& state = snapshot->renderables.at(indices.at(child)); + if (state.prepare_required) + continue; + state.prepare_required = true; + state.paint_required = true; + dirty.push_back(child); } } return snapshot; } -std::vector Scene_Base::paint_order_snapshot() const { - std::lock_guard lock(renderable_mutex_); - const auto ordered = display_order(*cache_renderables_); - return {ordered.begin(), ordered.end()}; -} + std::shared_ptr Scene_Base::render_plan_snapshot() const { return render_plan_history_.current(); } + std::shared_ptr Scene_Base::find_render_plan( Render_Plan_Version version) const { return render_plan_history_.find(version); } + Capture_Session_Id Scene_Base::capture_next_frame() { return capture_frames(1); } + Capture_Session_Id Scene_Base::capture_frames(std::size_t count) { std::lock_guard lock(task_mutex_); const Capture_Session_Id session_id = capture_controller_.capture_frames(count); capture_repository_.begin_session(session_id, count); return session_id; } + Capture_Controller_State Scene_Base::capture_state() const noexcept { return capture_controller_.state(); } + std::optional Scene_Base::capture_session( Capture_Session_Id session_id) const { return capture_repository_.session(session_id); } + std::vector Scene_Base::capture_sessions() const { return capture_repository_.sessions(); } + std::vector Scene_Base::node_statistics( Capture_Session_Id session_id) const { const auto captured = capture_repository_.session(session_id); @@ -404,6 +799,7 @@ std::vector Scene_Base::node_statistics( analyses.push_back(frame.analysis); return analyze_node_statistics(analyses); } + std::vector Scene_Base::plan_version_statistics( Capture_Session_Id session_id) const { const auto captured = capture_repository_.session(session_id); @@ -415,84 +811,77 @@ std::vector Scene_Base::plan_version_statistics( analyses.push_back(frame.analysis); return analyze_plan_versions(analyses); } + std::size_t Scene_Base::task_executor_worker_count() noexcept { return Execution_Context::executor().num_workers(); } + std::pmr::memory_resource& Scene_Base::memory_resource() const noexcept { return memory_domain_->resource(); } + std::pmr::memory_resource& Scene_Base::upstream_memory_resource() const noexcept { return memory_domain_->upstream(); } + Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy() { return frame_control_strategy_impl(); } + const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy() const { return frame_control_strategy_impl(); } + Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy_impl() { throw std::logic_error("frame control strategy is not available"); } + const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy_impl() const { throw std::logic_error("frame control strategy is not available"); } -Renderable_Base::Layer_Node& Scene_Base::layer_node(Renderable_Base& renderable) noexcept { - return renderable.layer_node_; + +std::shared_ptr Scene_Base::make_renderable_color_cache() { + return {}; } -Renderable_Base::Dependency_Node& Scene_Base::dependency_node(Renderable_Base& renderable) noexcept { - return renderable.dependency_node_; -} -Scene_Base::Renderable_List Scene_Base::display_order(const Renderable_List& renderables) const { - return topological_order(renderables, memory_resource(), [](Renderable_Base& renderable) -> auto& { - return renderable.layer_node_; - }, "display"); -} -Scene_Base::Renderable_List Scene_Base::dependency_order(const Renderable_List& renderables) const { - return topological_order(renderables, memory_resource(), [](Renderable_Base& renderable) -> auto& { - return renderable.dependency_node_; - }, "dependency"); -} -Renderable_Base::Layer_Node* Scene_Base::display_root_node() noexcept { - return nullptr; -} -Renderable_Base::Dependency_Node* Scene_Base::dependency_root_node() noexcept { - return nullptr; -} -void Scene_Base::on_renderable_attached(Renderable_Base&) {} -void Scene_Base::on_renderable_detached(Renderable_Base&) {} -void Scene_Base::prepare_render_task(Render_Task& task, const Renderable_List& renderables) { - task.render_order = renderables; - task.display_order = renderables; -} -Color_Cache* Scene_Base::prepare_renderable_cache(Renderable_Base&, bool) { - return nullptr; + +Frame_Viewport Scene_Base::frame_viewport() const { + return {}; } + std::uint64_t Scene_Base::acquire_scene_state() { return 0; } + void Scene_Base::observe_scene(const Observation&) noexcept {} + std::uint64_t Scene_Base::observer_now_ns() const noexcept { return 0; } + std::unique_lock Scene_Base::lock_render_idle() { - if (is_render_worker_thread()) { - throw std::logic_error("scene control mutation is not allowed during render execution"); - } - if (active_submitted_observer_scene_ == this) { - throw std::logic_error("scene control mutation is not allowed during render submission observation"); - } + if (is_render_worker_thread()) + throw std::logic_error( + "render-idle operation is not allowed during render execution"); + if (active_submitted_observer_scene_ == this) + throw std::logic_error( + "render-idle operation is not allowed during render submission observation"); std::unique_lock lock(task_mutex_); render_completed_.wait(lock, [this] { return !task_pending_ && !rendering_; }); return lock; } + bool Scene_Base::is_render_worker_thread() const noexcept { - return active_execution_scene_ == this || (worker_.joinable() && std::this_thread::get_id() == worker_.get_id()); + return active_execution_scene_ == this || + (worker_.joinable() && + std::this_thread::get_id() == worker_.get_id()); } + bool Scene_Base::consume_model_dirty() noexcept { return model_dirty_.exchange(false, std::memory_order_acq_rel); } + void Scene_Base::shutdown() noexcept { { std::unique_lock lock(task_mutex_); @@ -502,34 +891,41 @@ void Scene_Base::shutdown() noexcept { stop_ = true; } task_ready_.notify_one(); - if (worker_.joinable()) { + if (worker_.joinable()) worker_.join(); - } scene_lifetime_->invalidate(); } + void Scene_Base::render_loop() { for (;;) { Render_Task task(memory_resource()); { std::unique_lock lock(task_mutex_); - task_ready_.wait(lock, [this] { - return stop_ || task_pending_; - }); - if (stop_) { + task_ready_.wait(lock, [this] { return stop_ || task_pending_; }); + if (stop_) return; - } task = std::move(task_); task_pending_ = false; rendering_ = true; } - observe_scene({Observation_Event::render_started, observer_now_ns(), task.render_sequence, task.scene_state_revision, task.render_order.size()}); + const auto& snapshot = *task.snapshot; + observe_scene({Observation_Event::render_started, observer_now_ns(), + snapshot.render_sequence, + snapshot.scene_state_revision_, + snapshot.renderables.size()}); std::exception_ptr exception; try { execute_taskflow(task); - observe_scene({Observation_Event::render_completed, observer_now_ns(), task.render_sequence, task.scene_state_revision, task.render_order.size()}); + observe_scene({Observation_Event::render_completed, observer_now_ns(), + snapshot.render_sequence, + snapshot.scene_state_revision_, + snapshot.renderables.size()}); } catch (...) { exception = std::current_exception(); - observe_scene({Observation_Event::render_failed, observer_now_ns(), task.render_sequence, task.scene_state_revision, task.render_order.size()}); + observe_scene({Observation_Event::render_failed, observer_now_ns(), + snapshot.render_sequence, + snapshot.scene_state_revision_, + snapshot.renderables.size()}); } { std::lock_guard lock(task_mutex_); @@ -540,206 +936,247 @@ void Scene_Base::render_loop() { render_completed_.notify_all(); } } -std::shared_ptr Scene_Base::compile_render_plan(Render_Task& task) { + +std::shared_ptr Scene_Base::compile_render_plan( + const Frame_Render_Snapshot& snapshot, Render_Task& task) { struct Active_Renderable { - Renderable renderable; - std::shared_ptr graph; - bool prepare{}; - bool paint{}; + std::size_t snapshot_index{}; std::vector prepare_roots; std::vector prepare_terminals; std::vector paint_terminals; }; - std::vector active; - active.reserve(task.render_order.size()); - std::unordered_map indices; - indices.reserve(task.render_order.size()); - for (std::size_t index = 0; index < task.render_order.size(); ++index) { - auto& renderable = task.render_order[index]; - indices.emplace(renderable.get(), index); - active.push_back({renderable, renderable->render_graph(), - renderable->requires_prepare(), renderable->requires_paint()}); - } - std::vector dirty_queue; - dirty_queue.reserve(active.size()); - for (std::size_t index = 0; index < active.size(); ++index) { - if (active[index].prepare) - dirty_queue.push_back(index); - } - for (std::size_t cursor = 0; cursor < dirty_queue.size(); ++cursor) { - auto& dependency = dependency_node(*active[dirty_queue[cursor]].renderable); - for (std::size_t child_index = 0; child_index < dependency.child_count(); ++child_index) { - auto iterator = indices.find(dependency.child(child_index).owner()); - if (iterator == indices.end() || active[iterator->second].prepare) - continue; - active[iterator->second].renderable->invalidate_prepare(); - active[iterator->second].prepare = true; - active[iterator->second].paint = true; - dirty_queue.push_back(iterator->second); - } + + std::vector active(snapshot.renderables.size()); + std::unordered_map indices; + indices.reserve(snapshot.renderables.size()); + for (std::size_t index = 0; index < snapshot.renderables.size(); ++index) { + active[index].snapshot_index = index; + indices.emplace(snapshot.renderables[index].renderable_id, index); } Render_Graph graph; - std::unordered_map functions; + std::unordered_map functions; const auto append_edge = [&graph](Render_Node_Id from, Render_Node_Id to) { if (from == 0 || to == 0 || from == to) return; const Render_Edge edge{from, to}; - if (std::find(graph.edges.begin(), graph.edges.end(), edge) == graph.edges.end()) + if (std::find(graph.edges.begin(), graph.edges.end(), edge) == + graph.edges.end()) graph.edges.push_back(edge); }; - task.stage_commits.clear(); - task.stage_commits.reserve(active.size()); + for (auto& item : active) { + const auto& state = snapshot.renderables[item.snapshot_index]; + if (!state.render_graph) + throw std::logic_error("frame renderable has no graph snapshot"); std::unordered_set selected; - for (const auto& node : item.graph->graph.nodes) { + for (const auto& node : state.render_graph->graph.nodes) { if (node.kind == Render_Node_Kind::composite) - throw std::logic_error("renderable graph must not define composite nodes"); + throw std::logic_error( + "renderable graph must not define composite nodes"); const bool include = node.kind == Render_Node_Kind::prepare - ? item.prepare - : item.paint; - if (include) { - graph.nodes.push_back(node); + ? state.prepare_required + : state.paint_required; + if (!include) + continue; + graph.nodes.push_back(node); + const auto& render_function = + state.render_graph->functions.at(node.execution_index); + std::visit([&](const auto& function) { functions.emplace(node.node_id, - item.graph->functions.at(node.execution_index)); - selected.insert(node.node_id); - } + Execution_Binding{state.owner_, item.snapshot_index, + Execution_Function{function}}); + }, render_function); + selected.insert(node.node_id); } - for (const auto& edge : item.graph->graph.edges) { + for (const auto& edge : state.render_graph->graph.edges) { if (selected.contains(edge.from) && selected.contains(edge.to)) append_edge(edge.from, edge.to); } const auto stage_boundary = [&](Render_Node_Kind kind, bool roots) { std::vector result; - if ((kind == Render_Node_Kind::prepare && !item.prepare) || - (kind == Render_Node_Kind::paint && !item.paint)) + if ((kind == Render_Node_Kind::prepare && + !state.prepare_required) || + (kind == Render_Node_Kind::paint && !state.paint_required)) return result; - for (const auto& node : item.graph->graph.nodes) { + for (const auto& node : state.render_graph->graph.nodes) { if (node.kind != kind || !selected.contains(node.node_id)) continue; - const bool connected = std::any_of(item.graph->graph.edges.begin(), - item.graph->graph.edges.end(), [&](const Render_Edge& edge) { - return roots - ? edge.to == node.node_id && selected.contains(edge.from) && - std::any_of(item.graph->graph.nodes.begin(), item.graph->graph.nodes.end(), - [&](const Render_Node& candidate) { - return candidate.node_id == edge.from && - candidate.kind == kind; - }) - : edge.from == node.node_id && selected.contains(edge.to) && - std::any_of(item.graph->graph.nodes.begin(), item.graph->graph.nodes.end(), - [&](const Render_Node& candidate) { - return candidate.node_id == edge.to && - candidate.kind == kind; - }); + const bool connected = std::any_of( + state.render_graph->graph.edges.begin(), + state.render_graph->graph.edges.end(), + [&](const Render_Edge& edge) { + const Render_Node_Id adjacent = + roots ? edge.from : edge.to; + const bool matches = roots ? edge.to == node.node_id + : edge.from == node.node_id; + if (!matches || !selected.contains(adjacent)) + return false; + return std::any_of( + state.render_graph->graph.nodes.begin(), + state.render_graph->graph.nodes.end(), + [&](const Render_Node& candidate) { + return candidate.node_id == adjacent && + candidate.kind == kind; + }); }); if (!connected) result.push_back(node.node_id); } return result; }; - item.prepare_roots = stage_boundary(Render_Node_Kind::prepare, true); - item.prepare_terminals = stage_boundary(Render_Node_Kind::prepare, false); - item.paint_terminals = stage_boundary(Render_Node_Kind::paint, false); - if (item.paint) - prepare_renderable_cache(*item.renderable, true); - task.stage_commits.push_back({item.renderable, - item.renderable->prepare_revision(), - item.renderable->paint_revision(), - item.prepare, item.paint}); + item.prepare_roots = + stage_boundary(Render_Node_Kind::prepare, true); + item.prepare_terminals = + stage_boundary(Render_Node_Kind::prepare, false); + item.paint_terminals = + stage_boundary(Render_Node_Kind::paint, false); } - for (std::size_t child_index = 0; child_index < active.size(); ++child_index) { - auto& dependency = dependency_node(*active[child_index].renderable); - for (std::size_t parent_index = 0; parent_index < dependency.parent_count(); ++parent_index) { - auto iterator = indices.find(dependency.parent(parent_index).owner()); - if (iterator == indices.end()) + + for (const auto& child : active) { + const auto& child_state = + snapshot.renderables[child.snapshot_index]; + for (const Renderable_Id parent_id : + child_state.dependency_parent_ids) { + const auto parent = indices.find(parent_id); + if (parent == indices.end()) continue; - for (Render_Node_Id parent : active[iterator->second].prepare_terminals) { - for (Render_Node_Id child : active[child_index].prepare_roots) - append_edge(parent, child); + for (const Render_Node_Id parent_terminal : + active[parent->second].prepare_terminals) { + for (const Render_Node_Id child_root : child.prepare_roots) + append_edge(parent_terminal, child_root); } } } + if (auto* compositor = dynamic_cast(this)) { graph.nodes.push_back({composite_begin_node_id_, 0, "Composite Frame", Render_Node_Kind::composite, 0}); functions.emplace(composite_begin_node_id_, - [compositor](const Scene_Render_Context& context) { - compositor->begin_composite(context); - }); + Execution_Binding{{}, std::nullopt, + Composite_Render_Node_Function{ + [compositor](const Composite_Render_Context& context) { + compositor->begin_composite(context); + }}}); Render_Node_Id previous = composite_begin_node_id_; - for (const auto& renderable : task.display_order) { - graph.nodes.push_back({renderable->composite_node_id_, renderable->renderable_id(), + for (const Renderable_Id id : snapshot.display_order) { + const std::size_t index = indices.at(id); + const auto& state = snapshot.renderables[index]; + graph.nodes.push_back({state.owner_->composite_node_id_, id, "Composite", Render_Node_Kind::composite, 0}); - functions.emplace(renderable->composite_node_id_, - [compositor, target = renderable.get()]( - const Scene_Render_Context& context) { - compositor->composite(context, *target); - }); - append_edge(previous, renderable->composite_node_id_); - previous = renderable->composite_node_id_; - const auto iterator = indices.find(renderable.get()); - if (iterator != indices.end()) { - for (Render_Node_Id paint : active[iterator->second].paint_terminals) - append_edge(paint, renderable->composite_node_id_); - } + functions.emplace(state.owner_->composite_node_id_, + Execution_Binding{state.owner_, index, + Composite_Render_Node_Function{ + [compositor](const Composite_Render_Context& context) { + compositor->composite(context); + }}}); + append_edge(previous, state.owner_->composite_node_id_); + previous = state.owner_->composite_node_id_; + for (const Render_Node_Id paint : active[index].paint_terminals) + append_edge(paint, state.owner_->composite_node_id_); } } + auto plan = render_plan_history_.publish(std::move(graph)); - task.node_functions.clear(); - task.node_functions.resize(plan->graph.nodes.size()); + task.execution_bindings.clear(); + task.execution_bindings.resize(plan->graph.nodes.size()); for (const auto& node : plan->graph.nodes) { - const auto function = functions.find(node.node_id); + auto function = functions.find(node.node_id); if (function == functions.end()) - throw std::logic_error("render plan node has no execution binding"); - task.node_functions[node.execution_index] = std::move(function->second); + throw std::logic_error( + "render plan node has no execution binding"); + task.execution_bindings[node.execution_index] = + std::move(function->second); } return plan; } void Scene_Base::execute_taskflow(Render_Task& task) { - if (!task.plan || !task.frame) - throw std::logic_error("render task has no plan or frame"); - task.capture_ticket = capture_controller_.begin_frame(); + Abstract_Frame* const frame = std::visit( + [](Frame& value) -> Abstract_Frame* { + if constexpr (std::same_as>) + return value.get(); + else + return &value.get(); + }, + task.frame); + if (!task.snapshot || !task.plan || !frame) + throw std::logic_error("render task is incomplete"); + const auto& snapshot = *task.snapshot; + const Capture_Frame_Ticket capture_ticket = snapshot.capture_ticket_; bool frame_active{}; try { - std::unordered_map renderables; - renderables.reserve(task.render_order.size()); - for (const auto& renderable : task.render_order) - renderables.emplace(renderable->renderable_id(), renderable.get()); + for (const auto& state : snapshot.renderables) { + if (state.paint_required && state.paint_buffer_) + state.paint_buffer_->clear(); + } + tf::Taskflow taskflow; std::vector tasks; tasks.reserve(task.plan->graph.nodes.size()); - const bool capture = task.capture_ticket.capture; - task.frame->begin_render(task.render_sequence, *task.plan, capture, - capture ? render_clock_now_ns() : 0); + const bool capture = capture_ticket.capture; + frame->begin_render(snapshot.render_sequence, *task.plan, capture, + capture ? render_clock_now_ns() : 0); frame_active = true; for (const auto& node : task.plan->graph.nodes) { - Renderable_Base* renderable = node.owner_id == 0 - ? nullptr - : renderables.at(node.owner_id); - Color_Cache* color_cache = renderable && node.kind != Render_Node_Kind::prepare - ? prepare_renderable_cache(*renderable, false) - : nullptr; tasks.push_back(taskflow.emplace([ - this, &task, node, renderable, color_cache + this, &task, &snapshot, frame, node ](tf::Runtime& runtime) { - Node_Execution* execution = task.frame->execution_slot(node.execution_index); - Scene_Render_Context context{this, task.frame_control_state, - task.scene_state_revision, task.render_sequence, - task.viewport, renderable, color_cache, - execution ? &execution->metrics : nullptr}; + Node_Execution* execution = + frame->execution_slot(node.execution_index); + Node_Execution_Metrics* metrics = + execution ? &execution->metrics : nullptr; + const auto run = [&] { + const auto& binding = + task.execution_bindings.at(node.execution_index); + switch (node.kind) { + case Render_Node_Kind::prepare: { + const auto& state = snapshot.renderables.at( + binding.renderable_index.value()); + std::get(binding.function)( + Prepare_Render_Context{snapshot, state, metrics}); + break; + } + case Render_Node_Kind::paint: { + const auto& state = snapshot.renderables.at( + binding.renderable_index.value()); + if (!state.paint_buffer_) + throw std::logic_error( + "paint node has no frame color cache"); + std::get(binding.function)( + Paint_Render_Context{snapshot, state, + *state.paint_buffer_, metrics}); + break; + } + case Render_Node_Kind::composite: { + const Renderable_Frame_State* state = + binding.renderable_index + ? &snapshot.renderables.at( + *binding.renderable_index) + : nullptr; + const Color_Cache* cache = + state && state->paint_buffer_ + ? state->paint_buffer_.get() + : nullptr; + std::get(binding.function)( + Composite_Render_Context{snapshot, state, cache, + metrics}); + break; + } + } + }; if (!execution) { Render_Execution_Scope scope(*this); - task.node_functions.at(node.execution_index)(context); + run(); return; } - execution->worker_id = static_cast(runtime.worker().id()); + execution->worker_id = + static_cast(runtime.worker().id()); execution->start_time_ns = render_clock_now_ns(); execution->status = Node_Execution_Status::running; Render_Execution_Scope scope(*this); try { - task.node_functions.at(node.execution_index)(context); + run(); execution->end_time_ns = render_clock_now_ns(); execution->status = Node_Execution_Status::complete; } catch (...) { @@ -754,57 +1191,70 @@ void Scene_Base::execute_taskflow(Render_Task& task) { for (const auto& node : task.plan->graph.nodes) node_indices.emplace(node.node_id, node.execution_index); for (const auto& edge : task.plan->graph.edges) - tasks.at(node_indices.at(edge.from)).precede(tasks.at(node_indices.at(edge.to))); + tasks.at(node_indices.at(edge.from)) + .precede(tasks.at(node_indices.at(edge.to))); Execution_Context::executor().run(taskflow).get(); - for (const auto& commit : task.stage_commits) { - if (commit.prepared) - commit.renderable->mark_prepared(commit.prepare_revision); - if (commit.painted) - commit.renderable->mark_painted(commit.paint_revision, - commit.prepare_revision); + + for (const auto& state : snapshot.renderables) { + if (state.prepare_required) + state.owner_->mark_prepared(state.prepare_revision); + if (state.paint_required) + state.owner_->mark_painted(state.paint_revision, + state.prepare_revision); } - const auto snapshot = task.frame->complete_render( + const auto completed = frame->complete_render( capture ? render_clock_now_ns() : 0); frame_active = false; - for (const auto& commit : task.stage_commits) { - if (!commit.painted) + for (const auto& state : snapshot.renderables) { + if (!state.paint_required) continue; if (auto* completion = dynamic_cast( - commit.renderable.get())) { + state.owner_.get())) { completion->render_frame_completed( - task.frame_control_state.next_refresh_interval_ns); + snapshot.next_refresh_interval_ns_); } } - if (snapshot) { - capture_repository_.publish(task.capture_ticket.session_id, snapshot, - analyze_frame(*task.plan, *snapshot)); + if (completed) { + capture_repository_.publish( + capture_ticket.session_id, completed, + analyze_frame(*task.plan, *completed)); } - capture_controller_.finish_frame(task.capture_ticket, true); + capture_controller_.finish_frame(capture_ticket, true); } catch (...) { if (frame_active) - task.frame->discard_render(); - capture_controller_.finish_frame(task.capture_ticket, false); + frame->discard_render(); + capture_controller_.finish_frame(capture_ticket, false); throw; } } -std::uint64_t Scene_Base::allocate_renderable_id() { + +Renderable_Id Scene_Base::allocate_renderable_id() { return allocate_id(next_renderable_id_, "renderable id exhausted"); } + Render_Node_Id Scene_Base::allocate_node_id() { return allocate_id(next_node_id_, "render node id exhausted"); } -void Scene_Base::validate_renderable_scene(const Renderable_Base& renderable) const { - if (renderable.real_time_data_state_->scene_lifetime.get() != scene_lifetime_.get()) { + +void Scene_Base::validate_renderable_scene( + const Renderable_Base& renderable) const { + if (renderable.real_time_data_state_->scene_lifetime.get() != + scene_lifetime_.get()) throw std::invalid_argument("renderable belongs to another scene"); - } } -bool Scene_Base::is_renderable_attached_locked(const Renderable_Base& renderable) const { - return std::find_if(cache_renderables_->begin(), cache_renderables_->end(), [&renderable](const Renderable& value) { - return value.get() == &renderable; - }) != cache_renderables_->end(); + +bool Scene_Base::is_renderable_attached_locked( + const Renderable& renderable) const { + if (!renderable) + return false; + const auto iterator = renderables_.find(renderable->renderable_id()); + return iterator != renderables_.end() && + iterator->second.get() == renderable.get(); } -void Scene_Base::validate_renderable_attached_locked(const Renderable_Base& renderable) const { - if (!is_renderable_attached_locked(renderable)) { - throw std::invalid_argument("renderable is not attached to this scene"); - } + +void Scene_Base::validate_renderable_attached_locked( + const Renderable& renderable) const { + if (!is_renderable_attached_locked(renderable)) + throw std::invalid_argument( + "renderable is not attached to this scene"); } diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index 10e35a6..0b1b4cb 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -1,45 +1,62 @@ #pragma once -#include + #include #include #include #include #include +#include #include #include #include +#include #include +#include #include +#include #include + #include "renderive/base/memory/Memory_Resource.hpp" #include "renderive/capture/Capture.hpp" #include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp" #include "renderive/renderable/base/Renderable_Base.hpp" #include "renderive/render_graph/Render_Plan.hpp" #include "Abstract_Frame.hpp" -#include "Scene_Lifetime.hpp" +#include "Frame_Render_Snapshot.hpp" #include "Frame_Viewport.hpp" -#include "Scene_Render_Context.hpp" +#include "Render_Contexts.hpp" +#include "Scene_Lifetime.hpp" + class Color_Cache; + class Scene_Compositor { public: virtual ~Scene_Compositor() = default; - virtual void begin_composite(const Scene_Render_Context& context) = 0; - virtual void composite(const Scene_Render_Context& context, - Renderable_Base& renderable) = 0; + virtual void begin_composite(const Composite_Render_Context& context) = 0; + virtual void composite(const Composite_Render_Context& context) = 0; }; + class Scene_Base { private: struct Render_Completion; + public: using Renderable = std::shared_ptr; - using Renderable_List = std::pmr::vector; + using Const_Renderable = std::shared_ptr; + + struct Attach_Relationships { + std::vector display_parents; + std::vector display_children; + std::vector dependency_parents; + }; + enum class Observation_Event { render_submitted, render_started, render_completed, render_failed }; + struct Observation { Observation_Event event{}; std::uint64_t time_ns{}; @@ -47,16 +64,18 @@ public: std::uint64_t scene_state_revision{}; std::size_t renderable_count{}; }; - using Const_Renderable = std::shared_ptr; + struct Topology_Relationship { Const_Renderable child; Const_Renderable parent; }; + struct Topology_Snapshot { std::vector renderables; std::vector display; std::vector dependency; }; + Scene_Base(); explicit Scene_Base(std::pmr::memory_resource& upstream_memory_resource); Scene_Base(const Scene_Base&) = delete; @@ -64,24 +83,32 @@ public: Scene_Base(Scene_Base&&) = delete; Scene_Base& operator=(Scene_Base&&) = delete; virtual ~Scene_Base(); + void render(); void render(Abstract_Frame& frame); void wait_for_render(); void publish_frame_state(); void notify_model_dirty() noexcept; - void attach_renderable(Renderable renderable); - void detach_renderable(Renderable_Base& renderable); - void set_display_parent(Renderable_Base& renderable, Renderable_Base* parent); - void add_display_parent(Renderable_Base& renderable, Renderable_Base& parent); - void set_dependency_parent(Renderable_Base& renderable, Renderable_Base* parent); - void add_dependency_parent(Renderable_Base& renderable, Renderable_Base& parent); - void set_renderable_configuration(Renderable_Base& renderable, Renderable_Configuration configuration); - std::size_t renderable_count() const; - Topology_Snapshot topology_snapshot() const; - std::vector paint_order_snapshot() const; + + void attach_renderable(Renderable renderable, + Attach_Relationships relationships = {}); + void detach_renderable(const Renderable& renderable); + void set_display_parent(const Renderable& child, const Renderable& parent); + void add_display_parent(const Renderable& child, const Renderable& parent); + void clear_display_parent(const Renderable& child); + void set_dependency_parent(const Renderable& child, const Renderable& parent); + void add_dependency_parent(const Renderable& child, const Renderable& parent); + void clear_dependency_parent(const Renderable& child); + void set_renderable_configuration(const Renderable& renderable, + Renderable_Configuration configuration); + + [[nodiscard]] std::size_t renderable_count() const; + [[nodiscard]] Topology_Snapshot topology_snapshot() const; + [[nodiscard]] std::vector paint_order_snapshot() const; [[nodiscard]] std::shared_ptr render_plan_snapshot() const; [[nodiscard]] std::shared_ptr find_render_plan( Render_Plan_Version version) const; + Capture_Session_Id capture_next_frame(); Capture_Session_Id capture_frames(std::size_t count); [[nodiscard]] Capture_Controller_State capture_state() const noexcept; @@ -92,88 +119,134 @@ public: Capture_Session_Id session_id) const; [[nodiscard]] std::vector plan_version_statistics( Capture_Session_Id session_id) const; + [[nodiscard]] static std::size_t task_executor_worker_count() noexcept; - std::pmr::memory_resource& memory_resource() const noexcept; - std::pmr::memory_resource& upstream_memory_resource() const noexcept; + [[nodiscard]] std::pmr::memory_resource& memory_resource() const noexcept; + [[nodiscard]] std::pmr::memory_resource& upstream_memory_resource() const noexcept; Frame_Control_Strategy_Base& frame_control_strategy(); const Frame_Control_Strategy_Base& frame_control_strategy() const; + template - requires std::derived_from && std::constructible_from + requires std::derived_from && + std::constructible_from std::shared_ptr make_renderable(Args&&... args) { - return std::allocate_shared(Scene_Memory_Allocator{memory_domain_}, *this, std::forward(args)...); + return std::allocate_shared( + Scene_Memory_Allocator{memory_domain_}, *this, + std::forward(args)...); } + protected: - struct Render_Task { - struct Stage_Commit { - Renderable renderable; - std::uint64_t prepare_revision{}; - std::uint64_t paint_revision{}; - bool prepared{}; - bool painted{}; - }; - explicit Render_Task(std::pmr::memory_resource& memory_resource) - : render_order(&memory_resource), display_order(&memory_resource), - stage_commits(&memory_resource), node_functions(&memory_resource) {} - Frame_Control_Strategy_Base::State frame_control_state; - std::uint64_t scene_state_revision{}; - std::uint64_t render_sequence{}; - Frame_Viewport viewport; - Renderable_List render_order; - Renderable_List display_order; - std::pmr::vector stage_commits; - std::pmr::vector node_functions; - std::shared_ptr plan; - std::shared_ptr owned_frame; - Abstract_Frame* frame{}; - Capture_Frame_Ticket capture_ticket; - std::shared_ptr completion; - }; - static Renderable_Base::Layer_Node& layer_node(Renderable_Base& renderable) noexcept; - static Renderable_Base::Dependency_Node& dependency_node(Renderable_Base& renderable) noexcept; - Renderable_List display_order(const Renderable_List& renderables) const; - Renderable_List dependency_order(const Renderable_List& renderables) const; virtual Frame_Control_Strategy_Base& frame_control_strategy_impl(); virtual const Frame_Control_Strategy_Base& frame_control_strategy_impl() const; - virtual Renderable_Base::Layer_Node* display_root_node() noexcept; - virtual Renderable_Base::Dependency_Node* dependency_root_node() noexcept; - virtual void on_renderable_attached(Renderable_Base& renderable); - virtual void on_renderable_detached(Renderable_Base& renderable); - virtual void prepare_render_task(Render_Task& task, const Renderable_List& renderables); - virtual Color_Cache* prepare_renderable_cache(Renderable_Base& renderable, bool clear); + virtual std::shared_ptr make_renderable_color_cache(); + virtual Frame_Viewport frame_viewport() const; virtual std::uint64_t acquire_scene_state(); virtual void observe_scene(const Observation& observation) noexcept; virtual std::uint64_t observer_now_ns() const noexcept; + std::unique_lock lock_render_idle(); - bool is_render_worker_thread() const noexcept; + [[nodiscard]] bool is_render_worker_thread() const noexcept; bool consume_model_dirty() noexcept; void shutdown() noexcept; + private: friend class Renderable_Base; + + struct Topology_Node { + explicit Topology_Node(std::pmr::memory_resource& memory_resource) + : parents(&memory_resource), children(&memory_resource) {} + + std::pmr::vector parents; + std::pmr::vector children; + }; + + class Id_Topology { + public: + explicit Id_Topology(std::pmr::memory_resource& memory_resource) + : nodes_(&memory_resource) {} + + void attach(Renderable_Id id, const std::vector& parents, + const std::vector& children = {}); + void erase(Renderable_Id id) noexcept; + std::vector detach_and_promote(Renderable_Id id); + bool replace_parents(Renderable_Id child, + const std::vector& parents); + bool add_parent(Renderable_Id child, Renderable_Id parent); + bool clear_parents(Renderable_Id child); + [[nodiscard]] const Topology_Node& node(Renderable_Id id) const; + [[nodiscard]] std::vector order() const; + + private: + [[nodiscard]] bool reaches(Renderable_Id from, Renderable_Id target) const; + void validate_parent(Renderable_Id child, Renderable_Id parent) const; + static void erase_id(std::pmr::vector& values, + Renderable_Id id) noexcept; + + std::pmr::unordered_map nodes_; + }; + + using Composite_Render_Node_Function = + std::function; + using Execution_Function = + std::variant; + + struct Execution_Binding { + Renderable owner; + std::optional renderable_index; + Execution_Function function; + }; + + struct Render_Task { + using Frame = std::variant, + std::reference_wrapper>; + + explicit Render_Task(std::pmr::memory_resource& memory_resource) + : execution_bindings(&memory_resource) {} + + std::shared_ptr snapshot; + std::shared_ptr plan; + std::pmr::vector execution_bindings; + Frame frame; + std::shared_ptr completion; + }; + class Execution_Context; class Render_Execution_Scope; + struct Render_Completion { std::exception_ptr exception; bool completed{}; bool observed{}; }; + void submit_render(Abstract_Frame* frame); - std::shared_ptr compile_render_plan(Render_Task& task); + [[nodiscard]] std::shared_ptr snapshot_live_model(); + std::shared_ptr compile_render_plan( + const Frame_Render_Snapshot& snapshot, Render_Task& task); void render_loop(); void execute_taskflow(Render_Task& task); - std::uint64_t allocate_renderable_id(); + Renderable_Id allocate_renderable_id(); Render_Node_Id allocate_node_id(); void validate_renderable_scene(const Renderable_Base& renderable) const; - bool is_renderable_attached_locked(const Renderable_Base& renderable) const; - void validate_renderable_attached_locked(const Renderable_Base& renderable) const; + [[nodiscard]] bool is_renderable_attached_locked( + const Renderable& renderable) const; + void validate_renderable_attached_locked(const Renderable& renderable) const; + [[nodiscard]] std::vector relationship_ids_locked( + const std::vector& renderables) const; + inline static thread_local Scene_Base* active_execution_scene_{}; inline static thread_local Scene_Base* active_submitted_observer_scene_{}; + std::shared_ptr scene_lifetime_; std::shared_ptr memory_domain_; std::atomic_bool model_dirty_{true}; - std::array renderable_states_; - Renderable_List* render_renderables_; - Renderable_List* cache_renderables_; - mutable std::mutex renderable_mutex_; + std::pmr::unordered_map renderables_; + Id_Topology display_topology_; + Id_Topology dependency_topology_; + std::pmr::unordered_map> color_caches_; + mutable std::mutex model_mutex_; + mutable std::recursive_mutex task_mutex_; std::condition_variable_any task_ready_; std::condition_variable_any render_completed_; @@ -187,20 +260,25 @@ private: bool task_pending_{}; bool rendering_{}; bool stop_{}; - std::atomic next_renderable_id_{1}; + + std::atomic next_renderable_id_{1}; std::atomic next_node_id_{1}; const Render_Node_Id composite_begin_node_id_; Render_Plan_History render_plan_history_; Capture_Controller capture_controller_; Capture_Repository capture_repository_; }; + class Scene_2D_Base : public Scene_Base { public: Scene_2D_Base() = default; - explicit Scene_2D_Base(std::pmr::memory_resource& memory_resource) : Scene_Base(memory_resource) {} + explicit Scene_2D_Base(std::pmr::memory_resource& memory_resource) + : Scene_Base(memory_resource) {} }; + class Scene_3D_Base : public Scene_Base { public: Scene_3D_Base() = default; - explicit Scene_3D_Base(std::pmr::memory_resource& memory_resource) : Scene_Base(memory_resource) {} + explicit Scene_3D_Base(std::pmr::memory_resource& memory_resource) + : Scene_Base(memory_resource) {} }; diff --git a/Kernel/src/renderive/scene/base/Scene_Render_Context.hpp b/Kernel/src/renderive/scene/base/Scene_Render_Context.hpp deleted file mode 100644 index b91f63f..0000000 --- a/Kernel/src/renderive/scene/base/Scene_Render_Context.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp" -#include "Frame_Viewport.hpp" -class Color_Cache; -class Renderable_Base; -class Scene_Base; -struct Node_Execution_Metrics; -struct Scene_Render_Context { - Scene_Base* scene{}; - Frame_Control_Strategy_Base::State frame_control_state; - std::uint64_t scene_state_revision{}; - std::uint64_t render_sequence{}; - Frame_Viewport viewport; - Renderable_Base* renderable{}; - Color_Cache* color_cache{}; - Node_Execution_Metrics* metrics{}; -}; diff --git a/Kernel/src/renderive/state/Render_State_View.hpp b/Kernel/src/renderive/state/Render_State_View.hpp index 0b683ad..615ad41 100644 --- a/Kernel/src/renderive/state/Render_State_View.hpp +++ b/Kernel/src/renderive/state/Render_State_View.hpp @@ -1,6 +1,7 @@ #pragma once class Renderable_Base; +class Frame_Render_Snapshot; class Render_State_View { public: @@ -11,5 +12,6 @@ public: private: friend class Renderable_Base; + friend class Frame_Render_Snapshot; Render_State_View() = default; }; diff --git a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp index 5717e92..5b0f249 100644 --- a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp @@ -17,7 +17,7 @@ struct Real_Time_Data_Test_Time_Source { }; struct Real_Time_Data_Test_Renderable : Renderable_Base { explicit Real_Time_Data_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { ++render_count; } std::atomic render_count{}; @@ -36,8 +36,8 @@ class Real_Time_Data_Render_Probe : public Renderable_Base { }; public: explicit Real_Time_Data_Render_Probe(Scene_Base& scene) : Renderable_Base(scene), data(*this) {} - void prepare(const Scene_Render_Context&) override { - const auto& value = render_state_view().get(data); + void prepare(const Prepare_Render_Context& context) override { + const auto& value = context.frame.render_state.get(data); rendered_value = value.value_or(-1); } Bound_Latest data; diff --git a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp index f6b39f7..1978c8f 100644 --- a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp +++ b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp @@ -8,7 +8,7 @@ #include "renderive/scene/Scene.hpp" struct Renderable_Base_Test_Renderable : Renderable_Base { Renderable_Base_Test_Renderable(Scene_Base& scene, bool cache_enabled) : Renderable_Base(scene, {.cache_enabled = cache_enabled}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { ++render_count; } int render_count{}; @@ -47,7 +47,7 @@ TEST(renderable_base_test, changes_configuration_through_scene) { scene.attach_renderable(renderable); scene.render(); scene.wait_for_render(); - scene.set_renderable_configuration(*renderable, {.cache_enabled = true}); + scene.set_renderable_configuration(renderable, {.cache_enabled = true}); scene.render(); scene.wait_for_render(); EXPECT_TRUE(renderable->configuration().cache_enabled); @@ -55,7 +55,7 @@ TEST(renderable_base_test, changes_configuration_through_scene) { } struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Base { explicit Renderable_Base_Cache_Invalidation_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = true}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { const int count = render_count.fetch_add(1, std::memory_order_acq_rel) + 1; if (count != 1) { return; @@ -111,8 +111,8 @@ struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Base { return release_build; }); } - graph.emplace("prepare", "graph_rebuild_test", Render_Node_Kind::prepare, - [](const Scene_Render_Context&) {}); + graph.emplace("prepare", "graph_rebuild_test", + [](const Prepare_Render_Context&) {}); } void wait_build_started() { std::unique_lock lock(mutex); @@ -183,7 +183,7 @@ TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) std::atomic running{true}; std::thread writer([&] { for (int index = 0; index < 1000; ++index) { - scene.set_renderable_configuration(*renderable, {.cache_enabled = index % 2 != 0}); + scene.set_renderable_configuration(renderable, {.cache_enabled = index % 2 != 0}); } running.store(false, std::memory_order_release); }); diff --git a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp index 2878a95..c45c5e4 100644 --- a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp +++ b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp @@ -4,10 +4,9 @@ #include "renderive/scene/Scene.hpp" struct Renderable_Concept_Test : Renderable_Base { explicit Renderable_Concept_Test(Scene_Base& scene) : Renderable_Base(scene) {} - void prepare(const Scene_Render_Context&) override {} + void prepare(const Prepare_Render_Context&) override {} }; static_assert(Renderable); -static_assert(!std::same_as); TEST(renderable_concept_test, exposes_scene_and_configuration_state) { Scene2D_Context<> scene; Renderable_Concept_Test renderable(scene); diff --git a/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp new file mode 100644 index 0000000..016f96c --- /dev/null +++ b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp @@ -0,0 +1,487 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "renderive/renderable/Renderable.hpp" +#include "renderive/scene/Scene.hpp" + +namespace { + +using namespace std::chrono_literals; + +class Gate { +public: + void arrive_and_wait() { + std::unique_lock lock(mutex_); + arrived_ = true; + condition_.notify_all(); + condition_.wait(lock, [this] { return open_; }); + } + + void wait_until_arrived() { + std::unique_lock lock(mutex_); + ASSERT_TRUE(condition_.wait_for(lock, 2s, [this] { return arrived_; })); + } + + void open() { + std::lock_guard lock(mutex_); + open_ = true; + condition_.notify_all(); + } + +private: + std::mutex mutex_; + std::condition_variable condition_; + bool arrived_{}; + bool open_{}; +}; + +template +bool wait_until(Predicate&& predicate) { + const auto deadline = std::chrono::steady_clock::now() + 2s; + while (!predicate()) { + if (std::chrono::steady_clock::now() >= deadline) + return false; + std::this_thread::yield(); + } + return true; +} + +class Counting_Renderable : public Renderable_Base { +public: + explicit Counting_Renderable(Scene_Base& scene) + : Renderable_Base(scene, {.cache_enabled = false}) {} + + std::atomic prepare_count{}; + +protected: + void prepare(const Prepare_Render_Context&) override { + prepare_count.fetch_add(1, std::memory_order_release); + } +}; + +class Blocking_Renderable : public Counting_Renderable { +public: + Blocking_Renderable(Scene_Base& scene, std::shared_ptr gate, + int block_on_call = 1, + std::atomic* destroyed = nullptr) + : Counting_Renderable(scene), gate_(std::move(gate)), + block_on_call_(block_on_call), destroyed_(destroyed) {} + + ~Blocking_Renderable() override { + if (destroyed_) + destroyed_->store(true, std::memory_order_release); + } + +protected: + void prepare(const Prepare_Render_Context&) override { + const int call = prepare_count.fetch_add(1, std::memory_order_acq_rel) + 1; + if (call == block_on_call_) + gate_->arrive_and_wait(); + } + +private: + std::shared_ptr gate_; + int block_on_call_{}; + std::atomic* destroyed_{}; +}; + +TEST(dynamic_renderable_lifecycle_test, + attach_during_render_is_non_blocking_and_visible_on_the_next_frame) { + Scene2D_Context<> scene; + auto gate = std::make_shared(); + auto current = std::make_shared(scene, gate); + auto attached = std::make_shared(scene); + scene.attach_renderable(current); + + scene.render(); + gate->wait_until_arrived(); + std::atomic attach_finished{}; + std::thread controller([&] { + scene.attach_renderable(attached); + attach_finished.store(true, std::memory_order_release); + }); + EXPECT_TRUE(wait_until([&] { return attach_finished.load(std::memory_order_acquire); })); + EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 0); + + gate->open(); + controller.join(); + scene.wait_for_render(); + EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 0); + + scene.render(); + scene.wait_for_render(); + EXPECT_EQ(attached->prepare_count.load(std::memory_order_acquire), 1); +} + +TEST(dynamic_renderable_lifecycle_test, + detach_during_render_keeps_the_frame_owner_alive_until_execution_finishes) { + Scene2D_Context<> scene; + auto gate = std::make_shared(); + std::atomic destroyed{}; + auto renderable = std::make_shared(scene, gate, 1, &destroyed); + std::weak_ptr weak = renderable; + scene.attach_renderable(renderable); + + scene.render(); + gate->wait_until_arrived(); + std::atomic detach_finished{}; + std::thread controller([&] { + scene.detach_renderable(renderable); + detach_finished.store(true, std::memory_order_release); + }); + EXPECT_TRUE(wait_until([&] { return detach_finished.load(std::memory_order_acquire); })); + controller.join(); + renderable.reset(); + EXPECT_FALSE(destroyed.load(std::memory_order_acquire)); + EXPECT_FALSE(weak.expired()); + + gate->open(); + scene.wait_for_render(); + EXPECT_TRUE(wait_until([&] { return destroyed.load(std::memory_order_acquire); })); + EXPECT_TRUE(weak.expired()); + EXPECT_EQ(scene.renderable_count(), 0u); +} + +TEST(dynamic_renderable_lifecycle_test, + reparent_during_render_changes_only_the_next_frame_dependency_plan) { + Scene2D_Context<> scene; + auto first_gate = std::make_shared(); + auto second_gate = std::make_shared(); + auto first_parent = std::make_shared(scene, first_gate, 1); + auto second_parent = std::make_shared(scene, second_gate, 2); + auto child = std::make_shared(scene); + scene.attach_renderable(first_parent); + scene.attach_renderable(second_parent); + scene.attach_renderable(child, { + .dependency_parents = {first_parent} + }); + + scene.render(); + first_gate->wait_until_arrived(); + scene.set_dependency_parent(child, second_parent); + EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0); + first_gate->open(); + scene.wait_for_render(); + EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1); + + scene.render(); + second_gate->wait_until_arrived(); + EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 1); + second_gate->open(); + scene.wait_for_render(); + EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 2); +} + +class Rebuildable_Renderable final : public Renderable_Base { +public: + Rebuildable_Renderable(Scene_Base& scene, std::shared_ptr first_graph_gate) + : Renderable_Base(scene, {.cache_enabled = false}), + first_graph_gate_(std::move(first_graph_gate)) {} + + void publish_second_graph() { + graph_version_.store(2, std::memory_order_release); + rebuild_render_graph(); + } + + std::vector executions() const { + std::lock_guard lock(executions_mutex_); + return executions_; + } + +protected: + void build_prepare_graph(Renderable_Graph_Builder& builder) override { + const int version = graph_version_.load(std::memory_order_acquire); + builder.emplace("versioned_prepare", version == 1 ? "Graph A" : "Graph B", + [this, version](const Prepare_Render_Context&) { + if (version == 1) + first_graph_gate_->arrive_and_wait(); + std::lock_guard lock(executions_mutex_); + executions_.push_back(version); + }); + } + +private: + std::shared_ptr first_graph_gate_; + std::atomic graph_version_{1}; + mutable std::mutex executions_mutex_; + std::vector executions_; +}; + +TEST(dynamic_renderable_lifecycle_test, + graph_rebuild_during_render_publishes_an_immutable_graph_for_the_next_frame) { + Scene2D_Context<> scene; + auto gate = std::make_shared(); + auto renderable = std::make_shared(scene, gate); + scene.attach_renderable(renderable); + + scene.render(); + gate->wait_until_arrived(); + renderable->publish_second_graph(); + gate->open(); + scene.wait_for_render(); + EXPECT_EQ(renderable->executions(), std::vector({1})); + + scene.render(); + scene.wait_for_render(); + EXPECT_EQ(renderable->executions(), std::vector({1, 2})); +} + +class Self_Detaching_Renderable final : public Counting_Renderable { +public: + using Counting_Renderable::Counting_Renderable; + + std::weak_ptr self; + +protected: + void prepare(const Prepare_Render_Context&) override { + prepare_count.fetch_add(1, std::memory_order_release); + if (auto owner = self.lock()) + scene().detach_renderable(owner); + } +}; + +TEST(dynamic_renderable_lifecycle_test, + renderable_can_detach_itself_without_deadlock_and_finishes_the_current_frame) { + Scene2D_Context<> scene; + auto renderable = std::make_shared(scene); + renderable->self = renderable; + scene.attach_renderable(renderable); + + scene.render(); + scene.wait_for_render(); + EXPECT_EQ(renderable->prepare_count.load(std::memory_order_acquire), 1); + EXPECT_EQ(scene.renderable_count(), 0u); + + scene.render(); + scene.wait_for_render(); + EXPECT_EQ(renderable->prepare_count.load(std::memory_order_acquire), 1); +} + +class Tracking_Color_Cache final : public Color_Cache { +public: + Tracking_Color_Cache() : instance_id(next_instance_id.fetch_add(1) + 1) {} + explicit Tracking_Color_Cache(std::pmr::memory_resource&) : Tracking_Color_Cache() {} + ~Tracking_Color_Cache() override { + if (instance_id == watched_instance.load(std::memory_order_acquire)) + watched_destroyed.store(true, std::memory_order_release); + } + + void clear() override { + samples.clear(); + } + + void composite(const Color_Cache& source) override { + const auto& cache = dynamic_cast(source); + samples.insert(samples.end(), cache.samples.begin(), cache.samples.end()); + } + + static void reset() { + next_instance_id.store(0, std::memory_order_release); + watched_instance.store(0, std::memory_order_release); + watched_destroyed.store(false, std::memory_order_release); + } + + int instance_id{}; + std::vector samples; + inline static std::atomic next_instance_id{}; + inline static std::atomic watched_instance{}; + inline static std::atomic watched_destroyed{}; +}; + +class Blocking_Painted_Renderable final : public Renderable_Base { +public: + Blocking_Painted_Renderable(Scene_Base& scene, std::shared_ptr gate) + : Renderable_Base(scene, {.cache_enabled = false}), gate_(std::move(gate)) {} + +protected: + void prepare(const Prepare_Render_Context&) override { + if (prepare_count_.fetch_add(1, std::memory_order_acq_rel) == 0) + gate_->arrive_and_wait(); + } + + void build_paint_graph(Renderable_Graph_Builder& builder) override { + builder.emplace("paint", "Paint", + [](const Paint_Render_Context& context) { + auto& cache = dynamic_cast(context.color_cache); + cache.samples.push_back(cache.instance_id); + }); + } + +private: + std::shared_ptr gate_; + std::atomic prepare_count_{}; +}; + +TEST(dynamic_renderable_lifecycle_test, + detached_color_cache_lives_through_the_frame_and_reattach_uses_a_new_cache) { + Tracking_Color_Cache::reset(); + Scene2D_Context, Tracking_Color_Cache> scene; + auto gate = std::make_shared(); + auto renderable = std::make_shared(scene, gate); + scene.attach_renderable(renderable); + ASSERT_EQ(Tracking_Color_Cache::next_instance_id.load(), 2); + Tracking_Color_Cache::watched_instance.store(2, std::memory_order_release); + + scene.render(); + gate->wait_until_arrived(); + scene.detach_renderable(renderable); + EXPECT_FALSE(Tracking_Color_Cache::watched_destroyed.load(std::memory_order_acquire)); + scene.attach_renderable(renderable); + ASSERT_EQ(Tracking_Color_Cache::next_instance_id.load(), 3); + gate->open(); + scene.wait_for_render(); + EXPECT_TRUE(wait_until([] { + return Tracking_Color_Cache::watched_destroyed.load(std::memory_order_acquire); + })); + scene.with_final_color_cache([](const Tracking_Color_Cache& cache) { + EXPECT_EQ(cache.samples, std::vector({2})); + }); + + scene.render(); + scene.wait_for_render(); + scene.with_final_color_cache([](const Tracking_Color_Cache& cache) { + EXPECT_EQ(cache.samples, std::vector({3})); + }); +} + +class Sequence_Parent final : public Renderable_Base { +public: + Sequence_Parent(Scene_Base& scene, std::atomic& completed_sequence) + : Renderable_Base(scene, {.cache_enabled = false}), + completed_sequence_(completed_sequence) {} + +protected: + void prepare(const Prepare_Render_Context& context) override { + completed_sequence_.store(context.frame.render_sequence, std::memory_order_release); + } + +private: + std::atomic& completed_sequence_; +}; + +class Sequence_Child final : public Renderable_Base { +public: + Sequence_Child(Scene_Base& scene, std::atomic& parent_sequence, + std::atomic& violations) + : Renderable_Base(scene, {.cache_enabled = false}), + parent_sequence_(parent_sequence), violations_(violations) {} + + void invalidate_graph() { + rebuild_render_graph(); + } + +protected: + void prepare(const Prepare_Render_Context& context) override { + if (parent_sequence_.load(std::memory_order_acquire) != context.frame.render_sequence) + violations_.fetch_add(1, std::memory_order_relaxed); + } + +private: + std::atomic& parent_sequence_; + std::atomic& violations_; +}; + +TEST(dynamic_renderable_lifecycle_test, + atomic_attach_and_concurrent_membership_updates_never_publish_partial_topology) { + Scene2D_Context<> scene; + std::atomic parent_sequence{}; + std::atomic violations{}; + std::atomic failures{}; + auto parent = std::make_shared(scene, parent_sequence); + auto alternate_parent = + std::make_shared(scene, parent_sequence); + scene.attach_renderable(parent); + scene.attach_renderable(alternate_parent, { + .display_parents = {parent}, + .dependency_parents = {parent} + }); + + std::atomic controls_done{}; + std::thread renderer([&] { + while (!controls_done.load(std::memory_order_acquire)) { + try { + scene.render(); + scene.wait_for_render(); + } catch (...) { + failures.fetch_add(1, std::memory_order_relaxed); + } + } + }); + + constexpr int controller_count = 2; + constexpr int iterations = 1000; + std::vector controllers; + for (int controller = 0; controller < controller_count; ++controller) { + controllers.emplace_back([&] { + for (int index = 0; index < iterations; ++index) { + try { + auto child = std::make_shared( + scene, parent_sequence, violations); + scene.attach_renderable(child, { + .display_parents = {parent}, + .dependency_parents = {parent} + }); + scene.set_display_parent(child, alternate_parent); + scene.set_dependency_parent(child, alternate_parent); + child->invalidate_graph(); + scene.set_display_parent(child, parent); + scene.set_dependency_parent(child, parent); + scene.detach_renderable(child); + } catch (...) { + failures.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + for (auto& controller : controllers) + controller.join(); + controls_done.store(true, std::memory_order_release); + renderer.join(); + + EXPECT_EQ(failures.load(std::memory_order_acquire), 0); + EXPECT_EQ(violations.load(std::memory_order_acquire), 0); + EXPECT_EQ(scene.renderable_count(), 2u); +} + +TEST(dynamic_renderable_lifecycle_test, + captures_before_and_after_dynamic_membership_keep_both_plan_versions_resolvable) { + Scene2D_Context<> scene; + auto first = std::make_shared(scene); + auto second = std::make_shared(scene); + scene.attach_renderable(first); + const Capture_Session_Id session_id = scene.capture_frames(2); + + scene.render(); + scene.wait_for_render(); + scene.attach_renderable(second); + scene.render(); + scene.wait_for_render(); + + const auto session = scene.capture_session(session_id); + ASSERT_TRUE(session); + ASSERT_EQ(session->frames.size(), 2u); + const auto first_version = session->frames[0].snapshot->render_plan_version; + const auto second_version = session->frames[1].snapshot->render_plan_version; + EXPECT_NE(first_version, second_version); + const auto first_plan = scene.find_render_plan(first_version); + const auto second_plan = scene.find_render_plan(second_version); + ASSERT_TRUE(first_plan); + ASSERT_TRUE(second_plan); + EXPECT_TRUE(std::ranges::none_of(first_plan->graph.nodes, [&](const Render_Node& node) { + return node.owner_id == second->renderable_id(); + })); + EXPECT_TRUE(std::ranges::any_of(second_plan->graph.nodes, [&](const Render_Node& node) { + return node.owner_id == second->renderable_id(); + })); +} + +} // namespace diff --git a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp index 65024fd..a245194 100644 --- a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp +++ b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp @@ -23,8 +23,8 @@ public: std::atomic paint_count{}; protected: void build_prepare_graph(Renderable_Graph_Builder& builder) override { - builder.emplace("prepare", "Prepare", Render_Node_Kind::prepare, - [this](const Scene_Render_Context&) { + builder.emplace("prepare", "Prepare", + [this](const Prepare_Render_Context&) { ++prepare_count; if (prepare_action) prepare_action(); @@ -32,8 +32,8 @@ protected: } void build_paint_graph(Renderable_Graph_Builder& builder) override { const auto paint = builder.emplace( - "paint", "Paint", Render_Node_Kind::paint, - [this](const Scene_Render_Context&) { + "paint", "Paint", + [this](const Paint_Render_Context&) { ++paint_count; if (paint_action) paint_action(); @@ -55,12 +55,12 @@ public: protected: void build_prepare_graph(Renderable_Graph_Builder& builder) override { const auto root = builder.emplace("prepare", "Prepare", - Render_Node_Kind::prepare, [](const Scene_Render_Context&) {}); + [](const Prepare_Render_Context&) {}); for (std::size_t index = 0; index < chunk_count_; ++index) { const auto chunk = builder.emplace( "chunk:" + std::to_string(index), - "Chunk " + std::to_string(index), Render_Node_Kind::prepare, - [](const Scene_Render_Context&) {}); + "Chunk " + std::to_string(index), + [](const Prepare_Render_Context&) {}); builder.precede(root, chunk); } } @@ -95,8 +95,8 @@ TEST(render_plan_execution_test, dependency_edges_connect_prepare_only_and_layer auto child = std::make_shared(scene); scene.attach_renderable(parent); scene.attach_renderable(child); - scene.set_dependency_parent(*child, parent.get()); - scene.set_display_parent(*child, parent.get()); + scene.set_dependency_parent(child, parent); + scene.set_display_parent(child, parent); scene.render(); scene.wait_for_render(); @@ -166,7 +166,7 @@ TEST(render_plan_execution_test, dependent_prepare_does_not_wait_for_dependency_ child->prepare_action = [&] { child_prepared.store(true, std::memory_order_release); }; scene.attach_renderable(parent); scene.attach_renderable(child); - scene.set_dependency_parent(*child, parent.get()); + scene.set_dependency_parent(child, parent); scene.render(); { std::unique_lock lock(mutex); diff --git a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp index 299a154..307715b 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp @@ -8,9 +8,9 @@ static_assert(std::same_as&>().topology_snapshot()), Scene_Base::Topology_Snapshot>); struct Scene2D_Context_Test_Renderable : Renderable_Base { explicit Scene2D_Context_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context& context) override { + void prepare(const Prepare_Render_Context&) override { ++render_count; - scene_is_2d = dynamic_cast(context.scene) != nullptr; + scene_is_2d = dynamic_cast(&scene()) != nullptr; render_thread = std::this_thread::get_id(); } std::atomic render_count{}; @@ -28,7 +28,7 @@ TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) { EXPECT_EQ(first->render_count.load(), 1); EXPECT_TRUE(first->scene_is_2d); EXPECT_NE(first->render_thread, caller_thread); - scene.detach_renderable(*first); + scene.detach_renderable(first); scene.attach_renderable(second); scene.render(); scene.wait_for_render(); @@ -38,7 +38,7 @@ TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) { } struct Scene2D_Final_Cache_Blocking_Renderable : Renderable_Base { explicit Scene2D_Final_Cache_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { while (!release.load(std::memory_order_acquire)) { started.store(true, std::memory_order_release); std::this_thread::yield(); @@ -73,13 +73,13 @@ TEST(scene2d_context_test, final_color_cache_access_waits_for_render_completion) } struct Scene2D_Cached_Renderable : Renderable_Base { Scene2D_Cached_Renderable(Scene_Base& scene, std::uint64_t value) : Renderable_Base(scene, {.cache_enabled = true}), value(value) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { ++render_count; } void build_paint_graph(Renderable_Graph_Builder& graph) override { - graph.emplace("paint", "Paint", Render_Node_Kind::paint, - [this](const Scene_Render_Context& context) { - dynamic_cast(*context.color_cache).append(value); + graph.emplace("paint", "Paint", + [this](const Paint_Render_Context& context) { + dynamic_cast(context.color_cache).append(value); }); } std::uint64_t value; @@ -92,7 +92,7 @@ TEST(scene2d_context_test, reattached_cached_renderable_rebuilds_removed_color_c scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->render_count, 1); - scene.detach_renderable(*renderable); + scene.detach_renderable(renderable); scene.attach_renderable(renderable); scene.render(); scene.wait_for_render(); @@ -110,13 +110,13 @@ TEST(scene2d_context_test, dependency_reparent_invalidates_cached_child) { scene.attach_renderable(first_parent); scene.attach_renderable(second_parent); scene.attach_renderable(child); - scene.set_dependency_parent(*child, first_parent.get()); + scene.set_dependency_parent(child, first_parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(first_parent->render_count, 1); EXPECT_EQ(second_parent->render_count, 1); EXPECT_EQ(child->render_count, 1); - scene.set_dependency_parent(*child, second_parent.get()); + scene.set_dependency_parent(child, second_parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(first_parent->render_count, 1); @@ -131,8 +131,8 @@ TEST(scene2d_context_test, multiple_dependency_parents_invalidate_cached_child) scene.attach_renderable(first_parent); scene.attach_renderable(second_parent); scene.attach_renderable(child); - scene.set_dependency_parent(*child, first_parent.get()); - scene.add_dependency_parent(*child, *second_parent); + scene.set_dependency_parent(child, first_parent); + scene.add_dependency_parent(child, second_parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); @@ -159,7 +159,7 @@ TEST(scene2d_context_test, final_color_cache_callback_can_reenter_scene_control_ scene.render(); scene.wait_for_render(); scene.with_final_color_cache([&](const Recording_Color_Cache&) { - scene.set_renderable_configuration(*renderable, {.cache_enabled = false}); + scene.set_renderable_configuration(renderable, {.cache_enabled = false}); }); EXPECT_FALSE(renderable->configuration().cache_enabled); } @@ -171,14 +171,14 @@ TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_ scene.attach_renderable(grandparent); scene.attach_renderable(parent); scene.attach_renderable(child); - scene.set_dependency_parent(*parent, grandparent.get()); - scene.set_dependency_parent(*child, parent.get()); + scene.set_dependency_parent(parent, grandparent); + scene.set_dependency_parent(child, parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(grandparent->render_count, 1); EXPECT_EQ(parent->render_count, 1); EXPECT_EQ(child->render_count, 1); - scene.detach_renderable(*parent); + scene.detach_renderable(parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(grandparent->render_count, 1); @@ -216,8 +216,8 @@ TEST(scene2d_context_test, failed_attach_leaves_renderable_fully_detached) { } struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Base { explicit Scene2D_Viewport_Snapshot_Renderable(Scene_Base& scene) : Renderable_Base(scene) {} - void prepare(const Scene_Render_Context& context) override { - viewport = context.viewport; + void prepare(const Prepare_Render_Context& context) override { + viewport = context.frame.viewport; } Frame_Viewport viewport; }; diff --git a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp index 2685411..5f08165 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp @@ -20,13 +20,13 @@ struct Scene2D_Render_Order_State { struct Scene2D_Render_Order_Renderable : Renderable_Base { Scene2D_Render_Order_Renderable(Scene_Base& scene, Scene2D_Render_Order_State& state, std::uint64_t id) : Renderable_Base(scene, {.cache_enabled = false}), state(&state), id(id) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { state->append(id); } void build_paint_graph(Renderable_Graph_Builder& graph) override { - graph.emplace("paint", "Paint", Render_Node_Kind::paint, - [this](const Scene_Render_Context& context) { - dynamic_cast(*context.color_cache).append(id); + graph.emplace("paint", "Paint", + [this](const Paint_Render_Context& context) { + dynamic_cast(context.color_cache).append(id); }); } Scene2D_Render_Order_State* state; @@ -39,8 +39,8 @@ TEST(scene2d_render_order_test, separates_dependency_order_from_display_order) { auto axis = std::make_shared(scene, state, 1); scene.attach_renderable(spectrum); scene.attach_renderable(axis); - scene.set_dependency_parent(*spectrum, axis.get()); - scene.set_display_parent(*axis, spectrum.get()); + scene.set_dependency_parent(spectrum, axis); + scene.set_display_parent(axis, spectrum); scene.render(); scene.wait_for_render(); EXPECT_EQ(state.snapshot(), (std::vector{1, 2})); @@ -61,10 +61,10 @@ TEST(scene2d_render_order_test, shared_overlay_waits_for_every_display_parent) { scene.attach_renderable(first_plot); scene.attach_renderable(axis); scene.attach_renderable(second_plot); - scene.add_dependency_parent(*first_plot, *axis); - scene.add_dependency_parent(*second_plot, *axis); - scene.add_display_parent(*axis, *first_plot); - scene.add_display_parent(*axis, *second_plot); + scene.add_dependency_parent(first_plot, axis); + scene.add_dependency_parent(second_plot, axis); + scene.add_display_parent(axis, first_plot); + scene.add_display_parent(axis, second_plot); scene.render(); scene.wait_for_render(); const auto render_order = state.snapshot(); diff --git a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp index 32c00f2..127b986 100644 --- a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp @@ -5,8 +5,8 @@ #include "renderive/scene/Scene.hpp" struct Scene3D_Context_Test_Renderable : Renderable_Base { explicit Scene3D_Context_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {} - void prepare(const Scene_Render_Context& context) override { - scene_is_3d = dynamic_cast(context.scene) != nullptr; + void prepare(const Prepare_Render_Context&) override { + scene_is_3d = dynamic_cast(&scene()) != nullptr; ++render_count; } std::atomic render_count{}; @@ -23,7 +23,7 @@ TEST(scene3d_context_test, uses_scene_3d_base_contract) { } struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Base { explicit Scene3D_Dependency_Cache_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = true}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { ++render_count; } int render_count{}; @@ -36,8 +36,8 @@ TEST(scene3d_context_test, propagates_cache_invalidation_through_unsorted_depend scene.attach_renderable(leaf); scene.attach_renderable(middle); scene.attach_renderable(root); - scene.set_dependency_parent(*leaf, middle.get()); - scene.set_dependency_parent(*middle, root.get()); + scene.set_dependency_parent(leaf, middle); + scene.set_dependency_parent(middle, root); scene.render(); scene.wait_for_render(); EXPECT_EQ(root->render_count, 1); @@ -58,14 +58,14 @@ TEST(scene3d_context_test, detach_parent_promotes_children_and_invalidates_depen scene.attach_renderable(grandparent); scene.attach_renderable(parent); scene.attach_renderable(child); - scene.set_display_parent(*parent, grandparent.get()); - scene.set_display_parent(*child, parent.get()); - scene.set_dependency_parent(*parent, grandparent.get()); - scene.set_dependency_parent(*child, parent.get()); + scene.set_display_parent(parent, grandparent); + scene.set_display_parent(child, parent); + scene.set_dependency_parent(parent, grandparent); + scene.set_dependency_parent(child, parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); - scene.detach_renderable(*parent); + scene.detach_renderable(parent); const auto topology = scene.topology_snapshot(); bool display_checked{}; bool dependency_checked{}; diff --git a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp index 65c8f25..35c37c2 100644 --- a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp @@ -35,11 +35,9 @@ struct Scene_Memory_Resource_Test_Renderable : Renderable_Base { explicit Scene_Memory_Resource_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} void build_prepare_graph(Renderable_Graph_Builder& graph) override { auto first = graph.emplace("first", "prepare_memory_resource_test", - Render_Node_Kind::prepare, - [](const Scene_Render_Context&) {}); + [](const Prepare_Render_Context&) {}); auto second = graph.emplace("second", "render_memory_resource_test", - Render_Node_Kind::prepare, - [](const Scene_Render_Context&) {}); + [](const Prepare_Render_Context&) {}); graph.precede(first, second); } void build_graph_for_test() { diff --git a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp index fceb2d3..89827a2 100644 --- a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp +++ b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include #include @@ -7,10 +9,23 @@ #include #include "renderive/renderable/Renderable.hpp" #include "renderive/scene/Scene.hpp" +namespace { +template +bool wait_until(Predicate&& predicate) { + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(2); + while (!predicate()) { + if (std::chrono::steady_clock::now() >= deadline) + return false; + std::this_thread::yield(); + } + return true; +} +} struct Scene_Base_Test_Renderable : Renderable_Base { explicit Scene_Base_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {} - void prepare(const Scene_Render_Context& context) override { - render_sequence = context.render_sequence; + void prepare(const Prepare_Render_Context& context) override { + render_sequence = context.frame.render_sequence; ++render_count; } std::uint64_t render_sequence{}; @@ -28,7 +43,7 @@ TEST(scene_base_test, publishes_renderable_collection_before_background_render) } struct Scene_Base_Throwing_Renderable : Renderable_Base { explicit Scene_Base_Throwing_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { throw std::runtime_error("render failed"); } }; @@ -50,7 +65,7 @@ TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_f } struct Scene_Base_Blocking_Renderable : Renderable_Base { explicit Scene_Base_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { std::unique_lock lock(mutex); rendering = true; condition.notify_all(); @@ -74,7 +89,7 @@ struct Scene_Base_Blocking_Renderable : Renderable_Base { bool rendering{}; bool released{}; }; -TEST(scene_base_test, scene_mutation_waits_until_background_render_is_idle) { +TEST(scene_base_test, scene_model_mutation_does_not_wait_for_background_render) { Scene2D_Context<> scene; auto renderable = std::make_shared(scene); scene.attach_renderable(renderable); @@ -82,11 +97,13 @@ TEST(scene_base_test, scene_mutation_waits_until_background_render_is_idle) { renderable->wait_rendering(); std::atomic mutation_finished{}; std::thread mutation([&] { - scene.set_renderable_configuration(*renderable, {.cache_enabled = true}); + scene.set_renderable_configuration(renderable, {.cache_enabled = true}); mutation_finished.store(true, std::memory_order_release); }); std::this_thread::yield(); - EXPECT_FALSE(mutation_finished.load(std::memory_order_acquire)); + EXPECT_TRUE(wait_until([&] { + return mutation_finished.load(std::memory_order_acquire); + })); renderable->release(); mutation.join(); EXPECT_TRUE(mutation_finished.load(std::memory_order_acquire)); @@ -106,24 +123,28 @@ TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized) }); std::thread mutator([&] { for (int index = 0; index < 100; ++index) { - Renderable_Base* value = index % 2 == 0 ? parent.get() : nullptr; - scene.set_dependency_parent(*child, value); - scene.set_display_parent(*child, value); - scene.set_renderable_configuration(*child, {.cache_enabled = index % 2 == 0}); + if (index % 2 == 0) { + scene.set_dependency_parent(child, parent); + scene.set_display_parent(child, parent); + } else { + scene.clear_dependency_parent(child); + scene.clear_display_parent(child); + } + scene.set_renderable_configuration(child, {.cache_enabled = index % 2 == 0}); } }); renderer.join(); mutator.join(); EXPECT_EQ(scene.renderable_count(), 2); } -TEST(scene_base_test, detach_releases_renderable_from_both_scene_buffers) { +TEST(scene_base_test, detach_releases_renderable_from_the_live_model) { Scene2D_Context<> scene; auto renderable = std::make_shared(scene); std::weak_ptr weak = renderable; scene.attach_renderable(renderable); scene.render(); scene.wait_for_render(); - scene.detach_renderable(*renderable); + scene.detach_renderable(renderable); renderable.reset(); EXPECT_TRUE(weak.expired()); EXPECT_EQ(scene.renderable_count(), 0); @@ -137,11 +158,11 @@ TEST(scene_base_test, topology_rejects_unattached_renderables) { auto parent = std::make_shared(scene); auto child = std::make_shared(scene); scene.attach_renderable(child); - EXPECT_THROW(scene.set_dependency_parent(*child, parent.get()), std::invalid_argument); - EXPECT_THROW(scene.set_display_parent(*child, parent.get()), std::invalid_argument); + EXPECT_THROW(scene.set_dependency_parent(child, parent), std::invalid_argument); + EXPECT_THROW(scene.set_display_parent(child, parent), std::invalid_argument); scene.attach_renderable(parent); auto detached_child = std::make_shared(scene); - EXPECT_THROW(scene.set_dependency_parent(*detached_child, parent.get()), std::invalid_argument); + EXPECT_THROW(scene.set_dependency_parent(detached_child, parent), std::invalid_argument); } TEST(scene_base_test, setting_same_topology_parent_is_a_noop) { Scene2D_Context<> scene; @@ -149,9 +170,9 @@ TEST(scene_base_test, setting_same_topology_parent_is_a_noop) { auto child = std::make_shared(scene); scene.attach_renderable(parent); scene.attach_renderable(child); - scene.set_dependency_parent(*child, parent.get()); + scene.set_dependency_parent(child, parent); const auto revision = child->prepare_revision(); - EXPECT_NO_THROW(scene.set_dependency_parent(*child, parent.get())); + EXPECT_NO_THROW(scene.set_dependency_parent(child, parent)); EXPECT_EQ(child->prepare_revision(), revision); const auto topology = scene.topology_snapshot(); EXPECT_EQ(topology.dependency.size(), 2); @@ -172,9 +193,53 @@ TEST(scene_base_test, display_graph_rejects_cycles) { scene.attach_renderable(first); scene.attach_renderable(second); scene.attach_renderable(third); - scene.set_display_parent(*second, first.get()); - scene.add_display_parent(*third, *second); - EXPECT_THROW(scene.add_display_parent(*first, *third), std::invalid_argument); + scene.set_display_parent(second, first); + scene.add_display_parent(third, second); + EXPECT_THROW(scene.add_display_parent(first, third), std::invalid_argument); +} +TEST(scene_base_test, atomic_attach_can_place_existing_display_children) { + Scene2D_Context<> scene; + auto parent = std::make_shared(scene); + auto axis = std::make_shared(scene); + auto renderable = std::make_shared(scene); + scene.attach_renderable(parent); + scene.attach_renderable(axis); + scene.attach_renderable(renderable, { + .display_parents = {parent}, + .display_children = {axis}, + .dependency_parents = {parent, axis} + }); + + const auto topology = scene.topology_snapshot(); + const auto contains = [](const auto& relationships, const auto& child, + const auto& parent_value) { + return std::ranges::any_of(relationships, [&](const auto& relationship) { + return relationship.child.get() == child.get() && + relationship.parent.get() == parent_value.get(); + }); + }; + EXPECT_TRUE(contains(topology.display, renderable, parent)); + EXPECT_TRUE(contains(topology.display, axis, renderable)); + EXPECT_TRUE(contains(topology.dependency, renderable, parent)); + EXPECT_TRUE(contains(topology.dependency, renderable, axis)); +} +TEST(scene_base_test, failed_atomic_attach_rolls_back_every_topology) { + Scene2D_Context<> scene; + auto parent = std::make_shared(scene); + auto renderable = std::make_shared(scene); + scene.attach_renderable(parent); + + EXPECT_THROW(scene.attach_renderable(renderable, { + .display_parents = {parent}, + .display_children = {parent}, + .dependency_parents = {parent} + }), std::invalid_argument); + EXPECT_EQ(scene.renderable_count(), 1u); + EXPECT_NO_THROW(scene.attach_renderable(renderable, { + .display_parents = {parent}, + .dependency_parents = {parent} + })); + EXPECT_EQ(scene.renderable_count(), 2u); } TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) { Scene2D_Context<> scene; @@ -182,7 +247,7 @@ TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) { std::weak_ptr weak = renderable; scene.attach_renderable(renderable); const auto snapshot = scene.topology_snapshot(); - scene.detach_renderable(*renderable); + scene.detach_renderable(renderable); renderable.reset(); EXPECT_FALSE(weak.expired()); EXPECT_EQ(snapshot.renderables.size(), 1); @@ -196,8 +261,13 @@ TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) { scene.attach_renderable(child); std::thread writer([&] { for (int index = 0; index < 500; ++index) { - scene.set_dependency_parent(*child, index % 2 == 0 ? parent.get() : nullptr); - scene.set_display_parent(*child, index % 2 == 0 ? parent.get() : nullptr); + if (index % 2 == 0) { + scene.set_dependency_parent(child, parent); + scene.set_display_parent(child, parent); + } else { + scene.clear_dependency_parent(child); + scene.clear_display_parent(child); + } } }); for (int index = 0; index < 500; ++index) { @@ -229,7 +299,7 @@ TEST(scene_base_test, all_waiters_receive_the_same_render_failure) { } struct Scene_Base_Throw_Once_Renderable : Renderable_Base { explicit Scene_Base_Throw_Once_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context&) override { + void prepare(const Prepare_Render_Context&) override { if (render_count.fetch_add(1, std::memory_order_acq_rel) == 0) { throw std::runtime_error("first render failed"); } @@ -250,25 +320,26 @@ TEST(scene_base_test, unobserved_render_failure_survives_next_successful_submiss } struct Scene_Base_Render_Execution_Renderable : Renderable_Base { explicit Scene_Base_Render_Execution_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {} - void prepare(const Scene_Render_Context& context) override { - context.scene->wait_for_render(); + void prepare(const Prepare_Render_Context&) override { + scene().wait_for_render(); wait_returned.store(true, std::memory_order_release); try { - context.scene->render(); + scene().render(); } catch (const std::logic_error&) { nested_render_rejected.store(true, std::memory_order_release); } try { - context.scene->set_renderable_configuration(*this, {.cache_enabled = true}); - } catch (const std::logic_error&) { - mutation_rejected.store(true, std::memory_order_release); + scene().set_renderable_configuration(shared_from_this(), + {.cache_enabled = true}); + mutation_completed.store(true, std::memory_order_release); + } catch (...) { } } std::atomic wait_returned{}; std::atomic nested_render_rejected{}; - std::atomic mutation_rejected{}; + std::atomic mutation_completed{}; }; -TEST(scene_base_test, taskflow_execution_thread_cannot_wait_on_or_mutate_current_scene) { +TEST(scene_base_test, taskflow_execution_can_mutate_live_model_but_cannot_submit_nested_render) { Scene2D_Context<> scene; auto renderable = std::make_shared(scene); scene.attach_renderable(renderable); @@ -276,5 +347,5 @@ TEST(scene_base_test, taskflow_execution_thread_cannot_wait_on_or_mutate_current scene.wait_for_render(); EXPECT_TRUE(renderable->wait_returned.load(std::memory_order_acquire)); EXPECT_TRUE(renderable->nested_render_rejected.load(std::memory_order_acquire)); - EXPECT_TRUE(renderable->mutation_rejected.load(std::memory_order_acquire)); + EXPECT_TRUE(renderable->mutation_completed.load(std::memory_order_acquire)); } diff --git a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp index 73bcfa8..b35e928 100644 --- a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp +++ b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp @@ -74,10 +74,10 @@ using Threading_Test_Triple_State = Triple_State_Strategy& render_count, std::atomic& maximum_sequence) : Renderable_Base(scene, {.cache_enabled = false}), render_count(&render_count), maximum_sequence(&maximum_sequence) {} - void prepare(const Scene_Render_Context& context) override { + void prepare(const Prepare_Render_Context& context) override { render_count->fetch_add(1, std::memory_order_relaxed); std::uint64_t maximum = maximum_sequence->load(std::memory_order_acquire); - while (maximum < context.render_sequence && !maximum_sequence->compare_exchange_weak(maximum, context.render_sequence, std::memory_order_acq_rel)) {} + while (maximum < context.frame.render_sequence && !maximum_sequence->compare_exchange_weak(maximum, context.frame.render_sequence, std::memory_order_acq_rel)) {} } std::atomic* render_count; std::atomic* maximum_sequence; @@ -85,8 +85,8 @@ struct Threading_Test_Scene_Renderable : Renderable_Base { struct Threading_Test_Render_Graph_Renderable : Renderable_Base { Threading_Test_Render_Graph_Renderable(Scene_Base& scene, std::atomic& executed) : Renderable_Base(scene, {.cache_enabled = false}), executed(&executed) {} void build_prepare_graph(Renderable_Graph_Builder& graph) override { - graph.emplace("prepare", "Prepare", Render_Node_Kind::prepare, - [this](const Scene_Render_Context&) { + graph.emplace("prepare", "Prepare", + [this](const Prepare_Render_Context&) { executed->fetch_add(1, std::memory_order_relaxed); }); } diff --git a/cmake/RenderivePackage.cmake b/cmake/RenderivePackage.cmake index 94638eb..ee5ecd6 100644 --- a/cmake/RenderivePackage.cmake +++ b/cmake/RenderivePackage.cmake @@ -1,5 +1,7 @@ get_filename_component(renderive_package_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) set(renderive_package_includes "^.*$") +set(renderive_package_build_output + "${CMAKE_CURRENT_BINARY_DIR}/package/renderive_package.zip") set(renderive_package_excludes "^data/.*$" "^performance/.*$" @@ -21,8 +23,15 @@ set(renderive_package_excludes ) _add_project_zip_target( renderive_package_zip - "${renderive_package_root}/renderive_package.zip" + "${renderive_package_build_output}" "${renderive_package_root}" renderive_package_includes renderive_package_excludes ) +add_custom_command( + TARGET renderive_package_zip POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${renderive_package_build_output}" + "${renderive_package_root}/renderive_package.zip" + COMMENT "Publishing ${renderive_package_root}/renderive_package.zip" +) diff --git a/render_2D/axis/Axis_Builder.h b/render_2D/axis/Axis_Builder.h index dbaed91..ff7124e 100644 --- a/render_2D/axis/Axis_Builder.h +++ b/render_2D/axis/Axis_Builder.h @@ -74,7 +74,10 @@ public: if (!parent_) return {}; auto result = parent_->scene().template make_renderable(properties_); - attach_renderable_child(parent_->scene(), result, parent_); + parent_->scene().attach_renderable(result, { + .display_parents = {parent_}, + .dependency_parents = {parent_} + }); return result; } diff --git a/render_2D/axis/Axis_State_Strategy.hpp b/render_2D/axis/Axis_State_Strategy.hpp index 2c2a3c3..f107983 100644 --- a/render_2D/axis/Axis_State_Strategy.hpp +++ b/render_2D/axis/Axis_State_Strategy.hpp @@ -121,8 +121,8 @@ private: double unit_width{}; bool valid{}; }; - void prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) final { + void prepare_frame(const Prepare_Render_Context& context) final { + const auto& view = context.frame.render_state; const auto& state = view.get(static_cast(*this)); auto& output = prepare_buffer_; output = {}; @@ -185,11 +185,11 @@ private: } output.valid = true; } - void paint(Painter& painter, const Scene_Render_Context&) final { + void paint(Painter& painter, const Paint_Render_Context& context) final { const auto& output = prepare_buffer_; if (!output.valid) return; - const auto view = this->render_state_view(); + const auto& view = context.frame.render_state; const auto& state = view.get(static_cast(*this)); const Pen axis_pen{state.color, 1.0}; for (const auto& line : output.lines) diff --git a/render_2D/plottable/Afterglow.cpp b/render_2D/plottable/Afterglow.cpp index af7e3a5..bfbd9aa 100644 --- a/render_2D/plottable/Afterglow.cpp +++ b/render_2D/plottable/Afterglow.cpp @@ -84,9 +84,8 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( builder, "prepare", "Prepare Afterglow", - [this, partition_count](const Render_State_View& frame_view, - const Scene_Render_Context& context) { - prepare_render_frame(frame_view, partition_count); + [this, partition_count](const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, impl_->prepare_buffer.history.size()); @@ -101,8 +100,7 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { const auto task = builder.emplace( "accumulate:" + std::to_string(index), "Afterglow Chunk " + std::to_string(index + 1) + " Accumulate", - Render_Node_Kind::prepare, - [this, index](const Scene_Render_Context& context) { + [this, index](const Prepare_Render_Context& context) { accumulate_partition(index); if (context.metrics) { const auto range = render_partition_range( @@ -119,16 +117,15 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { if (partition_count == 0) return; const auto normalize = builder.emplace( - "normalize", "Normalize Afterglow", Render_Node_Kind::prepare, - [this](const Scene_Render_Context&) { normalize_render_frame(); }); + "normalize", "Normalize Afterglow", + [this](const Prepare_Render_Context&) { normalize_render_frame(); }); for (const auto task : accumulation) builder.precede(task, normalize); for (int index = 0; index < partition_count; ++index) { const auto task = builder.emplace( "color:" + std::to_string(index), "Afterglow Chunk " + std::to_string(index + 1) + " Color", - Render_Node_Kind::prepare, - [this, index](const Scene_Render_Context&) { color_partition(index); }); + [this, index](const Prepare_Render_Context&) { color_partition(index); }); builder.precede(normalize, task); } } @@ -152,7 +149,7 @@ void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto paint_image = add_paint_task( builder, "paint", "Paint Afterglow", - [this](Painter& painter, const Scene_Render_Context& context) { + [this](Painter& painter, const Paint_Render_Context& context) { paint_render_frame(painter); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, @@ -271,16 +268,15 @@ void Afterglow_Control::render_frame_completed( render_graph_changed(); } -void Afterglow_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { - prepare_render_frame(view, 1); +void Afterglow_Control::prepare_frame(const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, 1); accumulate_partition(0); normalize_render_frame(); color_partition(0); } void Afterglow_Control::paint(Painter& painter, - const Scene_Render_Context&) { + const Paint_Render_Context&) { paint_render_frame(painter); } } diff --git a/render_2D/plottable/Afterglow.h b/render_2D/plottable/Afterglow.h index fd1991a..ffd03e7 100644 --- a/render_2D/plottable/Afterglow.h +++ b/render_2D/plottable/Afterglow.h @@ -33,9 +33,8 @@ public: append_spectrum(std::span(values.data(), values.size())); } protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; void build_prepare_graph(Renderable_Graph_Builder& builder) override; void build_paint_graph(Renderable_Graph_Builder& builder) override; private: diff --git a/render_2D/plottable/Constellation_Diagram.cpp b/render_2D/plottable/Constellation_Diagram.cpp index 7b55946..e67b173 100644 --- a/render_2D/plottable/Constellation_Diagram.cpp +++ b/render_2D/plottable/Constellation_Diagram.cpp @@ -54,8 +54,9 @@ void Constellation_Diagram_Control::fit_square_to_axes() { void Constellation_Diagram_Control::publish() { publish_properties(); } -void Constellation_Diagram_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { +void Constellation_Diagram_Control::prepare_frame( + const Prepare_Render_Context& context) { + const auto& view = context.frame.render_state; const auto& state = render_properties(view); const auto& points = view.get(impl_->points); const Axis_Transform x = impl_->i_axis->transform(view); @@ -79,9 +80,9 @@ void Constellation_Diagram_Control::prepare_frame(const Render_State_View& view, } } void Constellation_Diagram_Control::paint(Painter& painter, - const Scene_Render_Context&) { + const Paint_Render_Context& context) { const auto& output = impl_->prepare_buffer; - const auto view = render_state_view(); + const auto& view = context.frame.render_state; const auto& state = render_properties(view); for (const PointF point : output.anchors) painter.circle(point, 3.0, Pen{state.anchor_color}, diff --git a/render_2D/plottable/Constellation_Diagram.h b/render_2D/plottable/Constellation_Diagram.h index 0127ef7..56bbded 100644 --- a/render_2D/plottable/Constellation_Diagram.h +++ b/render_2D/plottable/Constellation_Diagram.h @@ -25,9 +25,8 @@ public: [[nodiscard]] std::size_t point_count() const; void fit_square_to_axes(); protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; std::unique_ptr impl_; diff --git a/render_2D/plottable/Frequency_Trace.cpp b/render_2D/plottable/Frequency_Trace.cpp index ade9317..b969fb7 100644 --- a/render_2D/plottable/Frequency_Trace.cpp +++ b/render_2D/plottable/Frequency_Trace.cpp @@ -42,8 +42,8 @@ std::size_t Frequency_Trace_Control::rendered_point_count() const { void Frequency_Trace_Control::publish() { publish_properties(); } -void Frequency_Trace_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { +void Frequency_Trace_Control::prepare_frame(const Prepare_Render_Context& context) { + const auto& view = context.frame.render_state; const auto& state = render_properties(view); const auto& samples = view.get(impl_->samples); auto& output = impl_->prepare_buffer; @@ -57,8 +57,8 @@ void Frequency_Trace_Control::prepare_frame(const Render_State_View& view, output.points.push_back(mapped_point(x, tick, y, value)); } void Frequency_Trace_Control::paint(Painter& painter, - const Scene_Render_Context&) { - const auto view = render_state_view(); + const Paint_Render_Context& context) { + const auto& view = context.frame.render_state; painter.polyline(impl_->prepare_buffer.points, render_properties(view).pen); } } diff --git a/render_2D/plottable/Frequency_Trace.h b/render_2D/plottable/Frequency_Trace.h index ee09186..4add745 100644 --- a/render_2D/plottable/Frequency_Trace.h +++ b/render_2D/plottable/Frequency_Trace.h @@ -15,9 +15,8 @@ public: [[nodiscard]] std::size_t sample_count() const; [[nodiscard]] std::size_t rendered_point_count() const; protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; std::unique_ptr impl_; diff --git a/render_2D/plottable/Plottable.h b/render_2D/plottable/Plottable.h index ca6aaa2..7620b1c 100644 --- a/render_2D/plottable/Plottable.h +++ b/render_2D/plottable/Plottable.h @@ -119,26 +119,5 @@ private: template using Attach_Plottable = Attach_Builder; -template -requires std::derived_from && std::derived_from -void attach_renderable_dependency(Renderable_Type& renderable, const std::shared_ptr& axis) { - if (axis) { - renderable.scene().add_dependency_parent(renderable, *axis); - if constexpr (std::derived_from) { - renderable.scene().add_display_parent(renderable, *axis); - } else { - renderable.scene().add_display_parent(*axis, renderable); - } - } -} - -template -void attach_renderable_dependency(Renderable_Type&, const Value&) {} - -template -requires std::derived_from -void attach_renderable_dependencies(Control& renderable, const Args&... args) { - (attach_renderable_dependency(renderable, args), ...); -} } } diff --git a/render_2D/plottable/Selection_Rectangle_Overlay.cpp b/render_2D/plottable/Selection_Rectangle_Overlay.cpp index 4913c62..1076fa5 100644 --- a/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -110,7 +110,8 @@ void Selection_Rectangle_Overlay_Control::publish() { impl_->interaction.publish(); } void Selection_Rectangle_Overlay_Control::prepare_frame( - const Render_State_View& view, const Scene_Render_Context&) { + const Prepare_Render_Context& context) { + const auto& view = context.frame.render_state; const auto& state = render_properties(view); const auto& interaction = view.get(impl_->interaction); const auto horizontal = impl_->horizontal_axis->transform(view); @@ -133,9 +134,9 @@ void Selection_Rectangle_Overlay_Control::prepare_frame( interaction.selection_current.y - interaction.selection_start.y}; } void Selection_Rectangle_Overlay_Control::paint( - Painter& painter, const Scene_Render_Context&) { + Painter& painter, const Paint_Render_Context& context) { const auto& output = impl_->prepare_buffer; - const auto view = render_state_view(); + const auto& view = context.frame.render_state; const auto& state = render_properties(view); for (const auto& region : output.regions) { painter.rect(region.pixels, state.selection_border_pen, diff --git a/render_2D/plottable/Selection_Rectangle_Overlay.h b/render_2D/plottable/Selection_Rectangle_Overlay.h index 83fecbe..2575478 100644 --- a/render_2D/plottable/Selection_Rectangle_Overlay.h +++ b/render_2D/plottable/Selection_Rectangle_Overlay.h @@ -17,9 +17,8 @@ public: void clear_selected_regions(); void handle_event(const Event& event) override; protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; std::unique_ptr impl_; diff --git a/render_2D/plottable/Spectrum.cpp b/render_2D/plottable/Spectrum.cpp index 1baffd2..f43f962 100644 --- a/render_2D/plottable/Spectrum.cpp +++ b/render_2D/plottable/Spectrum.cpp @@ -302,9 +302,8 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); const auto prepare = add_prepare_task( builder, "prepare", "Prepare Spectrum", - [this, partition_count](const Render_State_View& frame_view, - const Scene_Render_Context& context) { - prepare_render_frame(frame_view, partition_count); + [this, partition_count](const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, impl_->prepare_buffer.frame.samples.size()); @@ -317,8 +316,7 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { const auto partition = builder.emplace( "chunk_prepare:" + std::to_string(index), "Spectrum Chunk " + std::to_string(index + 1) + " Prepare", - Render_Node_Kind::prepare, - [this, index](const Scene_Render_Context& context) { + [this, index](const Prepare_Render_Context& context) { prepare_partition(index); if (context.metrics) { const auto range = render_partition_range( @@ -345,11 +343,11 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { const auto paint = add_paint_task( builder, "paint", "Paint Spectrum", [this, partition_count](Painter& painter, - const Scene_Render_Context& context) { - paint_background(painter); + const Paint_Render_Context& context) { + paint_background(painter, context.frame.render_state); for (int index = 0; index < partition_count; ++index) - paint_partition(painter, index); - paint_overlay(painter); + paint_partition(painter, index, context.frame.render_state); + paint_overlay(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::primitive_count, impl_->prepare_buffer.work_size); @@ -483,12 +481,12 @@ void Spectrum_Control::prepare_partition(int partition_index) { state.visible_range_only, state.interpolation_mode); } -void Spectrum_Control::paint_partition(Painter& painter, int partition_index) { +void Spectrum_Control::paint_partition(Painter& painter, int partition_index, + const Render_State_View& view) { const auto& output = impl_->prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& partition = output.partitions[static_cast(partition_index)]; - const auto view = render_state_view(); const auto& state = render_properties(view); const auto clip = painter.scoped_clip(partition.clip); paint_curve(painter, partition.maximum, state.max_pen, state.max_brush); @@ -496,20 +494,20 @@ void Spectrum_Control::paint_partition(Painter& painter, int partition_index) { paint_curve(painter, partition.current, state.current_pen, state.current_brush); } -void Spectrum_Control::paint_background(Painter& painter) { +void Spectrum_Control::paint_background(Painter& painter, + const Render_State_View& view) { const auto& output = impl_->prepare_buffer; if (!output.valid || output.sweep_region.empty()) return; - const auto view = render_state_view(); painter.rect(output.sweep_region, Pen{.style = Line_Style::None}, render_properties(view).sweep_region_brush); } -void Spectrum_Control::paint_overlay(Painter& painter) { +void Spectrum_Control::paint_overlay(Painter& painter, + const Render_State_View& view) { const auto& output = impl_->prepare_buffer; if (!output.valid) return; - const auto view = render_state_view(); const auto& paint_state = render_properties(view); for (const auto& marker : output.markers) { const Pen& pen = marker.style == Spectrum_Marker_Style::middle @@ -547,16 +545,15 @@ void Spectrum_Control::render_frame_completed( render_graph_changed(); } -void Spectrum_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { - prepare_render_frame(view, 1); +void Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, 1); prepare_partition(0); } void Spectrum_Control::paint(Painter& painter, - const Scene_Render_Context&) { - paint_background(painter); - paint_partition(painter, 0); - paint_overlay(painter); + const Paint_Render_Context& context) { + paint_background(painter, context.frame.render_state); + paint_partition(painter, 0, context.frame.render_state); + paint_overlay(painter, context.frame.render_state); } } diff --git a/render_2D/plottable/Spectrum.h b/render_2D/plottable/Spectrum.h index aa8935f..429f1bf 100644 --- a/render_2D/plottable/Spectrum.h +++ b/render_2D/plottable/Spectrum.h @@ -65,9 +65,8 @@ public: void set_current_marker_frequency(double frequency); void handle_event(const Event& event) override; protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; void build_prepare_graph(Renderable_Graph_Builder& builder) override; void build_paint_graph(Renderable_Graph_Builder& builder) override; private: @@ -75,9 +74,10 @@ private: std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); void prepare_partition(int partition_index); - void paint_partition(Painter& painter, int partition_index); - void paint_background(Painter& painter); - void paint_overlay(Painter& painter); + void paint_partition(Painter& painter, int partition_index, + const Render_State_View& state); + void paint_background(Painter& painter, const Render_State_View& state); + void paint_overlay(Painter& painter, const Render_State_View& state); void render_frame_completed(std::uint64_t target_interval_ns) noexcept override; void publish() override; }; diff --git a/render_2D/plottable/Sweep_Spectrum.cpp b/render_2D/plottable/Sweep_Spectrum.cpp index bdef379..8712511 100644 --- a/render_2D/plottable/Sweep_Spectrum.cpp +++ b/render_2D/plottable/Sweep_Spectrum.cpp @@ -63,8 +63,8 @@ void Sweep_Spectrum_Control::publish() { const int limit = get<&Sweep_Spectrum_Properties::block_count>(); impl_->blocks.retain_latest(static_cast(limit)); } -void Sweep_Spectrum_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { +void Sweep_Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) { + const auto& view = context.frame.render_state; const auto& state = render_properties(view); const auto& blocks = view.get(impl_->blocks); const auto values = flatten(blocks); @@ -83,11 +83,11 @@ void Sweep_Spectrum_Control::prepare_frame(const Render_State_View& view, output.valid = true; } void Sweep_Spectrum_Control::paint(Painter& painter, - const Scene_Render_Context&) { + const Paint_Render_Context& context) { const auto& output = impl_->prepare_buffer; if (!output.valid) return; - const auto view = render_state_view(); + const auto& view = context.frame.render_state; const auto& state = render_properties(view); painter.polyline(output.points, state.pen); painter.line(output.current_first, output.current_second, diff --git a/render_2D/plottable/Sweep_Spectrum.h b/render_2D/plottable/Sweep_Spectrum.h index 01a2584..fcc6c2b 100644 --- a/render_2D/plottable/Sweep_Spectrum.h +++ b/render_2D/plottable/Sweep_Spectrum.h @@ -28,9 +28,8 @@ public: [[nodiscard]] std::size_t stored_point_count() const; [[nodiscard]] std::size_t rendered_point_count() const; protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; std::unique_ptr impl_; diff --git a/render_2D/plottable/Waterfall.cpp b/render_2D/plottable/Waterfall.cpp index 242c0c2..a7d03e0 100644 --- a/render_2D/plottable/Waterfall.cpp +++ b/render_2D/plottable/Waterfall.cpp @@ -133,9 +133,8 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( builder, "prepare", "Prepare Waterfall", - [this, partition_count](const Render_State_View& frame_view, - const Scene_Render_Context& context) { - prepare_render_frame(frame_view, partition_count); + [this, partition_count](const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, impl_->prepare_buffer.rows.size()); @@ -150,8 +149,7 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { const auto partition = builder.emplace( "chunk_prepare:" + std::to_string(index), "Waterfall Chunk " + std::to_string(index + 1) + " Prepare", - Render_Node_Kind::prepare, - [this, index](const Scene_Render_Context& context) { + [this, index](const Prepare_Render_Context& context) { render_partition(index); if (context.metrics) { const auto range = render_partition_range( @@ -168,8 +166,8 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { void Waterfall_Control::build_paint_graph(Renderable_Graph_Builder& builder) { const auto paint_image = add_paint_task( builder, "paint", "Paint Waterfall", - [this](Painter& painter, const Scene_Render_Context& context) { - paint_render_frame(painter); + [this](Painter& painter, const Paint_Render_Context& context) { + paint_render_frame(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, impl_->prepare_buffer.work_size); @@ -263,11 +261,11 @@ void Waterfall_Control::render_partition(int partition_index) { } } -void Waterfall_Control::paint_render_frame(Painter& painter) { +void Waterfall_Control::paint_render_frame(Painter& painter, + const Render_State_View& view) { const auto& output = impl_->prepare_buffer; if (!output.valid) return; - const auto view = render_state_view(); const auto& paint_state = render_properties(view); painter.heatmap(output.layout.target, output.layout.width, output.layout.height, output.pixels, paint_state.interpolation_mode); @@ -293,12 +291,12 @@ void Waterfall_Control::render_frame_completed( render_graph_changed(); } -void Waterfall_Control::prepare_frame(const Render_State_View& view, - const Scene_Render_Context&) { - prepare_render_frame(view, 1); +void Waterfall_Control::prepare_frame(const Prepare_Render_Context& context) { + prepare_render_frame(context.frame.render_state, 1); render_partition(0); } -void Waterfall_Control::paint(Painter& painter, const Scene_Render_Context&) { - paint_render_frame(painter); +void Waterfall_Control::paint(Painter& painter, + const Paint_Render_Context& context) { + paint_render_frame(painter, context.frame.render_state); } } diff --git a/render_2D/plottable/Waterfall.h b/render_2D/plottable/Waterfall.h index 599e5f3..b92eeb1 100644 --- a/render_2D/plottable/Waterfall.h +++ b/render_2D/plottable/Waterfall.h @@ -41,16 +41,15 @@ public: [[nodiscard]] std::size_t rendered_cell_count() const; void handle_event(const Event& event) override; protected: - void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) override; - void paint(Painter& painter, const Scene_Render_Context& context) override; + void prepare_frame(const Prepare_Render_Context& context) override; + void paint(Painter& painter, const Paint_Render_Context& context) override; void build_prepare_graph(Renderable_Graph_Builder& builder) override; void build_paint_graph(Renderable_Graph_Builder& builder) override; private: struct Impl; std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); - void paint_render_frame(Painter& painter); + void paint_render_frame(Painter& painter, const Render_State_View& state); void render_frame_completed(std::uint64_t target_interval_ns) noexcept override; void render_partition(int partition_index); void publish() override; diff --git a/render_2D/renderable/Renderable.cpp b/render_2D/renderable/Renderable.cpp index 39ea197..e4a64fa 100644 --- a/render_2D/renderable/Renderable.cpp +++ b/render_2D/renderable/Renderable.cpp @@ -3,8 +3,7 @@ #include "../render/Blend2D_Cache.h" -#include -#include +#include namespace renderive { @@ -29,18 +28,6 @@ Renderable::Renderable(::Scene_Base& scene, bool cache_enabled) Renderable::~Renderable() = default; -void attach_renderable_child(::Scene_Base& scene, const std::shared_ptr& renderable, const std::shared_ptr& parent) { - scene.attach_renderable(renderable); - try { - scene.set_display_parent(*renderable, parent.get()); - scene.set_dependency_parent(*renderable, parent.get()); - } catch (...) { - scene.detach_renderable(*renderable); - throw; - } - scene.notify_model_dirty(); -} - Renderable_Cache_Mode Renderable::get_cache_mode() const noexcept { return configuration().cache_enabled ? Renderable_Cache_Mode::Local_Pixel @@ -48,8 +35,9 @@ Renderable_Cache_Mode Renderable::get_cache_mode() const noexcept { } void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { - scene().set_renderable_configuration(*this, {.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel}); - scene().notify_model_dirty(); + scene().set_renderable_configuration( + shared_from_this(), + {.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel}); } std::string Renderable::object_name() const { @@ -62,15 +50,6 @@ void Renderable::set_object_name(std::string name) { object_name_ = std::move(name); } -bool Renderable::is_visible() const noexcept { - return visible_.load(std::memory_order_acquire); -} - -void Renderable::set_visible(bool visible) { - if (visible_.exchange(visible, std::memory_order_acq_rel) != visible) - changed(); -} - Renderable_Observation Renderable::observation() const noexcept { return observer_.observation(); } @@ -84,14 +63,14 @@ void Renderable::observe_state(Renderable_Observer_Event event, void Renderable::build_prepare_graph(Renderable_Graph_Builder& builder) { add_prepare_task(builder, "prepare", "Prepare", - [this](const Render_State_View& state, const Scene_Render_Context& context) { - prepare_frame(state, context); + [this](const Prepare_Render_Context& context) { + prepare_frame(context); }); } void Renderable::build_paint_graph(Renderable_Graph_Builder& builder) { const auto paint_task = add_paint_task(builder, "paint", "Paint", - [this](detail::Painter& painter, const Scene_Render_Context& context) { + [this](detail::Painter& painter, const Paint_Render_Context& context) { paint(painter, context); }); builder.precede(builder.find("prepare"), paint_task); @@ -100,13 +79,10 @@ void Renderable::build_paint_graph(Renderable_Graph_Builder& builder) { Renderable_Graph_Builder::Task Renderable::add_prepare_task( Renderable_Graph_Builder& builder, std::string logical_key, - std::string name, - Prepare_Task_Function function) { + std::string name, + Prepare_Task_Function function) { return builder.emplace(std::move(logical_key), std::move(name), - Render_Node_Kind::prepare, - [this, function = std::move(function)](const Scene_Render_Context& context) { - function(render_state_view(), context); - }); + std::move(function)); } Renderable_Graph_Builder::Task Renderable::add_paint_task( @@ -115,14 +91,14 @@ Renderable_Graph_Builder::Task Renderable::add_paint_task( std::string name, Paint_Task_Function function) { return builder.emplace(std::move(logical_key), std::move(name), - Render_Node_Kind::paint, - [this, function = std::move(function)](const Scene_Render_Context& context) { - if (!is_visible() || context.color_cache == nullptr) + [function = std::move(function)](const Paint_Render_Context& context) { + if (!context.renderable.visible) return; - auto* cache = dynamic_cast(context.color_cache); + auto* cache = dynamic_cast(&context.color_cache); if (!cache) return; - detail::Painter painter(*cache, {context.viewport.width, context.viewport.height}); + detail::Painter painter(*cache, {context.frame.viewport.width, + context.frame.viewport.height}); if (painter) function(painter, context); }); diff --git a/render_2D/renderable/Renderable.h b/render_2D/renderable/Renderable.h index 645e0d0..e0884fa 100644 --- a/render_2D/renderable/Renderable.h +++ b/render_2D/renderable/Renderable.h @@ -51,8 +51,6 @@ private: Renderable_Observation observation_; }; -LIB_DECL void attach_renderable_child(::Scene_Base& scene, const std::shared_ptr& renderable, const std::shared_ptr& parent); - class LIB_DECL Renderable : public ::Renderable_Base { public: explicit Renderable(::Scene_Base& scene, bool cache_enabled = true); @@ -63,20 +61,17 @@ public: [[nodiscard]] std::string object_name() const; void set_object_name(std::string name); - [[nodiscard]] bool is_visible() const noexcept; - void set_visible(bool visible); [[nodiscard]] Renderable_Observation observation() const noexcept; protected: using Prepare_Task_Function = - std::function; + std::function; using Paint_Task_Function = - std::function; + std::function; - virtual void prepare_frame(const Render_State_View& state, - const Scene_Render_Context& context) = 0; + virtual void prepare_frame(const Prepare_Render_Context& context) = 0; virtual void paint(detail::Painter& painter, - const Scene_Render_Context& context) = 0; + const Paint_Render_Context& context) = 0; virtual void build_prepare_graph(Renderable_Graph_Builder& builder) override; virtual void build_paint_graph(Renderable_Graph_Builder& builder) override; Renderable_Graph_Builder::Task add_prepare_task(Renderable_Graph_Builder& builder, @@ -98,7 +93,6 @@ private: friend class detail::Renderable_State_Observer; mutable std::mutex metadata_mutex_; std::string object_name_; - std::atomic visible_{true}; Renderable_Observer observer_; }; diff --git a/render_2D/renderable/Renderable_Builder.h b/render_2D/renderable/Renderable_Builder.h index d1e6635..bfa2f1e 100644 --- a/render_2D/renderable/Renderable_Builder.h +++ b/render_2D/renderable/Renderable_Builder.h @@ -3,12 +3,58 @@ #include #include #include +#include +#include #include #include -#include -#include #include +#include namespace renderive { +class Abs_Axis; +namespace detail { +class Paint_Overlay; + +template +void append_unique(std::vector<::Scene_Base::Renderable>& renderables, + const std::shared_ptr& renderable) { + ::Scene_Base::Renderable base = renderable; + if (base && std::find(renderables.begin(), renderables.end(), base) == + renderables.end()) + renderables.push_back(std::move(base)); +} + +template +requires std::derived_from && + std::derived_from +void append_renderable_dependency( + ::Scene_Base::Attach_Relationships& relationships, + const std::shared_ptr& axis) { + if (!axis) + return; + append_unique(relationships.dependency_parents, axis); + if constexpr (std::derived_from) + append_unique(relationships.display_parents, axis); + else + append_unique(relationships.display_children, axis); +} + +template +void append_renderable_dependency( + ::Scene_Base::Attach_Relationships&, const Value&) {} + +template +requires std::derived_from +auto renderable_attach_relationships( + const std::shared_ptr& parent, const Args&... args) + -> ::Scene_Base::Attach_Relationships { + ::Scene_Base::Attach_Relationships relationships; + append_unique(relationships.display_parents, parent); + append_unique(relationships.dependency_parents, parent); + (append_renderable_dependency(relationships, args), ...); + return relationships; +} +} + template Validator_Type = No_Property_Validator> class Renderable_Builder { public: @@ -38,23 +84,10 @@ public: if (!parent || !(valid_argument(args) && ...)) return {}; validator(properties); - if constexpr (requires(Product& product) { attach_renderable_dependencies(product, args...); } && - (std::copy_constructible> && ...)) { - auto dependency_args = std::make_tuple(args...); - auto result = parent->scene().template make_renderable(properties, std::forward(args)...); - attach_renderable_child(parent->scene(), result, parent); - try { - std::apply([&result](const auto&... values) { - attach_renderable_dependencies(*result, values...); - }, dependency_args); - } catch (...) { - result->scene().detach_renderable(*result); - throw; - } - return result; - } + auto relationships = + detail::renderable_attach_relationships(parent, args...); auto result = parent->scene().template make_renderable(properties, std::forward(args)...); - attach_renderable_child(parent->scene(), result, parent); + parent->scene().attach_renderable(result, std::move(relationships)); return result; } private: diff --git a/render_2D/scene/Scene.cpp b/render_2D/scene/Scene.cpp index 2abb40b..e5212fc 100644 --- a/render_2D/scene/Scene.cpp +++ b/render_2D/scene/Scene.cpp @@ -7,8 +7,8 @@ class Renderable_Group final : public Renderable { public: using Renderable::Renderable; private: - void prepare_frame(const Render_State_View&, const Scene_Render_Context&) override {} - void paint(Painter&, const Scene_Render_Context&) override {} + void prepare_frame(const Prepare_Render_Context&) override {} + void paint(Painter&, const Paint_Render_Context&) override {} }; } std::shared_ptr make_renderable_group(::Scene_Base& scene, bool cache_enabled) { diff --git a/render_2D/scene/Scene.h b/render_2D/scene/Scene.h index a29ef0b..c064269 100644 --- a/render_2D/scene/Scene.h +++ b/render_2D/scene/Scene.h @@ -210,7 +210,6 @@ public: auto root = detail::make_renderable_group(*this, true); root->set_object_name("root"); this->attach_renderable(root); - this->notify_model_dirty(); } [[nodiscard]] std::shared_ptr root_renderable() const { const auto topology = this->topology_snapshot(); @@ -239,8 +238,7 @@ public: void remove_renderable(const std::shared_ptr& renderable) { if (!renderable || renderable == root_renderable()) return; - this->detach_renderable(*renderable); - this->notify_model_dirty(); + this->detach_renderable(renderable); } void set_background_color(Color color) { if (background_color() == color) @@ -479,22 +477,16 @@ private: void attach_renderable_node(const std::shared_ptr& renderable, const std::shared_ptr& parent) { if (!renderable) throw std::invalid_argument("renderable is null"); - Renderable* parent_pointer = parent.get(); - if (!parent_pointer) { - auto root = root_renderable(); - if (!root) + auto actual_parent = parent; + if (!actual_parent) { + actual_parent = root_renderable(); + if (!actual_parent) throw std::logic_error("Scene2D::init must be called before adding renderables"); - parent_pointer = root.get(); } - this->attach_renderable(renderable); - try { - this->set_display_parent(*renderable, parent_pointer); - this->set_dependency_parent(*renderable, parent_pointer); - } catch (...) { - this->detach_renderable(*renderable); - throw; - } - this->notify_model_dirty(); + this->attach_renderable(renderable, { + .display_parents = {actual_parent}, + .dependency_parents = {actual_parent} + }); } std::shared_ptr observer_; mutable std::mutex control_mutex_; diff --git a/renderive_package.zip b/renderive_package.zip index e39eac3..585d71c 100644 Binary files a/renderive_package.zip and b/renderive_package.zip differ