#pragma once #include "../detail/Async_Render_Backend.hpp" #include #include #include #include #include #include #include namespace aethera::render_3d { namespace detail { template struct Scene_Paint_Context { std::shared_ptr backend{}; /* Scene 拥有、异步命令延长生命周期的后端。 */ Object* scene{}; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */ Frame_3D* frame{}; /* 当前 Submit 图对应的外部帧;process 返回后清空。 */ Scene_3D_Parameters parameters{}; /* 本帧从 Scene 组件读取的一致快照。 */ std::shared_ptr visuals{ /* Plot 准备域拥有的 Visual 快照;提交只共享不可变版本。 */ std::make_shared()}; }; } struct Render_Scene_3D::Private : Prev_Private { using Render_Run = Render_Result (*)(Root*, Frame_3D*); using Callback_Run = void (*)(Root*, Frame_Callback); using Active_Run = void (*)(Root*, bool); Frame_Statistics_Accumulator frame_statistics{}; /* 完成线程在 Scene 状态发布临界区内更新。 */ struct Dispatch { void (*reset_statistics)(Root*); Render_Run render; /* 执行 CPU 图并异步提交 Paint。 */ Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */ Active_Run set_active; /* 修改最终 Scene 的活动属性。 */ }; std::mutex render_mutex{}; /* 只保护完成回调和单帧准入。 */ bool frame_in_flight{}; /* render 准入到异步完成回调返回的唯一状态源。 */ Frame_Callback frame_callback{}; /* Scene 的唯一对外完成出口。 */ Task_Graph completion_graph{"render_3d.completion"}; /* GPU 像素完成后、发布回调前执行的外部续写图。 */ std::mutex completion_mutex{}; /* 只保护 completion 图拓扑的在途生命周期。 */ std::condition_variable completion_condition{}; bool completion_running{}; /* 析构等待异步 completion 图结束的唯一状态源。 */ bool frame_topology_finished{}; bool gpu_frame_finished{}; bool frame_finish_started{}; std::shared_ptr backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */ std::shared_ptr paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */ Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */ Root* axes_component{}; /* Builder 绑定的三轴组件。 */ Camera_Descriptor (*read_camera)(const Root*){}; /* 读取 Camera 当前配置。 */ std::array (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */ Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */ const Dispatch* dispatch{}; /* 最终 Scene 类型对应的静态公开分派表。 */ std::unique_ptr submit_taskflow{}; std::unique_ptr frame_taskflow{}; Event_Batch active_events{}; bool active_backend_submitted{}; bool active_trace{}; std::chrono::steady_clock::time_point active_prepare_started{}; ~Private(); /* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */ template void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled, std::vector visuals, Root* camera, Root* axes, Camera_Descriptor (*camera_reader)(const Root*), std::array (*axes_reader)(const Root*)); template [[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const; template void complete_frame(Object* object, Frame_3D* frame); /* CRTP 覆盖:绑定 Scene 机制和 Render_Scene_3D 公开薄壳。 */ template void bind_private_crtp(Object* object); template void ensure_frame_taskflow(Object* object); template [[nodiscard]] Render_Result render(Object* object, Frame_3D* frame); template [[nodiscard]] static const Dispatch& dispatch_for(); }; template Render_Scene_3D::Builder::Builder() : Base() {} template template typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Builder::add_renderable(Visual_Object* visual_value) { if (!visual_value) throw std::invalid_argument("Render_Scene_3D cannot attach a null Visual"); if (std::ranges::any_of(visuals, [visual_value](const Visual_Binding& value) { return value.object == visual_value; })) throw std::logic_error("Render_Scene_3D cannot attach the same Visual twice"); Visual_Binding binding{}; binding.object = visual_value; binding.family = Visual_Object::Attached_Object::Specification::family; binding.bind = [](Root* root, std::shared_ptr context) { using Definition = typename Visual_Object::Attached_Object; using Prepared = typename Definition::Prepared_Visual; auto* object = static_cast(root); Base::private_access(object).template get() .bind_paint_target(std::move(context), [](void* raw_context, const Root* identity, const Prepared& prepared) { auto& submission = *static_cast*>(raw_context); const auto visual_identity = reinterpret_cast(identity); auto erased = detail::erase_prepared_visual(prepared); auto& visuals = *submission.visuals; const auto found = std::ranges::find( visuals, visual_identity, &detail::Prepared_Visual_Instance::identity); if (found == visuals.end()) throw std::logic_error( "3D Visual published outside its Scene registration"); /* Builder 已经为每个 Visual 建立稳定槽位。并行 Submit 节点只写各自 * 的 visual 成员,不扩容容器,也不互相读写同一对象。 */ found->visual = std::move(erased); }); }; visuals.push_back(binding); this->template add_dependency_node(visual_value); this->template add_dependency_node(visual_value); return static_cast(*this); } template template requires std::derived_from typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Builder::add_camera(Camera_Object* camera_value) { if (!camera_value) throw std::invalid_argument("Render_Scene_3D cannot attach a null Camera"); if (camera) throw std::logic_error("Render_Scene_3D accepts one Camera component"); camera = camera_value; read_camera = [](const Root* root) { const auto& prop = static_cast(root)->template read_prop(); return Camera_Descriptor{prop.initial_view, prop.projection, prop.controller, prop.turntable_control, prop.arcball_control, prop.fly_control, prop.panzoom_control, prop.vertical_field_of_view_degrees, prop.near_plane, prop.far_plane}; }; return static_cast(*this); } template template requires std::derived_from typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Builder::add_axes(Axes_Object* axes_value) { if (!axes_value) throw std::invalid_argument("Render_Scene_3D cannot attach null Axes"); if (axes) throw std::logic_error("Render_Scene_3D accepts one Axes component"); axes = axes_value; read_axes = [](const Root* root) { const auto& prop = static_cast(root)->template read_prop(); return std::array{prop.x_axis, prop.y_axis, prop.z_axis}; }; return static_cast(*this); } template typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Builder::use_gpu(std::uint32_t gpu_index_value, bool validation_enabled_value) { gpu_index = gpu_index_value; validation_enabled = validation_enabled_value; return static_cast(*this); } template std::expected, Dependency_Graph_Error> Render_Scene_3D::Builder::build() { if (visuals.empty()) throw std::invalid_argument("Render_Scene_3D requires at least one Visual"); if (!camera || !read_camera) throw std::invalid_argument("Render_Scene_3D requires one Camera component"); if (!axes || !read_axes) throw std::invalid_argument("Render_Scene_3D requires one Axes component"); auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto scene = std::move(result).value(); auto& private_data = Base::private_access(scene.get()).template get(); std::vector registrations; registrations.reserve(visuals.size()); for (const auto& visual : visuals) registrations.push_back({reinterpret_cast(visual.object), visual.family}); private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, std::move(registrations), camera, axes, read_camera, read_axes); for (const auto& visual : visuals) visual.bind(visual.object, private_data.paint_context); return scene; } template detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object) const { const auto& prop = object->template read_prop(); const auto axis = read_axes(axes_component); return {prop.viewport, prop.clear_color, read_camera(camera_component), axis[0], axis[1], axis[2]}; } template void Render_Scene_3D::Private::complete_frame(Object* object, Frame_3D* frame) { Frame_Callback callback; { std::lock_guard lock(render_mutex); callback = frame_callback; } auto finish = [this, object, frame, callback = std::move(callback)]() mutable { aethera::detail::finish_taskflow_trace(*frame); active_trace = false; frame->mark(Frame_Trace_Marker::callback_started); if (callback) callback(frame); frame->mark(Frame_Trace_Marker::callback_finished); frame->mark(Frame_Trace_Marker::frame_ready); object->template publish_state( [this, frame](State_Access states) { auto& state = states.template get(); state.frame_statistics = frame_statistics.submit( *frame, Frame_Dimension::three_dimensional); state.event_statistics = event_statistics.state(); }); auto context = std::static_pointer_cast< detail::Scene_Paint_Context>(paint_context); if (context->frame == frame) context->frame = nullptr; if (active_frame == frame) active_frame = nullptr; { std::lock_guard lock(render_mutex); frame_in_flight = false; } { std::lock_guard lock(completion_mutex); completion_running = false; frame_topology_finished = false; gpu_frame_finished = false; frame_finish_started = false; } completion_condition.notify_all(); }; if (completion_graph.empty()) { finish(); return; } if (frame->taskflow_trace_requested()) aethera::detail::run_taskflow(completion_graph, *frame, "render_3d.completion", std::move(finish)); else aethera::detail::run_taskflow(completion_graph, std::move(finish)); } template void Render_Scene_3D::Private::initialize_backend( Object* object, std::uint32_t gpu_index, bool validation_enabled, std::vector visuals, Root* camera, Root* axes, Camera_Descriptor (*camera_reader)(const Root*), std::array (*axes_reader)(const Root*)) { camera_component = camera; axes_component = axes; read_camera = camera_reader; read_axes = axes_reader; const auto initial = parameters(object); auto prepared_visuals = std::make_shared(); prepared_visuals->reserve(visuals.size()); for (const auto& visual : visuals) prepared_visuals->push_back({visual.identity, {}}); backend = std::make_shared(gpu_index, validation_enabled, std::move(visuals), initial); backend->set_frame_callback([this, object](Frame_3D* frame) { bool finish{}; { std::lock_guard lock(completion_mutex); gpu_frame_finished = true; if (frame_topology_finished && !frame_finish_started) { frame_finish_started = true; finish = true; } } if (finish) complete_frame(object, frame); }); paint_context = std::make_shared>( detail::Scene_Paint_Context{ backend, object, nullptr, initial, std::move(prepared_visuals)}); } 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。 */ object->template current_dependency_graph().for_each_bound( [](Renderable* renderable, Renderable::Private& data) { auto* dispatch = data.dispatch; if (!dispatch->prepare.builder || data.prepare_graph_built) return; if (!data.prepare_graph) data.prepare_graph = std::make_unique( std::string(dispatch->business_name) + ".prepare.graph"); *data.prepare_graph = dispatch->prepare.builder(renderable); data.prepare_graph_built = true; renderable->template mark_dirty(); }); 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 = object->template current_dependency_graph(); 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; auto* dispatch = data->dispatch; const auto prefix = std::string(dispatch->business_name) + ".submit"; if (dispatch->paint.builder && !data->paint_graph) { data->paint_graph = std::make_unique(prefix + ".graph"); *data->paint_graph = dispatch->paint.builder(root); data->paint_graph_built = true; } auto condition = submit_taskflow->add_condition(prefix + ".condition", [data, dispatch, root] { auto& state = *dispatch->state.pending(root); state.paint_graph_rebuilt = false; state.paint_execution_time_ns = 0; const bool dirty = root->template dirty(); state.paint_dirty = dirty; state.paint_executed = dispatch->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(dispatch->business_name)) .describe("dimension", "3D") .describe("stage", "visual submit predicate"); Task_Node publish; if (dispatch->paint.builder) publish = submit_taskflow->compose(prefix + ".visual", *data->paint_graph); else publish = submit_taskflow->add(prefix + ".visual", [dispatch, root] { if (dispatch->paint.run) dispatch->paint.run(root); }); publish.describe("renderable", std::string(dispatch->business_name)) .describe("dimension", "3D") .describe("stage", "prepared visual publish"); auto done = submit_taskflow->add(prefix + ".complete", [dispatch, root] { auto& state = *dispatch->state.pending(root); if (state.paint_executed) { root->template take_dirty(); 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; } dispatch->state.publish(root); }); done.describe("renderable", std::string(dispatch->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(dispatch->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()); } }); 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"); 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); context->parameters = parameters(object); if (!detail::prepared_visual_batch_complete(*context->visuals)) object->template current_dependency_graph().for_each_bound( [](Renderable* renderable, Renderable::Private&) { renderable->template mark_dirty(); }); if (context->visuals.use_count() != 1) 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_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 = object->template current_dependency_graph(); 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* dispatch = data->dispatch; auto task = graph.add( std::string(dispatch->business_name) + ".prepare.data", [dispatch, root] { auto& state = *dispatch->state.pending(root); const bool dirty = root->template dirty(); state.prepare_dirty = dirty; state.prepare_graph_rebuilt = false; state.prepare_task_count = dispatch->prepare.run ? 1 : 0; state.prepare_executed = dispatch->prepare.predicate(root, dirty); state.prepare_execution_time_ns = 0; if (state.prepare_executed) { const auto started = std::chrono::steady_clock::now(); if (dispatch->prepare.run) dispatch->prepare.run(root); root->template take_dirty(); state.prepare_execution_time_ns = static_cast( std::chrono::duration_cast< std::chrono::nanoseconds>( std::chrono::steady_clock::now() - started) .count()); } dispatch->state.publish(root); }); task.describe("renderable", std::string(dispatch->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< detail::Scene_Paint_Context>(paint_context); const bool initial_publish = !detail::prepared_visual_batch_complete(*context->visuals); const auto prepare_graph = object->template current_dependency_graph(); prepare_graph.for_each_bound([initial_publish](Renderable* renderable, Renderable::Private& data) { if (initial_publish || data.dispatch->state.pending(renderable)->prepare_executed) renderable->template mark_dirty(); }); auto& private_data = static_cast(*this); auto& state = static_cast(*private_data.state.pending); state.taskflow_execution_time_ns = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now() - active_prepare_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, object] { 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"); active_backend_submitted = context->backend->render( context->visuals, context->parameters, active_frame, std::move(active_events)) == detail::Async_Render_Backend::Submit_Result::queued; active_frame->mark(Frame_Trace_Marker::paint_finished); if (active_backend_submitted) active_frame->mark(Frame_Trace_Marker::scene_render_finished); }); 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", "GPU fence callback") .describe("output", "RGBA8 readback"); for (const auto& [root, tasks] : prepare_tasks) { static_cast(root); tasks.task.precede(prepare_done); } prepare_done.precede(visuals); visuals.precede(submit); } 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"); const auto& prop = object->template read_prop(); if (!prop.view_active) return Render_Result::view_inactive; if (prop.viewport.empty()) return Render_Result::empty_viewport; if (!backend || !backend->available()) return Render_Result::backend_unavailable; ensure_frame_taskflow(object); { std::lock_guard lock(render_mutex); if (!frame_callback) throw std::logic_error("Render_Scene_3D requires a frame callback before render"); if (frame_in_flight) return Render_Result::frame_in_flight; frame_in_flight = true; active_frame = frame; active_backend_submitted = false; } frame->mark(Frame_Trace_Marker::scene_render_requested); active_trace = aethera::detail::begin_taskflow_trace(*frame); { std::lock_guard lock(completion_mutex); completion_running = true; frame_topology_finished = false; gpu_frame_finished = false; frame_finish_started = false; } try { auto completion = [this, object, frame] { auto& private_data = static_cast(*this); private_data.state.advance(); object->template notify_state(); bool finish{}; { std::lock_guard lock(completion_mutex); frame_topology_finished = true; if ((!active_backend_submitted || gpu_frame_finished) && !frame_finish_started) { frame_finish_started = true; finish = true; } } if (finish) complete_frame(object, frame); }; if (frame->taskflow_trace_requested()) aethera::detail::run_taskflow( *frame_taskflow, *frame, "render_3d.frame", std::move(completion)); else aethera::detail::run_taskflow(*frame_taskflow, std::move(completion)); } catch (...) { if (active_trace) { aethera::detail::finish_taskflow_trace(*frame); active_trace = false; } active_frame = nullptr; std::lock_guard lock(render_mutex); frame_in_flight = false; { std::lock_guard completion_lock(completion_mutex); completion_running = false; frame_topology_finished = false; gpu_frame_finished = false; frame_finish_started = false; } completion_condition.notify_all(); throw; } return Render_Result::submitted; } template const Render_Scene_3D::Private::Dispatch& Render_Scene_3D::Private::dispatch_for() { static const Dispatch value{[](Root* root) { auto* object = static_cast(root); auto& data = static_cast(*object->d); object->template publish_state([&data](State_Access states) { data.frame_statistics.reset(); data.event_statistics.reset(); auto& state = states.template get(); state.frame_statistics = {}; state.event_statistics = {}; }); }, [](Root* root, Frame_3D* frame) { auto* object = static_cast(root); return static_cast(*object->d).render(object, frame); }, [](Root* root, Frame_Callback callback) { auto* object = static_cast(root); auto& data = static_cast(*object->d); std::lock_guard lock(data.render_mutex); data.frame_callback = std::move(callback); }, [](Root* root, bool active) { static_cast(root)->template set<&Prop::view_active>(active); }}; return value; } template void Render_Scene_3D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for(); } }