diff --git a/kernel/src/kernel/double_buffer/model.hpp b/kernel/src/kernel/double_buffer/model.hpp index ca1ef2f..2e4724d 100644 --- a/kernel/src/kernel/double_buffer/model.hpp +++ b/kernel/src/kernel/double_buffer/model.hpp @@ -199,105 +199,8 @@ struct Def : Base, detail::Public_Access { }; // Attach_Object 是对象运行时唯一数据入口:Prop 向外发布,State 向内提交,普通 Buffer 只推进,Dependency_Graph 负责依赖图同步。 namespace detail { -template -struct Renderable_Runtime_Private : Base {}; - -template - requires requires { typename Base::Renderable_Runtime_Interface; } -struct Renderable_Runtime_Private : Base { -private: - [[nodiscard]] Attached_Private& runtime() noexcept { - return static_cast&>(*this); - } - [[nodiscard]] const Attached_Private& runtime() const noexcept { - return static_cast&>(*this); - } -public: - [[nodiscard]] std::string_view business_name() const override { - static const std::string value = [] { - std::string name = typeid(Obj).name(); - for (const std::string_view prefix : {"struct ", "class "}) - if (name.starts_with(prefix)) name.erase(0, prefix.size()); - if (const auto separator = name.rfind("::"); separator != std::string::npos) - name.erase(0, separator + 2); - return name; - }(); - return value; - } - [[nodiscard]] bool prepare_predicate(Root*, bool dirty) override { - if constexpr (requires { typename Base::No_Prepare; }) return false; - else if constexpr (requires { this->should_prepare(runtime().object, *runtime().state.current, dirty); }) - return this->should_prepare(runtime().object, *runtime().state.current, dirty); - else return false; - } - [[nodiscard]] bool prepare_rebuild_predicate(Root*) override { - if constexpr (requires { this->should_rebuild_prepare_graph(runtime().object, *runtime().current); }) - return this->should_rebuild_prepare_graph(runtime().object, *runtime().current); - else return false; - } - [[nodiscard]] bool has_prepare_graph() const override { - return requires(Renderable_Runtime_Private& value, Obj* object) { - static_cast(value).build_prepare_graph(object, *value.runtime().current); - }; - } - [[nodiscard]] bool has_prepare_run() const override { - return !requires { typename Base::No_Prepare; } && - requires(Renderable_Runtime_Private& value, Obj* object) { value.prepare_data(object); }; - } - void run_prepare(Root*) override { - if constexpr (!requires { typename Base::No_Prepare; } && - requires { this->prepare_data(runtime().object); }) { - this->prepare_data(runtime().object); - auto callback = [&](auto& value) { - if constexpr (requires { value.after_prepare_data(runtime().object); }) - value.after_prepare_data(runtime().object); - }; - Obj::template walk_private(runtime().object, callback); - } - } - [[nodiscard]] typename Base::Task_Graph_Type build_prepare_graph(Root*) override { - using Result = typename Base::Task_Graph_Type; - if constexpr (requires { static_cast(*this).build_prepare_graph(runtime().object, *runtime().current); }) - return static_cast(*this).build_prepare_graph(runtime().object, *runtime().current); - else return Result{}; - } - [[nodiscard]] bool paint_predicate(Root*, bool dirty) override { - if constexpr (requires { this->should_paint(runtime().object, *runtime().state.current, dirty); }) - return this->should_paint(runtime().object, *runtime().state.current, dirty); - else return false; - } - [[nodiscard]] bool paint_rebuild_predicate(Root*) override { - if constexpr (requires { this->should_rebuild_paint_graph(runtime().object, *runtime().current); }) - return this->should_rebuild_paint_graph(runtime().object, *runtime().current); - else return false; - } - [[nodiscard]] bool has_paint_graph() const override { - return requires(Renderable_Runtime_Private& value, Obj* object) { - static_cast(value).build_paint_graph(object, *value.runtime().current); - }; - } - [[nodiscard]] bool has_paint_run() const override { - return requires(Renderable_Runtime_Private& value, Obj* object) { value.paint(object); }; - } - void run_paint(Root*) override { - if constexpr (requires { this->paint(runtime().object); }) this->paint(runtime().object); - } - [[nodiscard]] typename Base::Task_Graph_Type build_paint_graph(Root*) override { - using Result = typename Base::Task_Graph_Type; - if constexpr (requires { static_cast(*this).build_paint_graph(runtime().object, *runtime().current); }) - return static_cast(*this).build_paint_graph(runtime().object, *runtime().current); - else return Result{}; - } - [[nodiscard]] void* pending_renderable_state(Root*) override { - return runtime().state.pending; - } - void publish_renderable_state(Root*) override { - runtime().template publish_state_snapshot(); - } -}; - template > + typename Base = typename Obj::Private> struct Axis_Runtime_Private : Base {}; template diff --git a/kernel/src/kernel/render_common.hpp b/kernel/src/kernel/render_common.hpp index a45b581..e6938c3 100644 --- a/kernel/src/kernel/render_common.hpp +++ b/kernel/src/kernel/render_common.hpp @@ -26,8 +26,8 @@ using double_buffer::Attached; using double_buffer::Dependency_Graph_Type; using double_buffer::Dependency_Graph; using double_buffer::Dependency_Graph_Error; -/* Prepare 数据阶段在 Dependency_Graph 图中的标签。 */ -struct Prepare_Data_Tag {}; +/* Renderable 业务图在 Scene 依赖图中的唯一标签。 */ +struct Render_Graph_Tag {}; /* 全局 Taskflow 运行时状态标签,用于注册状态回调。 */ struct Task_Runtime_State_Tag {}; /* 单一 Taskflow 任务类型的累计统计。 */ diff --git a/kernel/src/kernel/renderable.cpp b/kernel/src/kernel/renderable.cpp index 1736b15..4943659 100644 --- a/kernel/src/kernel/renderable.cpp +++ b/kernel/src/kernel/renderable.cpp @@ -4,10 +4,7 @@ std::optional Renderable::event_routing_distance(const Event& event) con const auto& data = static_cast(*d); return data.event_routing_distance_run ? data.event_routing_distance_run(this, event) : std::nullopt; } -Task_Graph& Renderable::prepare_taskflow() { - return static_cast(*d).prepare_extension; -} -Task_Graph& Renderable::paint_taskflow() { - return static_cast(*d).paint_extension; +Task_Graph& Renderable::taskflow() { + return static_cast(*d).extension; } } diff --git a/kernel/src/kernel/renderable.hpp b/kernel/src/kernel/renderable.hpp index 5459fb0..35c9add 100644 --- a/kernel/src/kernel/renderable.hpp +++ b/kernel/src/kernel/renderable.hpp @@ -14,73 +14,30 @@ template concept Event_Renderable = Attached && requires(typename T::Private& private_data, T* object, const Event& event) { { private_data.handle_event(object, event) } -> std::same_as; }; -/* - * Prepare 数据模式定制点。 - * 最终对象的 Private 提供 void prepare_data(T* object) 即满足;若同时存在 build_prepare_graph(...),子图模式优先。 - */ +/* 最终 Private 提供 build_graph(T*, const Prop&) 时使用其自定义业务图。 */ template -concept Prepare_Data_Renderable = Attached && requires(typename T::Private& private_data, T* object) { - { private_data.prepare_data(object) } -> std::same_as; +concept Graph_Renderable = Attached && requires(typename T::Private& private_data, + T* object, + const typename T::Prop& prop) { + { private_data.build_graph(object, prop) } -> std::same_as; }; -/* - * Prepare 子图模式定制点。 - * 最终对象的 Private 提供 Task_Graph build_prepare_graph(T* object, const T::State& state) 即满足。 - * 子图首次执行前一定构建,之后由 should_rebuild_prepare_graph(...) 决定是否重建。 - */ +/* 最终可交给 Scene 执行的 Renderable 契约。 */ template -concept Prepare_Graph_Renderable = Attached && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) { - { private_data.build_prepare_graph(object, prop) } -> std::same_as; -}; -/* 最终 Private 声明 No_Prepare 时,该 Renderable 不参与 Prepare 阶段。 */ -template -concept No_Prepare_Renderable = Attached && requires { typename T::Private::No_Prepare; }; -/* - * Paint 数据模式定制点。 - * 最终对象的 Private 提供 void paint(T* object) 即满足;若同时存在 build_paint_graph(...),子图模式优先。 - */ -template -concept Paint_Data_Renderable = Attached && requires(typename T::Private& private_data, T* object) { - { private_data.paint(object) } -> std::same_as; -}; -/* - * Paint 子图模式定制点。 - * 最终对象的 Private 提供 Task_Graph build_paint_graph(T* object, const T::State& state) 即满足。 - * 子图首次执行前一定构建,之后由 should_rebuild_paint_graph(...) 决定是否重建。 - */ -template -concept Paint_Graph_Renderable = Attached && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) { - { private_data.build_paint_graph(object, prop) } -> std::same_as; -}; -/* - * 最终可交给 Scene 执行的 Renderable 契约。 - * Prepare 与 Paint 各自至少提供数据模式或子图模式之一,并继承 Renderable 提供的默认阶段策略。 - */ -template -concept Renderable_Object = Attached && std::derived_from && requires(typename T::Private& private_data, T* object, const typename T::State& state, const typename T::Prop& prop, bool dirty) { - { private_data.should_prepare(object, state, dirty) } -> std::same_as; - { private_data.should_paint(object, state, dirty) } -> std::same_as; - { private_data.should_rebuild_prepare_graph(object, prop) } -> std::same_as; - { private_data.should_rebuild_paint_graph(object, prop) } -> std::same_as; -} && (No_Prepare_Renderable || Prepare_Data_Renderable || Prepare_Graph_Renderable) && (Paint_Data_Renderable || Paint_Graph_Renderable); +concept Renderable_Object = Attached && std::derived_from; /* * Renderable 定义 Scene 可调度对象的公共机制。 - * 用户继续派生该定义,在最终具体类型的 Private 中提供 Prepare/Paint 定制点并直接创建该具体对象。 + * 用户继续派生该定义,在最终具体类型的 Private 中定义自己的业务图。 */ struct Renderable : Def { /* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */ struct Prop : Prev_Prop {}; - /* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */ + /* Renderable 每次 Scene 执行后发布的业务图状态与统计。 */ struct State : Prev_State { - bool prepare_dirty{}; /* Prepare 条件判断时观察到的 Prepare_Data_Tag dirty 状态。 */ - bool paint_dirty{}; /* 专用渲染阶段条件判断时观察到的 dirty 状态。 */ - bool prepare_executed{}; /* 本次 Scene 执行是否运行了 Prepare 数据函数或子图。 */ - bool paint_executed{}; /* 本次 Scene 执行是否运行了 Paint 数据函数或子图。 */ - bool prepare_graph_rebuilt{}; /* 本次 Prepare 条件判断是否重建了 Prepare 子图。 */ - bool paint_graph_rebuilt{}; /* 本次 Paint 条件判断是否重建了 Paint 子图。 */ - std::size_t prepare_task_count{}; /* Prepare 子图的任务数;数据模式固定为 1。 */ - std::size_t paint_task_count{}; /* Paint 子图的任务数;数据模式固定为 1。 */ - std::uint64_t prepare_execution_time_ns{}; /* 本次 Prepare 阶段实际执行耗时,单位为纳秒;未执行时为 0。 */ - std::uint64_t paint_execution_time_ns{}; /* 本次 Paint 阶段实际执行耗时,单位为纳秒;未执行时为 0。 */ + bool render_dirty{}; /* 条件判断时观察到的 Render_Graph_Tag dirty 状态。 */ + bool render_executed{}; /* 本次 Scene 执行是否运行了业务图。 */ + bool graph_rebuilt{}; /* 本次 advance 是否重建了业务图。 */ + std::size_t task_count{}; /* 当前业务图的任务数。 */ + std::uint64_t execution_time_ns{}; /* 本次业务图实际执行耗时,单位为纳秒。 */ /* 支持测试、快照比较和变更检测的逐字段相等比较。 */ bool operator==(const State&) const = default; }; @@ -92,17 +49,9 @@ struct Renderable : Def { * 返回 Prepare 阶段完成后执行的业务 DAG 扩展端口。 * Task_Graph 只能在该 Renderable 所属 Scene 没有运行时修改。 */ - [[nodiscard]] Task_Graph& prepare_taskflow(); - /* - * 返回 Paint 阶段完成后执行的业务 DAG 扩展端口。 - * Task_Graph 只能在该 Renderable 所属 Scene 没有运行时修改。 - */ - [[nodiscard]] Task_Graph& paint_taskflow(); + [[nodiscard]] Task_Graph& taskflow(); private: - /* 数据模式的内部调度入口:执行最终对象 prepare_data(...),再触发各 CRTP 层 after_prepare_data(...)。 */ - template - static void run_prepare_data(Object* object); - /* 依赖图绑定时为最终对象建立无虚函数分派表,并标记首次 Prepare。 */ + /* 依赖图绑定时为最终对象建立 CRTP 类型擦除入口,并标记首次执行。 */ void bind_dependency_graph_object(Attached auto* object); friend struct Dependency_Graph; }; diff --git a/kernel/src/kernel/renderable.ipp b/kernel/src/kernel/renderable.ipp index 50f07ca..21ef1ac 100644 --- a/kernel/src/kernel/renderable.ipp +++ b/kernel/src/kernel/renderable.ipp @@ -1,89 +1,115 @@ #pragma once #include +#include #include #include namespace aethera { struct Renderable::Private : Prev_Private { - using Renderable_Runtime_Interface = void; - using Task_Graph_Type = Task_Graph; - using Renderable_State_Tag = Renderable::Base_Tag; - /* - * 派生 Renderable 的 Private 必须继承 Prev_Private,并为 Prepare/Paint 各提供一种执行能力。 - * Prepare 在编译期按能力选择:存在 build_prepare_graph(...) 时使用子图模式,否则使用 prepare_data(...);两者同时存在时子图优先。 - * Paint 在编译期按能力选择:存在 build_paint_graph(...) 时使用子图模式,否则使用 paint(...);两者同时存在时子图优先。 - * should_rebuild_prepare_graph(...) 和 should_rebuild_paint_graph(...) 只决定已选中子图模式后的重建时机,不负责选择模式。 - */ using Event_Run = void (*)(Root*, const Event&); using Event_Routing_Distance_Run = std::optional (*)(const Root*, const Event&); using Color_Cache_Visitor = void (*)(void*, const Color_Cache&); using Color_Cache_Visit = void (*)(Root*, void*, Color_Cache_Visitor); - [[nodiscard]] virtual std::string_view business_name() const = 0; - [[nodiscard]] virtual bool prepare_predicate(Root* object, bool dirty) = 0; - [[nodiscard]] virtual bool prepare_rebuild_predicate(Root* object) = 0; - [[nodiscard]] virtual bool has_prepare_graph() const = 0; - [[nodiscard]] virtual bool has_prepare_run() const = 0; - virtual void run_prepare(Root* object) = 0; - [[nodiscard]] virtual Task_Graph build_prepare_graph(Root* object) = 0; - [[nodiscard]] virtual bool paint_predicate(Root* object, bool dirty) = 0; - [[nodiscard]] virtual bool paint_rebuild_predicate(Root* object) = 0; - [[nodiscard]] virtual bool has_paint_graph() const = 0; - [[nodiscard]] virtual bool has_paint_run() const = 0; - virtual void run_paint(Root* object) = 0; - [[nodiscard]] virtual Task_Graph build_paint_graph(Root* object) = 0; - [[nodiscard]] virtual void* pending_renderable_state(Root* object) = 0; - virtual void publish_renderable_state(Root* object) = 0; - Event_Run event_run{}; /* 最终 Private 具备事件能力时的无虚函数入口。 */ - Event_Routing_Distance_Run event_routing_distance_run{}; /* 可选事件候选距离;Scene 路由规则按需查询。 */ - Color_Cache_Visit color_cache_visit{}; /* 最终对象存在 Color_Cache Buffer 时访问本轮写入结果。 */ - std::unique_ptr prepare_graph; /* Prepare 子图模式的当前构建产物。 */ - std::unique_ptr paint_graph; /* Paint 子图模式的当前构建产物。 */ - Task_Graph prepare_extension{"renderable.prepare.extension"}; /* 外部直接续写的 Prepare 完成图。 */ - Task_Graph paint_extension{"renderable.paint.extension"}; /* 外部直接续写的 Paint 完成图。 */ - bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */ - bool paint_graph_built{}; /* Paint 子图是否至少成功构建过一次。 */ - /* CRTP 可覆盖:决定已选中子图模式的 Prepare 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */ - bool should_rebuild_prepare_graph(Attached auto* object, const Prop& prop); - /* CRTP 可覆盖:决定已选中子图模式的 Paint 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */ - bool should_rebuild_paint_graph(Attached auto* object, const Prop& prop); - /* CRTP 可覆盖:决定本次是否执行 Prepare;object 为最终对象,state 为当前发布状态,dirty 为 Prepare dirty;默认返回 dirty。 */ - bool should_prepare(Attached auto* object, const State& state, bool dirty); - /* CRTP 可覆盖:决定本次是否执行 Paint;object 为最终对象,state 为当前发布状态,dirty 为 Paint dirty;默认返回 dirty。 */ - bool should_paint(Attached auto* object, const State& state, bool dirty); - /* CRTP 可覆盖:prepare_data(...) 完成后按派生类到基类顺序调用;object 为最终对象;默认不处理。 */ - void after_prepare_data(Attached auto* object); - /* CRTP 覆盖:Builder 挂接最终 Private 后绑定事件、颜色缓存和阶段分派;派生 Private 必须先调用此实现。 */ + using Build_Graph_Run = std::optional (*)(Root*); + using Graph_Rebuild_Run = bool (*)(Root*); + using Render_Predicate_Run = bool (*)(Root*, bool); + using Pending_State_Run = void* (*)(Root*); + using Publish_State_Run = void (*)(Root*); + + std::string business_name_value{}; + Build_Graph_Run build_graph_run{}; + Graph_Rebuild_Run graph_rebuild_run{}; + Render_Predicate_Run render_predicate_run{}; + Pending_State_Run pending_state_run{}; + Publish_State_Run publish_state_run{}; + Event_Run event_run{}; + Event_Routing_Distance_Run event_routing_distance_run{}; + Color_Cache_Visit color_cache_visit{}; + std::optional graph{}; + Task_Graph extension{"renderable.extension"}; + + [[nodiscard]] std::string_view business_name() const noexcept { + return business_name_value; + } + [[nodiscard]] std::optional build_runtime_graph(Root* object) const { + return build_graph_run ? build_graph_run(object) : std::nullopt; + } + [[nodiscard]] bool should_rebuild_runtime_graph(Root* object) const { + return graph_rebuild_run && graph_rebuild_run(object); + } + [[nodiscard]] bool should_render(Root* object, bool dirty) const { + return render_predicate_run ? render_predicate_run(object, dirty) : dirty; + } + [[nodiscard]] void* pending_renderable_state(Root* object) const { + return pending_state_run(object); + } + void publish_renderable_state(Root* object) const { + publish_state_run(object); + } + template void bind_private_crtp(Object* object); - /* Def::Private 的四个 State 生命周期 hook 同样可在派生 Private 中覆盖,并通过 State_Access::get() 访问状态层。 */ }; -template -void Renderable::run_prepare_data(Object* object) { - auto& private_data = double_buffer::detail::Internal_Access::get(object); - private_data.prepare_data(object); - auto callback = [&](auto& value) { - if constexpr (requires { value.after_prepare_data(object); }) value.after_prepare_data(object); - }; - walk_private(object, callback); -} -inline bool Renderable::Private::should_rebuild_prepare_graph(Attached auto*, const Prop&) { - return false; -} -inline bool Renderable::Private::should_rebuild_paint_graph(Attached auto*, const Prop&) { - return false; -} -inline bool Renderable::Private::should_prepare(Attached auto*, const State&, bool dirty) { - return dirty; -} -inline bool Renderable::Private::should_paint(Attached auto*, const State&, bool dirty) { - return dirty; -} -inline void Renderable::Private::after_prepare_data(Attached auto*) {} + template void Renderable::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - if constexpr (!No_Prepare_Renderable) - double_buffer::detail::Internal_Access::mark_dirty(object); auto& data = static_cast(*this); + data.business_name_value = [] { + std::string name = typeid(Object).name(); + for (const std::string_view prefix : {"struct ", "class "}) + if (name.starts_with(prefix)) name.erase(0, prefix.size()); + if (const auto separator = name.rfind("::"); separator != std::string::npos) + name.erase(0, separator + 2); + return name; + }(); + data.build_graph_run = [](Root* root) -> std::optional { + auto* value = static_cast(root); + auto& private_data = double_buffer::detail::Internal_Access::get(value); + const auto& prop = double_buffer::detail::Internal_Access::current_prop(value); + if constexpr (requires { private_data.build_graph(value, prop); }) { + return private_data.build_graph(value, prop); + } + else if constexpr (requires { private_data.prepare_data(value); } || + requires { private_data.paint(value); }) { + Task_Graph graph{"renderable.graph"}; + graph.add("render", [value] { + auto& runtime_data = double_buffer::detail::Internal_Access::get(value); + if constexpr (requires { runtime_data.prepare_data(value); }) + runtime_data.prepare_data(value); + if constexpr (requires { runtime_data.paint(value); }) + runtime_data.paint(value); + }); + return graph; + } + else { + return std::nullopt; + } + }; + data.graph_rebuild_run = [](Root* root) { + auto* value = static_cast(root); + auto& private_data = double_buffer::detail::Internal_Access::get(value); + const auto& prop = double_buffer::detail::Internal_Access::current_prop(value); + if constexpr (requires { private_data.should_rebuild_graph(value, prop); }) + return private_data.should_rebuild_graph(value, prop); + return false; + }; + data.render_predicate_run = [](Root* root, bool dirty) { + auto* value = static_cast(root); + auto& private_data = double_buffer::detail::Internal_Access::get(value); + const auto& state = static_cast( + double_buffer::detail::Internal_Access::current_state(value)); + if constexpr (requires { private_data.should_render(value, state, dirty); }) + return private_data.should_render(value, state, dirty); + return dirty; + }; + data.pending_state_run = [](Root* root) -> void* { + auto* value = static_cast(root); + return &double_buffer::detail::Internal_Access::pending_state(value); + }; + data.publish_state_run = [](Root* root) { + auto* value = static_cast(root); + double_buffer::detail::Internal_Access::publish_state(value); + }; if constexpr (Event_Renderable) { data.event_run = [](Root* root, const Event& event) { auto* value = static_cast(root); @@ -97,21 +123,23 @@ void Renderable::Private::bind_private_crtp(Object* object) { using Cache = std::remove_reference_t(object))>; if constexpr (std::derived_from) { - data.color_cache_visit = [](Root* root, void* context, Private::Color_Cache_Visitor visitor) { + data.color_cache_visit = [](Root* root, void* context, Color_Cache_Visitor visitor) { auto* value = static_cast(root); const auto& render_state = static_cast( double_buffer::detail::Internal_Access::current_state(value)); - if (render_state.paint_executed) + if (render_state.render_executed) visitor(context, double_buffer::detail::Internal_Access::pending_buffer(value)); else visitor(context, double_buffer::detail::Internal_Access::current_buffer(value)); }; } } + double_buffer::detail::Internal_Access::mark_dirty(object); } + inline void Renderable::bind_dependency_graph_object(Attached auto* object) { using Object = std::remove_pointer_t; static_assert(Renderable_Object); - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } } diff --git a/kernel/src/kernel/scene.cpp b/kernel/src/kernel/scene.cpp index 38cd779..cfcd4ed 100644 --- a/kernel/src/kernel/scene.cpp +++ b/kernel/src/kernel/scene.cpp @@ -2,4 +2,7 @@ namespace aethera { Scene::Private::Private() = default; Scene::Private::~Private() = default; +Task_Graph& Scene::completion_taskflow() { + return static_cast(*d).completion_graph; +} } diff --git a/kernel/src/kernel/scene.hpp b/kernel/src/kernel/scene.hpp index 3bf7d86..78fdf38 100644 --- a/kernel/src/kernel/scene.hpp +++ b/kernel/src/kernel/scene.hpp @@ -12,7 +12,7 @@ struct Scene_Event_Stream_Tag {}; * 最终具体 Scene 直接持有其 Private;编辑 Dependency_Graph 后提交结构变化,再执行当前场景。 */ struct Scene : Def, + Dependency_Graph_Type, Mpmc_Triple_Buffer>> { using Event_Pointer = std::shared_ptr; using Event_Batch = std::pmr::vector; @@ -37,6 +37,7 @@ struct Scene : Def [[nodiscard]] std::shared_ptr make_event(Arguments&&... arguments); + [[nodiscard]] Task_Graph& completion_taskflow(); }; } #include "scene.ipp" diff --git a/kernel/src/kernel/scene.ipp b/kernel/src/kernel/scene.ipp index cb3f9a5..cadba00 100644 --- a/kernel/src/kernel/scene.ipp +++ b/kernel/src/kernel/scene.ipp @@ -11,6 +11,7 @@ struct Scene::Private : Prev_Private { struct Result {}; /* process(...) 完成回调的结果类型;当前仅表示完成。 */ struct Runtime; /* Scene 的 Taskflow 构建产物;完整定义位于本文件下方。 */ std::unique_ptr runtime; /* Scene 唯一运行时构建产物的所有权。 */ + Task_Graph completion_graph{"scene.completion"}; Event_Statistics_Accumulator event_statistics{}; /* Scene 单帧域内按事件类型增量计算。 */ Private(); ~Private(); @@ -70,10 +71,10 @@ void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& cal if (frame) frame->mark(Frame_Trace_Marker::prepare_started); if (runtime->taskflow && !runtime->taskflow->empty()) state.taskflow_execution_time_ns = frame && frame->taskflow_trace_requested() - ? detail::run_taskflow(*runtime->taskflow, *frame, "scene.prepare") + ? detail::run_taskflow(*runtime->taskflow, *frame, "scene.renderables") : detail::run_taskflow(*runtime->taskflow); if (frame) frame->mark(Frame_Trace_Marker::prepare_finished); - double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( + double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); }); @@ -104,103 +105,95 @@ void Scene::Private::after_advance(Object* object, ); } ); - auto prepare_dependencies = - double_buffer::detail::Internal_Access::current_dependency_graph(object); + auto render_dependencies = + double_buffer::detail::Internal_Access::current_dependency_graph(object); bool taskflow_dirty = !runtime->taskflow; - double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, - [&](auto& prepare_state) { taskflow_dirty = taskflow_dirty || prepare_state.dirty(); }); + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, + [&](auto& graph_state) { taskflow_dirty = taskflow_dirty || graph_state.dirty(); }); + render_dependencies.for_each_bound( + [&](Renderable* renderable, Renderable::Private& data) { + auto& state = *static_cast( + data.pending_renderable_state(renderable)); + state.graph_rebuilt = false; + if (!data.graph || data.should_rebuild_runtime_graph(renderable)) { + data.graph = data.build_runtime_graph(renderable); + state.graph_rebuilt = true; + double_buffer::detail::Internal_Access::mark_dirty( + renderable); + } + state.task_count = data.graph ? data.graph->size() : 0; + }); if (!taskflow_dirty) return; - if (!runtime->taskflow) runtime->taskflow = std::make_unique("scene.prepare"); + if (!runtime->taskflow) runtime->taskflow = std::make_unique("scene.render"); auto& taskflow = *runtime->taskflow; std::pmr::unordered_set renderables{resource}; - prepare_dependencies.for_each_bound( + render_dependencies.for_each_bound( [&](Renderable* renderable, Renderable::Private&) { renderables.insert(renderable); } ); scene_state.renderable_count = renderables.size(); taskflow.clear(); - struct Stage_Tasks { - Task_Node prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */ - Task_Node prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */ + struct Render_Tasks { + Task_Node entry; + Task_Node exit; }; - std::pmr::unordered_map stage_tasks{resource}; + std::pmr::unordered_map render_tasks{resource}; /* - * Prepare 的并行关系只有一个权威来源:Prepare_Data_Tag。 - * 每个 Renderable 先建立独立的条件、执行和完成节点;下面只为显式 - * 依赖连边,不得按遍历顺序增加 previous 链或其他隐式串行关系。 + * Render_Graph_Tag 是对象间执行顺序的唯一权威来源。对象内部节点和 + * 对其他 Private 图的直接访问都属于具体实现,公共层不定义阶段端口。 */ for (auto* renderable : renderables) { Root* root = renderable; - auto* data = prepare_dependencies.private_data(root); + auto* data = render_dependencies.private_data(root); if (!data) continue; - const auto task_prefix = std::string(data->business_name()) + ".prepare"; - auto prepare_if = taskflow.add_condition(task_prefix + ".condition", [data, root] { + const auto task_prefix = std::string(data->business_name()) + ".render"; + auto render_if = taskflow.add_condition(task_prefix + ".condition", [data, root] { auto& state = *static_cast(data->pending_renderable_state(root)); - state.prepare_graph_rebuilt = false; - state.prepare_execution_time_ns = 0; - if (data->has_prepare_graph()) { - bool rebuild = data->prepare_rebuild_predicate(root); - if (!data->prepare_graph_built || rebuild) { - *data->prepare_graph = data->build_prepare_graph(root); - data->prepare_graph_built = true; - state.prepare_graph_rebuilt = true; - double_buffer::detail::Internal_Access::mark_dirty(root); - } - state.prepare_task_count = data->prepare_graph->size(); - } - else { - state.prepare_task_count = data->has_prepare_run() ? 1 : 0; - } - bool dirty = double_buffer::detail::Internal_Access::dirty(root); - state.prepare_dirty = dirty; - state.prepare_executed = data->prepare_predicate(root, dirty); - if (state.prepare_executed) { - state.prepare_execution_time_ns = static_cast( + state.execution_time_ns = 0; + const bool dirty = + double_buffer::detail::Internal_Access::dirty(root); + state.render_dirty = dirty; + state.render_executed = data->should_render(root, dirty); + if (state.render_executed) { + state.execution_time_ns = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); } - return state.prepare_executed ? 0 : 1; + return state.render_executed ? 0 : 1; }); - Task_Node prepare_run; - if (data->has_prepare_graph()) { - if (!data->prepare_graph) data->prepare_graph = std::make_unique(task_prefix + ".graph"); - prepare_run = taskflow.compose(task_prefix + ".graph", *data->prepare_graph); - } - else { - prepare_run = taskflow.add(task_prefix + ".data", [data, root] { - if (data->has_prepare_run()) data->run_prepare(root); - }); - } - auto prepare_done = taskflow.add(task_prefix + ".complete", [data, root] { + Task_Node render_run = data->graph + ? taskflow.compose(task_prefix + ".graph", *data->graph) + : taskflow.add(task_prefix + ".empty", [] {}); + auto render_done = taskflow.add(task_prefix + ".complete", [data, root] { auto& state = *static_cast(data->pending_renderable_state(root)); - if (state.prepare_executed) { - (void)double_buffer::detail::Internal_Access::take_dirty(root); + if (state.render_executed) { + (void)double_buffer::detail::Internal_Access::take_dirty(root); const auto finished = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); - state.prepare_execution_time_ns = finished - state.prepare_execution_time_ns; + state.execution_time_ns = finished - state.execution_time_ns; } }); - prepare_if.precede(prepare_run); - prepare_if.precede(prepare_done); - if (data->prepare_extension.empty()) { - prepare_run.precede(prepare_done); + render_if.precede(render_run); + render_if.precede(render_done); + if (data->extension.empty()) { + render_run.precede(render_done); } else { - auto prepare_extension = taskflow.compose( - task_prefix + ".extension", data->prepare_extension); - prepare_run.precede(prepare_extension); - prepare_extension.precede(prepare_done); + auto extension = taskflow.compose( + task_prefix + ".extension", data->extension); + render_run.precede(extension); + extension.precede(render_done); } - stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done}); + render_tasks.emplace(root, Render_Tasks{render_if, render_done}); } auto connect_dependencies = [&](const auto& dependency_graph) { dependency_graph.for_each( [&](const Dependency_Graph::Node& dependency_node) { if (!dependency_graph.private_data(dependency_node)) return; - auto target = stage_tasks.find(dependency_node.object); - if (target == stage_tasks.end()) return; + auto target = render_tasks.find(dependency_node.object); + if (target == render_tasks.end()) return; std::pmr::unordered_set visited{resource}; std::pmr::vector pending{resource}; for (const auto* dependency : dependency_node.dependencies) pending.push_back(dependency); @@ -209,9 +202,9 @@ void Scene::Private::after_advance(Object* object, pending.pop_back(); if (!visited.insert(dependency->object).second) continue; if (dependency_graph.private_data(*dependency)) { - auto source = stage_tasks.find(dependency->object); - if (source != stage_tasks.end()) { - source->second.prepare_exit.precede(target->second.prepare_entry); + auto source = render_tasks.find(dependency->object); + if (source != render_tasks.end()) { + source->second.exit.precede(target->second.entry); } continue; } @@ -220,7 +213,7 @@ void Scene::Private::after_advance(Object* object, } ); }; - connect_dependencies(prepare_dependencies); + connect_dependencies(render_dependencies); scene_state.taskflow_task_count = taskflow.size(); scene_state.taskflow_dependency_count = 0; scene_state.taskflow_max_predecessors = 0; @@ -232,8 +225,8 @@ void Scene::Private::after_advance(Object* object, scene_state.taskflow_max_successors = std::max( scene_state.taskflow_max_successors, node.successors.size()); } - double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, - [](auto& prepare_state) { if (prepare_state.dirty()) prepare_state.take_dirty(); }); + double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, + [](auto& graph_state) { if (graph_state.dirty()) graph_state.take_dirty(); }); scene_state.taskflow_rebuilt = true; } } diff --git a/kernel/src/test/render_test.cpp b/kernel/src/test/render_test.cpp index 8c7b5ae..5573936 100644 --- a/kernel/src/test/render_test.cpp +++ b/kernel/src/test/render_test.cpp @@ -30,18 +30,17 @@ struct Graph_Renderable : double_buffer::Def(*d); } -struct Dependency_Renderable : double_buffer::Def { - struct Prop : Prev_Prop {}; - struct State : Prev_State { - int revision{}; - bool operator==(const State&) const = default; - }; - struct Private : Prev_Private { - int prepare_calls{}; - int paint_calls{}; - bool publish_revision{}; - void prepare_data(double_buffer::Attached auto* object) { - ++prepare_calls; - using Object = std::remove_pointer_t; - auto& private_data = static_cast&>(*this); - if (std::exchange(publish_revision, false)) - private_data.template update_state<&State::revision>(private_data.state.pending->revision + 1); - } - void paint(double_buffer::Attached auto*) { - ++paint_calls; - } - }; - Private& data_for_test(); -}; -using Dependency = Dependency_Renderable; -inline Dependency_Renderable::Private& Dependency_Renderable::data_for_test() { - return static_cast(*d); -} template std::unique_ptr build_object() { auto result = typename Object_Type::template Builder{}.build(); @@ -89,7 +61,7 @@ std::unique_ptr build_object() { } template void add_renderable(Scene& scene, Renderable* renderable) { - ASSERT_TRUE(scene.edit_dependency_graph([&](auto& prepare) { prepare.add(renderable); }).has_value()); + ASSERT_TRUE(scene.edit_dependency_graph([&](auto& prepare) { prepare.add(renderable); }).has_value()); } } TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) { @@ -98,19 +70,18 @@ TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) { auto scene = build_object(); add_renderable(*scene, renderable.get()); int prepare_extension_calls{}; - renderable->prepare_taskflow().add( - "test.prepare.extension", [&] { ++prepare_extension_calls; }); + renderable->taskflow().add( + "test.render.extension", [&] { ++prepare_extension_calls; }); scene->process([](const auto&) {}); auto& data = renderable->data_for_test(); auto& base = static_cast(data); EXPECT_EQ(data.prepare_calls, 1); - EXPECT_EQ(data.paint_calls, 0); + EXPECT_EQ(data.paint_calls, 1); EXPECT_EQ(prepare_extension_calls, 1); - EXPECT_EQ(base.prepare_graph, nullptr); - EXPECT_EQ(base.paint_graph, nullptr); + EXPECT_TRUE(base.graph.has_value()); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_calls, 1); - EXPECT_EQ(data.paint_calls, 0); + EXPECT_EQ(data.paint_calls, 1); EXPECT_EQ(prepare_extension_calls, 1); } TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condition) { @@ -120,16 +91,15 @@ TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condit add_renderable(*scene, renderable.get()); auto& data = renderable->data_for_test(); auto& base = static_cast(data); - EXPECT_EQ(base.prepare_graph, nullptr); scene->process([](const auto&) {}); - ASSERT_NE(base.prepare_graph, nullptr); + ASSERT_TRUE(base.graph.has_value()); EXPECT_EQ(data.prepare_builds, 1); EXPECT_EQ(data.prepare_calls, 1); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_builds, 1); EXPECT_EQ(data.prepare_calls, 1); data.rebuild_prepare = true; - renderable->mark_dirty(); + renderable->mark_dirty(); scene->process([](const auto&) {}); EXPECT_EQ(data.prepare_builds, 2); EXPECT_EQ(data.prepare_calls, 2); @@ -144,8 +114,7 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie int runtime_updates = 0; renderable->set_state_callback([&](const auto& state) { ++renderable_updates; - EXPECT_TRUE(state.prepare_executed); - EXPECT_FALSE(state.paint_executed); + EXPECT_TRUE(state.render_executed); }); scene->set_state_callback([&](const auto& state) { ++scene_updates; @@ -189,31 +158,31 @@ TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) { scene->process([](const auto&) {}); EXPECT_EQ(updates, 2); } -TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) { + +TEST(scene_condition, render_order_does_not_create_a_false_data_dependency) { aethera::initialize_runtime({.workers = 2}); - auto source = build_object(); - auto target = build_object(); + auto source = build_object(); + auto target = build_object(); auto scene = build_object(); - ASSERT_TRUE((scene->edit_dependency_graph( - [&](auto& prepare) { - prepare.add(source.get()); - prepare.add(target.get()); - prepare.template add_dependency<&Dependency_Renderable::State::revision>(target.get(), source.get()); - } - ).has_value())); + ASSERT_TRUE((scene->edit_dependency_graph( + [&](auto& graph) { + graph.add(source.get()); + graph.add(target.get()); + graph.add_dependency(target.get(), source.get()); + }).has_value())); + scene->process([](const auto&) {}); - auto& source_data = source->data_for_test(); - auto& target_data = target->data_for_test(); - EXPECT_EQ(source_data.prepare_calls, 1); - EXPECT_EQ(target_data.prepare_calls, 1); + EXPECT_EQ(source->data_for_test().prepare_calls, 1); + EXPECT_EQ(target->data_for_test().prepare_calls, 1); + scene->process([](const auto&) {}); - EXPECT_EQ(source_data.prepare_calls, 1); - EXPECT_EQ(target_data.prepare_calls, 1); - source_data.publish_revision = true; - source->mark_dirty(); + EXPECT_EQ(source->data_for_test().prepare_calls, 1); + EXPECT_EQ(target->data_for_test().prepare_calls, 1); + + source->mark_dirty(); scene->process([](const auto&) {}); - EXPECT_EQ(source_data.prepare_calls, 2); - EXPECT_EQ(target_data.prepare_calls, 2); + EXPECT_EQ(source->data_for_test().prepare_calls, 2); + EXPECT_EQ(target->data_for_test().prepare_calls, 1); } TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity) { diff --git a/render_2D/render_2D/axis/Abs_Axis.ipp b/render_2D/render_2D/axis/Abs_Axis.ipp index 1011f6a..750a3a5 100644 --- a/render_2D/render_2D/axis/Abs_Axis.ipp +++ b/render_2D/render_2D/axis/Abs_Axis.ipp @@ -198,7 +198,7 @@ inline void Abs_Axis::Private::prepare_data(Attached auto* object) { output.unit_width = std::max(4.0, state.unit_text.size() * state.unit_text_font.size * 0.65); } output.valid = true; - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } inline void Abs_Axis::Private::paint(Attached auto* object) { using Object = std::remove_pointer_t; @@ -222,7 +222,7 @@ inline void Abs_Axis::Private::paint(Attached auto* object) { } template void Abs_Axis::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } inline int Abs_Axis::Private::sub_tick_count(const Attached auto*, double) const { return 4; diff --git a/render_2D/render_2D/axis/Numeric_Axis.ipp b/render_2D/render_2D/axis/Numeric_Axis.ipp index d16ac16..c3de8f7 100644 --- a/render_2D/render_2D/axis/Numeric_Axis.ipp +++ b/render_2D/render_2D/axis/Numeric_Axis.ipp @@ -50,7 +50,7 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve anchor + (range.origin - anchor) * factor, anchor + (range.target - anchor) * factor }); - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); event.accept(); return; } @@ -74,7 +74,7 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve numeric_state.coordinate_range.origin + shift, numeric_state.coordinate_range.target + shift }); - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); event.accept(); return; } diff --git a/render_2D/render_2D/axis/Time_Axis.ipp b/render_2D/render_2D/axis/Time_Axis.ipp index 93ae0da..11e867a 100644 --- a/render_2D/render_2D/axis/Time_Axis.ipp +++ b/render_2D/render_2D/axis/Time_Axis.ipp @@ -74,7 +74,7 @@ inline std::size_t Time_Axis::Private::time_point_count(const Attached auto* obj inline int Time_Axis::Private::append_time(Attached auto* object, Time_Of_Day time) { const int tick = next_tick.fetch_add(1, std::memory_order_relaxed); double_buffer::detail::Internal_Access::submit_stream(object, std::pair{tick, time}); - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); return tick; } inline Time_Of_Day Time_Axis::Private::tick_to_time(const Attached auto* object, int tick) const { @@ -112,6 +112,6 @@ inline void Time_Axis::Private::paint(Attached auto* object) { template void Time_Axis::Private::after_state_set(Object* object, Member Owner::*, State_Access) { if constexpr (std::same_as) - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } } diff --git a/render_2D/render_2D/base/Renderable_2D.ipp b/render_2D/render_2D/base/Renderable_2D.ipp index 4431dd4..039cb3a 100644 --- a/render_2D/render_2D/base/Renderable_2D.ipp +++ b/render_2D/render_2D/base/Renderable_2D.ipp @@ -34,12 +34,12 @@ template void Renderable_2D::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } template void Renderable_2D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); this->event_region_run = [](const Root* root, Size viewport) { const auto* value = static_cast(root); const auto& private_data = double_buffer::detail::Internal_Access::get(value); diff --git a/render_2D/render_2D/base/Types.hpp b/render_2D/render_2D/base/Types.hpp index d66fa11..eccb7d2 100644 --- a/render_2D/render_2D/base/Types.hpp +++ b/render_2D/render_2D/base/Types.hpp @@ -5,10 +5,8 @@ #include #include namespace aethera::render_2d { -/* Paint 阶段在 Dependency_Graph 图中的标签。 */ -struct Paint_Tag {}; -/* Paint 缓存依赖标签;具体字段变化通过该图使缓存拥有者失效。 */ -struct Paint_Cache_Tag {}; +/* 缓存依赖标签;具体字段变化通过该图使缓存拥有者失效。 */ +struct Render_Cache_Tag {}; /* 整数二维坐标。 */ struct Point { int x{}; /* 水平方向坐标。 */ diff --git a/render_2D/render_2D/plottable/Afterglow.ipp b/render_2D/render_2D/plottable/Afterglow.ipp index 0e280a9..29a2482 100644 --- a/render_2D/render_2D/plottable/Afterglow.ipp +++ b/render_2D/render_2D/plottable/Afterglow.ipp @@ -20,12 +20,8 @@ struct Afterglow::Private : Prev_Private { Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); /* CRTP 覆盖:构建累加、归一化和着色三阶段 Prepare 子图。 */ - [[nodiscard]] Task_Graph build_prepare_graph(Afterglow* object, const Prop& state); - /* CRTP 覆盖:构建消费色块矩阵的 Paint 子图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Afterglow* object, const Prop& state); - /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - [[nodiscard]] bool should_rebuild_prepare_graph(Afterglow* object, const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Afterglow* object, const Prop& state); + [[nodiscard]] Task_Graph build_graph(Afterglow* object, const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Afterglow* object, const Prop& state); void prepare_frame(Afterglow* object); void accumulate_partition(Afterglow* object, Plot_Partition_Count index); void normalize_frame(); @@ -57,12 +53,9 @@ std::expected, Dependency_Graph_Error> Afterglow::Builde data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { - prepare.add_dependency(object, scene); - prepare.add_dependency(object, frequency_axis); - prepare.add_dependency(object, power_axis); - paint.add_dependency(frequency_axis, object); - paint.add_dependency(power_axis, object); + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis, object); + graph.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); @@ -76,49 +69,39 @@ std::expected, Dependency_Graph_Error> Afterglow::Builde }; return plot; } -inline bool Afterglow::Private::should_rebuild_prepare_graph(Afterglow*, +inline bool Afterglow::Private::should_rebuild_graph(Afterglow*, const Prop& state) { return graph_partition_grid != state.partition_grid; } -inline bool Afterglow::Private::should_rebuild_paint_graph(Afterglow*, const Prop&) { - return !paint_graph || paint_graph->size() != - detail::raster_partition_count(graph_partition_grid); -} -inline Task_Graph Afterglow::Private::build_prepare_graph(Afterglow* object, const Prop& state) { +inline Task_Graph Afterglow::Private::build_graph(Afterglow* object, const Prop& state) { graph_partition_grid = state.partition_grid; const Plot_Partition_Count partition_count = detail::raster_partition_count(graph_partition_grid); - Task_Graph graph{"afterglow.prepare"}; - auto begin = graph.add("frame", [this, object] { + Task_Graph graph{"afterglow"}; + auto begin = graph.add("prepare.frame", [this, object] { prepare_frame(object); }); - auto normalize = graph.add("normalize", [this] { + auto normalize = graph.add("prepare.normalize", [this] { normalize_frame(); }); if (partition_count == 0) begin.precede(normalize); for (Plot_Partition_Count index = 0; index < partition_count; ++index) { - auto accumulate = graph.add("accumulate", [this, object, index] { + auto accumulate = graph.add("prepare.accumulate", [this, object, index] { accumulate_partition(object, index); }); - auto color = graph.add("color", [this, object, index] { + auto color = graph.add("prepare.color", [this, object, index] { color_partition(object, index); }); + auto paint = graph.add("paint.partition", [this, object, index] { + paint_partition(object, index); + }); begin.precede(accumulate); accumulate.precede(normalize); normalize.precede(color); + color.precede(paint); } return graph; } -inline Task_Graph Afterglow::Private::build_paint_graph(Afterglow* object, const Prop&) { - Task_Graph graph{"afterglow.paint"}; - const Plot_Partition_Count partition_count = - detail::raster_partition_count(graph_partition_grid); - for (Plot_Partition_Count index = 0; index < partition_count; ++index) - graph.add("partition", [this, object, index] { - paint_partition(object, index); - }); - return graph; -} inline void Afterglow::Private::prepare_frame(Afterglow* object) { const auto& state = this->template read_current_prop(object); detail::Internal_Access::exchange_stream(object); @@ -235,7 +218,7 @@ void Afterglow::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Afterglow::Private::bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { diff --git a/render_2D/render_2D/plottable/Constellation_Diagram.ipp b/render_2D/render_2D/plottable/Constellation_Diagram.ipp index beda509..8d9b934 100644 --- a/render_2D/render_2D/plottable/Constellation_Diagram.ipp +++ b/render_2D/render_2D/plottable/Constellation_Diagram.ipp @@ -18,10 +18,10 @@ struct Constellation_Diagram::Private : Prev_Private { /* CRTP 覆盖:从当前点集和轴状态准备画布坐标。 */ void prepare_data(Constellation_Diagram* object); /* CRTP 覆盖:构建锚点前驱与并行接收点 Paint 子图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Constellation_Diagram* object, - const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Constellation_Diagram* object, - const Prop& state); + [[nodiscard]] Task_Graph build_graph(Constellation_Diagram* object, + const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Constellation_Diagram* object, + const Prop& state); void paint_anchors(Constellation_Diagram* object); void paint_partition(Constellation_Diagram* object, Plot_Partition_Count index); [[nodiscard]] static Rect_F point_region( @@ -49,12 +49,9 @@ std::expected, Dependency_Graph_Error> Constellation_Dia data.scene = scene; auto* i_axis = data.i_axis; auto* q_axis = data.q_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { - prepare.add_dependency(object, scene); - prepare.add_dependency(object, i_axis); - prepare.add_dependency(object, q_axis); - // paint.add_dependency(i_axis, object); - // paint.add_dependency(q_axis, object); + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { + graph.add_dependency(i_axis, object); + graph.add_dependency(q_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, i_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, i_axis); @@ -99,26 +96,30 @@ inline void Constellation_Diagram::Private::prepare_data(Constellation_Diagram* prepared.anchors.push_back(detail::map_plot_point(i_axis, center_i + std::cos(angle) * radius, q_axis, center_q + std::sin(angle) * radius, i_layout.orientation)); } prepared.valid = true; - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } -inline Task_Graph Constellation_Diagram::Private::build_paint_graph( +inline Task_Graph Constellation_Diagram::Private::build_graph( Constellation_Diagram* object, const Prop& state) { - Task_Graph graph{"constellation.paint"}; - auto anchors = graph.add("anchors", [this, object] { + Task_Graph graph{"constellation"}; + auto prepare = graph.add("prepare", [this, object] { + prepare_data(object); + }); + auto anchors = graph.add("paint.anchors", [this, object] { paint_anchors(object); }); + prepare.precede(anchors); for (Plot_Partition_Count index = 0; index < state.partition_count; ++index) { - auto partition = graph.add("partition", [this, object, index] { + auto partition = graph.add("paint.partition", [this, object, index] { paint_partition(object, index); }); anchors.precede(partition); } return graph; } -inline bool Constellation_Diagram::Private::should_rebuild_paint_graph( +inline bool Constellation_Diagram::Private::should_rebuild_graph( Constellation_Diagram*, const Prop& state) { - return !paint_graph || paint_graph->size() != state.partition_count + 1; + return !graph || graph->size() != state.partition_count + 2; } inline Rect_F Constellation_Diagram::Private::point_region( std::span points, double radius) { @@ -183,11 +184,11 @@ void Constellation_Diagram::Private::after_prop_set( if constexpr (std::same_as) { if constexpr (std::same_as) { if (member == &Prop::partition_count) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); return; } } - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Constellation_Diagram::Private::bind_sources(Axis_Object* i_axis_value, Axis_Object* q_axis_value) { diff --git a/render_2D/render_2D/plottable/Frequency_Trace.ipp b/render_2D/render_2D/plottable/Frequency_Trace.ipp index 0744238..d07cd54 100644 --- a/render_2D/render_2D/plottable/Frequency_Trace.ipp +++ b/render_2D/render_2D/plottable/Frequency_Trace.ipp @@ -15,12 +15,8 @@ struct Frequency_Trace::Private : Prev_Private { Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图固化的分块数。 */ void bind_sources(Time_Object* time_axis_value, Value_Object* value_axis_value); /* CRTP 覆盖:按当前样本规模构建分块 Prepare 子图。 */ - [[nodiscard]] Task_Graph build_prepare_graph(Frequency_Trace* object, const Prop& state); - /* CRTP 覆盖:构建消费已准备曲线的 Paint 子图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Frequency_Trace* object, const Prop& state); - /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - [[nodiscard]] bool should_rebuild_prepare_graph(Frequency_Trace* object, const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Frequency_Trace* object, const Prop& state); + [[nodiscard]] Task_Graph build_graph(Frequency_Trace* object, const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Frequency_Trace* object, const Prop& state); void prepare_frame(Frequency_Trace* object, Plot_Partition_Count partition_count); void prepare_partition(Frequency_Trace* object, Plot_Partition_Count partition_index); void paint_partition(Frequency_Trace* object, Plot_Partition_Count partition_index); @@ -37,20 +33,19 @@ Frequency_Trace::Builder::Builder( template std::expected, Dependency_Graph_Error> Frequency_Trace::Builder::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto trace = std::move(result).value(); auto& private_data = detail::Internal_Access::get(trace.get()); private_data.bind_sources(time_axis, value_axis); - private_data.scene_attach = [object = trace.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* time_axis = data.time_axis; auto* value_axis = data.value_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, time_axis); prepare.add_dependency(object, value_axis); paint.add_dependency(time_axis, object); paint.add_dependency(value_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, time_axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, value_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, value_axis); }); }; return trace; + private_data.scene_attach = [object = trace.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* time_axis = data.time_axis; auto* value_axis = data.value_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { graph.add_dependency(time_axis, object); graph.add_dependency(value_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, time_axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, time_axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, time_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, value_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, value_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, value_axis); }); }; return trace; } -inline bool Frequency_Trace::Private::should_rebuild_prepare_graph(Frequency_Trace*, const Prop& state) { return graph_partition_count != state.partition_count; } -inline bool Frequency_Trace::Private::should_rebuild_paint_graph(Frequency_Trace*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size(); } -inline Task_Graph Frequency_Trace::Private::build_prepare_graph(Frequency_Trace* object, const Prop& state) { - graph_partition_count = state.partition_count; Task_Graph graph{"frequency_trace.prepare"}; - auto begin = graph.add("frame", [this, object] { prepare_frame(object, graph_partition_count); }); - for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.add("partition", [this, object, index] { prepare_partition(object, index); }); begin.precede(task); } - return graph; -} -inline Task_Graph Frequency_Trace::Private::build_paint_graph(Frequency_Trace* object, const Prop&) { - Task_Graph graph{"frequency_trace.paint"}; - for (Plot_Partition_Count index = 0; index < prepared.partitions.size(); ++index) - graph.add("partition", [this, object, index] { paint_partition(object, index); }); +inline bool Frequency_Trace::Private::should_rebuild_graph(Frequency_Trace*, const Prop& state) { return graph_partition_count != state.partition_count; } +inline Task_Graph Frequency_Trace::Private::build_graph(Frequency_Trace* object, const Prop& state) { + graph_partition_count = state.partition_count; + Task_Graph graph{"frequency_trace"}; + auto begin = graph.add("prepare.frame", [this, object] { prepare_frame(object, graph_partition_count); }); + for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { + auto prepare = graph.add("prepare.partition", [this, object, index] { prepare_partition(object, index); }); + auto paint = graph.add("paint.partition", [this, object, index] { paint_partition(object, index); }); + begin.precede(prepare); + prepare.precede(paint); + } return graph; } inline void Frequency_Trace::Private::prepare_frame(Frequency_Trace* object, Plot_Partition_Count partition_count) { @@ -95,7 +90,7 @@ template void Frequency_Trace::Private::after_prop_set( Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Frequency_Trace::Private::bind_sources(Time_Object* time_axis_value, Value_Object* value_axis_value) { time_axis = time_axis_value; value_axis = value_axis_value; } diff --git a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp index f44d09d..e069099 100644 --- a/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp +++ b/render_2D/render_2D/plottable/Selection_Rectangle_Overlay.ipp @@ -5,7 +5,6 @@ #include namespace aethera::render_2d { struct Selection_Rectangle_Overlay::Private : Prev_Private { - using No_Prepare = void; using Axis_Source = std::variant; Scene_Object* scene{}; /* 不拥有的所属 Scene。 */ Axis_Source horizontal_axis{}; /* 保留最终类型的水平坐标轴。 */ @@ -53,8 +52,10 @@ Selection_Rectangle_Overlay::Builder::Builder(Horizontal_Axis* horizonta auto* scene = static_cast(root); auto& data = detail::Internal_Access::layer(object); data.scene = scene; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { - prepare.add_dependency(object, scene); prepare.add_dependency(object, horizontal_axis); prepare.add_dependency(object, vertical_axis); paint.add_dependency(horizontal_axis, object); paint.add_dependency(vertical_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { + graph.add_dependency(horizontal_axis, object); + graph.add_dependency(vertical_axis, object); + cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); const auto bind_axis = [&](Axis* axis) { cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, axis); if constexpr (std::derived_from) { cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::precision>(object, axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::locale>(object, axis); } else if constexpr (std::derived_from) { cache.template add_prop_dependency<&Time_Axis::Prop::visible_count>(object, axis); cache.template add_prop_dependency<&Time_Axis::Prop::newest_at_start>(object, axis); cache.template add_prop_dependency<&Time_Axis::Prop::format>(object, axis); cache.template add_dependency<&Time_Axis::State::next_tick>(object, axis); } }; bind_axis(horizontal_axis); bind_axis(vertical_axis); }); @@ -101,13 +102,13 @@ inline void Selection_Rectangle_Overlay::Private::handle_event(Selection_Rectang const auto modifiers = static_cast>(pointer->keyboard_modifiers()); const auto control = static_cast>(Keyboard_Modifier::control); if ((modifiers & control) == 0) detail::Internal_Access::set<&Prop::selected_regions>(object, std::vector{}); - dragging = true; drag_origin = axis_point; drag_current = axis_point; press_position = point; detail::Internal_Access::mark_dirty(object); event.accept(); return; + dragging = true; drag_origin = axis_point; drag_current = axis_point; press_position = point; detail::Internal_Access::mark_dirty(object); event.accept(); return; } - if (event.type == Event_Type::pointer_move && dragging) { drag_current = axis_point; detail::Internal_Access::mark_dirty(object); event.accept(); return; } + if (event.type == Event_Type::pointer_move && dragging) { drag_current = axis_point; detail::Internal_Access::mark_dirty(object); event.accept(); return; } if (event.type != Event_Type::pointer_release || !dragging) return; dragging = false; drag_current = axis_point; if (std::hypot(point.x - press_position.x, point.y - press_position.y) < minimum_drag_distance_pixels) { - detail::Internal_Access::mark_dirty(object); event.accept(); return; + detail::Internal_Access::mark_dirty(object); event.accept(); return; } const Axis_Rectangle selected = axis_rectangle(drag_origin, drag_current); detail::Internal_Access::update_prop<&Prop::selected_regions>(object, [=](Prop_Access props) { @@ -120,7 +121,7 @@ inline Rect_F Selection_Rectangle_Overlay::Private::event_region(const Attached return map_rect(horizontal_axis, coordinate_range(horizontal_axis), vertical_axis, coordinate_range(vertical_axis)); } template -void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) detail::Internal_Access::mark_dirty(object); } +void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) detail::Internal_Access::mark_dirty(object); } template void Selection_Rectangle_Overlay::Private::before_advance(Object*, Prop_Type*, State_Access pending_states, const Prop_Type* current_prop, State_Access) { pending_states.template get().selected_region_count = static_cast(*current_prop).selected_regions.size(); } template diff --git a/render_2D/render_2D/plottable/Spectrum.ipp b/render_2D/render_2D/plottable/Spectrum.ipp index 8187159..29eade3 100644 --- a/render_2D/render_2D/plottable/Spectrum.ipp +++ b/render_2D/render_2D/plottable/Spectrum.ipp @@ -36,19 +36,15 @@ struct Spectrum::Private : Prev_Private { Prepared prepared{}; /* 当前权威 State、Frame 和轴状态推导出的 Paint 输入。 */ std::vector maxima{}; /* 当前样本历史逐点最大值;只由 Prepare 更新。 */ std::vector minima{}; /* 当前样本历史逐点最小值;只由 Prepare 更新。 */ - std::size_t prepare_graph_partition_count{}; /* 当前 Prepare 子图实际固化的任务分块数。 */ + std::size_t graph_partition_count{}; /* Builder 内部绑定 Scene 与两根轴;三个来源都必须比 Spectrum 生命周期更长。 */ void bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); /* CRTP State 钩子:Spectrum 业务状态写入后标记自身 Prepare;其他继承层状态由各自 Private 负责。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access pending_states); /* CRTP 子图能力:按显式 partition_count 构建并行 Prepare 图。 */ - [[nodiscard]] Task_Graph build_prepare_graph(Spectrum* object, const Prop& state); - /* CRTP 子图能力:构建背景、分块曲线和覆盖标记的 Paint 图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Spectrum* object, const Prop& state); - /* CRTP 覆盖:样本规模或分块配置改变时重建 Prepare 子图。 */ - [[nodiscard]] bool should_rebuild_prepare_graph(Spectrum* object, const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Spectrum* object, const Prop& state); + [[nodiscard]] Task_Graph build_graph(Spectrum* object, const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Spectrum* object, const Prop& state); void prepare_frame(Spectrum* object, std::size_t partition_count); void prepare_partition(Spectrum* object, std::size_t partition_index); void begin_paint(Spectrum* object); @@ -77,12 +73,9 @@ std::expected, Dependency_Graph_Error> Spectrum::Builder data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { - prepare.add_dependency(object, scene); - prepare.add_dependency(object, frequency_axis); - prepare.add_dependency(object, power_axis); - paint.add_dependency(frequency_axis, object); - paint.add_dependency(power_axis, object); + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis, object); + graph.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); @@ -96,43 +89,41 @@ std::expected, Dependency_Graph_Error> Spectrum::Builder }; return spectrum; } -inline bool Spectrum::Private::should_rebuild_prepare_graph(Spectrum*, const Prop& state) { - return prepare_graph_partition_count != state.partition_count; +inline bool Spectrum::Private::should_rebuild_graph(Spectrum*, const Prop& state) { + return graph_partition_count != state.partition_count; } -inline bool Spectrum::Private::should_rebuild_paint_graph(Spectrum*, const Prop&) { - return !paint_graph || paint_graph->size() != prepared.partitions.size() + 2; -} -inline Task_Graph Spectrum::Private::build_prepare_graph(Spectrum* object, const Prop& state) { +inline Task_Graph Spectrum::Private::build_graph(Spectrum* object, const Prop& state) { const Plot_Partition_Count partition_count = state.partition_count; - prepare_graph_partition_count = partition_count; - Task_Graph graph{"spectrum.prepare"}; - auto begin = graph.add("frame", [this, object, partition_count] { + graph_partition_count = partition_count; + Task_Graph graph{"spectrum"}; + auto prepare_begin = graph.add("prepare.frame", [this, object, partition_count] { prepare_frame(object, partition_count); }); + std::vector prepared_partitions; + prepared_partitions.reserve(partition_count); for (std::size_t index = 0; index < partition_count; ++index) { - auto partition = graph.add("partition", [this, object, index] { + auto partition = graph.add("prepare.partition", [this, object, index] { prepare_partition(object, index); }); - begin.precede(partition); + prepare_begin.precede(partition); + prepared_partitions.push_back(partition); } - return graph; -} -inline Task_Graph Spectrum::Private::build_paint_graph(Spectrum* object, const Prop&) { - Task_Graph graph{"spectrum.paint"}; - auto begin = graph.add("begin", [this, object] { + auto paint_begin = graph.add("paint.begin", [this, object] { begin_paint(object); }); - auto overlays = graph.add("overlays", [this, object] { + if (prepared_partitions.empty()) prepare_begin.precede(paint_begin); + for (const auto& partition : prepared_partitions) partition.precede(paint_begin); + auto overlays = graph.add("paint.overlays", [this, object] { paint_overlays(object); }); - for (std::size_t index = 0; index < prepared.partitions.size(); ++index) { - auto partition = graph.add("partition", [this, object, index] { + for (std::size_t index = 0; index < partition_count; ++index) { + auto partition = graph.add("paint.partition", [this, object, index] { paint_partition(object, index); }); - begin.precede(partition); + paint_begin.precede(partition); partition.precede(overlays); } - if (prepared.partitions.empty()) begin.precede(overlays); + if (partition_count == 0) paint_begin.precede(overlays); return graph; } inline void Spectrum::Private::prepare_frame(Spectrum* object, std::size_t partition_count) { @@ -250,7 +241,7 @@ template void Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Spectrum::Private::bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { diff --git a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp index 2851222..050ef25 100644 --- a/render_2D/render_2D/plottable/Sweep_Spectrum.ipp +++ b/render_2D/render_2D/plottable/Sweep_Spectrum.ipp @@ -27,12 +27,8 @@ struct Sweep_Spectrum::Private : Prev_Private { Plot_Partition_Count graph_partition_count{}; /* 当前 Prepare 子图分块数。 */ void bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); /* CRTP 覆盖:按扫描点规模构建分块 Prepare 子图。 */ - [[nodiscard]] Task_Graph build_prepare_graph(Sweep_Spectrum* object, const Prop& state); - /* CRTP 覆盖:构建消费曲线分块的 Paint 子图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Sweep_Spectrum* object, const Prop& state); - /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - [[nodiscard]] bool should_rebuild_prepare_graph(Sweep_Spectrum* object, const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Sweep_Spectrum* object, const Prop& state); + [[nodiscard]] Task_Graph build_graph(Sweep_Spectrum* object, const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Sweep_Spectrum* object, const Prop& state); void prepare_frame(Sweep_Spectrum* object); void prepare_partition(Sweep_Spectrum* object, Plot_Partition_Count index); void paint_partition(Sweep_Spectrum* object, Plot_Partition_Count index); @@ -52,16 +48,25 @@ Sweep_Spectrum::Builder::Builder( template std::expected, Dependency_Graph_Error> Sweep_Spectrum::Builder::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto sweep = std::move(result).value(); auto& private_data = detail::Internal_Access::get(sweep.get()); private_data.bind_sources(frequency_axis, power_axis); - private_data.scene_attach = [object = sweep.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, frequency_axis); prepare.add_dependency(object, power_axis); paint.add_dependency(frequency_axis, object); paint.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, frequency_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, power_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, power_axis); }); }; return sweep; + private_data.scene_attach = [object = sweep.get()](Root* root) -> std::expected { auto* scene = static_cast(root); auto& data = detail::Internal_Access::get(object); data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* power_axis = data.power_axis; return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { graph.add_dependency(frequency_axis, object); graph.add_dependency(power_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, frequency_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, power_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, power_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, power_axis); }); }; return sweep; } -inline bool Sweep_Spectrum::Private::should_rebuild_prepare_graph(Sweep_Spectrum*, const Prop& state) { return graph_partition_count != state.partition_count; } -inline bool Sweep_Spectrum::Private::should_rebuild_paint_graph(Sweep_Spectrum*, const Prop&) { return !paint_graph || paint_graph->size() != prepared.partitions.size() + 1; } -inline Task_Graph Sweep_Spectrum::Private::build_prepare_graph(Sweep_Spectrum* object, const Prop& state) { graph_partition_count = state.partition_count; Task_Graph graph{"sweep_spectrum.prepare"}; auto begin = graph.add("frame", [this, object] { prepare_frame(object); }); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.add("partition", [this, object, index] { prepare_partition(object, index); }); begin.precede(task); } return graph; } -inline Task_Graph Sweep_Spectrum::Private::build_paint_graph(Sweep_Spectrum* object, const Prop&) { - Task_Graph graph{"sweep_spectrum.paint"}; - auto marker = graph.add("marker", [this, object] { paint_marker(object); }); - for (Plot_Partition_Count index = 0; index < prepared.partitions.size(); ++index) { - auto partition = graph.add("partition", [this, object, index] { paint_partition(object, index); }); +inline bool Sweep_Spectrum::Private::should_rebuild_graph(Sweep_Spectrum*, const Prop& state) { return graph_partition_count != state.partition_count; } +inline Task_Graph Sweep_Spectrum::Private::build_graph(Sweep_Spectrum* object, const Prop& state) { + graph_partition_count = state.partition_count; + Task_Graph graph{"sweep_spectrum"}; + auto begin = graph.add("prepare.frame", [this, object] { prepare_frame(object); }); + std::vector prepare_tasks; + for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { + auto task = graph.add("prepare.partition", [this, object, index] { prepare_partition(object, index); }); + begin.precede(task); + prepare_tasks.push_back(task); + } + auto marker = graph.add("paint.marker", [this, object] { paint_marker(object); }); + if (graph_partition_count == 0) begin.precede(marker); + for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { + auto partition = graph.add("paint.partition", [this, object, index] { paint_partition(object, index); }); + if (prepare_tasks.empty()) begin.precede(partition); + else prepare_tasks[index].precede(partition); partition.precede(marker); } return graph; @@ -132,7 +137,7 @@ template void Sweep_Spectrum::Private::after_prop_set( Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Sweep_Spectrum::Private::bind_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { frequency_axis = frequency_axis_value; power_axis = power_axis_value; } diff --git a/render_2D/render_2D/plottable/Waterfall.ipp b/render_2D/render_2D/plottable/Waterfall.ipp index 99d7723..05c0894 100644 --- a/render_2D/render_2D/plottable/Waterfall.ipp +++ b/render_2D/render_2D/plottable/Waterfall.ipp @@ -26,12 +26,8 @@ struct Waterfall::Private : Prev_Private { Plot_Partition_Grid graph_partition_grid{}; /* Prepare/Paint 子图当前固化的 framebuffer 列×行网格。 */ void bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value); /* CRTP 覆盖:按当前色块工作量构建分块 Prepare 子图。 */ - [[nodiscard]] Task_Graph build_prepare_graph(Waterfall* object, const Prop& state); - /* CRTP 覆盖:构建消费色块矩阵和提示信息的 Paint 子图。 */ - [[nodiscard]] Task_Graph build_paint_graph(Waterfall* object, const Prop& state); - /* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */ - [[nodiscard]] bool should_rebuild_prepare_graph(Waterfall* object, const Prop& state); - [[nodiscard]] bool should_rebuild_paint_graph(Waterfall* object, const Prop& state); + [[nodiscard]] Task_Graph build_graph(Waterfall* object, const Prop& state); + [[nodiscard]] bool should_rebuild_graph(Waterfall* object, const Prop& state); void prepare_frame(Waterfall* object); void prepare_partition(Waterfall* object, Plot_Partition_Count index); void paint_partition(Waterfall* object, Plot_Partition_Count index); @@ -63,12 +59,9 @@ std::expected, Dependency_Graph_Error> Waterfall::Builde data.scene = scene; auto* frequency_axis = data.frequency_axis; auto* time_axis = data.time_axis; - return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& prepare, auto& paint, auto& cache) { - prepare.add_dependency(object, scene); - prepare.add_dependency(object, frequency_axis); - prepare.add_dependency(object, time_axis); - paint.add_dependency(frequency_axis, object); - paint.add_dependency(time_axis, object); + return detail::Internal_Access::edit_dependency_graph(scene, [&](auto& graph, auto& cache) { + graph.add_dependency(frequency_axis, object); + graph.add_dependency(time_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, frequency_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, frequency_axis); @@ -84,42 +77,32 @@ std::expected, Dependency_Graph_Error> Waterfall::Builde }; return plot; } -inline bool Waterfall::Private::should_rebuild_prepare_graph(Waterfall*, const Prop& state) { +inline bool Waterfall::Private::should_rebuild_graph(Waterfall*, const Prop& state) { return graph_partition_grid != state.partition_grid; } -inline bool Waterfall::Private::should_rebuild_paint_graph(Waterfall*, const Prop&) { - return !paint_graph || paint_graph->size() != - detail::raster_partition_count(graph_partition_grid) + 1; -} -inline Task_Graph Waterfall::Private::build_prepare_graph(Waterfall* object, +inline Task_Graph Waterfall::Private::build_graph(Waterfall* object, const Prop& state) { graph_partition_grid = state.partition_grid; const Plot_Partition_Count partition_count = detail::raster_partition_count(graph_partition_grid); - Task_Graph graph{"waterfall.prepare"}; - auto begin = graph.add("frame", [this, object] { + Task_Graph graph{"waterfall"}; + auto begin = graph.add("prepare.frame", [this, object] { prepare_frame(object); }); - for (Plot_Partition_Count index = 0; index < partition_count; ++index) { - auto task = graph.add("partition", [this, object, index] { - prepare_partition(object, index); - }); - begin.precede(task); - } - return graph; -} -inline Task_Graph Waterfall::Private::build_paint_graph(Waterfall* object, const Prop&) { - Task_Graph graph{"waterfall.paint"}; - auto tooltip_task = graph.add("tooltip", [this, object] { + auto tooltip_task = graph.add("paint.tooltip", [this, object] { paint_tooltip(object); }); - const Plot_Partition_Count partition_count = - detail::raster_partition_count(graph_partition_grid); + if (partition_count == 0) begin.precede(tooltip_task); for (Plot_Partition_Count index = 0; index < partition_count; ++index) { - auto partition = graph.add("partition", [this, object, index] { + auto prepare = graph.add("prepare.partition", [this, object, index] { + prepare_partition(object, index); + }); + auto paint = graph.add("paint.partition", [this, object, index] { paint_partition(object, index); }); - partition.precede(tooltip_task); + begin.precede(prepare); + prepare.precede(paint); + paint.precede(tooltip_task); } return graph; } @@ -239,14 +222,14 @@ inline void Waterfall::Private::paint_tooltip(Waterfall* object) { } inline void Waterfall::Private::handle_event(Waterfall* object, const Event& event) { if (detail::update_hover_tooltip(tooltip, event)) - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } template void Waterfall::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) { - detail::Internal_Access::mark_dirty(object); + detail::Internal_Access::mark_dirty(object); } } inline void Waterfall::Private::bind_sources(Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) { diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index 78e2992..6f8b623 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -23,7 +23,4 @@ void Render_Scene_2D::reset_frame_statistics() { double_buffer::detail::Internal_Access::get(this).reset_frame_statistics(this); } -Task_Graph& Render_Scene_2D::completion_taskflow() { - return static_cast(*d).completion_graph; -} } diff --git a/render_2D/render_2D/scene/Render_Scene_2D.hpp b/render_2D/render_2D/scene/Render_Scene_2D.hpp index ab13141..8159444 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.hpp @@ -10,8 +10,7 @@ namespace aethera::render_2d { /* 执行二维 Renderable 图、合成颜色层并发布最终像素帧。 */ struct Render_Scene_2D : Def, - Dependency_Graph_Type> { + Dependency_Graph_Type> { struct Prop : Prev_Prop { Size viewport{}; /* 最终帧的像素尺寸;空尺寸不执行渲染。 */ Color background{Color::black()}; /* 每帧合成前写入的背景颜色。 */ @@ -56,7 +55,6 @@ struct Render_Scene_2D : Def template requires std::derived_from typename Render_Scene_2D::Builder::Final_Builder& Render_Scene_2D::Builder::add_renderable(Renderable_Object* renderable) { + this->template add_dependency_node(renderable); attachments.emplace_back([renderable](Object* scene) { auto& data = Base::private_access(renderable).template get(); if (!data.scene_attach) @@ -65,11 +66,8 @@ struct Render_Scene_2D::Private : Prev_Private { Frame_Callback frame_callback{}; /* Plot publish 后的外接消费出口。 */ Frame_Callback frame_retired_callback{}; /* 全链路 trace 完成后的物理帧归还出口。 */ Frame_Statistics_Accumulator frame_statistics{}; /* Scene 内部增量计算;State 只发布定长统计结果。 */ - Task_Graph completion_graph{"render_2d.completion"}; /* 最终像素完成后、发布回调前执行的外部续写图。 */ - std::unique_ptr paint_taskflow{}; /* 仅由二维 Paint 图构建的执行图。 */ std::unique_ptr frame_taskflow{}; - std::vector active_prepare_executions{}; - std::chrono::steady_clock::time_point active_prepare_started{}; + std::chrono::steady_clock::time_point active_render_started{}; ~Private(); Frame_2D* active_frame{}; /* 异步帧 DAG 借用的外部帧;完成回调前保持存活。 */ Blend2D_Cache* frame_target{}; /* 当前异步帧的最终颜色层;帧 DAG 完成后清空。 */ @@ -98,9 +96,15 @@ struct Render_Scene_2D::Private : Prev_Private { void bind_private_crtp(Object* object); }; template -void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access, const Prop*, State_Access) { - bool rebuild = !paint_taskflow; - double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [&](auto& paint_state) { rebuild = rebuild || paint_state.dirty(); }); +void Render_Scene_2D::Private::after_advance( + Object* object, Prop*, State_Access pending_states, + const Prop*, State_Access) { + bool rebuild = paint_order.empty(); + double_buffer::detail::Internal_Access:: + access_pending_dependency_graph( + object, [&](auto& graph_state) { + rebuild = rebuild || graph_state.dirty(); + }); for (auto& node : paint_order) { const bool enabled = node.private_data->cache_enabled && node.private_data->cache_enabled(node.object); @@ -109,211 +113,54 @@ void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access rebuild = true; } if (!rebuild) return; - if (!paint_taskflow) paint_taskflow = std::make_unique("render_2d.paint"); - auto& taskflow = *paint_taskflow; - taskflow.clear(); - const auto dependencies = double_buffer::detail::Internal_Access::current_dependency_graph(object); + + const auto dependencies = + double_buffer::detail::Internal_Access::current_dependency_graph(object); paint_order.clear(); cache_groups.clear(); std::unordered_map cache_owner_by_object; std::unordered_map cache_group_by_owner; - const auto order_result = dependencies.for_each_topological_view([&](const auto& view, const Dependency_Graph::Node& node) { - auto* data = view.private_data(node); - if (!data) return; - Root* cache_owner{}; - if (data->cache_enabled && data->cache_enabled(node.object)) cache_owner = node.object; - else { - bool conflict{}; - for (const auto* dependency : node.dependencies) { - if (!view.private_data(*dependency)) continue; - auto current = cache_owner_by_object.find(dependency->object); - const Root* dependency_owner = current == cache_owner_by_object.end() ? nullptr : current->second; - if (!dependency_owner || (cache_owner && cache_owner != dependency_owner)) { conflict = true; break; } - cache_owner = const_cast(dependency_owner); - } - if (conflict) cache_owner = nullptr; - } - cache_owner_by_object.emplace(node.object, cache_owner); - paint_order.push_back(Paint_Node{node.object, data, cache_owner, false}); - if (!cache_owner) return; - auto [group, inserted] = cache_group_by_owner.emplace(cache_owner, cache_groups.size()); - if (inserted) cache_groups.push_back(Cache_Group{cache_owner}); - cache_groups[group->second].members.push_back(node.object); - }); - if (!order_result) throw std::logic_error("render scene paint graph became invalid while building cache groups"); - struct Stage_Tasks { - Task_Node entry; /* Renderable Paint 条件入口。 */ - Task_Node exit; /* Renderable Paint 状态发布出口。 */ - }; - std::unordered_map stage_tasks; - std::unordered_map cache_composites; - for (std::size_t index = 0; index < paint_order.size(); ++index) { - auto& paint_node = paint_order[index]; - Root* root = paint_node.object; - auto& data = *paint_node.private_data; - const auto task_prefix = std::string(data.business_name()) + ".paint"; - auto paint_if = taskflow.add_condition(task_prefix + ".condition", [this, &data, root, index] { - auto& state = *static_cast(data.pending_renderable_state(root)); - state.paint_graph_rebuilt = false; - state.paint_execution_time_ns = 0; - if (data.has_paint_graph()) { - const bool rebuild_graph = data.paint_rebuild_predicate(root); - if (!data.paint_graph_built || rebuild_graph) { - *data.paint_graph = data.build_paint_graph(root); - data.paint_graph_built = true; - state.paint_graph_rebuilt = true; - double_buffer::detail::Internal_Access::mark_dirty(root); - } - state.paint_task_count = data.paint_graph->size(); - } else state.paint_task_count = data.has_paint_run() ? 1 : 0; - const bool dirty = double_buffer::detail::Internal_Access::dirty(root); - state.paint_dirty = dirty; - state.paint_executed = paint_order[index].paint_requested && data.paint_predicate(root, dirty); - if (state.paint_executed) { - state.paint_execution_time_ns = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - } - return state.paint_executed ? 0 : 1; - }); - Task_Node paint_run; - if (data.has_paint_graph()) { - if (!data.paint_graph) data.paint_graph = std::make_unique(task_prefix + ".graph"); - paint_run = taskflow.compose(task_prefix + ".graph", *data.paint_graph); - } else paint_run = taskflow.add(task_prefix + ".data", [&data, root] { - if (data.has_paint_run()) data.run_paint(root); - }); - auto paint_done = taskflow.add(task_prefix + ".complete", [&data, root] { - auto& state = *static_cast(data.pending_renderable_state(root)); - if (state.paint_executed) { - (void)double_buffer::detail::Internal_Access::take_dirty(root); - const auto finished = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - state.paint_execution_time_ns = finished - state.paint_execution_time_ns; - } - }); - paint_if.precede(paint_run); - paint_if.precede(paint_done); - if (data.paint_extension.empty()) { - paint_run.precede(paint_done); - } - else { - auto paint_extension = taskflow.compose( - task_prefix + ".extension", data.paint_extension); - paint_run.precede(paint_extension); - paint_extension.precede(paint_done); - } - stage_tasks.emplace(root, Stage_Tasks{paint_if, paint_done}); - } - /* - * 缓存组是最终 framebuffer 上的一个原子颜色层:组内 Renderable 仍按 - * Paint_Tag 连边,组外后继必须等待该层完成合成。未重建时成员条件会 - * 直接走完成分支,但缓存层仍按同一拓扑位置合成。 - */ - for (auto& group : cache_groups) { - Root* cache_owner = group.owner; - auto owner_node = std::find_if( - paint_order.begin(), paint_order.end(), - [cache_owner](const Paint_Node& value) { - return value.object == cache_owner; - }); - if (owner_node == paint_order.end()) - throw std::logic_error("2D cache group lost its owner node"); - const auto task_prefix = - std::string(owner_node->private_data->business_name()) + - ".paint.cache_composite"; - auto composite = taskflow.add(task_prefix, [this, cache_owner] { - const auto group = std::find_if( - cache_groups.begin(), cache_groups.end(), - [cache_owner](const Cache_Group& value) { - return value.owner == cache_owner; - }); - const auto owner_node = std::find_if( - paint_order.begin(), paint_order.end(), - [cache_owner](const Paint_Node& value) { - return value.object == cache_owner; - }); - if (group == cache_groups.end() || owner_node == paint_order.end()) - return; - if (group->rebuild) - owner_node->private_data->valid_cache = - owner_node->private_data->paint_target; - if (!owner_node->private_data->valid_cache) return; - if (!frame_target) - throw std::logic_error( - "2D paint graph has no external frame target"); - frame_target->composite(*owner_node->private_data->valid_cache); - }); - for (Root* member : group.members) { - const auto tasks = stage_tasks.find(member); - if (tasks != stage_tasks.end()) tasks->second.exit.precede(composite); - } - cache_composites.emplace(cache_owner, composite); - } - /* - * Paint_Tag 是绘制顺序的唯一权威来源。只连接最近的二维依赖;没有 - * 显式前驱的节点在 Scene Prepare 与 framebuffer 初始化完成后直接就绪。 - */ - dependencies.for_each([&](const Dependency_Graph::Node& target_node) { - if (!dependencies.private_data(target_node)) return; - const auto target = stage_tasks.find(target_node.object); - if (target == stage_tasks.end()) return; - const auto target_paint = std::find_if( - paint_order.begin(), paint_order.end(), - [&](const Paint_Node& value) { - return value.object == target_node.object; - }); - if (target_paint == paint_order.end()) return; - std::unordered_set visited; - std::vector pending{ - target_node.dependencies.begin(), target_node.dependencies.end()}; - while (!pending.empty()) { - const auto* source_node = pending.back(); - pending.pop_back(); - if (!visited.insert(source_node->object).second) continue; - if (!dependencies.private_data(*source_node)) { - for (const auto* dependency : source_node->dependencies) - pending.push_back(dependency); - continue; - } - const auto source = stage_tasks.find(source_node->object); - if (source == stage_tasks.end()) continue; - const auto source_paint = std::find_if( - paint_order.begin(), paint_order.end(), - [&](const Paint_Node& value) { - return value.object == source_node->object; - }); - if (source_paint == paint_order.end()) continue; - if (source_paint->cache_owner == target_paint->cache_owner) { - source->second.exit.precede(target->second.entry); - continue; - } - const auto source_composite = source_paint->cache_owner - ? cache_composites.find(source_paint->cache_owner) - : cache_composites.end(); - const auto target_composite = target_paint->cache_owner - ? cache_composites.find(target_paint->cache_owner) - : cache_composites.end(); - if ((source_paint->cache_owner && - source_composite == cache_composites.end()) || - (target_paint->cache_owner && - target_composite == cache_composites.end())) - throw std::logic_error( - "2D cached Paint dependency has no composite task"); - if (source_paint->cache_owner) { - if (target_paint->cache_owner) - source_composite->second.precede( - target_composite->second); - else - source_composite->second.precede(target->second.entry); + const auto order_result = dependencies.for_each_topological_view( + [&](const auto& view, const Dependency_Graph::Node& node) { + auto* base_data = view.private_data(node); + if (!base_data) return; + auto* data = + static_cast(base_data); + Root* cache_owner{}; + if (data->cache_enabled && data->cache_enabled(node.object)) { + cache_owner = node.object; } else { - source->second.exit.precede(target_composite->second); + bool conflict{}; + for (const auto* dependency : node.dependencies) { + if (!view.private_data(*dependency)) continue; + const auto current = + cache_owner_by_object.find(dependency->object); + Root* dependency_owner = + current == cache_owner_by_object.end() + ? nullptr + : current->second; + if (!dependency_owner || + (cache_owner && cache_owner != dependency_owner)) { + conflict = true; + break; + } + cache_owner = dependency_owner; + } + if (conflict) cache_owner = nullptr; } - } - }); - double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [](auto& paint_state) { if (paint_state.dirty()) paint_state.take_dirty(); }); + cache_owner_by_object.emplace(node.object, cache_owner); + paint_order.push_back( + Paint_Node{node.object, data, cache_owner, false}); + if (!cache_owner) return; + auto [group, inserted] = + cache_group_by_owner.emplace(cache_owner, cache_groups.size()); + if (inserted) cache_groups.push_back(Cache_Group{cache_owner}); + cache_groups[group->second].members.push_back(node.object); + }); + if (!order_result) + throw std::logic_error( + "render scene graph became invalid while building cache groups"); } template void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& frame, Size viewport) { @@ -325,18 +172,15 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra for (Root* member : group.members) { const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; }); if (node == paint_order.end()) continue; - group.rebuild = group.rebuild || double_buffer::detail::Internal_Access::dirty(member); - if (node->private_data->has_paint_graph()) { - group.rebuild = group.rebuild || !node->private_data->paint_graph_built || - node->private_data->paint_rebuild_predicate(member); - } + group.rebuild = group.rebuild || + double_buffer::detail::Internal_Access::dirty(member); } if (group.rebuild) { auto* target = owner_node->private_data->pending_cache(group.owner); target->ensure_size(viewport); target->clear(); for (Root* member : group.members) { - double_buffer::detail::Internal_Access::mark_dirty(member); + double_buffer::detail::Internal_Access::mark_dirty(member); const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; }); if (node != paint_order.end()) { node->private_data->paint_target = target; node->paint_requested = true; } } @@ -354,138 +198,139 @@ void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& fra if (node.cache_owner) continue; node.private_data->paint_target = &frame; node.paint_requested = true; - double_buffer::detail::Internal_Access::mark_dirty(node.object); + double_buffer::detail::Internal_Access::mark_dirty(node.object); } } template void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { if (frame_taskflow) return; if (!runtime->taskflow) - runtime->taskflow = std::make_unique("scene.prepare"); - if (!paint_taskflow) - paint_taskflow = std::make_unique("render_2d.paint"); + runtime->taskflow = std::make_unique("scene.render"); frame_taskflow = std::make_unique("render_2d.frame"); auto& graph = *frame_taskflow; auto begin = graph.add("scene.begin", [this, object] { auto* frame = active_frame; if (!frame) throw std::logic_error("2D frame DAG lost its active frame"); - const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); + const Prop& prop = + double_buffer::detail::Internal_Access::current_prop_layer< + Render_Scene_2D::Base_Tag>(object); frame->mark(Frame_Trace_Marker::scene_render_started); frame->mark(Frame_Trace_Marker::event_dispatch_started); dispatch_events(object, prop.viewport, frame->identity().sequence); frame->mark(Frame_Trace_Marker::event_dispatch_finished); - double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each( - [](const Dependency_Graph::Node& node) { - if (!double_buffer::detail::Internal_Access::take_dirty(node.object)) return; - double_buffer::detail::Internal_Access::mark_dirty(node.object); - double_buffer::detail::Internal_Access::mark_dirty(node.object); - }); - active_prepare_executions.clear(); - double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( - [this](Renderable* renderable, Renderable::Private& data) { - bool execute = double_buffer::detail::Internal_Access::dirty(renderable); - if (data.has_prepare_graph()) - execute = execute || !data.prepare_graph_built || - data.prepare_rebuild_predicate(renderable); - if (execute) active_prepare_executions.push_back(renderable); - }); - auto& state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); + double_buffer::detail::Internal_Access:: + current_dependency_graph(object).for_each( + [](const Dependency_Graph::Node& node) { + if (!double_buffer::detail::Internal_Access:: + take_dirty(node.object)) + return; + double_buffer::detail::Internal_Access:: + mark_dirty(node.object); + }); + auto& state = static_cast( + double_buffer::detail::Internal_Access::pending_state(object)); state.taskflow_execution_time_ns = 0; - active_prepare_started = std::chrono::steady_clock::now(); + active_render_started = std::chrono::steady_clock::now(); + auto& target = detail::Frame_2D_Access::render_target(frame); + frame_target = ⌖ frame->mark(Frame_Trace_Marker::prepare_started); }); - begin.describe("dimension", "2D").describe("stage", "event dispatch"); - auto prepare = graph.compose("scene.prepare", *runtime->taskflow); - prepare.describe("dimension", "2D").describe("stage", "data prepare"); - auto setup_paint = graph.add("scene.paint.setup", [this, object] { - auto* frame_object = active_frame; - if (!frame_object) throw std::logic_error("2D Paint lost its active frame"); - auto& scene_state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); - scene_state.taskflow_execution_time_ns = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - active_prepare_started).count()); - scene_state.event_statistics = event_statistics.state(); - frame_object->mark(Frame_Trace_Marker::prepare_finished); - frame_object->mark(Frame_Trace_Marker::paint_started); - for (auto* renderable : active_prepare_executions) - double_buffer::detail::Internal_Access::mark_dirty(renderable); - auto& frame = detail::Frame_2D_Access::render_target(frame_object); - frame_target = &frame; - }); - setup_paint.describe("dimension", "2D") - .describe("stage", "paint dispatch"); + begin.describe("dimension", "2D").describe("stage", "frame setup"); + auto prepare_frame_target = graph.add( - "scene.paint.frame_target", [this, object] { - auto* frame_object = active_frame; - if (!frame_object || !frame_target) - throw std::logic_error("2D frame target preparation lost its frame"); - const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); - frame_object->mark(Frame_Trace_Marker::paint_frame_target_started); - frame_target->ensure_size(prop.viewport); - frame_target->clear(); - frame_object->mark(Frame_Trace_Marker::paint_frame_target_finished); - }); - prepare_frame_target.describe("dimension", "2D") - .describe("stage", "frame target clear"); + "scene.frame_target", [this, object] { + auto* frame = active_frame; + if (!frame || !frame_target) + throw std::logic_error("2D frame target lost its frame"); + const Prop& prop = + double_buffer::detail::Internal_Access::current_prop_layer< + Render_Scene_2D::Base_Tag>(object); + frame->mark(Frame_Trace_Marker::paint_frame_target_started); + frame_target->ensure_size(prop.viewport); + frame_target->clear(); + frame->mark(Frame_Trace_Marker::paint_frame_target_finished); + }); auto paint_background = graph.add( - "scene.paint.background", [this, object] { - auto* frame_object = active_frame; - if (!frame_object || !frame_target) - throw std::logic_error("2D background paint lost its frame target"); - const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); - frame_object->mark(Frame_Trace_Marker::paint_background_started); - { - detail::Painter painter(*frame_target, prop.viewport); - painter.rect({0.0, 0.0, static_cast(prop.viewport.width), - static_cast(prop.viewport.height)}, - Pen{.style = Line_Style::none}, - Brush{prop.background, Brush_Style::solid}); - } - frame_object->mark(Frame_Trace_Marker::paint_background_finished); - }); - paint_background.describe("dimension", "2D") - .describe("stage", "background fill"); + "scene.background", [this, object] { + auto* frame = active_frame; + if (!frame || !frame_target) + throw std::logic_error("2D background lost its frame target"); + const Prop& prop = + double_buffer::detail::Internal_Access::current_prop_layer< + Render_Scene_2D::Base_Tag>(object); + frame->mark(Frame_Trace_Marker::paint_background_started); + { + detail::Painter painter(*frame_target, prop.viewport); + painter.rect( + {0.0, 0.0, static_cast(prop.viewport.width), + static_cast(prop.viewport.height)}, + Pen{.style = Line_Style::none}, + Brush{prop.background, Brush_Style::solid}); + } + frame->mark(Frame_Trace_Marker::paint_background_finished); + }); auto prepare_cache_targets = graph.add( - "scene.paint.cache_targets", [this, object] { - auto* frame_object = active_frame; - if (!frame_object || !frame_target) - throw std::logic_error("2D cache target preparation lost its frame"); - const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer(object); - frame_object->mark(Frame_Trace_Marker::paint_cache_targets_started); - prepare_paint_targets(object, *frame_target, prop.viewport); - frame_object->mark(Frame_Trace_Marker::paint_cache_targets_finished); - }); - prepare_cache_targets.describe("dimension", "2D") - .describe("stage", "cache target clear"); - auto paint_ready = graph.add("scene.paint.ready", [this] { + "scene.cache_targets", [this, object] { + auto* frame = active_frame; + if (!frame || !frame_target) + throw std::logic_error("2D cache setup lost its frame"); + const Prop& prop = + double_buffer::detail::Internal_Access::current_prop_layer< + Render_Scene_2D::Base_Tag>(object); + frame->mark(Frame_Trace_Marker::paint_cache_targets_started); + prepare_paint_targets(object, *frame_target, prop.viewport); + frame->mark(Frame_Trace_Marker::paint_cache_targets_finished); + }); + auto render_ready = graph.add("scene.render.ready", [this] { if (!active_frame) - throw std::logic_error("2D Paint ready lost its active frame"); + throw std::logic_error("2D render lost its active frame"); + active_frame->mark(Frame_Trace_Marker::prepare_finished); + active_frame->mark(Frame_Trace_Marker::paint_started); active_frame->mark(Frame_Trace_Marker::paint_taskflow_started); }); - paint_ready.describe("dimension", "2D") - .describe("stage", "paint branch join"); - auto paint = graph.compose("scene.paint", *paint_taskflow); - paint.describe("dimension", "2D").describe("backend", "Blend2D"); - auto finish_paint = graph.add("scene.paint.complete", [this] { + auto renderables = graph.compose("scene.renderables", *runtime->taskflow); + renderables.describe("dimension", "2D").describe("backend", "Blend2D"); + auto composite_caches = graph.add("scene.cache_composite", [this] { + if (!frame_target) + throw std::logic_error("2D cache composite lost its frame target"); + for (auto& group : cache_groups) { + auto owner = std::find_if( + paint_order.begin(), paint_order.end(), + [&](const Paint_Node& value) { + return value.object == group.owner; + }); + if (owner == paint_order.end()) continue; + if (group.rebuild) + owner->private_data->valid_cache = + owner->private_data->paint_target; + if (owner->private_data->valid_cache) + frame_target->composite(*owner->private_data->valid_cache); + } + }); + auto finish = graph.add("scene.render.complete", [this, object] { + auto& state = static_cast( + double_buffer::detail::Internal_Access::pending_state(object)); + state.taskflow_execution_time_ns = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now() - active_render_started).count()); + state.event_statistics = event_statistics.state(); active_frame->mark(Frame_Trace_Marker::paint_taskflow_finished); active_frame->mark(Frame_Trace_Marker::paint_finished); active_frame->mark(Frame_Trace_Marker::scene_render_finished); }); - finish_paint.describe("dimension", "2D").describe("stage", "pixel complete"); auto completion = graph.compose("scene.completion", completion_graph); completion.describe("owner", "scene").describe("stage", "frame consumers"); - begin.precede(prepare); - prepare.precede(setup_paint); - setup_paint.precede(prepare_frame_target); - setup_paint.precede(prepare_cache_targets); - prepare_frame_target.precede(paint_background); - paint_background.precede(paint_ready); - prepare_cache_targets.precede(paint_ready); - paint_ready.precede(paint); - paint.precede(finish_paint); - finish_paint.precede(completion); -} + begin.precede(prepare_frame_target); + begin.precede(prepare_cache_targets); + prepare_frame_target.precede(paint_background); + paint_background.precede(render_ready); + prepare_cache_targets.precede(render_ready); + render_ready.precede(renderables); + renderables.precede(composite_caches); + composite_caches.precede(finish); + finish.precede(completion); +} template std::expected Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { @@ -543,18 +388,11 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { * 渲染统计并释放准入,再进入调用方 callback;callback 可以继续 * corun 外接 H264/WebRTC DAG,而下一帧已经允许开始。 */ - std::unordered_set published_renderables; - const auto publish_renderable_states = [&](const auto& graph) { - graph.for_each_bound([&](Renderable* renderable, - Renderable::Private& data) { - if (published_renderables.insert(renderable).second) + double_buffer::detail::Internal_Access:: + current_dependency_graph(object).for_each_bound( + [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); - }); - }; - publish_renderable_states( - double_buffer::detail::Internal_Access::current_dependency_graph(object)); - publish_renderable_states( - double_buffer::detail::Internal_Access::current_dependency_graph(object)); + }); auto& scene_state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); scene_state.frame_statistics = frame_statistics.submit( *frame, Frame_Dimension::two_dimensional); @@ -562,7 +400,6 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { active_frame = nullptr; frame_target = nullptr; - active_prepare_executions.clear(); { std::lock_guard lock(render_mutex); frame_in_flight = false; @@ -594,7 +431,6 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { aethera::detail::finish_taskflow_trace(*frame); active_frame = nullptr; frame_target = nullptr; - active_prepare_executions.clear(); std::lock_guard lock(render_mutex); frame_in_flight = false; throw; @@ -609,11 +445,13 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, /* Paint 拓扑在一帧事件批次内不会变化。只解析一次候选对象;每个事件仍按 * 当时的动态区域重新筛选、排序并完整分发,不合并也不丢弃输入。 */ std::vector candidates; - const auto graph = double_buffer::detail::Internal_Access::current_dependency_graph(object); + const auto graph = double_buffer::detail::Internal_Access::current_dependency_graph(object); const auto graph_result = graph.for_each_topological_view( [&](const auto& view, const Dependency_Graph::Node& node) { - auto* renderable = view.object(node); - auto* data = view.private_data(node); + auto* renderable = + static_cast(view.object(node)); + auto* data = static_cast( + view.private_data(node)); if (renderable && data && data->event_run) candidates.push_back({renderable, data}); }); diff --git a/render_2D/tests/Async_Render_Contract_Test.cpp b/render_2D/tests/Async_Render_Contract_Test.cpp index eedcbc9..0524d32 100644 --- a/render_2D/tests/Async_Render_Contract_Test.cpp +++ b/render_2D/tests/Async_Render_Contract_Test.cpp @@ -54,7 +54,7 @@ int main() { power, q_axis, 3u); auto* spectrum_painted = new std::atomic_bool{}; auto* topology_valid = new std::atomic_bool{true}; - spectrum->paint_taskflow().add("test.spectrum.paint.complete", [spectrum_painted] { + spectrum->taskflow().add("test.spectrum.render.complete", [spectrum_painted] { spectrum_painted->store(true, std::memory_order_release); }); const auto verify_spectrum_precedes_axis = @@ -62,9 +62,9 @@ int main() { if (!spectrum_painted->load(std::memory_order_acquire)) topology_valid->store(false, std::memory_order_relaxed); }; - frequency->paint_taskflow().add("test.frequency.paint.order", + frequency->taskflow().add("test.frequency.render.order", verify_spectrum_precedes_axis); - power->paint_taskflow().add("test.power.paint.order", + power->taskflow().add("test.power.render.order", verify_spectrum_precedes_axis); typename Scene_Object::template Builder scene_builder; @@ -96,7 +96,7 @@ int main() { Axis_Range{0.0, 100.0}); spectrum->pending_buffer() = Spectrum_Frame{std::vector(512, -50.0)}; - spectrum->mark_dirty(); + spectrum->mark_dirty(); waterfall->set<&Waterfall::Prop::frequency_range>( Axis_Range{0.0, 100.0}); waterfall->set<&Waterfall::Prop::power_range>( @@ -109,14 +109,14 @@ int main() { waterfall->submit_stream( std::make_shared(Waterfall_Row{ second_tick, std::vector(64, -30.0)})); - waterfall->mark_dirty(); + waterfall->mark_dirty(); constellation->set<&Constellation_Diagram::Prop::i_range>( Axis_Range{-1.0, 1.0}); constellation->set<&Constellation_Diagram::Prop::q_range>( Axis_Range{-1.0, 1.0}); constellation->submit_stream( Constellation_Point{{-50.0, 0.25}, monotonic_milliseconds()}); - constellation->mark_dirty(); + constellation->mark_dirty(); scene->set<&Render_Scene_2D::Prop::viewport>(Size{160, 120}); scene->activate_view(); @@ -133,21 +133,16 @@ int main() { const auto& constellation_state = constellation->read_state(); const bool valid = completed == frame && - spectrum_state.prepare_executed && - spectrum_state.paint_executed && - spectrum_state.prepare_task_count == 5 && - spectrum_state.paint_task_count == 6 && - waterfall_state.prepare_executed && - waterfall_state.paint_executed && - waterfall_state.prepare_task_count == 5 && - waterfall_state.paint_task_count == 5 && - constellation_state.prepare_executed && - constellation_state.paint_executed && - constellation_state.paint_task_count == 4 && + spectrum_state.render_executed && + spectrum_state.task_count == 11 && + waterfall_state.render_executed && + waterfall_state.task_count == 10 && + constellation_state.render_executed && + constellation_state.task_count == 5 && spectrum->read_state().sample_count == 512 && - frequency->read_state().paint_executed && - power->read_state().paint_executed && + frequency->read_state().render_executed && + power->read_state().render_executed && topology_valid->load(std::memory_order_relaxed) && contains_color(completed->image()); if (!valid) std::_Exit(1); @@ -170,20 +165,16 @@ int main() { constellation->read_state(); const bool resized_valid = next == resized_partition_frame && - next_spectrum.prepare_graph_rebuilt && - next_spectrum.paint_graph_rebuilt && - next_spectrum.prepare_task_count == 3 && - next_spectrum.paint_task_count == 4 && - next_waterfall.prepare_graph_rebuilt && - next_waterfall.paint_graph_rebuilt && - next_waterfall.prepare_task_count == 7 && - next_waterfall.paint_task_count == 7 && - next_constellation.paint_graph_rebuilt && - next_constellation.paint_task_count == 3 && + next_spectrum.graph_rebuilt && + next_spectrum.task_count == 7 && + next_waterfall.graph_rebuilt && + next_waterfall.task_count == 14 && + next_constellation.graph_rebuilt && + next_constellation.task_count == 4 && frequency->read_state() - .paint_executed && + .render_executed && power->read_state() - .paint_executed && + .render_executed && topology_valid->load(std::memory_order_relaxed) && contains_color(next->image()); std::_Exit(resized_valid ? 0 : 1); diff --git a/render_2D/tests/Axis_Test.cpp b/render_2D/tests/Axis_Test.cpp index 0d2c50b..bd824e3 100644 --- a/render_2D/tests/Axis_Test.cpp +++ b/render_2D/tests/Axis_Test.cpp @@ -89,14 +89,13 @@ TEST(axis_render, scene_prepares_and_paints_uncached_axis_into_frame) { axis->set<&Abs_Axis::Prop::pixel_length>(120.0); scene->set<&Render_Scene_2D::Prop::viewport>(Size{160, 64}); scene->activate_view(); - ASSERT_TRUE((scene->edit_dependency_graph([&](auto& prepare, auto& paint) { - prepare.add(axis.get()); - paint.add(axis.get()); + ASSERT_TRUE((scene->edit_dependency_graph([&](auto& graph) { + graph.add(axis.get()); }).has_value())); int paint_extension_calls{}; int completion_calls{}; - axis->paint_taskflow().add( - "test.axis.paint.extension", [&] { ++paint_extension_calls; }); + axis->taskflow().add( + "test.axis.render.extension", [&] { ++paint_extension_calls; }); scene->completion_taskflow().add("test.scene_2d.completion", [&] { EXPECT_EQ(paint_extension_calls, 1); ++completion_calls; @@ -123,8 +122,8 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) { axis->set<&Abs_Axis::Prop::pixel_length>(100.0); axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0}); scene->set<&Render_Scene_2D::Prop::viewport>(Size{120, 64}); - ASSERT_TRUE((scene->edit_dependency_graph([&](auto& prepare, auto& paint) { - prepare.add(axis.get()); paint.add(axis.get()); + ASSERT_TRUE((scene->edit_dependency_graph([&](auto& graph) { + graph.add(axis.get()); }).has_value())); scene->activate_view(); render_once(scene.get()); @@ -159,9 +158,9 @@ TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) { vertical->set<&Abs_Axis::Prop::orientation>(Axis_Orientation::vertical); vertical->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0}); scene->set<&Render_Scene_2D::Prop::viewport>(Size{200, 200}); - ASSERT_TRUE((scene->edit_dependency_graph([&](auto& prepare, auto& paint) { - prepare.add(horizontal.get()); prepare.add(vertical.get()); - paint.add(horizontal.get()); paint.add(vertical.get()); + ASSERT_TRUE((scene->edit_dependency_graph([&](auto& graph) { + graph.add(horizontal.get()); + graph.add(vertical.get()); }).has_value())); scene->activate_view(); render_once(scene.get()); @@ -217,14 +216,14 @@ TEST(axis_time, appending_each_time_sample_invalidates_tick_preparation) { using Object = Time_Axis; auto axis = build_axis(); axis->advance(); - axis->template take_dirty(); - EXPECT_FALSE(axis->template dirty()); + axis->template take_dirty(); + EXPECT_FALSE(axis->template dirty()); EXPECT_EQ(axis->append_time({1'000}), 0); - EXPECT_TRUE(axis->template dirty()); + EXPECT_TRUE(axis->template dirty()); axis->advance(); - axis->template take_dirty(); + axis->template take_dirty(); EXPECT_EQ(axis->append_time({2'000}), 1); - EXPECT_TRUE(axis->template dirty()); + EXPECT_TRUE(axis->template dirty()); axis->advance(); EXPECT_EQ(axis->coordinate_range(), (Axis_Range{-98.5, 1.5})); } diff --git a/render_2D/tests/Plottable_Migration_Test.cpp b/render_2D/tests/Plottable_Migration_Test.cpp index 9a900a7..974901a 100644 --- a/render_2D/tests/Plottable_Migration_Test.cpp +++ b/render_2D/tests/Plottable_Migration_Test.cpp @@ -68,7 +68,7 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) { Frequency_Trace_Sample{time->append_time({2'000}), 50.0}); trace->submit_stream( Frequency_Trace_Sample{time->append_time({3'000}), 90.0}); - trace->mark_dirty(); + trace->mark_dirty(); sweep->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); sweep->set<&Sweep_Spectrum::Prop::block_count>(2u); const std::array first_block{-90.0, -60.0, -30.0, -10.0}; @@ -80,7 +80,7 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) { sweep->submit_stream( std::make_shared(Sweep_Spectrum_Block{ 1, {second_block.begin(), second_block.end()}})); - sweep->mark_dirty(); + sweep->mark_dirty(); auto scene = build_scene(trace.get(), sweep.get()); scene->set<&Render_Scene_2D::Prop::viewport>(canvas); scene->activate_view(); @@ -94,8 +94,8 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) { 0, {replacement_block.begin(), replacement_block.end()}})); trace->submit_stream( Frequency_Trace_Sample{time->append_time({4'000}), 60.0}); - trace->mark_dirty(); - sweep->mark_dirty(); + trace->mark_dirty(); + sweep->mark_dirty(); render_once(scene.get()); EXPECT_EQ(trace->access_rendering_stream( [](std::span samples) { return samples.size(); }), 4u); @@ -132,12 +132,12 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) { const std::array row{-90.0, -60.0, -30.0, -10.0}; glow->submit_stream( std::make_shared>(row.begin(), row.end())); - glow->mark_dirty(); + glow->mark_dirty(); waterfall->submit_stream(std::make_shared( Waterfall_Row{time->append_time({1'000}), {row.begin(), row.end()}})); waterfall->submit_stream(std::make_shared( Waterfall_Row{time->append_time({2'000}), {row.begin(), row.end()}})); - waterfall->mark_dirty(); + waterfall->mark_dirty(); auto scene = build_scene(glow.get(), waterfall.get()); scene->set<&Render_Scene_2D::Prop::viewport>(canvas); scene->activate_view(); @@ -148,10 +148,10 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) { [](std::span> rows) { return rows.size(); }), 2u); glow->submit_stream( std::make_shared>(row.begin(), row.end())); - glow->mark_dirty(); + glow->mark_dirty(); waterfall->submit_stream(std::make_shared( Waterfall_Row{time->append_time({3'000}), {row.begin(), row.end()}})); - waterfall->mark_dirty(); + waterfall->mark_dirty(); render_once(scene.get()); EXPECT_EQ(glow->access_rendering_stream( [](std::span>> spectra) { return spectra.size(); }), 2u); @@ -178,15 +178,14 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) { diagram->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.0, 1.0}); diagram->submit_stream( Constellation_Point{{0.25, -0.25}, monotonic_milliseconds()}); - diagram->mark_dirty(); + diagram->mark_dirty(); auto scene = build_scene(diagram.get(), overlay.get()); scene->set<&Render_Scene_2D::Prop::viewport>(canvas); scene->activate_view(); render_once(scene.get()); EXPECT_EQ(diagram->access_rendering_stream( [](std::span points) { return points.size(); }), 1u); - EXPECT_EQ(overlay->read_state().prepare_task_count, 0u); - EXPECT_EQ(overlay->read_state().paint_task_count, 1u); + EXPECT_EQ(overlay->read_state().task_count, 1u); } TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) { diff --git a/render_2D/tests/Spectrum_Test.cpp b/render_2D/tests/Spectrum_Test.cpp index c40c70f..3c2ccae 100644 --- a/render_2D/tests/Spectrum_Test.cpp +++ b/render_2D/tests/Spectrum_Test.cpp @@ -63,7 +63,7 @@ TEST(spectrum_data, publishes_samples_through_tagged_frame_buffer) { const double samples[]{-100.0, -50.0, 0.0}; spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; - spectrum->mark_dirty(); + spectrum->mark_dirty(); spectrum->advance(); EXPECT_EQ(spectrum->current_buffer().samples, (std::vector{-100.0, -50.0, 0.0})); @@ -126,67 +126,64 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) { const double samples[]{-90.0, -65.0, -20.0, -45.0, -75.0}; spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; - spectrum->mark_dirty(); + spectrum->mark_dirty(); scene->set<&Render_Scene_2D::Prop::viewport>(canvas); scene->set<&Render_Scene_2D::Prop::background>(Color::transparent()); scene->activate_view(); - const auto prepare_graph = scene->pending_dependency_graph(); - EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), scene.get())); - EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), frequency.get())); - EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), power.get())); - const auto paint_graph = scene->pending_dependency_graph(); - EXPECT_TRUE(paint_graph.depends_on(frequency.get(), spectrum.get())); - EXPECT_TRUE(paint_graph.depends_on(power.get(), spectrum.get())); - const auto cache_graph = scene->pending_dependency_graph(); + const auto render_graph = scene->pending_dependency_graph(); + EXPECT_FALSE(render_graph.depends_on(spectrum.get(), scene.get())); + EXPECT_FALSE(render_graph.depends_on(spectrum.get(), frequency.get())); + EXPECT_FALSE(render_graph.depends_on(spectrum.get(), power.get())); + EXPECT_TRUE(render_graph.depends_on(frequency.get(), spectrum.get())); + EXPECT_TRUE(render_graph.depends_on(power.get(), spectrum.get())); + const auto cache_graph = scene->pending_dependency_graph(); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), scene.get())); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), frequency.get())); EXPECT_TRUE(cache_graph.depends_on(spectrum.get(), power.get())); auto output_frame = render_frame(scene.get()); EXPECT_GT(spectrum->read_state().rendered_point_count, 0u); const auto& render_state = spectrum->read_state(); - EXPECT_EQ(render_state.prepare_task_count, 4u); - EXPECT_EQ(render_state.paint_task_count, 5u); + EXPECT_EQ(render_state.task_count, 9u); const Image_View frame = output_frame->image(); ASSERT_FALSE(frame.empty()); EXPECT_TRUE(contains_color(frame)); - EXPECT_FALSE(spectrum->dirty()); - EXPECT_FALSE(spectrum->dirty()); - EXPECT_FALSE(frequency->dirty()); - EXPECT_FALSE(power->dirty()); + EXPECT_FALSE(spectrum->dirty()); + EXPECT_FALSE(spectrum->dirty()); + EXPECT_FALSE(frequency->dirty()); + EXPECT_FALSE(power->dirty()); EXPECT_FALSE(spectrum->read_prop().cache_enabled); spectrum->set<&Renderable_2D::Prop::cache_enabled>(true); output_frame = render_frame(scene.get()); EXPECT_TRUE(contains_color(output_frame->image())); frequency->set<&Numeric_Axis::Prop::precision>(3); - EXPECT_FALSE(spectrum->dirty()); + EXPECT_FALSE(spectrum->dirty()); output_frame = render_frame(scene.get()); - EXPECT_TRUE(spectrum->read_state().paint_executed); - EXPECT_TRUE(frequency->read_state().paint_executed); - EXPECT_TRUE(power->read_state().paint_executed); + EXPECT_TRUE(spectrum->read_state().render_executed); + EXPECT_TRUE(frequency->read_state().render_executed); + EXPECT_TRUE(power->read_state().render_executed); EXPECT_TRUE(contains_color(output_frame->image())); frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 120.0}); - EXPECT_TRUE(spectrum->dirty()); + EXPECT_TRUE(spectrum->dirty()); output_frame = render_frame(scene.get()); - EXPECT_FALSE(spectrum->dirty()); - EXPECT_TRUE(spectrum->read_state().prepare_executed); + EXPECT_FALSE(spectrum->dirty()); + EXPECT_TRUE(spectrum->read_state().render_executed); spectrum->set<&Spectrum::Prop::partition_count>(2u); spectrum->pending_buffer() = Spectrum_Frame{std::vector(std::begin(samples), std::end(samples))}; - spectrum->mark_dirty(); + spectrum->mark_dirty(); output_frame = render_frame(scene.get()); const auto& rebuilt_state = spectrum->read_state(); - EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt); - EXPECT_EQ(rebuilt_state.prepare_task_count, 3u); + EXPECT_TRUE(rebuilt_state.graph_rebuilt); + EXPECT_EQ(rebuilt_state.task_count, 7u); EXPECT_TRUE(contains_color(output_frame->image())); const Size resized_canvas{200, 140}; scene->set<&Render_Scene_2D::Prop::viewport>(resized_canvas); - EXPECT_TRUE(spectrum->dirty()); - EXPECT_FALSE(frequency->dirty()); - EXPECT_FALSE(power->dirty()); + EXPECT_TRUE(spectrum->dirty()); + EXPECT_FALSE(frequency->dirty()); + EXPECT_FALSE(power->dirty()); output_frame = render_frame(scene.get()); EXPECT_EQ(scene->read_prop().viewport, resized_canvas); - EXPECT_FALSE(frequency->read_state().prepare_executed); - EXPECT_TRUE(frequency->read_state().paint_executed); + EXPECT_TRUE(frequency->read_state().render_executed); const Image_View resized_frame = output_frame->image(); EXPECT_EQ(resized_frame.width, resized_canvas.width); EXPECT_EQ(resized_frame.height, resized_canvas.height); @@ -223,13 +220,12 @@ TEST(spectrum_data, repeatedly_publishes_new_samples_during_long_running_render) samples[peak] = -15.0; spectrum->pending_buffer() = Spectrum_Frame{std::move(samples)}; - spectrum->mark_dirty(); + spectrum->mark_dirty(); auto frame = render_frame(scene.get()); const auto hash = image_hash(frame->image()); if (frame_index != 0 && hash != previous_hash) ++changed_frames; previous_hash = hash; - EXPECT_TRUE(spectrum->read_state().prepare_executed); - EXPECT_TRUE(spectrum->read_state().paint_executed); + EXPECT_TRUE(spectrum->read_state().render_executed); EXPECT_EQ(spectrum->read_state().sample_count, 128u); } EXPECT_GT(changed_frames, 700u); diff --git a/render_3D/render_3D/scene/Render_Scene_3D.cpp b/render_3D/render_3D/scene/Render_Scene_3D.cpp index 6a5de5d..749bf86 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.cpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.cpp @@ -18,7 +18,4 @@ void Render_Scene_3D::deactivate_view() { void Render_Scene_3D::reset_frame_statistics() { double_buffer::detail::Internal_Access::get(this).reset_frame_statistics(this); } -Task_Graph& Render_Scene_3D::completion_taskflow() { - return static_cast(*d).completion_graph; -} } diff --git a/render_3D/render_3D/scene/Render_Scene_3D.hpp b/render_3D/render_3D/scene/Render_Scene_3D.hpp index 2dd074d..eee73e3 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.hpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.hpp @@ -12,10 +12,8 @@ #include #include namespace aethera::render_3d { -/* 三维 Prepare 完成后的异步后端提交阶段标签。 */ -struct Submit_Tag {}; -/* 执行共有 Prepare 图,并把三维 Submit 阶段无等待地提交给 Datoviz 渲染域。 */ -struct Render_Scene_3D : Def> { +/* 执行公共 Renderable 图,并把结果无等待地提交给 Datoviz 渲染域。 */ +struct Render_Scene_3D : Def { struct Prop : Prev_Prop { Extent viewport{560, 320}; /* 离屏渲染目标尺寸,单位为像素。 */ Linear_Color clear_color{}; /* 每帧开始时写入的线性背景色。 */ @@ -70,7 +68,6 @@ struct Render_Scene_3D : Def backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */ std::shared_ptr paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */ Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */ @@ -31,11 +30,10 @@ struct Render_Scene_3D::Private : Prev_Private { Camera_Descriptor (*read_camera)(const Root*){}; /* 读取 Camera 当前配置。 */ std::array (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */ Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */ - std::unique_ptr submit_taskflow{}; std::unique_ptr frame_taskflow{}; Event_Batch active_events{}; bool active_trace{}; - std::chrono::steady_clock::time_point active_prepare_started{}; + std::chrono::steady_clock::time_point active_render_started{}; /* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */ template void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled, @@ -88,8 +86,7 @@ Render_Scene_3D::Builder::add_renderable(Visual_Object* visual_value) { }); }; visuals.push_back(binding); - this->template add_dependency_node(visual_value); - this->template add_dependency_node(visual_value); + this->template add_dependency_node(visual_value); return static_cast(*this); } template template @@ -213,251 +210,59 @@ template void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { if (frame_taskflow) return; if (!runtime->taskflow) - runtime->taskflow = std::make_unique("scene.prepare"); - /* 模块图在外层 topology 提交前必须已经包含真实节点。若把首次 builder - * 留给模块内部 condition,Taskflow 已经物化的父 topology 看不到本次新增 - * 节点,Visual 会发布尚未 Prepare 的空 payload。 */ - double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( - [](Renderable* renderable, Renderable::Private& data) { - if (!data.has_prepare_graph() || data.prepare_graph_built) return; - if (!data.prepare_graph) - data.prepare_graph = std::make_unique( - std::string(data.business_name()) + ".prepare.graph"); - *data.prepare_graph = data.build_prepare_graph(renderable); - data.prepare_graph_built = true; - double_buffer::detail::Internal_Access::mark_dirty(renderable); - }); - submit_taskflow = std::make_unique("render_3d.visual.submit"); - struct Submit_Tasks { - Task_Node entry; - Task_Node exit; - }; - std::unordered_map submit_tasks; - const auto submit_dependencies = - double_buffer::detail::Internal_Access::current_dependency_graph(object); - const auto order = submit_dependencies.for_each_topological_view( - [&](const auto& view, const Dependency_Graph::Node& node) { - auto* data = view.private_data(node); - if (!data) return; - Root* root = node.object; - const auto prefix = std::string(data->business_name()) + ".submit"; - if (data->has_paint_graph() && !data->paint_graph) { - data->paint_graph = std::make_unique(prefix + ".graph"); - *data->paint_graph = data->build_paint_graph(root); - data->paint_graph_built = true; - } - auto condition = submit_taskflow->add_condition(prefix + ".condition", - [data, root] { - auto& state = *static_cast( - data->pending_renderable_state(root)); - state.paint_graph_rebuilt = false; - state.paint_execution_time_ns = 0; - const bool dirty = double_buffer::detail::Internal_Access::dirty(root); - state.paint_dirty = dirty; - state.paint_executed = data->paint_predicate(root, dirty); - if (state.paint_executed) - state.paint_execution_time_ns = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - return state.paint_executed ? 0 : 1; - }); - condition.describe("renderable", std::string(data->business_name())) - .describe("dimension", "3D") - .describe("stage", "visual submit predicate"); - Task_Node publish; - if (data->has_paint_graph()) - publish = submit_taskflow->compose(prefix + ".visual", *data->paint_graph); - else - publish = submit_taskflow->add(prefix + ".visual", [data, root] { - if (data->has_paint_run()) data->run_paint(root); - }); - publish.describe("renderable", std::string(data->business_name())) - .describe("dimension", "3D") - .describe("stage", "prepared visual publish"); - auto done = submit_taskflow->add(prefix + ".complete", [data, root] { - auto& state = *static_cast( - data->pending_renderable_state(root)); - if (state.paint_executed) { - (void)double_buffer::detail::Internal_Access::take_dirty(root); - const auto finished = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - state.paint_execution_time_ns = finished - state.paint_execution_time_ns; - } - }); - done.describe("renderable", std::string(data->business_name())) - .describe("dimension", "3D") - .describe("stage", "visual state publish"); - condition.precede(publish); - condition.precede(done); - if (data->paint_extension.empty()) - publish.precede(done); - else { - auto extension = submit_taskflow->compose( - prefix + ".extension", data->paint_extension); - extension.describe("owner", std::string(data->business_name())) - .describe("stage", "renderable extension"); - publish.precede(extension); - extension.precede(done); - } - submit_tasks.emplace(root, Submit_Tasks{condition, done}); - }); - if (!order) - throw std::logic_error("render scene submit graph became invalid while building"); - /* Submit 只继承业务依赖图中的真实前驱;互不依赖的 Visual 不再被人为 - * 串成一个长条。未绑定的中间依赖节点只用于传递依赖关系。 */ - submit_dependencies.for_each( - [&](const Dependency_Graph::Node& target_node) { - if (!submit_dependencies.private_data(target_node)) return; - const auto target = submit_tasks.find(target_node.object); - if (target == submit_tasks.end()) return; - std::unordered_set visited; - std::vector pending{ - target_node.dependencies.begin(), target_node.dependencies.end()}; - while (!pending.empty()) { - const auto* dependency = pending.back(); - pending.pop_back(); - if (!visited.insert(dependency->object).second) continue; - if (submit_dependencies.private_data(*dependency)) { - const auto source = submit_tasks.find(dependency->object); - if (source != submit_tasks.end()) - source->second.exit.precede(target->second.entry); - continue; - } - pending.insert(pending.end(), dependency->dependencies.begin(), - dependency->dependencies.end()); - } - }); + runtime->taskflow = std::make_unique("scene.render"); frame_taskflow = std::make_unique("render_3d.frame"); auto& graph = *frame_taskflow; auto begin = graph.add("scene.begin", [this, object] { - if (!active_frame) throw std::logic_error("3D frame DAG lost its active frame"); + if (!active_frame) + throw std::logic_error("3D frame DAG lost its active frame"); active_frame->mark(Frame_Trace_Marker::scene_render_started); active_frame->mark(Frame_Trace_Marker::event_dispatch_started); active_events = take_events(object); active_frame->mark(Frame_Trace_Marker::event_dispatch_finished); - auto context = std::static_pointer_cast>(paint_context); + + auto context = std::static_pointer_cast< + detail::Scene_Paint_Context>(paint_context); context->parameters = parameters(object); - if (!detail::prepared_visual_batch_complete(*context->visuals)) - double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( - [](Renderable* renderable, Renderable::Private&) { - double_buffer::detail::Internal_Access::mark_dirty(renderable); - }); + if (!detail::prepared_visual_batch_complete(*context->visuals)) { + double_buffer::detail::Internal_Access:: + current_dependency_graph(object).for_each_bound( + [](Renderable* renderable, Renderable::Private&) { + double_buffer::detail::Internal_Access:: + mark_dirty(renderable); + }); + } if (context->visuals.use_count() != 1) - context->visuals = std::make_shared(*context->visuals); + context->visuals = + std::make_shared(*context->visuals); context->frame = active_frame; camera_component->advance_object(); axes_component->advance_object(); - active_prepare_started = std::chrono::steady_clock::now(); + active_render_started = std::chrono::steady_clock::now(); active_frame->mark(Frame_Trace_Marker::prepare_started); - }); - begin.describe("dimension", "3D").describe("stage", "event and frame setup"); - struct Prepare_Tasks { - Task_Node task; - }; - std::unordered_map prepare_tasks; - const auto prepare_dependencies = - double_buffer::detail::Internal_Access::current_dependency_graph(object); - const auto prepare_order = prepare_dependencies.for_each_topological_view( - [&](const auto& view, const Dependency_Graph::Node& node) { - auto* data = view.private_data(node); - if (!data) return; - Root* root = node.object; - auto task = graph.add( - std::string(data->business_name()) + ".prepare.data", - [data, root] { - auto& state = *static_cast( - data->pending_renderable_state(root)); - const bool dirty = double_buffer::detail::Internal_Access::dirty(root); - state.prepare_dirty = dirty; - state.prepare_graph_rebuilt = false; - state.prepare_task_count = data->has_prepare_run() ? 1 : 0; - state.prepare_executed = - data->prepare_predicate(root, dirty); - state.prepare_execution_time_ns = 0; - if (state.prepare_executed) { - const auto started = std::chrono::steady_clock::now(); - if (data->has_prepare_run()) data->run_prepare(root); - (void)double_buffer::detail::Internal_Access::take_dirty(root); - state.prepare_execution_time_ns = - static_cast( - std::chrono::duration_cast< - std::chrono::nanoseconds>( - std::chrono::steady_clock::now() - started) - .count()); - } - }); - task.describe("renderable", std::string(data->business_name())) - .describe("dimension", "3D") - .describe("stage", "CPU prepared data") - .describe("execution_domain", "Taskflow worker"); - prepare_tasks.emplace(root, Prepare_Tasks{task}); - }); - if (!prepare_order) - throw std::logic_error( - "render scene prepare graph became invalid while building"); - prepare_dependencies.for_each( - [&](const Dependency_Graph::Node& target_node) { - if (!prepare_dependencies.private_data(target_node)) return; - const auto target = prepare_tasks.find(target_node.object); - if (target == prepare_tasks.end()) return; - std::unordered_set visited; - std::vector pending{ - target_node.dependencies.begin(), target_node.dependencies.end()}; - while (!pending.empty()) { - const auto* dependency = pending.back(); - pending.pop_back(); - if (!visited.insert(dependency->object).second) continue; - if (prepare_dependencies.private_data(*dependency)) { - const auto source = prepare_tasks.find(dependency->object); - if (source != prepare_tasks.end()) - source->second.task.precede(target->second.task); - continue; - } - pending.insert(pending.end(), dependency->dependencies.begin(), - dependency->dependencies.end()); - } - }); - for (const auto& [root, tasks] : prepare_tasks) { - static_cast(root); - begin.precede(tasks.task); - } - auto prepare_done = graph.add("scene.prepare.complete", [this, object] { active_frame->mark(Frame_Trace_Marker::prepare_finished); - const auto context = std::static_pointer_cast< + active_frame->mark(Frame_Trace_Marker::paint_started); + }); + begin.describe("dimension", "3D").describe("stage", "frame setup"); + + auto renderables = graph.compose("scene.renderables", *runtime->taskflow); + renderables.describe("dimension", "3D") + .describe("stage", "visual graph") + .describe("execution_domain", "Taskflow workers"); + + auto submit = graph.add("scene.backend.submit", [this, object] { + auto context = std::static_pointer_cast< detail::Scene_Paint_Context>(paint_context); - const bool initial_publish = - !detail::prepared_visual_batch_complete(*context->visuals); - const auto prepare_graph = - double_buffer::detail::Internal_Access::current_dependency_graph(object); - prepare_graph.for_each_bound([initial_publish](Renderable* renderable, Renderable::Private& data) { - const auto* state = static_cast( - data.pending_renderable_state(renderable)); - if (initial_publish || state->prepare_executed) - double_buffer::detail::Internal_Access::mark_dirty(renderable); - }); + if (!detail::prepared_visual_batch_complete(*context->visuals)) + throw std::logic_error( + "render scene published an incomplete Visual batch"); auto& state = static_cast( double_buffer::detail::Internal_Access::pending_state(object)); state.taskflow_execution_time_ns = static_cast( std::chrono::duration_cast( - std::chrono::steady_clock::now() - active_prepare_started).count()); + std::chrono::steady_clock::now() - active_render_started).count()); state.event_statistics = event_statistics.state(); - active_frame->mark(Frame_Trace_Marker::paint_started); - }); - prepare_done.describe("dimension", "3D") - .describe("stage", "prepare state publish") - .describe("state_direction", "internal to external"); - auto visuals = graph.compose("scene.visuals", *submit_taskflow); - visuals.describe("dimension", "3D") - .describe("stage", "prepared visual collection") - .describe("execution_domain", "Taskflow workers") - .describe("node_count", std::to_string(submit_taskflow->size())); - auto submit = graph.add("scene.backend.submit", [this] { - auto context = std::static_pointer_cast>(paint_context); - if (!detail::prepared_visual_batch_complete(*context->visuals)) - throw std::logic_error( - "render scene published an incomplete Visual batch"); context->backend->render( context->visuals, context->parameters, active_frame, std::move(active_events), [this](Frame_3D* completed) { @@ -470,23 +275,15 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { }); submit.describe("dimension", "3D") .describe("backend", "Datoviz") - .describe("stage", "backend prepare and submission enqueue") - .describe("cpu_owner", "Taskflow worker") - .describe("gpu_submit_owner", "GPU Render Domain") - .describe("completion", "cooperative GPU fence continuation") - .describe("output", "RGBA8 readback"); + .describe("stage", "backend submission"); auto completion = graph.compose("scene.completion", completion_graph); completion.describe("owner", "scene") .describe("stage", "frame consumers"); - for (const auto& [root, tasks] : prepare_tasks) { - static_cast(root); - tasks.task.precede(prepare_done); - } - prepare_done.precede(visuals); - visuals.precede(submit); + + begin.precede(renderables); + renderables.precede(submit); submit.precede(completion); } - template Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object, Frame_3D* frame) { if (!frame) throw std::invalid_argument("Render_Scene_3D requires a non-null external frame"); @@ -508,19 +305,11 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object, active_trace = aethera::detail::begin_taskflow_trace(*frame); try { auto completion = [this, object, frame] { - auto& private_data = static_cast(*this); - std::unordered_set published_renderables; - const auto publish_renderable_states = [&](const auto& graph) { - graph.for_each_bound([&](Renderable* renderable, - Renderable::Private& data) { - if (published_renderables.insert(renderable).second) + double_buffer::detail::Internal_Access:: + current_dependency_graph(object).for_each_bound( + [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); - }); - }; - publish_renderable_states( - double_buffer::detail::Internal_Access::current_dependency_graph(object)); - publish_renderable_states( - double_buffer::detail::Internal_Access::current_dependency_graph(object)); + }); double_buffer::detail::Internal_Access::publish_state(object); complete_frame(object, frame); }; diff --git a/render_3D/render_3D/visual/Basic_Visual.ipp b/render_3D/render_3D/visual/Basic_Visual.ipp index 353a7e0..69f1562 100644 --- a/render_3D/render_3D/visual/Basic_Visual.ipp +++ b/render_3D/render_3D/visual/Basic_Visual.ipp @@ -17,12 +17,10 @@ struct Basic_Visual::Private : Basic_Visual::Prev_Private { /* CRTP 数据能力:从当前 Prop 构建后端字段,不访问其他缓冲角色。 */ template void prepare_data(Object* object); - /* CRTP 子图能力:提交本 Visual 最近一次 Prepare 生成的唯一产物。 */ template - [[nodiscard]] Task_Graph build_paint_graph(Object* object, const Prop& prop); - /* CRTP 覆盖:绑定 Scene 后每帧执行 Paint;dirty 只表示 Prepared 数据是否更新,不控制帧生成。 */ + [[nodiscard]] Task_Graph build_graph(Object* object, const Prop& prop); template - [[nodiscard]] bool should_paint(Object* object, const State& state, bool dirty); + [[nodiscard]] bool should_render(Object* object, const State& state, bool dirty); /* CRTP Prop hook:本层字段改变时标记 Prepare。 */ template void after_prop_set(Object* object, Member Owner::* member, Prop_Access props); @@ -67,9 +65,12 @@ void Basic_Visual::Private::prepare_data(Object* object) { } template template -Task_Graph Basic_Visual::Private::build_paint_graph(Object* object, const Prop&) { - Task_Graph graph{"render_3d.visual.paint"}; - graph.add("publish", [this, object] { +Task_Graph Basic_Visual::Private::build_graph(Object* object, const Prop&) { + Task_Graph graph{"render_3d.visual"}; + graph.add("prepare_and_publish", [this, object] { + if (double_buffer::detail::Internal_Access:: + dirty(object)) + prepare_data(object); if (auto context = paint_target.context.lock(); context && paint_target.enqueue) { paint_target.enqueue(context.get(), object, prepared); } @@ -78,13 +79,13 @@ Task_Graph Basic_Visual::Private::build_paint_graph(Object* object, const } template template -bool Basic_Visual::Private::should_paint(Object*, const State&, bool dirty) { - return dirty && paint_target.enqueue != nullptr && !paint_target.context.expired(); +bool Basic_Visual::Private::should_render(Object*, const State&, bool) { + return paint_target.enqueue != nullptr && !paint_target.context.expired(); } template template void Basic_Visual::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access) { if constexpr (std::same_as) - double_buffer::detail::Internal_Access::mark_dirty(object); + double_buffer::detail::Internal_Access::mark_dirty(object); } } diff --git a/web_server/src/Gallery_Plots_2D.cpp b/web_server/src/Gallery_Plots_2D.cpp index 8b117b8..0216d16 100644 --- a/web_server/src/Gallery_Plots_2D.cpp +++ b/web_server/src/Gallery_Plots_2D.cpp @@ -89,16 +89,11 @@ std::unique_ptr make_renderable_component( using State = typename Definition::State; using Adapter = detail::Renderable_Adapter, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field, - detail::State_Field >; + detail::State_Field, + detail::State_Field, + detail::State_Field, + detail::State_Field, + detail::State_Field >; return detail::make_renderable_descriptor(std::move(id), std::move(label), std::move(kind), Adapter{object}); } template @@ -309,7 +304,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { std::vector values(count); generate_spectral_row(values, animation_row, generator_count(input, "signal_count", 256), minimum, maximum, generator_number(input, "noise_stddev"), engine); object.template pending_buffer() = Spectrum_Frame{std::move(values)}; - object.template mark_dirty(); + object.template mark_dirty(); generated_count = count; } else if constexpr (std::same_as) { @@ -320,7 +315,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { std::vector samples(count); for (std::size_t index = 0; index < count; ++index) samples[index] = {static_cast(index * tick_step), distribution(engine)}; for (const auto& sample : samples) object.template submit_stream(sample); - object.template mark_dirty(); + object.template mark_dirty(); generated_count = count; } else if constexpr (std::same_as) { @@ -338,7 +333,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { object.template submit_stream( std::make_shared( Sweep_Spectrum_Block{block_index, std::move(blocks[block_index])})); - object.template mark_dirty(); + object.template mark_dirty(); generated_count = block_count * width; } else if constexpr (std::same_as) { @@ -353,7 +348,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { for (auto& spectrum : spectra) object.template submit_stream( std::make_shared>(std::move(spectrum))); - object.template mark_dirty(); + object.template mark_dirty(); generated_count = row_count * width; } else if constexpr (std::same_as) { @@ -374,7 +369,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { for (auto& row : rows) object.template submit_stream( std::make_shared(std::move(row))); - object.template mark_dirty(); + object.template mark_dirty(); generated_count = row_count * width; } else if constexpr (std::same_as) { @@ -386,7 +381,7 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) { const auto submitted = monotonic_milliseconds(); for (std::size_t index = 0; index < count; ++index) points[index] = {{i_distribution(engine), q_distribution(engine)}, submitted}; for (const auto& point : points) object.template submit_stream(point); - object.template mark_dirty(); + object.template mark_dirty(); generated_count = count; } else if constexpr (std::same_as) { @@ -544,12 +539,9 @@ std::shared_ptr make_axes_plot() { .set(&Render_Scene_2D::Prop::viewport, canvas) .set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255}) .set(&Render_Scene_2D::Prop::view_active, true) - .add_dependency_node(frequency.get()) - .add_dependency_node(numeric.get()) - .add_dependency_node(time.get()) - .add_dependency_node(frequency.get()) - .add_dependency_node(numeric.get()) - .add_dependency_node(time.get()) + .add_dependency_node(frequency.get()) + .add_dependency_node(numeric.get()) + .add_dependency_node(time.get()) .build(); auto update = [scene = scene.get(), frequency = frequency.get(), numeric = numeric.get(), time = time.get()](const Plot_Render_Tick& event, bool) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, numeric, time); @@ -617,7 +609,7 @@ std::shared_ptr make_spectrum_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(spectrum.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), spectrum.get()) + .add_dependency(selection.get(), spectrum.get()) .build(); auto update = [scene = scene.get(), raw = spectrum.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); @@ -631,7 +623,7 @@ std::shared_ptr make_spectrum_plot() { } raw->template pending_buffer() = Spectrum_Frame{std::vector(samples.begin(), samples.end())}; - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Spectrum::Prop::center_frequency, "center_frequency", "Frequency placed at the visual center of the spectrum axis.">, @@ -677,7 +669,7 @@ std::shared_ptr make_frequency_trace_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(trace.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), trace.get()) + .add_dependency(selection.get(), trace.get()) .build(); auto update = [scene = scene.get(), raw = trace.get(), time = time.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, time, vertical); @@ -691,7 +683,7 @@ std::shared_ptr make_frequency_trace_plot() { tick, std::sin(event.time_milliseconds * 0.0025) * 0.8 + std::sin(event.time_milliseconds * 0.0007) * 0.2 }); - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Frequency_Trace::Prop::partition_count, "partition_count", "Number of partitions used to prepare the time-ordered trace.">, @@ -716,7 +708,7 @@ std::shared_ptr make_sweep_spectrum_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(sweep.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), sweep.get()) + .add_dependency(selection.get(), sweep.get()) .build(); auto update = [scene = scene.get(), raw = sweep.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); @@ -733,7 +725,7 @@ std::shared_ptr make_sweep_spectrum_plot() { raw->template submit_stream( std::make_shared( Sweep_Spectrum_Block{block_index, std::move(values)})); - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Sweep_Spectrum::Prop::bins_per_block, "bins_per_block", "Number of frequency bins stored in each incoming sweep block.">, @@ -765,7 +757,7 @@ std::shared_ptr make_afterglow_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(afterglow.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), afterglow.get()) + .add_dependency(selection.get(), afterglow.get()) .build(); auto update = [scene = scene.get(), raw = afterglow.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, vertical); @@ -777,7 +769,7 @@ std::shared_ptr make_afterglow_plot() { - 0.18 * std::sin(event.time_milliseconds * 0.0008), 2.0)); raw->template submit_stream( std::make_shared>(values.begin(), values.end())); - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Afterglow::Prop::frequency_point_size, "frequency_point_size", "Number of frequency cells allocated across each afterglow row.">, @@ -808,7 +800,7 @@ std::shared_ptr make_waterfall_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(waterfall.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), waterfall.get()) + .add_dependency(selection.get(), waterfall.get()) .build(); auto update = [scene = scene.get(), raw = waterfall.get(), frequency = frequency.get(), time = time.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, frequency, time); @@ -827,7 +819,7 @@ std::shared_ptr make_waterfall_plot() { std::make_shared(Waterfall_Row{ tick, {values.begin(), values.end()} })); - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Waterfall::Prop::tooltip_enabled, "tooltip_enabled", "Enables value inspection tooltips over waterfall cells.">, @@ -862,7 +854,7 @@ std::shared_ptr make_constellation_plot() { .set(&Render_Scene_2D::Prop::view_active, true) .add_renderable(constellation.get()) .add_renderable(selection.get()) - .add_dependency(selection.get(), constellation.get()) + .add_dependency(selection.get(), constellation.get()) .build(); auto update = [scene = scene.get(), raw = constellation.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Render_Tick& event, bool demo_data) { resize_axes(scene, {static_cast(event.width), static_cast(event.height)}, horizontal, vertical); @@ -886,7 +878,7 @@ std::shared_ptr make_constellation_plot() { monotonic_milliseconds() }); } - raw->template mark_dirty(); + raw->template mark_dirty(); }; auto view = make_scene_view< Prop_Field<&Constellation_Diagram::Prop::partition_count, "partition_count", "Number of partitions used to render received constellation points.">, diff --git a/web_server/src/Renderable_Adapter.ipp b/web_server/src/Renderable_Adapter.ipp index 3fc3542..e37223f 100644 --- a/web_server/src/Renderable_Adapter.ipp +++ b/web_server/src/Renderable_Adapter.ipp @@ -287,11 +287,11 @@ inline std::string_view protocol_field_label(std::string_view key) { {"selected_regions", "已选区域"}, {"cache_enabled", "启用绘制缓存"}, {"transform", "空间变换"}, {"visible", "是否可见"}, {"depth_test", "深度测试"}, {"items", "项目数据"}, - {"prepare_dirty", "准备阶段待更新"}, {"paint_dirty", "绘制阶段待更新"}, - {"prepare_executed", "准备阶段已执行"}, {"paint_executed", "绘制阶段已执行"}, - {"prepare_graph_rebuilt", "准备任务图已重建"}, {"paint_graph_rebuilt", "绘制任务图已重建"}, - {"prepare_task_count", "准备任务数量"}, {"paint_task_count", "绘制任务数量"}, - {"prepare_execution_time_ns", "准备耗时"}, {"paint_execution_time_ns", "绘制耗时"}, + {"render_dirty", "渲染图待更新"}, + {"render_executed", "渲染图已执行"}, + {"graph_rebuilt", "渲染图已重建"}, + {"task_count", "任务数量"}, + {"execution_time_ns", "渲染耗时"}, {"taskflow_rebuilt", "场景任务图已重建"}, {"renderable_count", "可渲染对象数量"}, {"taskflow_task_count", "场景任务数量"}, {"taskflow_dependency_count", "任务依赖数量"}, {"taskflow_max_predecessors", "最大前驱数量"}, {"taskflow_max_successors", "最大后继数量"},