599 lines
33 KiB
C++
599 lines
33 KiB
C++
#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
|
||
* 留给模块内部 condition,Taskflow 已经物化的父 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>(); }
|
||
}
|