diff --git a/kernel/kernel/include/model/attachment/dag/Dag_Relation.hpp b/kernel/kernel/include/model/attachment/dag/Dag_Relation.hpp index c21fb8a..5bcf622 100644 --- a/kernel/kernel/include/model/attachment/dag/Dag_Relation.hpp +++ b/kernel/kernel/include/model/attachment/dag/Dag_Relation.hpp @@ -8,6 +8,18 @@ #include #include namespace aethera { +namespace detail { +template +struct Dag_Node_Facade { + using Type = Tag; +}; + +template +struct Dag_Node_Facade>> { + using Type = typename Tag::template Relation_Node_Facade; +}; +} // namespace detail + struct Dag_Node_Id { std::uint64_t value{}; /* 一张 DAG 内的稳定节点标识;0 表示未分配。 */ }; @@ -25,6 +37,7 @@ enum struct Dag_Result : std::uint8_t { /* 拥有节点 proxy 和实例级依赖边的完整 DAG 定义;proxy 的实际持有对象由使用方决定。 */ template struct Dag_Relation { + using Node = Node_Facade; struct Builder; struct Editor; Dag_Relation(); @@ -82,8 +95,8 @@ struct Dag_Relation::Editor { /* Model 可注册的拥有型 DAG 关系;内部 commit 在所有者安全点验证并提交待处理事务。 */ template struct Owned_Dag_Relation : detail::Relation_Definition, Node_Facade> { - template - using Attachment = Owned_Dag_Relation; + template + using Attachment = Owned_Dag_Relation::Type>; using Graph = Dag_Relation; using Edit = std::move_only_function; using Edit_Completion = std::move_only_function)>; diff --git a/kernel/kernel/include/model/attachment/design.md b/kernel/kernel/include/model/attachment/design.md index 056f0a4..86347a1 100644 --- a/kernel/kernel/include/model/attachment/design.md +++ b/kernel/kernel/include/model/attachment/design.md @@ -24,6 +24,6 @@ Model 的可组合能力统一分为两层: ## 关系附件契约 -关系附件必须提供权威 `Graph`、事务 `Edit`、事务完成回调,以及内部 `relation()`、`commit()`、`build(...)`。`Owned_Dag_Relation` 自身是一段式定义和实际附件,向最终 endpoint 选择性附加实体 `edit`、Builder `relation` 和 Private `relation/commit`。 +关系附件必须提供权威 `Graph`、事务 `Edit`、事务完成回调,以及内部 `relation()`、`commit()`、`build(...)`。`Owned_Dag_Relation` 自身是一段式定义和实际附件,向最终 endpoint 选择性附加实体 `edit`、Builder `relation` 和 Private `relation/commit`。Tag 默认同时是节点 facade;Tag 可以用 `Relation_Node_Facade` 为最终 endpoint 选择更窄的依赖 facade,但关系仍由原 Tag 唯一标识,禁止派生层再注册第二张同语义关系图。 `Dag_Relation::Builder` 在构建期验证完整关系;`Dag_Relation::Editor` 只记录单次事务命令;`Owned_Dag_Relation` 由关系所有者在安全点验证并提交。编辑 completion 在调用 `commit()` 的内部线程执行,不是跨线程异常传播边界。 diff --git a/kernel/kernel/include/model/design.md b/kernel/kernel/include/model/design.md index 93147ed..e55b30e 100644 --- a/kernel/kernel/include/model/design.md +++ b/kernel/kernel/include/model/design.md @@ -32,6 +32,6 @@ Root 的类型擦除 state 操作表保存一份 Private 与附件解析器,De 分别承载独占、共享和非拥有生命周期,同时把 proxy 解引用目标映射到 Model 的唯一 Private;实体公开类不实现 facade 值转发。 具体类型自身就是一段式附件定义。例如,并发结构使用 `Import_Struct_Concurrent`,拥有型 DAG 使用 -`Owned_Dag_Relation`。业务定义不再套额外的二段式注册包装。一个并发定义可连续绑定多个 +`Owned_Dag_Relation`。Tag 默认也作为节点 facade;如果 Tag 定义 `Relation_Node_Facade`,则最终 endpoint 只物化一份关系状态,并按最终类型选择节点 facade。业务定义不再套额外的二段式注册包装。一个并发定义可连续绑定多个 Tag;它继承 `Concurrent_Definition`,并在内部直接定义最终 endpoint 所物化的 `Attachment`。Builder 和实体只写 Tag,例如 `set(...)`;具体附件及其存储值由该定义和最终 endpoint 唯一确定。 diff --git a/kernel/kernel/module/scene/rely_facade.h b/kernel/kernel/module/scene/rely_facade.h index a50dd70..4683c09 100644 --- a/kernel/kernel/module/scene/rely_facade.h +++ b/kernel/kernel/module/scene/rely_facade.h @@ -12,5 +12,8 @@ struct Renderable : facade_builder ::add_convention<_event_routing_distance, std::optional(const Event&) const noexcept> ::add_convention<_dispatch_event, void(Event &)> ::support_relocation -::build {}; +::build { + template + using Relation_Node_Facade = typename Endpoint::Scene_Renderable_Facade; +}; } // namespace aethera::scene diff --git a/kernel/kernel/module/scene/src/Scene.cpp b/kernel/kernel/module/scene/src/Scene.cpp index dcd9888..31861b7 100644 --- a/kernel/kernel/module/scene/src/Scene.cpp +++ b/kernel/kernel/module/scene/src/Scene.cpp @@ -62,7 +62,6 @@ void Scene::Private::advance_renderables() { renderable->advance(); }); } - scene::Submit_Event_Result Scene::Private::submit_event(std::unique_ptr event) { if (!event) return scene::Submit_Event_Result::event_missing; event_collector.submit(std::move(event)); diff --git a/kernel/kernel/module/scene/src/Scene.hpp b/kernel/kernel/module/scene/src/Scene.hpp index e51f72e..adc7c11 100644 --- a/kernel/kernel/module/scene/src/Scene.hpp +++ b/kernel/kernel/module/scene/src/Scene.hpp @@ -9,6 +9,7 @@ namespace aethera { /* Scene 通过 Tag + Owned_Dag_Relation 在类型系统中表达其对 Renderable DAG 的唯一所有权。 */ struct Scene : Def> { + using Scene_Renderable_Facade = scene::Renderable; struct Private; Scene(); ~Scene() noexcept; diff --git a/kernel/kernel/test/model/attachment/dag/Dag_Relation_Tests.cpp b/kernel/kernel/test/model/attachment/dag/Dag_Relation_Tests.cpp index cf5b486..27d5c82 100644 --- a/kernel/kernel/test/model/attachment/dag/Dag_Relation_Tests.cpp +++ b/kernel/kernel/test/model/attachment/dag/Dag_Relation_Tests.cpp @@ -2,6 +2,7 @@ #include "model/attachment/dag/Dag_Relation.hpp" #include #include +#include namespace { struct Test_Relation_Node : aethera::facade_builder ::support_relocation @@ -9,6 +10,23 @@ struct Test_Relation_Node : aethera::facade_builder struct Test_Relation_Model : aethera::Def> { struct Private : Prev_Private {}; }; +struct Test_Adaptive_Relation_Tag : aethera::facade_builder +::support_relocation +::build { + template + using Relation_Node_Facade = typename Endpoint::Selected_Relation_Node_Facade; +}; +struct Test_Narrow_Relation_Node : aethera::facade_builder +::support_relocation +::build {}; +struct Test_Adaptive_Relation_Base : aethera::Def> { + using Selected_Relation_Node_Facade = Test_Adaptive_Relation_Tag; + struct Private : Prev_Private {}; +}; +struct Test_Adaptive_Relation_Model : aethera::Def { + using Selected_Relation_Node_Facade = Test_Narrow_Relation_Node; + struct Private : Prev_Private {}; +}; template concept Exposes_Concurrent_Model_API = requires(Model& model) { model.template set(0); }; template @@ -19,6 +37,7 @@ static_assert(std::same_as); static_assert(!Exposes_Concurrent_Model_API); static_assert(!Exposes_Concurrent_Builder_API); +static_assert(std::same_as, aethera::Owned_Dag_Relation>); TEST(Dag_Relation, Registration_Composes_Only_Relation_APIs) { bool initialized{}; Test_Relation_Model::Builder builder; @@ -42,4 +61,16 @@ TEST(Dag_Relation, Registration_Composes_Only_Relation_APIs) { aethera::detail::model_private(*model).relation().for_each_topological([&](auto, auto&) { ++nodes; }); EXPECT_EQ(nodes, 0); } +TEST(Dag_Relation, Inherited_Tag_Selects_Final_Endpoint_Node_Facade_Without_Second_Relation) { + bool selected{}; + Test_Adaptive_Relation_Model::Builder builder; + builder + .relation([&](auto& relation) { + static_assert(std::same_as::Node, Test_Narrow_Relation_Node>); + selected = true; + }); + auto model = builder.build(); + EXPECT_TRUE(model); + EXPECT_TRUE(selected); +} } // namespace diff --git a/kernel/使用规范.md b/kernel/使用规范.md index dfd6798..e6bfe7c 100644 --- a/kernel/使用规范.md +++ b/kernel/使用规范.md @@ -2,7 +2,7 @@ - 每个 Model 定义层的 `Prop`、`State` 等值类型必须定义在所属类内并继承 `Prev`,使同一 Tag 的值结构沿 Model 层级形成继承链。 - 每层统一使用 `Def<当前层, Base, Definitions...>`;中间类是普通类,不需要模板化,也不区分末尾层。 -- 一段式附件定义写在所属 `Def` 的模板参数上。并发结构直接使用 `Import_Struct_Concurrent` 等具体类型,关系结构直接使用 `Owned_Dag_Relation`。 +- 一段式附件定义写在所属 `Def` 的模板参数上。并发结构直接使用 `Import_Struct_Concurrent` 等具体类型,关系结构直接使用 `Owned_Dag_Relation`。Tag 默认同时是节点 facade;需要让派生 endpoint 使用模块专用 facade 时,由 Tag 的 `Relation_Node_Facade` 选择实际节点 facade,派生层不得重复注册同一语义的关系状态。 - 同一个注册组可以连续登记多个 Tag;相同 Tag 在完整继承链只能注册一次。 - 每个具体附件类型同时声明 Tag、能力来源和实际 `Attachment`。完整附件项链按 Builder 正在构建的最终 endpoint 求值,并由公共 `Root` 一次物化唯一的 `Private + Model_Attachment_Set`。 - Builder 只拥有一个最终 endpoint 对象槽;被继承的 Def 层不得创建自己的 endpoint state、对象副本、附件或 Private 指针。Private 解析器只随 Root 的唯一类型擦除 state 保存一份,定义层通过零状态 `model_private(model)` 选择自身的类型化视图。 diff --git a/mcp/core/runtime/Gallery_Plots_2D.cpp b/mcp/core/runtime/Gallery_Plots_2D.cpp index 352dc6b..1df5e45 100644 --- a/mcp/core/runtime/Gallery_Plots_2D.cpp +++ b/mcp/core/runtime/Gallery_Plots_2D.cpp @@ -531,7 +531,7 @@ std::unique_ptr make_scene_2d(Size canvas, std::optional(&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) - .relation([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); }); + .relation([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); }); return scene_builder.build(); } } diff --git a/mcp/core/runtime/Gallery_Plots_3D.cpp b/mcp/core/runtime/Gallery_Plots_3D.cpp index a892ae4..9e5ec0d 100644 --- a/mcp/core/runtime/Gallery_Plots_3D.cpp +++ b/mcp/core/runtime/Gallery_Plots_3D.cpp @@ -511,7 +511,7 @@ Gallery_Build make_visual_plot(std::string label, std::unique_ptr vi .set(&Render_Scene_3D::Prop::viewport, Extent{720, 420}) .set(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F}) .set(&Render_Scene_3D::Prop::view_active, true) - .relation([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); }); + .relation([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); }); auto scene = scene_builder.build(); auto view = std::make_unique>( *scene, std::move(camera), std::move(axes), std::move(visual), diff --git a/render_2D/render_2D/module/scene/Render_Scene_2D.cpp b/render_2D/render_2D/module/scene/Render_Scene_2D.cpp index 04cb54d..81e416a 100644 --- a/render_2D/render_2D/module/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/module/scene/Render_Scene_2D.cpp @@ -12,7 +12,7 @@ Render_Scene_2D::Render_Scene_2D() = default; Render_Scene_2D::~Render_Scene_2D() noexcept = default; Render_Scene_2D::Private::Build_Result Render_Scene_2D::Private::build_frame(Blend2D_Cache& target, Size viewport, Color background) { - auto& renderables = relation(); + auto& renderables = relation(); renderables.for_each_topological([&](Dag_Node_Id, proxy& renderable) { renderable->render(target); }); @@ -56,7 +56,7 @@ Render_Scene_2D::Private::Build_Result Render_Scene_2D::Private::build_frame(Ble } void Render_Scene_2D::Private::dispatch_events(Event_Batch& events) { - auto& renderables = relation(); + auto& renderables = relation(); aethera::detail::dispatch_scene_events(events, renderables); } @@ -65,7 +65,11 @@ Render_Scene_2D_Result Render_Scene_2D::Private::render(Frame_2D* frame, Complet if (!completion) return std::unexpected(Render_Scene_2D_Error{Render_Scene_2D_State_Result::completion_missing}); auto properties = concurrent(); properties.internal.advance(); - advance_renderables(); + commit(); + auto& renderables = relation(); + renderables.for_each_topological([](Dag_Node_Id, proxy& renderable) { + renderable->advance(); + }); const Prop& current = *properties.internal.use(); if (!current.view_active) return std::unexpected(Render_Scene_2D_Error{Render_Scene_2D_State_Result::view_inactive}); if (current.viewport.empty()) return std::unexpected(Render_Scene_2D_Error{Render_Scene_2D_State_Result::empty_viewport}); diff --git a/render_2D/render_2D/module/scene/Render_Scene_2D.hpp b/render_2D/render_2D/module/scene/Render_Scene_2D.hpp index c2bf5ba..09200b2 100644 --- a/render_2D/render_2D/module/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/module/scene/Render_Scene_2D.hpp @@ -10,7 +10,8 @@ #include "scene/src/Scene.hpp" namespace aethera::render_2d { /* 执行二维 Renderable DAG 并向调用方拥有的物理帧合成像素。 */ -struct Render_Scene_2D : Def, Owned_Dag_Relation> { +struct Render_Scene_2D : Def> { + using Scene_Renderable_Facade = scene::Renderable; struct Prop : Prev { Size viewport{}; /* 最终帧像素尺寸;空尺寸不提交渲染。 */ Color background{Color::black()}; /* 每帧绘制 Renderable 前写入的背景颜色。 */ diff --git a/render_2D/tests/Async_Render_Contract_Test.cpp b/render_2D/tests/Async_Render_Contract_Test.cpp index c885668..e3c820b 100644 --- a/render_2D/tests/Async_Render_Contract_Test.cpp +++ b/render_2D/tests/Async_Render_Contract_Test.cpp @@ -52,7 +52,7 @@ int main() { .set(&Render_Scene_2D::Prop::viewport, Size{120, 100}) .set(&Render_Scene_2D::Prop::background, Color::transparent()) .set(&Render_Scene_2D::Prop::view_active, true) - .relation([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); }); + .relation([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); }); auto* scene = scene_builder.build().release(); /* completion 前保持存活;随后随进程退出回收。 */ auto* frame = new Frame_2D{{1, 0}}; /* completion 前保持存活;随后随进程退出回收。 */ const auto submitted = scene->render(frame, [frame](Frame_2D* completed, std::exception_ptr failure) { diff --git a/render_2D/tests/Model_Adaptation_Test.cpp b/render_2D/tests/Model_Adaptation_Test.cpp index ad048a3..cf0f30e 100644 --- a/render_2D/tests/Model_Adaptation_Test.cpp +++ b/render_2D/tests/Model_Adaptation_Test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace { struct Test_Renderable_2D : aethera::Def { struct Private : Prev_Private {}; @@ -24,8 +25,13 @@ TEST(Model_Adaptation, Scene_Owns_The_Only_Renderable_Dag) { ASSERT_TRUE(dag.has_value()); aethera::render_2d::Render_Scene_2D::Builder scene_builder; scene_builder - .relation([dag = std::move(*dag)](auto& relation) mutable { relation = std::move(dag); }); + .relation([dag = std::move(*dag)](auto& relation) mutable { relation = std::move(dag); }); auto scene = scene_builder.build(); EXPECT_TRUE(scene->model_initialized()); + auto& relation = aethera::detail::model_private(*scene).relation(); + static_assert(std::same_as::Node, aethera::render_2d::scene::Renderable>); + std::size_t node_count{}; + relation.for_each_topological([&](auto, auto&) { ++node_count; }); + EXPECT_EQ(node_count, 1U); } } // namespace diff --git a/render_3D/render_3D/detail/Backend_Types.hpp b/render_3D/render_3D/detail/Backend_Types.hpp index 0b94c2a..8efd48b 100644 --- a/render_3D/render_3D/detail/Backend_Types.hpp +++ b/render_3D/render_3D/detail/Backend_Types.hpp @@ -20,7 +20,6 @@ struct Prepared_Visual_Instance { }; using Prepared_Visual_Batch = std::vector; -using Shared_Prepared_Visual_Batch = std::shared_ptr; [[nodiscard]] bool prepared_visual_batch_complete(const Prepared_Visual_Batch& visuals) noexcept; struct Scene_3D_Parameters { diff --git a/render_3D/render_3D/scene/Render_Scene_3D.hpp b/render_3D/render_3D/scene/Render_Scene_3D.hpp index 79d9dba..b60c168 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.hpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.hpp @@ -12,10 +12,12 @@ #include namespace aethera::render_3d { /* 推进并执行一棵 Renderable DAG;物理帧池与槽位阶段完全由 Frame Policy 管理。 */ -struct Render_Scene_3D : Def, Owned_Dag_Relation> { +struct Render_Scene_3D : Def> { + using Scene_Renderable_Facade = scene::Renderable; /* * Camera_3D 和 Axes_3D 是非拥有借用,必须覆盖 Scene 生命周期。 * 成功提交后,Scene 与传入的 Frame_3D 必须覆盖 completion 返回前的全部在途工作。 + * 同一 Scene 的 render() 由 Frame Policy 串行调用;直接调用者也必须维持单帧渲染在途。 * 析构不会等待、取消或脱离在途工作。 */ struct Prop : Prev { diff --git a/render_3D/render_3D/scene/Render_Scene_3D.ipp b/render_3D/render_3D/scene/Render_Scene_3D.ipp index 79db952..caf984a 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.ipp +++ b/render_3D/render_3D/scene/Render_Scene_3D.ipp @@ -99,7 +99,7 @@ struct Scene_Datoviz_State { void abandon_resources() noexcept; void destroy(); private: - struct Runtime_Slot { + struct Backend_Target_Runtime { DvzDrp2Runtime* runtime{}; /* 本目标槽独占的 DRP2 Runtime。 */ DvzFramePlanEmitter* emitter{}; /* 本目标槽独占的帧计划 emitter。 */ }; @@ -107,7 +107,7 @@ private: std::shared_ptr render_context_{}; /* 按 GPU 索引与验证模式共享的进程级上下文。 */ std::uintptr_t command_pool_{}; /* 本 Scene 独占 Vulkan Command Pool。 */ std::uintptr_t descriptor_pool_{}; /* 本 Scene 独占 Vulkan Descriptor Pool。 */ - std::array runtime_slots_{}; /* 三个固定目标槽的 Runtime。 */ + std::array backend_target_runtimes_{}; /* 仅对应三个 Datoviz/Vulkan 后端目标,不是 Frame Policy 槽。 */ DvzScene* scene_{}; /* Datoviz Scene 所有权。 */ DvzFigure* figure_{}; /* 可空、非拥有;由 scene_ 拥有。 */ DvzPanel* panel_{}; /* 可空、非拥有;由 scene_ 拥有。 */ diff --git a/render_3D/render_3D/scene/Render_Scene_3D_Model.ipp b/render_3D/render_3D/scene/Render_Scene_3D_Model.ipp index f9a56a2..8cb4b83 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D_Model.ipp +++ b/render_3D/render_3D/scene/Render_Scene_3D_Model.ipp @@ -17,8 +17,8 @@ namespace aethera::render_3d { namespace detail { struct Scene_Paint_Context final { - Scene_3D_Parameters parameters{}; /* 褰撳墠 CPU Prepare 鍞竴璇诲彇鐨?Scene 鍙傛暟銆?*/ - std::shared_ptr visuals{std::make_shared()}; /* 鍚?Visual 鑺傜偣鍙啓鑷繁鐨勯鍒嗛厤妲戒綅銆?*/ + Scene_3D_Parameters parameters{}; /* 当前 CPU Prepare 唯一读取的 Scene 参数。 */ + Prepared_Visual_Batch visuals{}; /* 各 Visual 节点只写自己的预分配位置。 */ }; inline float wheel_step(double pixel, double angle) { @@ -35,8 +35,8 @@ inline Mouse_Button held_button(Mouse_Button_Mask buttons) { inline void publish_visual(void* raw_context, std::size_t slot, const Prepared_Visual& prepared) { if (!raw_context) throw std::logic_error("3D Visual lost its Scene paint context"); auto& context = *static_cast(raw_context); - if (!context.visuals || slot >= context.visuals->size()) throw std::logic_error("3D Visual published outside its Scene slot"); - (*context.visuals)[slot].visual = prepared; + if (slot >= context.visuals.size()) throw std::logic_error("3D Visual published outside its Scene slot"); + context.visuals[slot].visual = prepared; } inline void publish_datoviz_observation(Frame_3D* frame, Datoviz_Frame_Observation observation) { @@ -64,7 +64,8 @@ inline void publish_datoviz_observation(Frame_3D* frame, Datoviz_Frame_Observati } // namespace detail struct Render_Scene_3D::Private : Prev_Private, private detail::Scene_Datoviz_State { - struct Frame_Execution final { + /* 一次 render() 的异步执行租约;不保存、选择或复用 Frame Policy 槽位。 */ + struct Render_Execution final { Frame_3D* frame{}; /* 必填、非拥有借用;由 Frame Policy 保持至 completion 返回。 */ Completion completion{}; std::atomic_bool cpu_finished{}; @@ -80,7 +81,7 @@ struct Render_Scene_3D::Private : Prev_Private, private detail::Scene_Datoviz_St std::shared_ptr paint_context{}; }; - using Shared_Frame_Execution = std::shared_ptr; + using Shared_Render_Execution = std::shared_ptr; Private(Camera_3D& camera_value, Axes_3D& axes_value, std::uint32_t gpu_index_value = 0, bool validation_enabled_value = false); ~Private() override = default; @@ -97,14 +98,14 @@ private: [[nodiscard]] const Prop& advance_scene(); [[nodiscard]] detail::Scene_3D_Parameters current_parameters(const Prop& properties); void initialize_or_validate_backend(const detail::Scene_3D_Parameters& parameters); - [[nodiscard]] proxy build_frame_graph(const Shared_Frame_Execution& execution, const detail::Scene_3D_Parameters& parameters); + [[nodiscard]] proxy build_frame_graph(const Shared_Render_Execution& execution, const detail::Scene_3D_Parameters& parameters); void dispatch_events(Event_Batch& events) override; void dispatch_datoviz(Event& event, Extent viewport); - void render_datoviz(const Shared_Frame_Execution& execution, detail::Shared_Prepared_Visual_Batch visuals, detail::Scene_3D_Parameters parameters) noexcept; - void observe_datoviz_completion(const Shared_Frame_Execution& execution, std::optional result, std::exception_ptr failure) noexcept; - void try_complete(const Shared_Frame_Execution& execution) noexcept; - [[nodiscard]] std::exception_ptr collect_datoviz(Frame_Execution& execution) noexcept; - static void complete_execution(Shared_Frame_Execution execution, std::exception_ptr runtime_failure); + void render_datoviz(const Shared_Render_Execution& execution, const detail::Prepared_Visual_Batch& visuals, detail::Scene_3D_Parameters parameters) noexcept; + void observe_datoviz_completion(const Shared_Render_Execution& execution, std::optional result, std::exception_ptr failure) noexcept; + void try_complete(const Shared_Render_Execution& execution) noexcept; + [[nodiscard]] std::exception_ptr collect_datoviz(Render_Execution& execution) noexcept; + static void complete_execution(Shared_Render_Execution execution, std::exception_ptr runtime_failure); void fail_datoviz(std::exception_ptr failure) noexcept; }; @@ -115,7 +116,11 @@ inline const Render_Scene_3D::Prop& Render_Scene_3D::Private::advance_scene() { properties.internal.advance(); camera->advance(); axes->advance(); - advance_renderables(); + commit(); + auto& renderables = relation(); + renderables.for_each_topological([](Dag_Node_Id, proxy& renderable) { + renderable->advance(); + }); return static_cast(*properties.internal.use()); } @@ -126,7 +131,7 @@ inline detail::Scene_3D_Parameters Render_Scene_3D::Private::current_parameters( inline void Render_Scene_3D::Private::initialize_or_validate_backend(const detail::Scene_3D_Parameters& parameters) { std::vector current; - auto& renderables = relation(); + auto& renderables = relation(); renderables.for_each_topological([&](Dag_Node_Id id, proxy& renderable) { current.push_back({static_cast(id.value), std::addressof(renderable->use())}); }); @@ -143,11 +148,11 @@ inline void Render_Scene_3D::Private::initialize_or_validate_backend(const detai backend_initialized = true; } -inline proxy Render_Scene_3D::Private::build_frame_graph(const Shared_Frame_Execution& execution, const detail::Scene_3D_Parameters& parameters) { +inline proxy Render_Scene_3D::Private::build_frame_graph(const Shared_Render_Execution& execution, const detail::Scene_3D_Parameters& parameters) { execution->paint_context = std::make_shared(); execution->paint_context->parameters = parameters; - execution->paint_context->visuals->reserve(registrations.size()); - for (const auto& registration : registrations) execution->paint_context->visuals->push_back({registration.identity, {}}); + execution->paint_context->visuals.reserve(registrations.size()); + for (const auto& registration : registrations) execution->paint_context->visuals.push_back({registration.identity, {}}); auto graph = scene::make_task_graph("render_3d.frame"); auto begin = graph->add("scene.begin", [execution] { execution->frame->mark(Frame_Trace_Marker::scene_render_started); @@ -156,7 +161,7 @@ inline proxy Render_Scene_3D::Private::build_frame_graph(cons execution->frame->mark(Frame_Trace_Marker::paint_started); }); if (!begin) throw std::logic_error("failed to create 3D frame setup task"); - auto& renderables = relation(); + auto& renderables = relation(); std::unordered_map> nodes; std::size_t slot{}; renderables.for_each_topological([&](Dag_Node_Id id, proxy& renderable) { @@ -171,7 +176,7 @@ inline proxy Render_Scene_3D::Private::build_frame_graph(cons if (graph->precede(nodes.at(source.value), nodes.at(target.value)) != Precede_Task_Graph_Result::preceded) throw std::logic_error("failed to preserve a 3D Renderable DAG dependency"); }); auto backend = graph->add("scene.backend.prepare", [this, execution, paint = execution->paint_context] { - if (!detail::prepared_visual_batch_complete(*paint->visuals)) throw std::logic_error("3D Scene published an incomplete Visual batch"); + if (!detail::prepared_visual_batch_complete(paint->visuals)) throw std::logic_error("3D Scene published an incomplete Visual batch"); render_datoviz(execution, paint->visuals, paint->parameters); }); if (!backend) throw std::logic_error("failed to create 3D backend task"); @@ -181,7 +186,8 @@ inline proxy Render_Scene_3D::Private::build_frame_graph(cons } inline void Render_Scene_3D::Private::dispatch_events(Event_Batch& events) { - Prev_Private::dispatch_events(events); + auto& renderables = relation(); + aethera::detail::dispatch_scene_events(events, renderables); const auto& properties = static_cast(*this->template attachment().internal.use()); for (auto& event : events) if (event) dispatch_datoviz(*event, properties.viewport); } @@ -205,10 +211,10 @@ inline void Render_Scene_3D::Private::dispatch_datoviz(Event& event, Extent view if ((wheel && pointer_valid) || pointer_valid || key) event.accept(); } -inline void Render_Scene_3D::Private::render_datoviz(const Shared_Frame_Execution& execution, detail::Shared_Prepared_Visual_Batch visuals, detail::Scene_3D_Parameters parameters) noexcept { +inline void Render_Scene_3D::Private::render_datoviz(const Shared_Render_Execution& execution, const detail::Prepared_Visual_Batch& visuals, detail::Scene_3D_Parameters parameters) noexcept { execution->backend_started.store(true, std::memory_order_release); try { - if (!visuals || visuals->empty()) throw std::invalid_argument("3D Scene requires prepared Visual data"); + if (visuals.empty()) throw std::invalid_argument("3D Scene requires prepared Visual data"); if (!backend_available.load(std::memory_order_acquire)) throw std::runtime_error("3D Scene resources are unavailable"); auto* frame = execution->frame; auto reservation = detail::Gpu_Completion_Service::instance().prepare([this, execution](detail::Gpu_Completion_Service::Result result) { @@ -221,8 +227,8 @@ inline void Render_Scene_3D::Private::render_datoviz(const Shared_Frame_Executio const bool observe = frame->taskflow_trace_requested(); const bool readback = frame->output() == Frame_3D_Output::pixels; frame->mark(Frame_Trace_Marker::backend_prepare_started); - auto prepared = reuse_recorded_target(parameters, *visuals, sequence, observe, readback); - if (!prepared) prepared = prepare(parameters, *visuals, sequence, observe, readback); + auto prepared = reuse_recorded_target(parameters, visuals, sequence, observe, readback); + if (!prepared) prepared = prepare(parameters, visuals, sequence, observe, readback); if (!prepared) throw std::logic_error("Datoviz did not provide a frame target after Scene admission"); frame->mark(Frame_Trace_Marker::backend_prepare_finished); execution->datoviz_frame.emplace(std::move(*prepared)); @@ -265,7 +271,7 @@ inline void Render_Scene_3D::Private::render_datoviz(const Shared_Frame_Executio } } -inline void Render_Scene_3D::Private::observe_datoviz_completion(const Shared_Frame_Execution& execution, std::optional result, std::exception_ptr failure) noexcept { +inline void Render_Scene_3D::Private::observe_datoviz_completion(const Shared_Render_Execution& execution, std::optional result, std::exception_ptr failure) noexcept { execution->frame->mark(Frame_Trace_Marker::gpu_completed); execution->completion_result = std::move(result); execution->backend_failure = std::move(failure); @@ -274,7 +280,7 @@ inline void Render_Scene_3D::Private::observe_datoviz_completion(const Shared_Fr try_complete(execution); } -inline void Render_Scene_3D::Private::try_complete(const Shared_Frame_Execution& execution) noexcept { +inline void Render_Scene_3D::Private::try_complete(const Shared_Render_Execution& execution) noexcept { if (!execution->cpu_finished.load(std::memory_order_acquire) || !execution->backend_finished.load(std::memory_order_acquire)) return; if (execution->completion_started.exchange(true, std::memory_order_acq_rel)) return; try { @@ -293,7 +299,7 @@ inline void Render_Scene_3D::Private::try_complete(const Shared_Frame_Execution& } } -inline std::exception_ptr Render_Scene_3D::Private::collect_datoviz(Frame_Execution& execution) noexcept { +inline std::exception_ptr Render_Scene_3D::Private::collect_datoviz(Render_Execution& execution) noexcept { if (execution.backend_failure) return execution.backend_failure; try { if (!execution.datoviz_frame) throw std::logic_error("3D completion lost its Datoviz target"); @@ -322,7 +328,7 @@ inline std::exception_ptr Render_Scene_3D::Private::collect_datoviz(Frame_Execut } } -inline void Render_Scene_3D::Private::complete_execution(Shared_Frame_Execution execution, std::exception_ptr runtime_failure) { +inline void Render_Scene_3D::Private::complete_execution(Shared_Render_Execution execution, std::exception_ptr runtime_failure) { auto* frame = execution->frame; auto callback = std::move(execution->completion); auto failure = execution->cpu_failure ? std::move(execution->cpu_failure) : std::move(execution->backend_failure); @@ -364,7 +370,7 @@ inline Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Frame_3D* if (!backend_available.load(std::memory_order_acquire)) return Render_Result::backend_unavailable; const auto parameters = current_parameters(properties); initialize_or_validate_backend(parameters); - auto execution = std::make_shared(); + auto execution = std::make_shared(); execution->frame = frame; execution->completion = std::move(completion); auto graph = build_frame_graph(execution, parameters); diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp index ac58fc1..8f6bb0b 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp @@ -41,7 +41,7 @@ void accumulate_execution_timing(const DvzDrp2ExecutionTiming& source, Datoviz_F /* Datoviz Scene 根据当前权威资源生成 DRP2 命令流描述,不提交 GPU。 */ DvzSceneFrameArtifact* Scene_Datoviz_State::emit(const Scene_3D_Parameters& scene, std::uint8_t target_index, DvzSceneFrameEmitTiming& timing) { - if (target_index >= runtime_slots_.size() || runtime_slots_[target_index].emitter == nullptr) throw std::logic_error("Datoviz frame target emitter is unavailable"); + if (target_index >= backend_target_runtimes_.size() || backend_target_runtimes_[target_index].emitter == nullptr) throw std::logic_error("Datoviz frame target emitter is unavailable"); DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config(); configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL; configuration.external_color_target = true; @@ -62,7 +62,7 @@ DvzSceneFrameArtifact* Scene_Datoviz_State::emit(const Scene_3D_Parameters& scen * across the three targets makes target N receive commands that assume target * N-1's resource state. Replaying retained payloads also compensates for the * Scene-wide dirty flags committed by another target's preceding emission. */ - auto* artifact = dvz_figure_emit_frame_with_emitter(figure_, runtime_slots_[target_index].emitter, true, &capabilities, &report, &configuration); + auto* artifact = dvz_figure_emit_frame_with_emitter(figure_, backend_target_runtimes_[target_index].emitter, true, &capabilities, &report, &configuration); if (artifact == nullptr) { std::string message = "failed to emit Datoviz visual frame"; const auto count = dvz_diagnostic_report_count(&report); @@ -171,7 +171,7 @@ std::optional Scene_Datoviz_State::prepare(const Scene_3D bool query_changed_scene{}; phase_started = trace_now_ns(); if (item_interaction_ != nullptr) { - static_cast(dvz_figure_process_queries(figure_, runtime_slots_[*target_index].runtime, nullptr)); + static_cast(dvz_figure_process_queries(figure_, backend_target_runtimes_[*target_index].runtime, nullptr)); DvzQueryResult query{}; bool resolved{}; while (dvz_scene_poll_query(scene_, &query)) resolved = true; @@ -214,7 +214,7 @@ std::optional Scene_Datoviz_State::prepare(const Scene_3D phase_started = trace_now_ns(); const std::uint64_t cpu_started = trace_thread_cpu_ns(); const auto replay_visuals = stale_runtime_visuals(visuals, *target_index); - std::unique_ptr update_stream{dvz_figure_prepare_runtime_resources(figure_, runtime_slots_[*target_index].emitter, replay_visuals.data(), static_cast(replay_visuals.size()), &uploaded_bytes, &command_recording_valid, &report), &dvz_drp2_stream_destroy}; + std::unique_ptr update_stream{dvz_figure_prepare_runtime_resources(figure_, backend_target_runtimes_[*target_index].emitter, replay_visuals.data(), static_cast(replay_visuals.size()), &uploaded_bytes, &command_recording_valid, &report), &dvz_drp2_stream_destroy}; observation.runtime_plan_ns = trace_now_ns() - phase_started; observation.runtime_plan_cpu_ns = trace_thread_cpu_ns() - cpu_started; if (!update_stream) { @@ -227,7 +227,7 @@ std::optional Scene_Datoviz_State::prepare(const Scene_3D DvzDrp2ValidationResult update_result{}; { DvzDrp2ExecutionTiming timing{}; - update_result = dvz_drp2_runtime_execute_timed(runtime_slots_[*target_index].runtime, update_stream.get(), &timing); + update_result = dvz_drp2_runtime_execute_timed(backend_target_runtimes_[*target_index].runtime, update_stream.get(), &timing); accumulate_execution_timing(timing, observation); } observation.runtime_execute_ns = trace_now_ns() - phase_started; @@ -247,7 +247,7 @@ std::optional Scene_Datoviz_State::prepare(const Scene_3D phase_started = trace_now_ns(); if (frame_target.can_reuse_commands(command_revision_, readback) && !frame_target.can_reuse(command_revision_, controller_revision_, readback)) { std::uint64_t mvp_uploaded_bytes{}; - if (dvz_figure_update_mvp_buffers(figure_, runtime_slots_[*target_index].emitter, runtime_slots_[*target_index].runtime, &mvp_uploaded_bytes)) { + if (dvz_figure_update_mvp_buffers(figure_, backend_target_runtimes_[*target_index].emitter, backend_target_runtimes_[*target_index].runtime, &mvp_uploaded_bytes)) { frame_target.mark_mvp_uploaded(controller_revision_); observation.uploaded_bytes += mvp_uploaded_bytes; mvp_updated = true; @@ -321,13 +321,13 @@ std::optional Scene_Datoviz_State::prepare(const Scene_3D bool attached{}; DvzDrp2ValidationResult result{}; phase_started = trace_now_ns(); - attached = stream != nullptr && dvz_drp2_runtime_attach_frame_target(runtime_slots_[*target_index].runtime, color_target_id, &target_frame); + attached = stream != nullptr && dvz_drp2_runtime_attach_frame_target(backend_target_runtimes_[*target_index].runtime, color_target_id, &target_frame); observation.frame_attach_ns = trace_now_ns() - phase_started; if (attached) { phase_started = trace_now_ns(); const std::uint64_t cpu_started = trace_thread_cpu_ns(); DvzDrp2ExecutionTiming timing{}; - result = dvz_drp2_runtime_execute_timed(runtime_slots_[*target_index].runtime, stream, &timing); + result = dvz_drp2_runtime_execute_timed(backend_target_runtimes_[*target_index].runtime, stream, &timing); accumulate_execution_timing(timing, observation); observation.frame_execute_ns = trace_now_ns() - phase_started; observation.frame_execute_cpu_ns = trace_thread_cpu_ns() - cpu_started; @@ -427,7 +427,7 @@ void Scene_Datoviz_State::destroy() { auto context = render_context_; { std::lock_guard target_lock(target_mutex_); - for (auto& slot : runtime_slots_) { + for (auto& slot : backend_target_runtimes_) { if (slot.runtime != nullptr) { dvz_drp2_runtime_destroy(slot.runtime); slot.runtime = nullptr; diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp index 251dfb7..f82bd44 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp @@ -31,7 +31,7 @@ void Scene_Datoviz_State::initialize(std::uint32_t gpu_index, bool validation_en DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config(device, dvz_gpu_ctx_alloc(gpu_context)); dvz_drp2_runtime_vklite_pools(&runtime_configuration, command_pool_, descriptor_pool_); render_context_->route_immediate_submissions(runtime_configuration); - for (auto& slot : runtime_slots_) { + for (auto& slot : backend_target_runtimes_) { slot.runtime = dvz_drp2_runtime_vklite(&runtime_configuration); slot.emitter = dvz_frame_plan_emitter(); if (slot.runtime == nullptr || slot.emitter == nullptr) throw std::runtime_error("failed to create a Datoviz frame-target runtime"); @@ -286,7 +286,7 @@ bool Scene_Datoviz_State::matches_command_structure(const Scene_3D_Parameters& s } bool Scene_Datoviz_State::target_runtime_resources_current(const Prepared_Visual_Batch& prepared, std::uint8_t target_index) const { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + if (target_index >= backend_target_runtimes_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); for (const auto& source : prepared) { const auto target = std::ranges::find_if(visuals_, [&](const Datoviz_Visual_Instance& value) { return value.identity == source.identity; @@ -298,7 +298,7 @@ bool Scene_Datoviz_State::target_runtime_resources_current(const Prepared_Visual } std::vector Scene_Datoviz_State::stale_runtime_visuals(const Prepared_Visual_Batch& prepared, std::uint8_t target_index) const { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + if (target_index >= backend_target_runtimes_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); std::vector result; result.reserve(prepared.size()); for (const auto& source : prepared) { @@ -312,7 +312,7 @@ std::vector Scene_Datoviz_State::stale_runtime_visuals(const Prepare } void Scene_Datoviz_State::mark_target_runtime_resources_uploaded(std::uint8_t target_index) { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + if (target_index >= backend_target_runtimes_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); for (auto& visual : visuals_) if (visual.operations->external_attributes.empty()) visual.uploaded_data_revisions[target_index] = visual.applied_data_revision; } diff --git a/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp index c795add..ff68161 100644 --- a/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp @@ -102,7 +102,7 @@ void Scene_Datoviz_State::bind_external_attributes(Datoviz_Visual_Instance& targ void Scene_Datoviz_State::register_external_attributes(const DvzDrp2CommandStream* stream, std::uint8_t target_index) { if (stream == nullptr) return; - if (target_index >= runtime_slots_.size()) throw std::logic_error("Datoviz external attribute target is invalid"); + if (target_index >= backend_target_runtimes_.size()) throw std::logic_error("Datoviz external attribute target is invalid"); const auto target_bit = static_cast(1U << target_index); for (auto& visual : visuals_) { for (auto& attribute : visual.attributes) { @@ -117,7 +117,7 @@ void Scene_Datoviz_State::register_external_attributes(const DvzDrp2CommandStrea descriptor.buffer = attribute.gpu_buffer; descriptor.size = scene_descriptor.byte_size; descriptor.usage = DVZ_DRP2_BUFFER_USAGE_VERTEX; - if (!dvz_drp2_runtime_register_external_buffer(runtime_slots_[target_index].runtime, id, &descriptor)) throw std::runtime_error("failed to register Datoviz external GPU buffer"); + if (!dvz_drp2_runtime_register_external_buffer(backend_target_runtimes_[target_index].runtime, id, &descriptor)) throw std::runtime_error("failed to register Datoviz external GPU buffer"); attribute.registered_targets |= target_bit; } } diff --git a/render_3D/tests/Async_Visual_Contract_Test.cpp b/render_3D/tests/Async_Visual_Contract_Test.cpp index 83e0829..5c37a15 100644 --- a/render_3D/tests/Async_Visual_Contract_Test.cpp +++ b/render_3D/tests/Async_Visual_Contract_Test.cpp @@ -28,8 +28,8 @@ int main() { auto visual = std::shared_ptr{builder.build()}; auto renderable = model_proxy_shared(visual); auto context = std::make_shared(); - renderable->render(context, 0, &publish); renderable->advance(); + renderable->render(context, 0, &publish); auto component = renderable->taskflow(); auto graph = task_flow::make_task_graph("render_3d.async_visual_contract"); diff --git a/render_3D/tests/Visual_Tests.cpp b/render_3D/tests/Visual_Tests.cpp index 613fbf4..3baedd4 100644 --- a/render_3D/tests/Visual_Tests.cpp +++ b/render_3D/tests/Visual_Tests.cpp @@ -61,14 +61,16 @@ TEST(Render_3D_Scene, Builder_Owns_Current_Renderable_Dag) { Render_Scene_3D::Builder scene_builder(*camera, *axes); scene_builder - .relation([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); }); + .relation([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); }); static_assert(std::same_as>); auto scene = scene_builder.build(); ASSERT_TRUE(scene); EXPECT_TRUE(scene->model_initialized()); + auto& relation = aethera::detail::model_private(*scene).relation(); + static_assert(std::same_as::Node, scene::Renderable>); std::size_t node_count{}; - aethera::detail::model_private(*scene).relation().for_each_topological([&](auto, auto&) { ++node_count; }); + relation.for_each_topological([&](auto, auto&) { ++node_count; }); EXPECT_EQ(node_count, 1U); }