This commit is contained in:
2026-09-03 16:57:18 +08:00
parent e31a4f342e
commit 26c1ed2e7c
23 changed files with 133 additions and 66 deletions
@@ -8,6 +8,18 @@
#include <optional>
#include <proxy/proxy.h>
namespace aethera {
namespace detail {
template <typename Tag, typename Endpoint, typename = void>
struct Dag_Node_Facade {
using Type = Tag;
};
template <typename Tag, typename Endpoint>
struct Dag_Node_Facade<Tag, Endpoint, std::void_t<typename Tag::template Relation_Node_Facade<Endpoint>>> {
using Type = typename Tag::template Relation_Node_Facade<Endpoint>;
};
} // 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 <typename Node_Facade>
struct Dag_Relation {
using Node = Node_Facade;
struct Builder;
struct Editor;
Dag_Relation();
@@ -82,8 +95,8 @@ struct Dag_Relation<Node_Facade>::Editor {
/* Model 可注册的拥有型 DAG 关系;内部 commit 在所有者安全点验证并提交待处理事务。 */
template <typename Node_Facade>
struct Owned_Dag_Relation : detail::Relation_Definition<Owned_Dag_Relation<Node_Facade>, Node_Facade> {
template <typename, typename Tag>
using Attachment = Owned_Dag_Relation<Tag>;
template <typename Endpoint, typename Tag>
using Attachment = Owned_Dag_Relation<typename detail::Dag_Node_Facade<Tag, Endpoint>::Type>;
using Graph = Dag_Relation<Node_Facade>;
using Edit = std::move_only_function<void(typename Graph::Editor&)>;
using Edit_Completion = std::move_only_function<void(std::expected<void, Dag_Result>)>;
@@ -24,6 +24,6 @@ Model 的可组合能力统一分为两层:
## 关系附件契约
关系附件必须提供权威 `Graph`、事务 `Edit`、事务完成回调,以及内部 `relation()``commit()``build(...)``Owned_Dag_Relation<Node_Facade>` 自身是一段式定义和实际附件,向最终 endpoint 选择性附加实体 `edit<Tag>`、Builder `relation<Tag>` 和 Private `relation<Tag>/commit<Tag>`
关系附件必须提供权威 `Graph`、事务 `Edit`、事务完成回调,以及内部 `relation()``commit()``build(...)``Owned_Dag_Relation<Tag>` 自身是一段式定义和实际附件,向最终 endpoint 选择性附加实体 `edit<Tag>`、Builder `relation<Tag>` 和 Private `relation<Tag>/commit<Tag>`Tag 默认同时是节点 facadeTag 可以用 `Relation_Node_Facade<Endpoint>` 为最终 endpoint 选择更窄的依赖 facade,但关系仍由原 Tag 唯一标识,禁止派生层再注册第二张同语义关系图。
`Dag_Relation::Builder` 在构建期验证完整关系;`Dag_Relation::Editor` 只记录单次事务命令;`Owned_Dag_Relation` 由关系所有者在安全点验证并提交。编辑 completion 在调用 `commit()` 的内部线程执行,不是跨线程异常传播边界。
+1 -1
View File
@@ -32,6 +32,6 @@ Root 的类型擦除 state 操作表保存一份 Private 与附件解析器,De
分别承载独占、共享和非拥有生命周期,同时把 proxy 解引用目标映射到 Model 的唯一 Private;实体公开类不实现 facade 值转发。
具体类型自身就是一段式附件定义。例如,并发结构使用 `Import_Struct_Concurrent<Tags...>`,拥有型 DAG 使用
`Owned_Dag_Relation<Node_Facade>`。业务定义不再套额外的二段式注册包装。一个并发定义可连续绑定多个
`Owned_Dag_Relation<Tag>`。Tag 默认也作为节点 facade;如果 Tag 定义 `Relation_Node_Facade<Endpoint>`,则最终 endpoint 只物化一份关系状态,并按最终类型选择节点 facade。业务定义不再套额外的二段式注册包装。一个并发定义可连续绑定多个
Tag;它继承 `Concurrent_Definition<Self, Tags...>`,并在内部直接定义最终 endpoint 所物化的 `Attachment<Endpoint, Tag>`。Builder
和实体只写 Tag,例如 `set<Prop_Tag>(...)`;具体附件及其存储值由该定义和最终 endpoint 唯一确定。
+4 -1
View File
@@ -12,5 +12,8 @@ struct Renderable : facade_builder
::add_convention<_event_routing_distance, std::optional<double>(const Event&) const noexcept>
::add_convention<_dispatch_event, void(Event &)>
::support_relocation<pro::constraint_level::nothrow>
::build {};
::build {
template <typename Endpoint>
using Relation_Node_Facade = typename Endpoint::Scene_Renderable_Facade;
};
} // namespace aethera::scene
-1
View File
@@ -62,7 +62,6 @@ void Scene::Private::advance_renderables() {
renderable->advance();
});
}
scene::Submit_Event_Result Scene::Private::submit_event(std::unique_ptr<Event> event) {
if (!event) return scene::Submit_Event_Result::event_missing;
event_collector.submit(std::move(event));
+1
View File
@@ -9,6 +9,7 @@
namespace aethera {
/* Scene 通过 Tag + Owned_Dag_Relation 在类型系统中表达其对 Renderable DAG 的唯一所有权。 */
struct Scene : Def<Scene, Root, Owned_Dag_Relation<scene::Renderable>> {
using Scene_Renderable_Facade = scene::Renderable;
struct Private;
Scene();
~Scene() noexcept;
@@ -2,6 +2,7 @@
#include "model/attachment/dag/Dag_Relation.hpp"
#include <gtest/gtest.h>
#include <optional>
#include <type_traits>
namespace {
struct Test_Relation_Node : aethera::facade_builder
::support_relocation<pro::constraint_level::nothrow>
@@ -9,6 +10,23 @@ struct Test_Relation_Node : aethera::facade_builder
struct Test_Relation_Model : aethera::Def<Test_Relation_Model, aethera::Root, aethera::Owned_Dag_Relation<Test_Relation_Node>> {
struct Private : Prev_Private {};
};
struct Test_Adaptive_Relation_Tag : aethera::facade_builder
::support_relocation<pro::constraint_level::nothrow>
::build {
template <typename Endpoint>
using Relation_Node_Facade = typename Endpoint::Selected_Relation_Node_Facade;
};
struct Test_Narrow_Relation_Node : aethera::facade_builder
::support_relocation<pro::constraint_level::nothrow>
::build {};
struct Test_Adaptive_Relation_Base : aethera::Def<Test_Adaptive_Relation_Base, aethera::Root, aethera::Owned_Dag_Relation<Test_Adaptive_Relation_Tag>> {
using Selected_Relation_Node_Facade = Test_Adaptive_Relation_Tag;
struct Private : Prev_Private {};
};
struct Test_Adaptive_Relation_Model : aethera::Def<Test_Adaptive_Relation_Model, Test_Adaptive_Relation_Base> {
using Selected_Relation_Node_Facade = Test_Narrow_Relation_Node;
struct Private : Prev_Private {};
};
template <typename Model>
concept Exposes_Concurrent_Model_API = requires(Model& model) { model.template set<Test_Relation_Node>(0); };
template <typename Builder>
@@ -19,6 +37,7 @@ static_assert(std::same_as<aethera::detail::Materialized_Model_Attachment<Test_R
static_assert(aethera::detail::Relation_Model_Tag<Test_Relation_Node, Test_Relation_Model>);
static_assert(!Exposes_Concurrent_Model_API<Test_Relation_Model>);
static_assert(!Exposes_Concurrent_Builder_API<Test_Relation_Model::Builder>);
static_assert(std::same_as<aethera::detail::Materialized_Model_Attachment<Test_Adaptive_Relation_Tag, Test_Adaptive_Relation_Model>, aethera::Owned_Dag_Relation<Test_Narrow_Relation_Node>>);
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<Test_Relation_Node>().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<Test_Adaptive_Relation_Tag>([&](auto& relation) {
static_assert(std::same_as<typename std::remove_reference_t<decltype(relation)>::Node, Test_Narrow_Relation_Node>);
selected = true;
});
auto model = builder.build();
EXPECT_TRUE(model);
EXPECT_TRUE(selected);
}
} // namespace
+1 -1
View File
@@ -2,7 +2,7 @@
- 每个 Model 定义层的 `Prop``State` 等值类型必须定义在所属类内并继承 `Prev<Tag>`,使同一 Tag 的值结构沿 Model 层级形成继承链。
- 每层统一使用 `Def<当前层, Base, Definitions...>`;中间类是普通类,不需要模板化,也不区分末尾层。
- 一段式附件定义写在所属 `Def` 的模板参数上。并发结构直接使用 `Import_Struct_Concurrent<Tags...>` 等具体类型,关系结构直接使用 `Owned_Dag_Relation<Node_Facade>`
- 一段式附件定义写在所属 `Def` 的模板参数上。并发结构直接使用 `Import_Struct_Concurrent<Tags...>` 等具体类型,关系结构直接使用 `Owned_Dag_Relation<Tag>`。Tag 默认同时是节点 facade;需要让派生 endpoint 使用模块专用 facade 时,由 Tag 的 `Relation_Node_Facade<Endpoint>` 选择实际节点 facade,派生层不得重复注册同一语义的关系状态
- 同一个注册组可以连续登记多个 Tag;相同 Tag 在完整继承链只能注册一次。
- 每个具体附件类型同时声明 Tag、能力来源和实际 `Attachment<Endpoint, Tag>`。完整附件项链按 Builder 正在构建的最终 endpoint 求值,并由公共 `Root` 一次物化唯一的 `Private + Model_Attachment_Set`
- Builder 只拥有一个最终 endpoint 对象槽;被继承的 Def 层不得创建自己的 endpoint state、对象副本、附件或 Private 指针。Private 解析器只随 Root 的唯一类型擦除 state 保存一份,定义层通过零状态 `model_private(model)` 选择自身的类型化视图。
+1 -1
View File
@@ -531,7 +531,7 @@ std::unique_ptr<Scene_2D> make_scene_2d(Size canvas, std::optional<std::pair<Dag
.set<Prop_Tag>(&Render_Scene_2D::Prop::viewport, canvas)
.set<Prop_Tag>(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
.set<Prop_Tag>(&Render_Scene_2D::Prop::view_active, true)
.relation<render_2d::scene::Renderable>([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); });
.relation<aethera::scene::Renderable>([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); });
return scene_builder.build();
}
}
+1 -1
View File
@@ -511,7 +511,7 @@ Gallery_Build make_visual_plot(std::string label, std::unique_ptr<Definition> vi
.set<Prop_Tag>(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
.set<Prop_Tag>(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F})
.set<Prop_Tag>(&Render_Scene_3D::Prop::view_active, true)
.relation<render_3d::scene::Renderable>([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); });
.relation<aethera::scene::Renderable>([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); });
auto scene = scene_builder.build();
auto view = std::make_unique<Visual_Gallery_Component<Visual_Object, Data_Generator>>(
*scene, std::move(camera), std::move(axes), std::move(visual),
@@ -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<scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
renderables.for_each_topological([&](Dag_Node_Id, proxy<scene::Renderable>& 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<scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
aethera::detail::dispatch_scene_events<scene::Renderable>(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<Prop_Tag>();
properties.internal.advance();
advance_renderables();
commit<aethera::scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
renderables.for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& 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});
@@ -10,7 +10,8 @@
#include "scene/src/Scene.hpp"
namespace aethera::render_2d {
/* 执行二维 Renderable DAG 并向调用方拥有的物理帧合成像素。 */
struct Render_Scene_2D : Def<Render_Scene_2D, Scene, Dirty_Import_Struct_Concurrent<Prop_Tag>, Owned_Dag_Relation<scene::Renderable>> {
struct Render_Scene_2D : Def<Render_Scene_2D, Scene, Dirty_Import_Struct_Concurrent<Prop_Tag>> {
using Scene_Renderable_Facade = scene::Renderable;
struct Prop : Prev<Prop_Tag> {
Size viewport{}; /* 最终帧像素尺寸;空尺寸不提交渲染。 */
Color background{Color::black()}; /* 每帧绘制 Renderable 前写入的背景颜色。 */
@@ -52,7 +52,7 @@ int main() {
.set<Prop_Tag>(&Render_Scene_2D::Prop::viewport, Size{120, 100})
.set<Prop_Tag>(&Render_Scene_2D::Prop::background, Color::transparent())
.set<Prop_Tag>(&Render_Scene_2D::Prop::view_active, true)
.relation<aethera::render_2d::scene::Renderable>([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); });
.relation<aethera::scene::Renderable>([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) {
+7 -1
View File
@@ -1,6 +1,7 @@
#include <render_2D/module/renderable/Renderable_2D.hpp>
#include <render_2D/module/scene/Render_Scene_2D.hpp>
#include <gtest/gtest.h>
#include <type_traits>
namespace {
struct Test_Renderable_2D : aethera::Def<Test_Renderable_2D, aethera::render_2d::Renderable_2D> {
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<aethera::render_2d::scene::Renderable>([dag = std::move(*dag)](auto& relation) mutable { relation = std::move(dag); });
.relation<aethera::scene::Renderable>([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<aethera::scene::Renderable>();
static_assert(std::same_as<typename std::remove_reference_t<decltype(relation)>::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
@@ -20,7 +20,6 @@ struct Prepared_Visual_Instance {
};
using Prepared_Visual_Batch = std::vector<Prepared_Visual_Instance>;
using Shared_Prepared_Visual_Batch = std::shared_ptr<const Prepared_Visual_Batch>;
[[nodiscard]] bool prepared_visual_batch_complete(const Prepared_Visual_Batch& visuals) noexcept;
struct Scene_3D_Parameters {
@@ -12,10 +12,12 @@
#include <scene/src/Scene.hpp>
namespace aethera::render_3d {
/* 推进并执行一棵 Renderable DAG;物理帧池与槽位阶段完全由 Frame Policy 管理。 */
struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dirty_Import_Struct_Concurrent<Prop_Tag>, Owned_Dag_Relation<scene::Renderable>> {
struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dirty_Import_Struct_Concurrent<Prop_Tag>> {
using Scene_Renderable_Facade = scene::Renderable;
/*
* Camera_3D Axes_3D Scene
* Scene Frame_3D completion
* Scene render() Frame Policy
*
*/
struct Prop : Prev<Prop_Tag> {
@@ -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<Datoviz_Render_Context> render_context_{}; /* 按 GPU 索引与验证模式共享的进程级上下文。 */
std::uintptr_t command_pool_{}; /* 本 Scene 独占 Vulkan Command Pool。 */
std::uintptr_t descriptor_pool_{}; /* 本 Scene 独占 Vulkan Descriptor Pool。 */
std::array<Runtime_Slot, 3> runtime_slots_{}; /* 三个固定目标槽的 Runtime。 */
std::array<Backend_Target_Runtime, 3> backend_target_runtimes_{}; /* 仅对应三个 Datoviz/Vulkan 后端目标,不是 Frame Policy 槽。 */
DvzScene* scene_{}; /* Datoviz Scene 所有权。 */
DvzFigure* figure_{}; /* 可空、非拥有;由 scene_ 拥有。 */
DvzPanel* panel_{}; /* 可空、非拥有;由 scene_ 拥有。 */
@@ -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<Prepared_Visual_Batch> visuals{std::make_shared<Prepared_Visual_Batch>()}; /* 鍚?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<Scene_Paint_Context*>(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<detail::Scene_Paint_Context> paint_context{};
};
using Shared_Frame_Execution = std::shared_ptr<Frame_Execution>;
using Shared_Render_Execution = std::shared_ptr<Render_Execution>;
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<scene::Task_Graph> build_frame_graph(const Shared_Frame_Execution& execution, const detail::Scene_3D_Parameters& parameters);
[[nodiscard]] proxy<scene::Task_Graph> 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<detail::Gpu_Completion_Service::Result> 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<detail::Gpu_Completion_Service::Result> 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<aethera::scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
renderables.for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) {
renderable->advance();
});
return static_cast<const Prop&>(*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<detail::Visual_Registration> current;
auto& renderables = relation<scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
current.push_back({static_cast<detail::Visual_Identity>(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<scene::Task_Graph> Render_Scene_3D::Private::build_frame_graph(const Shared_Frame_Execution& execution, const detail::Scene_3D_Parameters& parameters) {
inline proxy<scene::Task_Graph> Render_Scene_3D::Private::build_frame_graph(const Shared_Render_Execution& execution, const detail::Scene_3D_Parameters& parameters) {
execution->paint_context = std::make_shared<detail::Scene_Paint_Context>();
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<scene::Task_Graph> 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<scene::Renderable>();
auto& renderables = relation<aethera::scene::Renderable>();
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
std::size_t slot{};
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
@@ -171,7 +176,7 @@ inline proxy<scene::Task_Graph> 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<scene::Task_Graph> 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::scene::Renderable>();
aethera::detail::dispatch_scene_events<scene::Renderable>(events, renderables);
const auto& properties = static_cast<const Prop&>(*this->template attachment<Prop_Tag>().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<detail::Gpu_Completion_Service::Result> result, std::exception_ptr failure) noexcept {
inline void Render_Scene_3D::Private::observe_datoviz_completion(const Shared_Render_Execution& execution, std::optional<detail::Gpu_Completion_Service::Result> 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<Frame_Execution>();
auto execution = std::make_shared<Render_Execution>();
execution->frame = frame;
execution->completion = std::move(completion);
auto graph = build_frame_graph(execution, parameters);
@@ -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<Datoviz_Pending_Frame> Scene_Datoviz_State::prepare(const Scene_3D
bool query_changed_scene{};
phase_started = trace_now_ns();
if (item_interaction_ != nullptr) {
static_cast<void>(dvz_figure_process_queries(figure_, runtime_slots_[*target_index].runtime, nullptr));
static_cast<void>(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<Datoviz_Pending_Frame> 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<DvzDrp2CommandStream, decltype(&dvz_drp2_stream_destroy)> update_stream{dvz_figure_prepare_runtime_resources(figure_, runtime_slots_[*target_index].emitter, replay_visuals.data(), static_cast<std::uint32_t>(replay_visuals.size()), &uploaded_bytes, &command_recording_valid, &report), &dvz_drp2_stream_destroy};
std::unique_ptr<DvzDrp2CommandStream, decltype(&dvz_drp2_stream_destroy)> update_stream{dvz_figure_prepare_runtime_resources(figure_, backend_target_runtimes_[*target_index].emitter, replay_visuals.data(), static_cast<std::uint32_t>(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<Datoviz_Pending_Frame> 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<Datoviz_Pending_Frame> 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<Datoviz_Pending_Frame> 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;
@@ -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<DvzVisual*> 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<DvzVisual*> result;
result.reserve(prepared.size());
for (const auto& source : prepared) {
@@ -312,7 +312,7 @@ std::vector<DvzVisual*> 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;
}
@@ -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<std::uint8_t>(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;
}
}
@@ -28,8 +28,8 @@ int main() {
auto visual = std::shared_ptr<Point_Visual>{builder.build()};
auto renderable = model_proxy_shared<aethera::render_3d::scene::Renderable>(visual);
auto context = std::make_shared<Publish_Context>();
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");
+4 -2
View File
@@ -61,14 +61,16 @@ TEST(Render_3D_Scene, Builder_Owns_Current_Renderable_Dag) {
Render_Scene_3D::Builder scene_builder(*camera, *axes);
scene_builder
.relation<scene::Renderable>([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); });
.relation<aethera::scene::Renderable>([graph = std::move(*dag)](auto& relation) mutable { relation = std::move(graph); });
static_assert(std::same_as<decltype(scene_builder.build()), std::unique_ptr<Render_Scene_3D>>);
auto scene = scene_builder.build();
ASSERT_TRUE(scene);
EXPECT_TRUE(scene->model_initialized());
auto& relation = aethera::detail::model_private(*scene).relation<aethera::scene::Renderable>();
static_assert(std::same_as<typename std::remove_reference_t<decltype(relation)>::Node, scene::Renderable>);
std::size_t node_count{};
aethera::detail::model_private(*scene).relation<scene::Renderable>().for_each_topological([&](auto, auto&) { ++node_count; });
relation.for_each_topological([&](auto, auto&) { ++node_count; });
EXPECT_EQ(node_count, 1U);
}