@@ -23,7 +23,6 @@ struct Render_Scene_3D::Private : Prev_Private {
std::mutex render_mutex{}; /* 只保护完成回调和单帧准入。 */
bool frame_in_flight{}; /* render 准入到异步完成回调返回的唯一状态源。 */
Frame_Callback frame_callback{}; /* Scene 的唯一对外完成出口。 */
Task_Graph completion_graph{"render_3d.completion"}; /* GPU 像素完成后、发布回调前执行的外部续写图。 */
std::shared_ptr<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
@@ -31,11 +30,10 @@ struct Render_Scene_3D::Private : Prev_Private {
Camera_Descriptor (*read_camera)(const Root*){}; /* 读取 Camera 当前配置。 */
std::array<plot::Axis_Descriptor, 3> (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */
Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */
std::unique_ptr<Task_Graph> submit_taskflow{};
std::unique_ptr<Task_Graph> frame_taskflow{};
Event_Batch active_events{};
bool active_trace{};
std::chrono::steady_clock::time_point active_p repare _started{};
std::chrono::steady_clock::time_point active_render _started{};
/* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */
template <Attached Object>
void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled,
@@ -88,8 +86,7 @@ Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
});
};
visuals.push_back(binding);
this->template add_dependency_node<Prepare_Data _Tag>(visual_value);
this->template add_dependency_node<Submit_Tag>(visual_value);
this->template add_dependency_node<Render_Graph _Tag>(visual_value);
return static_cast<Final_Builder&>(*this);
}
template <typename Object> template <Attached Camera_Object>
@@ -213,251 +210,59 @@ 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.p repare ");
/* 模块图在外层 topology 提交前必须已经包含真实节点。若把首次 builder
* 留给模块内部 condition, Taskflow 已经物化的父 topology 看不到本次新增
* 节点,Visual 会发布尚未 Prepare 的空 payload。 */
double_buffer::detail::Internal_Access::current_dependency_graph<Prepare_Data_Tag>(object).for_each_bound(
[](Renderable* renderable, Renderable::Private& data) {
if (!data.has_prepare_graph() || data.prepare_graph_built) return;
if (!data.prepare_graph)
data.prepare_graph = std::make_unique<Task_Graph>(
std::string(data.business_name()) + ".prepare.graph");
*data.prepare_graph = data.build_prepare_graph(renderable);
data.prepare_graph_built = true;
double_buffer::detail::Internal_Access::mark_dirty<Prepare_Data_Tag>(renderable);
});
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 =
double_buffer::detail::Internal_Access::current_dependency_graph<Submit_Tag>(object);
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;
const auto prefix = std::string(data->business_name()) + ".submit";
if (data->has_paint_graph() && !data->paint_graph) {
data->paint_graph = std::make_unique<Task_Graph>(prefix + ".graph");
*data->paint_graph = data->build_paint_graph(root);
data->paint_graph_built = true;
}
auto condition = submit_taskflow->add_condition(prefix + ".condition",
[data, root] {
auto& state = *static_cast<Renderable::State*>(
data->pending_renderable_state(root));
state.paint_graph_rebuilt = false;
state.paint_execution_time_ns = 0;
const bool dirty = double_buffer::detail::Internal_Access::dirty<Submit_Tag>(root);
state.paint_dirty = dirty;
state.paint_executed = data->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(data->business_name()))
.describe("dimension", "3D")
.describe("stage", "visual submit predicate");
Task_Node publish;
if (data->has_paint_graph())
publish = submit_taskflow->compose(prefix + ".visual", *data->paint_graph);
else
publish = submit_taskflow->add(prefix + ".visual", [data, root] {
if (data->has_paint_run()) data->run_paint(root);
});
publish.describe("renderable", std::string(data->business_name()))
.describe("dimension", "3D")
.describe("stage", "prepared visual publish");
auto done = submit_taskflow->add(prefix + ".complete", [data, root] {
auto& state = *static_cast<Renderable::State*>(
data->pending_renderable_state(root));
if (state.paint_executed) {
(void)double_buffer::detail::Internal_Access::take_dirty<Submit_Tag>(root);
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;
}
});
done.describe("renderable", std::string(data->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(data->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());
}
});
runtime->taskflow = std::make_unique<Task_Graph>("scene.render ");
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");
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);
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))
double_buffer::detail::Internal_Access::current_dependency_graph<Prepare_Data_Tag>(object).for_each_bound(
[](Renderable* renderable, Renderable::Private&) {
double_buffer::detail::Internal_Access::mark_dirty<Prepare_Data_Tag>(renderable);
});
if (!detail::prepared_visual_batch_complete(*context->visuals)) {
double_buffer::detail::Internal_Access::
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
[](Renderable* renderable, Renderable::Private&) {
double_buffer::detail::Internal_Access::
mark_dirty<Render_Graph_Tag>(renderable);
});
}
if (context->visuals.use_count() != 1)
context->visuals = std::make_shared<detail::Prepared_Visual_Batch>(*context->visuals);
context->visuals =
std::make_shared<detail::Prepared_Visual_Batch>(*context->visuals);
context->frame = active_frame;
camera_component->advance_object();
axes_component->advance_object();
active_p repare _started = std::chrono::steady_clock::now();
active_render _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 =
double_buffer::detail::Internal_Access::current_dependency_graph<Prepare_Data_Tag>(object);
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 task = graph.add(
std::string(data->business_name()) + ".prepare.data",
[data, root] {
auto& state = *static_cast<Renderable::State*>(
data->pending_renderable_state(root));
const bool dirty = double_buffer::detail::Internal_Access::dirty<Prepare_Data_Tag>(root);
state.prepare_dirty = dirty;
state.prepare_graph_rebuilt = false;
state.prepare_task_count = data->has_prepare_run() ? 1 : 0;
state.prepare_executed =
data->prepare_predicate(root, dirty);
state.prepare_execution_time_ns = 0;
if (state.prepare_executed) {
const auto started = std::chrono::steady_clock::now();
if (data->has_prepare_run()) data->run_prepare(root);
(void)double_buffer::detail::Internal_Access::take_dirty<Prepare_Data_Tag>(root);
state.prepare_execution_time_ns =
static_cast<std::uint64_t>(
std::chrono::duration_cast<
std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - started)
.count());
}
});
task.describe("renderable", std::string(data->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<
active_frame->mark(Frame_Trace_Marker::paint_started);
});
begin.describe("dimension", "3D").describe("stage", "frame setup");
auto renderables = graph.compose("scene.renderables", *runtime->taskflow);
renderables.describe("dimension", "3D")
.describe("stage", "visual graph")
.describe("execution_domain", "Taskflow workers");
auto submit = graph.add("scene.backend.submit", [this, object] {
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 =
double_buffer::detail::Internal_Access::current_dependency_graph<Prepare_Data_Tag>(object);
prepare_graph.for_each_bound([initial_publish](Renderable* renderable, Renderable::Private& data) {
const auto* state = static_cast<const Renderable::State*>(
data.pending_renderable_state(renderable));
if (initial_publish || state->prepare_executed)
double_buffer::detail::Internal_Access::mark_dirty<Submit_Tag>(renderable);
});
if (!detail::prepared_visual_batch_complete(*context->visuals))
throw std::logic_error(
"render scene published an incomplete Visual batch");
auto& state = static_cast<State&>(
double_buffer::detail::Internal_Access::pending_state(object));
state.taskflow_execution_time_ns = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - active_p repare _started).count());
std::chrono::steady_clock::now() - active_render _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] {
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");
context->backend->render(
context->visuals, context->parameters, active_frame,
std::move(active_events), [this](Frame_3D* completed) {
@@ -470,23 +275,15 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
});
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", "cooperative GPU fence continuation")
.describe("output", "RGBA8 readback");
.describe("stage", "backend submission");
auto completion = graph.compose("scene.completion", completion_graph);
completion.describe("owner", "scene")
.describe("stage", "frame consumers");
for (const auto& [root, tasks] : prepare_tasks) {
static_cast<void>(root );
tasks.task.precede(prepare_done );
}
prepare_done.precede(visuals);
visuals.precede(submit);
begin.precede(renderables );
renderables.precede(submit );
submit.precede(completion);
}
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");
@@ -508,19 +305,11 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object,
active_trace = aethera::detail::begin_taskflow_trace(*frame);
try {
auto completion = [this, object, frame] {
auto& private_data = static_cast<typename Object::Private&>(*this);
std::unordered_set<Root*> published_renderables;
const auto publish_renderable_states = [&](const auto& graph ) {
graph.for_each_bound([&](Renderable* renderable,
Renderable::Private& data) {
if (published_renderables.insert(renderable).second)
double_buffer::detail::Internal_Access::
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
[](Renderable* renderable, Renderable::Private& data ) {
data.publish_renderable_state(renderable);
});
};
publish_renderable_states(
double_buffer::detail::Internal_Access::current_dependency_graph<Prepare_Data_Tag>(object));
publish_renderable_states(
double_buffer::detail::Internal_Access::current_dependency_graph<Submit_Tag>(object));
});
double_buffer::detail::Internal_Access::publish_state<Render_Scene_3D::Base_Tag>(object);
complete_frame(object, frame);
};