Files
Aethera/render_3D/render_3D/scene/Render_Scene_3D.ipp
T
2026-08-25 15:03:33 +08:00

599 lines
33 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "../detail/Async_Render_Backend.hpp"
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace aethera::render_3d {
namespace detail {
template <Attached Object>
struct Scene_Paint_Context {
std::shared_ptr<Async_Render_Backend> backend{}; /* Scene 拥有、异步命令延长生命周期的后端。 */
Object* scene{}; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */
Frame_3D* frame{}; /* 当前 Submit 图对应的外部帧;process 返回后清空。 */
Scene_3D_Parameters parameters{}; /* 本帧从 Scene 组件读取的一致快照。 */
std::shared_ptr<Prepared_Visual_Batch> visuals{ /* Plot 准备域拥有的 Visual 快照;提交只共享不可变版本。 */
std::make_shared<Prepared_Visual_Batch>()};
};
}
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<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
Root* axes_component{}; /* Builder 绑定的三轴组件。 */
Camera_Descriptor (*read_camera)(const Root*){}; /* 读取 Camera 当前配置。 */
std::array<plot::Axis_Descriptor, 3> (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */
Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */
const Dispatch* dispatch{}; /* 最终 Scene 类型对应的静态公开分派表。 */
std::unique_ptr<Task_Graph> submit_taskflow{};
std::unique_ptr<Task_Graph> 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 <Attached Object>
void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled,
std::vector<detail::Visual_Registration> visuals, Root* camera, Root* axes,
Camera_Descriptor (*camera_reader)(const Root*),
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(const Root*));
template <Attached Object> [[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const;
template <Attached Object> void complete_frame(Object* object, Frame_3D* frame);
/* CRTP 覆盖:绑定 Scene 机制和 Render_Scene_3D 公开薄壳。 */
template <Attached Object> void bind_private_crtp(Object* object);
template <Attached Object> void ensure_frame_taskflow(Object* object);
template <Attached Object> [[nodiscard]] Render_Result render(Object* object, Frame_3D* frame);
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
};
template <typename Object>
Render_Scene_3D::Builder<Object>::Builder() : Base() {}
template <typename Object> template <Attached Visual_Object>
typename Render_Scene_3D::Builder<Object>::Final_Builder&
Render_Scene_3D::Builder<Object>::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<void> context) {
using Definition = typename Visual_Object::Attached_Object;
using Prepared = typename Definition::Prepared_Visual;
auto* object = static_cast<Visual_Object*>(root);
Base::private_access(object).template get<typename Definition::Base_Tag>()
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
const Prepared& prepared) {
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
const auto visual_identity = reinterpret_cast<detail::Visual_Identity>(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<Prepare_Data_Tag>(visual_value);
this->template add_dependency_node<Submit_Tag>(visual_value);
return static_cast<Final_Builder&>(*this);
}
template <typename Object> template <Attached Camera_Object>
requires std::derived_from<Camera_Object, Camera_3D>
typename Render_Scene_3D::Builder<Object>::Final_Builder&
Render_Scene_3D::Builder<Object>::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<const Camera_Object*>(root)->template read_prop<Camera_3D::Base_Tag>();
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<Final_Builder&>(*this);
}
template <typename Object> template <Attached Axes_Object>
requires std::derived_from<Axes_Object, Axes_3D>
typename Render_Scene_3D::Builder<Object>::Final_Builder&
Render_Scene_3D::Builder<Object>::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<const Axes_Object*>(root)->template read_prop<Axes_3D::Base_Tag>();
return std::array<plot::Axis_Descriptor, 3>{prop.x_axis, prop.y_axis, prop.z_axis};
};
return static_cast<Final_Builder&>(*this);
}
template <typename Object>
typename Render_Scene_3D::Builder<Object>::Final_Builder&
Render_Scene_3D::Builder<Object>::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<Final_Builder&>(*this);
}
template <typename Object>
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::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<Render_Scene_3D::Base_Tag>();
std::vector<detail::Visual_Registration> registrations;
registrations.reserve(visuals.size());
for (const auto& visual : visuals)
registrations.push_back({reinterpret_cast<detail::Visual_Identity>(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 <Attached Object>
detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object) const {
const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>();
const auto axis = read_axes(axes_component);
return {prop.viewport, prop.clear_color, read_camera(camera_component), axis[0], axis[1], axis[2]};
}
template <Attached Object>
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<Render_Scene_3D::Base_Tag,
&State::frame_statistics>(
[this, frame](State_Access<typename Object::State> states) {
auto& state = states.template get<Render_Scene_3D::Base_Tag>();
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<Object>>(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 <Attached Object>
void Render_Scene_3D::Private::initialize_backend(
Object* object, std::uint32_t gpu_index, bool validation_enabled,
std::vector<detail::Visual_Registration> visuals,
Root* camera, Root* axes, Camera_Descriptor (*camera_reader)(const Root*),
std::array<plot::Axis_Descriptor, 3> (*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<detail::Prepared_Visual_Batch>();
prepared_visuals->reserve(visuals.size());
for (const auto& visual : visuals)
prepared_visuals->push_back({visual.identity, {}});
backend = std::make_shared<detail::Async_Render_Backend>(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<Object>>(
detail::Scene_Paint_Context<Object>{
backend, object, nullptr, initial,
std::move(prepared_visuals)});
}
template <Attached Object>
void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
if (frame_taskflow) return;
if (!runtime->taskflow)
runtime->taskflow = std::make_unique<Task_Graph>("scene.prepare");
/* 模块图在外层 topology 提交前必须已经包含真实节点。若把首次 builder
* 留给模块内部 conditionTaskflow 已经物化的父 topology 看不到本次新增
* 节点,Visual 会发布尚未 Prepare 的空 payload。 */
object->template current_dependency_graph<Prepare_Data_Tag>().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<Task_Graph>(
std::string(dispatch->business_name) + ".prepare.graph");
*data.prepare_graph = dispatch->prepare.builder(renderable);
data.prepare_graph_built = true;
renderable->template mark_dirty<Prepare_Data_Tag>();
});
submit_taskflow = std::make_unique<Task_Graph>("render_3d.visual.submit");
struct Submit_Tasks {
Task_Node entry;
Task_Node exit;
};
std::unordered_map<Root*, Submit_Tasks> submit_tasks;
const auto submit_dependencies = object->template current_dependency_graph<Submit_Tag>();
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<Task_Graph>(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<Submit_Tag>();
state.paint_dirty = dirty;
state.paint_executed = dispatch->paint.predicate(root, dirty);
if (state.paint_executed)
state.paint_execution_time_ns = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
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<Submit_Tag>();
const auto finished = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
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<Root*> visited;
std::vector<const Dependency_Graph::Node*> 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<Task_Graph>("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<detail::Scene_Paint_Context<Object>>(paint_context);
context->parameters = parameters(object);
if (!detail::prepared_visual_batch_complete(*context->visuals))
object->template current_dependency_graph<Prepare_Data_Tag>().for_each_bound(
[](Renderable* renderable, Renderable::Private&) {
renderable->template mark_dirty<Prepare_Data_Tag>();
});
if (context->visuals.use_count() != 1)
context->visuals = std::make_shared<detail::Prepared_Visual_Batch>(*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<Root*, Prepare_Tasks> prepare_tasks;
const auto prepare_dependencies =
object->template current_dependency_graph<Prepare_Data_Tag>();
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<Prepare_Data_Tag>();
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<Prepare_Data_Tag>();
state.prepare_execution_time_ns =
static_cast<std::uint64_t>(
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<Root*> visited;
std::vector<const Dependency_Graph::Node*> 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<void>(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<Object>>(paint_context);
const bool initial_publish =
!detail::prepared_visual_batch_complete(*context->visuals);
const auto prepare_graph = object->template current_dependency_graph<Prepare_Data_Tag>();
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<Submit_Tag>();
});
auto& private_data = static_cast<typename Object::Private&>(*this);
auto& state = static_cast<State&>(*private_data.state.pending);
state.taskflow_execution_time_ns = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
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<detail::Scene_Paint_Context<Object>>(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<void>(root);
tasks.task.precede(prepare_done);
}
prepare_done.precede(visuals);
visuals.precede(submit);
}
template <Attached Object>
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<Render_Scene_3D::Base_Tag>();
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<typename Object::Private&>(*this);
private_data.state.advance();
object->template notify_state<Render_Scene_3D::Base_Tag>();
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 <Attached Object> const Render_Scene_3D::Private::Dispatch& Render_Scene_3D::Private::dispatch_for() { static const Dispatch value{[](Root* root) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); object->template publish_state<Render_Scene_3D::Base_Tag, &State::frame_statistics>([&data](State_Access<typename Object::State> states) { data.frame_statistics.reset(); data.event_statistics.reset(); auto& state = states.template get<Render_Scene_3D::Base_Tag>(); state.frame_statistics = {}; state.event_statistics = {}; }); }, [](Root* root, Frame_3D* frame) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).render(object, frame); }, [](Root* root, Frame_Callback callback) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); std::lock_guard lock(data.render_mutex); data.frame_callback = std::move(callback); }, [](Root* root, bool active) { static_cast<Object*>(root)->template set<&Prop::view_active>(active); }}; return value; }
template <Attached Object> void Render_Scene_3D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
}