优化了 还是卡

This commit is contained in:
2026-08-25 15:03:33 +08:00
parent 2c3105cc07
commit fa93927d81
23 changed files with 1517 additions and 619 deletions
@@ -7,8 +7,6 @@
#include <cmath>
#include <cstdio>
#include <functional>
#include <deque>
#include <iterator>
#include <limits>
#include <mutex>
#include <optional>
@@ -16,7 +14,6 @@
#include <string>
#include <thread>
#include <utility>
#include <variant>
#include <vector>
namespace aethera::render_3d::detail {
namespace {
@@ -50,12 +47,30 @@ void record_datoviz_trace(Frame_3D* frame, const Datoviz_Frame_Trace& trace) {
frame->record(Frame_Trace_Measurement::gpu_copy_ns, trace.gpu->copy_ns);
frame->record(Frame_Trace_Measurement::gpu_total_ns, trace.gpu->total_ns);
}
void append_exception_description(const std::exception& error,
std::string& result) {
if (!result.empty()) result += ": ";
result += error.what();
const auto* nested = dynamic_cast<const std::nested_exception*>(&error);
if (!nested) return;
try {
nested->rethrow_nested();
}
catch (const std::exception& cause) {
append_exception_description(cause, result);
}
catch (...) {
result += ": non-standard nested exception";
}
}
std::string failure_description(const std::exception_ptr& failure) {
try {
if (failure) std::rethrow_exception(failure);
}
catch (const std::exception& error) {
return error.what();
std::string result;
append_exception_description(error, result);
return result;
}
catch (...) {
return "non-standard exception";
@@ -79,6 +94,7 @@ struct Async_Render_Backend::Implementation
Datoviz_Visual_Backend::Pending_Frame backend_frame{}; /* 三缓冲目标对应的已录制提交。 */
std::vector<Completion> completions{}; /* GPU 完成时一起结清的逻辑帧。 */
Extent extent{}; /* 失败时生成空结果所需的尺寸。 */
std::optional<Gpu_Completion_Service::Reservation> completion_reservation{};
std::atomic_bool submitted{}; /* 区分提交前取消与 GPU 已拥有资源后的终止。 */
};
struct Gpu_Completion {
@@ -86,15 +102,8 @@ struct Async_Render_Backend::Implementation
std::optional<Gpu_Completion_Service::Result> result{}; /* fence 正常交付时的结果。 */
std::exception_ptr failure{}; /* 提交域或完成服务的 Unknown Failure。 */
};
struct Stop {};
using Command = std::variant<Submission, Gpu_Completion, Stop>;
std::shared_ptr<Render_Domain> render_domain; /* 同 GPU 唯一的轻量 Queue Submit 域。 */
std::unique_ptr<Datoviz_Visual_Backend> backend{}; /* Builder build() 创建;随后由本 Scene 串行使用。 */
std::optional<Submission> deferred_submission{}; /* 三槽占满时仅保留的最新画面和历史回调。 */
std::mutex command_mutex{}; /* 只保护本 Scene 的入队和 drain 所有权。 */
std::deque<Command> commands{}; /* 不同 Scene 可并行,同一 Scene 保持严格串行。 */
bool drain_scheduled{}; /* 是否已有 Taskflow worker 负责本 Scene。 */
std::mutex callback_mutex{}; /* 保护完成回调替换与 render 捕获。 */
Frame_Callback frame_callback{}; /* 后续 render 捕获的完成出口。 */
std::mutex failure_mutex{}; /* 保护跨线程传播的最后 Unknown Failure。 */
@@ -114,16 +123,11 @@ struct Async_Render_Backend::Implementation
void stop() noexcept;
void fail(std::exception_ptr value) noexcept;
void enqueue(Command command);
void drain() noexcept;
void execute(Command command) noexcept;
void finish_stop();
[[nodiscard]] Async_Render_Backend::Submit_Result render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame, Scene::Event_Batch events);
void accept(Submission submission);
void merge_deferred(Submission submission);
void prepare(Submission submission);
void accept(Submission submission, std::shared_ptr<Pending> pending);
void prepare(Submission submission, std::shared_ptr<Pending> pending);
void submit(std::shared_ptr<Pending> pending);
void finish(Gpu_Completion completion);
void resolve(std::shared_ptr<Pending> pending,
@@ -153,7 +157,7 @@ void Async_Render_Backend::Implementation::stop() noexcept {
available.store(false, std::memory_order_release);
}
try {
enqueue(Stop{});
if (backend && backend->idle()) backend.reset();
}
catch (...) { fail(std::current_exception()); }
}
@@ -173,36 +177,6 @@ void Async_Render_Backend::Implementation::fail(
}
catch (...) {}
}
void Async_Render_Backend::Implementation::enqueue(Command command) {
bool schedule{};
{
std::lock_guard lock(command_mutex);
commands.push_back(std::move(command));
if (!drain_scheduled) {
drain_scheduled = true;
schedule = true;
}
}
if (!schedule) return;
auto self = shared_from_this();
aethera::schedule_task("render_3d.backend.drain",
[self] { self->drain(); });
}
void Async_Render_Backend::Implementation::drain() noexcept {
for (;;) {
std::optional<Command> command;
{
std::lock_guard lock(command_mutex);
if (commands.empty()) {
drain_scheduled = false;
break;
}
command.emplace(std::move(commands.front()));
commands.pop_front();
}
execute(std::move(*command));
}
}
Async_Render_Backend::Submit_Result
Async_Render_Backend::Implementation::render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
@@ -221,10 +195,40 @@ Async_Render_Backend::Implementation::render(
callback = frame_callback;
}
frame->mark(Frame_Trace_Marker::backend_queue_entered);
auto pending = std::make_shared<Pending>();
pending->extent = parameters.viewport;
pending->completions = {Completion{frame, std::move(callback)}};
auto self = shared_from_this();
auto reservation = Gpu_Completion_Service::instance().prepare(
[self, pending](Gpu_Completion_Service::Result result) {
aethera::schedule_task("render_3d.backend.complete",
[self, pending, result] {
try { self->finish(Gpu_Completion{pending, result, {}}); }
catch (...) { self->fail(std::current_exception()); }
});
},
[self, pending](std::exception_ptr value) {
aethera::schedule_task("render_3d.backend.complete.failure",
[self, pending, value = std::move(value)]() mutable {
try { self->finish(Gpu_Completion{
pending, std::nullopt, std::move(value)}); }
catch (...) { self->fail(std::current_exception()); }
});
}, frame->identity().sequence == 1 ||
frame->identity().sequence % backend_observation_period == 0);
if (!reservation) {
if (reservation.result ==
Gpu_Completion_Service::Admission_Result::capacity_exhausted)
return Async_Render_Backend::Submit_Result::completion_capacity_exhausted;
return Async_Render_Backend::Submit_Result::backend_unavailable;
}
pending->completion_reservation.emplace(std::move(reservation.reservation));
try {
enqueue(Submission{std::move(visuals), std::move(parameters),
{Completion{frame, std::move(callback)}},
std::move(events)});
/* CPU 事件处理、Datoviz apply/emit/execute 与帧目标准备属于调用 Scene
* 的 frame Taskflow。这里先完成可并行的重工作,Render Domain 只接收
* 已录制 Pending_Frame 的轻量 Vulkan submit。 */
accept(Submission{std::move(visuals), std::move(parameters), {},
std::move(events)}, std::move(pending));
}
catch (...) {
fail(std::current_exception());
@@ -232,50 +236,37 @@ Async_Render_Backend::Implementation::render(
}
return Async_Render_Backend::Submit_Result::queued;
}
void Async_Render_Backend::Implementation::merge_deferred(
Submission submission) {
if (deferred_submission) {
submission.events.insert(
submission.events.begin(),
std::make_move_iterator(deferred_submission->events.begin()),
std::make_move_iterator(deferred_submission->events.end()));
submission.completions.insert(
submission.completions.begin(),
std::make_move_iterator(deferred_submission->completions.begin()),
std::make_move_iterator(deferred_submission->completions.end()));
}
deferred_submission = std::move(submission);
}
void Async_Render_Backend::Implementation::accept(Submission submission) {
void Async_Render_Backend::Implementation::accept(
Submission submission, std::shared_ptr<Pending> pending) {
if (stop_requested.load(std::memory_order_acquire) ||
!available.load(std::memory_order_acquire)) {
auto pending = std::make_shared<Pending>();
pending->extent = submission.parameters.viewport;
pending->completions = std::move(submission.completions);
resolve(std::move(pending), std::nullopt);
return;
}
if (backend &&
!backend->can_prepare(submission.parameters, *submission.visuals)) {
merge_deferred(std::move(submission));
fail(std::make_exception_ptr(std::logic_error(
"3D Scene admitted a new frame before its previous frame completed")));
resolve(std::move(pending), std::nullopt);
return;
}
prepare(std::move(submission));
prepare(std::move(submission), std::move(pending));
}
void Async_Render_Backend::Implementation::prepare(Submission submission) {
void Async_Render_Backend::Implementation::prepare(
Submission submission, std::shared_ptr<Pending> pending) {
std::optional<Datoviz_Visual_Backend::Pending_Frame> prepared;
try {
const auto sequence =
submission.completions.back().output->identity().sequence;
pending->completions.back().output->identity().sequence;
for (const auto& event : submission.events) {
if (event) event->mark_dispatch_started(sequence);
dispatch(event, submission.parameters.viewport);
}
submission.events.clear();
for (const auto& completion : submission.completions)
for (const auto& completion : pending->completions)
completion.output->mark(Frame_Trace_Marker::backend_prepare_started);
const bool readback = std::ranges::any_of(
submission.completions, [](const Completion& completion) {
pending->completions, [](const Completion& completion) {
return completion.output->output() == Frame_3D_Output::pixels;
});
const bool observe = sequence == 1 ||
@@ -285,25 +276,21 @@ void Async_Render_Backend::Implementation::prepare(Submission submission) {
}
catch (...) {
fail(std::current_exception());
auto pending = std::make_shared<Pending>();
pending->extent = submission.parameters.viewport;
pending->completions = std::move(submission.completions);
resolve(std::move(pending), std::nullopt);
return;
}
if (!prepared) {
merge_deferred(std::move(submission));
fail(std::make_exception_ptr(std::logic_error(
"Datoviz did not provide a frame target after Scene admission")));
resolve(std::move(pending), std::nullopt);
return;
}
for (const auto& completion : submission.completions) {
for (const auto& completion : pending->completions) {
completion.output->mark(Frame_Trace_Marker::backend_prepare_finished);
record_datoviz_trace(completion.output, prepared->trace);
completion.output->mark(Frame_Trace_Marker::backend_submit_queued);
}
auto pending = std::make_shared<Pending>();
pending->backend_frame = std::move(*prepared);
pending->extent = submission.parameters.viewport;
pending->completions = std::move(submission.completions);
try {
submit(pending);
}
@@ -316,56 +303,38 @@ void Async_Render_Backend::Implementation::prepare(Submission submission) {
void Async_Render_Backend::Implementation::submit(
std::shared_ptr<Pending> pending) {
auto self = shared_from_this();
/* Completion 槽位分配、回调所有权建立和服务登记没有 GPU 线程亲和性。
* 它们在当前 Scene Taskflow worker 完成;唯一 Render Domain 只保留
* vkQueueSubmit 与 fence 交付。Reservation 随 Pending 存活,提交前失败
* 会由析构自动取消,不产生第二套生命周期状态。 */
const auto queued = render_domain->post(
[self, pending] {
for (const auto& completion : pending->completions)
completion.output->mark(Frame_Trace_Marker::backend_queue_left);
auto reservation = Gpu_Completion_Service::instance().prepare(
[self, pending](Gpu_Completion_Service::Result result) {
try {
self->enqueue(Gpu_Completion{pending, result, {}});
}
catch (...) {
self->fail(std::current_exception());
}
},
[self, pending](std::exception_ptr value) {
try {
self->enqueue(Gpu_Completion{pending, std::nullopt,
std::move(value)});
}
catch (...) {
self->fail(std::current_exception());
}
}, pending->backend_frame.trace.observed);
if (!reservation) {
self->enqueue(Gpu_Completion{
pending, std::nullopt,
std::make_exception_ptr(std::runtime_error(
"GPU completion service stopped before submission"))});
return;
}
self->backend->submit(pending->backend_frame);
pending->submitted.store(true, std::memory_order_release);
for (const auto& completion : pending->completions)
completion.output->mark(Frame_Trace_Marker::gpu_submitted);
reservation.reservation.watch(pending->backend_frame.device,
pending->backend_frame.fence);
pending->completion_reservation->watch(
pending->backend_frame.device, pending->backend_frame.fence);
pending->completion_reservation.reset();
},
[self, pending](std::exception_ptr value) {
try {
self->enqueue(Gpu_Completion{pending, std::nullopt,
std::move(value)});
}
catch (...) {
self->fail(std::current_exception());
}
aethera::schedule_task("render_3d.backend.complete.failure",
[self, pending, value = std::move(value)]() mutable {
try { self->finish(Gpu_Completion{
pending, std::nullopt, std::move(value)}); }
catch (...) { self->fail(std::current_exception()); }
});
});
if (queued != Render_Domain::Post_Result::queued) {
enqueue(Gpu_Completion{
std::move(pending), std::nullopt,
std::make_exception_ptr(std::runtime_error(
"render domain stopped before 3D queue submission"))});
aethera::schedule_task("render_3d.backend.complete.failure",
[self, pending = std::move(pending)] {
self->finish(Gpu_Completion{
pending, std::nullopt,
std::make_exception_ptr(std::runtime_error(
"render domain stopped before 3D queue submission"))});
});
}
}
void Async_Render_Backend::Implementation::resolve(
@@ -447,21 +416,8 @@ void Async_Render_Backend::Implementation::finish(Gpu_Completion completion) {
std::move(completion.pending->backend_frame));
}
resolve(std::move(completion.pending), std::move(completed));
if (deferred_submission &&
!stop_requested.load(std::memory_order_acquire) &&
available.load(std::memory_order_acquire)) {
auto next = std::move(*deferred_submission);
deferred_submission.reset();
prepare(std::move(next));
}
else if (deferred_submission &&
!available.load(std::memory_order_acquire)) {
auto pending = std::make_shared<Pending>();
pending->extent = deferred_submission->parameters.viewport;
pending->completions = std::move(deferred_submission->completions);
deferred_submission.reset();
resolve(std::move(pending), std::nullopt);
}
if (stop_requested.load(std::memory_order_acquire) && backend && backend->idle())
backend.reset();
}
void Async_Render_Backend::Implementation::dispatch(
const std::shared_ptr<Event>& event, Extent viewport) {
@@ -504,44 +460,6 @@ void Async_Render_Backend::Implementation::dispatch(
if ((wheel && pointer_valid) || pointer_valid || key) event->accept();
event->mark_dispatch_completed();
}
void Async_Render_Backend::Implementation::execute(Command command) noexcept {
try {
if (auto* submission = std::get_if<Submission>(&command))
accept(std::move(*submission));
else if (auto* completion = std::get_if<Gpu_Completion>(&command))
finish(std::move(*completion));
else
finish_stop();
}
catch (...) {
fail(std::current_exception());
if (auto* submission = std::get_if<Submission>(&command)) {
auto pending = std::make_shared<Pending>();
pending->extent = submission->parameters.viewport;
pending->completions = std::move(submission->completions);
try { resolve(std::move(pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
else if (auto* completion = std::get_if<Gpu_Completion>(&command)) {
try { resolve(std::move(completion->pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
}
if (stop_requested.load(std::memory_order_acquire)) finish_stop();
}
void Async_Render_Backend::Implementation::finish_stop() {
if (deferred_submission) {
auto pending = std::make_shared<Pending>();
pending->extent = deferred_submission->parameters.viewport;
pending->completions = std::move(deferred_submission->completions);
deferred_submission.reset();
resolve(std::move(pending), std::nullopt);
}
if (backend && !backend->idle()) return;
backend.reset();
}
Async_Render_Backend::Submit_Result Async_Render_Backend::render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame, Scene::Event_Batch events) {
@@ -10,6 +10,7 @@ public:
using Frame_Callback = std::function<void(Frame_3D*)>;
enum class Submit_Result : std::uint8_t {
queued,
completion_capacity_exhausted,
backend_unavailable
};
Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled,
@@ -5,6 +5,7 @@
#include "../camera/Camera.hpp"
#include <cstdint>
#include <memory>
#include <ranges>
#include <variant>
#include <vector>
namespace aethera::render_3d::detail {
@@ -50,6 +51,18 @@ struct Prepared_Visual_Instance {
using Prepared_Visual_Batch = std::vector<Prepared_Visual_Instance>;
using Shared_Prepared_Visual_Batch =
std::shared_ptr<const Prepared_Visual_Batch>;
[[nodiscard]] inline bool prepared_visual_has_payload(
const Prepared_Visual& visual) noexcept {
return std::visit([](const auto& data) { return static_cast<bool>(data); },
visual.data);
}
[[nodiscard]] inline bool prepared_visual_batch_complete(
const Prepared_Visual_Batch& visuals) noexcept {
return !visuals.empty() &&
std::ranges::all_of(visuals, [](const Prepared_Visual_Instance& value) {
return prepared_visual_has_payload(value.visual);
});
}
struct Scene_3D_Parameters {
Extent viewport{560, 320}; /* 离屏渲染目标尺寸,单位为像素。 */
Linear_Color clear_color{}; /* 每帧开始时写入的线性背景色。 */
@@ -180,7 +180,26 @@ bool supports_item_interaction(Visual_Family family) noexcept {
}
bool uses_external_attributes(Visual_Family family) noexcept {
return family == Visual_Family::mesh || family == Visual_Family::marker;
switch (family) {
case Visual_Family::point:
case Visual_Family::splat:
case Visual_Family::pixel:
case Visual_Family::marker:
case Visual_Family::sphere:
case Visual_Family::primitive:
case Visual_Family::mesh:
case Visual_Family::path:
return true;
case Visual_Family::segment:
case Visual_Family::vector:
case Visual_Family::image:
case Visual_Family::labels:
case Visual_Family::glyph:
case Visual_Family::text:
case Visual_Family::volume:
return false;
}
return false;
}
template <typename Data>
@@ -1354,9 +1373,23 @@ void Datoviz_Visual_Backend::ensure_external_attributes(
throw std::length_error("Datoviz external attribute item count exceeds uint32");
std::size_t expected_count{};
if (target.family == Visual_Family::mesh) expected_count = 3;
else if (target.family == Visual_Family::marker) expected_count = 5;
else throw std::logic_error("external attributes requested for an unsupported Visual family");
switch (target.family) {
case Visual_Family::point: expected_count = 3; break;
case Visual_Family::splat: expected_count = 4; break;
case Visual_Family::pixel: expected_count = 3; break;
case Visual_Family::marker: expected_count = 5; break;
case Visual_Family::sphere: expected_count = 3; break;
case Visual_Family::segment: expected_count = 4; break;
case Visual_Family::vector: expected_count = 4; break;
case Visual_Family::primitive:
case Visual_Family::mesh: expected_count = 3; break;
case Visual_Family::path: expected_count = 3; break;
case Visual_Family::image:
case Visual_Family::labels: expected_count = 2; break;
default:
throw std::logic_error(
"external attributes requested for an unsupported Visual family");
}
if (target.attributes.size() == expected_count &&
std::ranges::all_of(target.attributes, [&](const External_Attribute& value) {
return value.capacity >= item_count;
@@ -1400,14 +1433,66 @@ void Datoviz_Visual_Backend::ensure_external_attributes(
scene_buffer, gpu_buffer, false});
};
create("position", sizeof(std::array<float, 3>));
create("color", sizeof(std::array<std::uint8_t, 4>));
if (target.family == Visual_Family::mesh)
create("normal", sizeof(std::array<float, 3>));
else {
switch (target.family) {
case Visual_Family::point:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("diameter_px", sizeof(float));
break;
case Visual_Family::splat:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("sigma", sizeof(std::array<float, 2>));
create("angle", sizeof(float));
break;
case Visual_Family::pixel:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("pixel_size_px", sizeof(float));
break;
case Visual_Family::marker:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("diameter_px", sizeof(float));
create("angle", sizeof(float));
create("shape", sizeof(std::uint32_t));
break;
case Visual_Family::sphere:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("radius", sizeof(float));
break;
case Visual_Family::segment:
create("position_start", sizeof(Prepared_Position));
create("position_end", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("stroke_width_px", sizeof(float));
break;
case Visual_Family::vector:
create("position", sizeof(Prepared_Position));
create("vector", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("stroke_width_px", sizeof(float));
break;
case Visual_Family::primitive:
case Visual_Family::mesh:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("normal", sizeof(Prepared_Position));
break;
case Visual_Family::path:
create("position", sizeof(Prepared_Position));
create("color", sizeof(Prepared_Color));
create("stroke_width_px", sizeof(float));
break;
case Visual_Family::image:
case Visual_Family::labels:
create("position", sizeof(Prepared_Position));
create("extent", sizeof(std::array<float, 2>));
break;
default:
throw std::logic_error(
"external attribute layout requested for an unsupported Visual family");
}
target.attributes = std::move(attributes);
}
@@ -1427,18 +1512,97 @@ void Datoviz_Visual_Backend::upload_external_attributes(
dvz_buffer_upload(attribute.gpu_buffer, byte_offset, byte_count, source);
};
auto attribute = target.attributes.begin();
if (target.family == Visual_Family::mesh) {
const auto& data = prepared_data<Mesh_Prepared_Data>(visual);
switch (target.family) {
case Visual_Family::point: {
const auto& data = prepared_data<Point_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.normals.data(), data.normals.size());
} else {
upload(*attribute++, data.diameters.data(), data.diameters.size());
break;
}
case Visual_Family::splat: {
const auto& data = prepared_data<Splat_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.sigmas.data(), data.sigmas.size());
upload(*attribute++, data.angles.data(), data.angles.size());
break;
}
case Visual_Family::pixel: {
const auto& data = prepared_data<Pixel_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.sizes.data(), data.sizes.size());
break;
}
case Visual_Family::marker: {
const auto& data = prepared_data<Marker_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.diameters.data(), data.diameters.size());
upload(*attribute++, data.angles.data(), data.angles.size());
upload(*attribute++, data.shapes.data(), data.shapes.size());
break;
}
case Visual_Family::sphere: {
const auto& data = prepared_data<Sphere_Prepared_Data>(visual);
upload(*attribute++, data.centers.data(), data.centers.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.radii.data(), data.radii.size());
break;
}
case Visual_Family::segment: {
const auto& data = prepared_data<Segment_Prepared_Data>(visual);
upload(*attribute++, data.starts.data(), data.starts.size());
upload(*attribute++, data.ends.data(), data.ends.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.widths.data(), data.widths.size());
break;
}
case Visual_Family::vector: {
const auto& data = prepared_data<Vector_Prepared_Data>(visual);
upload(*attribute++, data.origins.data(), data.origins.size());
upload(*attribute++, data.directions.data(), data.directions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.widths.data(), data.widths.size());
break;
}
case Visual_Family::primitive: {
const auto& data = prepared_data<Primitive_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.normals.data(), data.normals.size());
break;
}
case Visual_Family::mesh: {
const auto& data = prepared_data<Mesh_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.normals.data(), data.normals.size());
break;
}
case Visual_Family::path: {
const auto& data = prepared_data<Path_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.widths.data(), data.widths.size());
break;
}
case Visual_Family::image: {
const auto& data = prepared_data<Image_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.extents.data(), data.extents.size());
break;
}
case Visual_Family::labels: {
const auto& data = prepared_data<Labels_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.extents.data(), data.extents.size());
break;
}
default:
throw std::logic_error(
"external attribute upload requested for an unsupported Visual family");
}
}
@@ -1566,13 +1730,19 @@ void Datoviz_Visual_Backend::apply_visual(
}
const auto count = static_cast<std::uint32_t>(item_count);
if (uses_external_attributes(target.family)) {
/*
* 直接顶点族在第一次录制前即绑定三槽外部属性。Segment/Vector/Image/
* Labels 仍依赖 Datoviz 的 CPU 派生几何或纹理资源,不进入这条路径;
* 把 dense 数据先写入再改绑会违反 Datoviz 的单一属性所有权契约。
*/
ensure_external_attributes(target, point);
if (bind_target)
bind_external_attributes(target, target_index, count);
upload_external_attributes(target, point, target_index);
if (structure_changed &&
dvz_visual_set_visible(visual, point.visible) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz external visual visibility");
throw std::runtime_error(
"failed to apply Datoviz external visual visibility");
target.applied_revision = point.revision;
target.applied = point;
return;
@@ -76,26 +76,22 @@ void Gpu_Completion_Service::cancel_reserved(
std::lock_guard lock(pending->mutex);
if (pending->status == Pending_Fence::Status::reserved) pending->status = Pending_Fence::Status::canceled;
}
void Gpu_Completion_Service::acquire_slot() {
const auto started = std::chrono::steady_clock::now();
if (!slots_.try_acquire()) {
backpressure_count_.fetch_add(1, std::memory_order_relaxed);
slots_.acquire();
const auto waited = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - started)
.count();
if (waited > 0)
backpressure_wait_ns_.fetch_add(
static_cast<std::uint64_t>(waited),
std::memory_order_relaxed);
bool Gpu_Completion_Service::acquire_slot() noexcept {
std::size_t current = in_flight_.load(std::memory_order_relaxed);
while (current < static_cast<std::size_t>(default_capacity)) {
if (in_flight_.compare_exchange_weak(
current, current + 1,
std::memory_order_acq_rel,
std::memory_order_relaxed)) {
update_peak(peak_in_flight_, current + 1);
return true;
}
}
const std::size_t in_flight =
in_flight_.fetch_add(1, std::memory_order_relaxed) + 1;
update_peak(peak_in_flight_, in_flight);
backpressure_count_.fetch_add(1, std::memory_order_relaxed);
return false;
}
void Gpu_Completion_Service::release_slot() noexcept {
in_flight_.fetch_sub(1, std::memory_order_relaxed);
slots_.release();
}
Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
Completion completion, Exception_Handler on_exception, bool observe) {
@@ -107,7 +103,10 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
pending->on_exception = std::move(on_exception);
pending->observe = observe;
pending->service = this;
acquire_slot();
/* 唯一 GPU Submit 域绝不等待 completion 容量。满载时把背压作为
* Scene */
if (!acquire_slot())
return {{}, Admission_Result::capacity_exhausted};
if (stopping_.load(std::memory_order_acquire)) {
release_slot();
return {{}, Admission_Result::stopping};
@@ -12,7 +12,6 @@
#include <exception>
#include <memory>
#include <mutex>
#include <semaphore>
#include <thread>
namespace aethera::render_3d::detail {
class Gpu_Completion_Service final {
@@ -20,6 +19,7 @@ class Gpu_Completion_Service final {
public:
enum class Admission_Result : std::uint8_t {
none,
capacity_exhausted,
stopping
};
enum class Completion_Error : std::uint8_t {
@@ -84,7 +84,7 @@ private:
static void update_max(std::atomic_uint64_t& maximum,
std::uint64_t value) noexcept;
static void cancel_reserved(const std::shared_ptr<Pending_Fence>& pending);
void acquire_slot();
[[nodiscard]] bool acquire_slot() noexcept;
void release_slot() noexcept;
void publish_state(std::size_t active_fences,
std::size_t pending_fences) noexcept;
@@ -94,7 +94,6 @@ private:
static constexpr std::uint64_t fence_wait_timeout_ns = 1'000'000;
static constexpr std::uint64_t maximum_fence_age_ns = 30'000'000'000ULL;
static constexpr auto state_publication_interval = std::chrono::milliseconds(100);
std::counting_semaphore<default_capacity> slots_{default_capacity}; /* 有界 reservation 槽位。 */
std::mutex pending_mutex_; /* 保护新登记 fence 队列。 */
std::deque<std::shared_ptr<Pending_Fence>> pending_; /* 完成线程尚未分组的 fence。 */
std::mutex wait_mutex_; /* 保护完成线程的条件等待。 */
+398 -183
View File
@@ -4,6 +4,9 @@
#include <condition_variable>
#include <mutex>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace aethera::render_3d {
namespace detail {
template <Attached Object>
@@ -34,6 +37,9 @@ struct Render_Scene_3D::Private : Prev_Private {
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 组件。 */
@@ -42,6 +48,12 @@ struct Render_Scene_3D::Private : Prev_Private {
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>
@@ -50,10 +62,10 @@ struct Render_Scene_3D::Private : Prev_Private {
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);
/* CRTP 覆盖:先执行 Kernel Prepare Taskflow,再运行只负责异步入队的三维 Submit 阶段。 */
template <Attached Object, typename Callback> void process(Object* object, Callback&& callback) requires std::invocable<Callback>;
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();
};
@@ -85,9 +97,11 @@ Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
visuals, visual_identity,
&detail::Prepared_Visual_Instance::identity);
if (found == visuals.end())
visuals.push_back({visual_identity, std::move(erased)});
else
found->visual = std::move(erased);
throw std::logic_error(
"3D Visual published outside its Scene registration");
/* Builder 已经为每个 Visual 建立稳定槽位。并行 Submit 节点只写各自
* 的 visual 成员,不扩容容器,也不互相读写同一对象。 */
found->visual = std::move(erased);
});
};
visuals.push_back(binding);
@@ -158,6 +172,55 @@ detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object)
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,
@@ -168,215 +231,367 @@ void Render_Scene_3D::Private::initialize_backend(
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) {
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);
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();
});
{
std::lock_guard lock(render_mutex);
frame_in_flight = false;
}
{
std::lock_guard lock(completion_mutex);
completion_running = false;
}
completion_condition.notify_all();
};
if (completion_graph.empty()) {
finish();
return;
}
bool finish{};
{
std::lock_guard lock(completion_mutex);
completion_running = true;
gpu_frame_finished = true;
if (frame_topology_finished && !frame_finish_started) {
frame_finish_started = true;
finish = true;
}
}
/*
* GPU 完成线程只触发 Taskflow topology,不等待编码/发送节点;最终回调在 topology 完成后执行。
* 帧准入在 finish() 之前始终有效,因此 completion 图运行期间该外部 Frame 不会被下一帧复用。
*/
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));
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::make_shared<detail::Prepared_Visual_Batch>()});
std::move(prepared_visuals)});
}
template <Attached Object, typename Callback>
void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback> {
Frame_3D* frame = active_frame;
if (!frame) throw std::logic_error("3D Scene process has no external frame");
const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>();
if (!prop.view_active || prop.viewport.empty() || !backend || !backend->available()) return;
frame->mark(Frame_Trace_Marker::scene_render_started);
frame->mark(Frame_Trace_Marker::event_dispatch_started);
auto events = this->take_events(object);
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);
/*
* Visual 的 Prepared 快照只由数据变化更新,并持久保存在 Scene_Paint_Context。
* 静态帧不推进/复制 Renderable 双缓冲,也不启动 Kernel Taskflow,只提交已经准备好的
* 完整快照。动态 Visual 变脏时才进入完整 Prepare/Submit 路径并原位更新这份缓存。
*/
bool prepare_dirty = context->visuals->empty();
const auto current_prepare = object->template current_dependency_graph<Prepare_Data_Tag>();
current_prepare.for_each_bound([&](Renderable* renderable, Renderable::Private&) {
prepare_dirty = prepare_dirty || renderable->template dirty<Prepare_Data_Tag>();
});
bool submit_dirty{};
const auto current_submit = object->template current_dependency_graph<Submit_Tag>();
current_submit.for_each_bound([&](Renderable* renderable, Renderable::Private&) {
submit_dirty = submit_dirty || renderable->template dirty<Submit_Tag>();
});
if (!prepare_dirty && !submit_dirty) {
frame->mark(Frame_Trace_Marker::prepare_started);
frame->mark(Frame_Trace_Marker::prepare_finished);
frame->mark(Frame_Trace_Marker::paint_started);
const auto result = context->backend->render(
context->visuals, context->parameters, frame, std::move(events));
frame->mark(Frame_Trace_Marker::paint_finished);
if (result == detail::Async_Render_Backend::Submit_Result::queued)
std::invoke(std::forward<Callback>(callback));
return;
}
/* 后端仍可能持有上一版快照时只在真正变脏的帧执行一次 COW。
* 静态帧仅复制 shared_ptr,禁止把完整 Prepared Visual 集合复制进提交队列。 */
if (context->visuals.use_count() != 1)
context->visuals =
std::make_shared<detail::Prepared_Visual_Batch>(*context->visuals);
camera_component->advance_object();
axes_component->advance_object();
struct Frame_Context_Scope {
Frame_3D*& target; /* Visual Submit 回调读取的当前外部帧槽位。 */
Frame_3D* previous{}; /* 嵌套调用前的帧;析构时恢复。 */
~Frame_Context_Scope() { target = previous; }
} frame_context_scope{context->frame, context->frame};
context->frame = frame;
bool backend_submitted{};
Prev_Private::process(object, frame, [&](const Scene::Private::Result&) {
frame->mark(Frame_Trace_Marker::paint_started);
const auto prepare = object->template current_dependency_graph<Prepare_Data_Tag>();
prepare.for_each_bound([](Renderable* renderable, Renderable::Private& data) { if (data.dispatch->state.pending(renderable)->prepare_executed) renderable->template mark_dirty<Submit_Tag>(); });
const auto submit = object->template current_dependency_graph<Submit_Tag>();
const auto result = submit.for_each_topological_view([&](const auto& view, const Dependency_Graph::Node& node) {
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;
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) return;
const auto paint_started = std::chrono::steady_clock::now();
if (dispatch->paint.builder) {
if (!data->paint_graph) data->paint_graph = std::make_unique<Task_Graph>(
std::string(dispatch->business_name) + ".paint");
const bool rebuild = dispatch->paint.rebuild_predicate(root);
if (!data->paint_graph_built || rebuild) { *data->paint_graph = dispatch->paint.builder(root); data->paint_graph_built = true; state.paint_graph_rebuilt = true; }
state.paint_task_count = data->paint_graph->size();
if (!data->paint_graph->empty()) {
if (frame->taskflow_trace_requested())
aethera::detail::run_taskflow(
*data->paint_graph, *frame,
std::string(dispatch->business_name) + ".paint");
else
aethera::detail::run_taskflow(*data->paint_graph);
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;
}
} else { state.paint_task_count = dispatch->paint.run ? 1 : 0; if (dispatch->paint.run) dispatch->paint.run(root); }
if (!data->paint_extension.empty())
if (frame->taskflow_trace_requested())
aethera::detail::run_taskflow(
data->paint_extension, *frame,
std::string(dispatch->business_name) + ".paint.extension");
else
aethera::detail::run_taskflow(data->paint_extension);
root->template take_dirty<Submit_Tag>();
state.paint_execution_time_ns = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - paint_started).count());
dispatch->state.publish(root);
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 (!result) throw std::logic_error("render scene submit graph became invalid during submission");
if (context->visuals->empty())
throw std::logic_error("render scene submit graph published no Visual snapshots");
backend_submitted = context->backend->render(
context->visuals, context->parameters, context->frame,
std::move(events)) ==
detail::Async_Render_Backend::Submit_Result::queued;
frame->mark(Frame_Trace_Marker::paint_finished);
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);
});
if (backend_submitted) std::invoke(std::forward<Callback>(callback));
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;
}
bool submitted{};
struct Admission_Scope {
Private& data;
bool& submitted;
~Admission_Scope() {
if (submitted) return;
std::lock_guard lock(data.render_mutex);
data.frame_in_flight = false;
}
} admission{*this, submitted};
frame->mark(Frame_Trace_Marker::scene_render_requested);
const bool trace_started = aethera::detail::begin_taskflow_trace(*frame);
struct Trace_Scope {
Frame_3D* frame{}; /* 提交前异常或拒绝时关闭 Observer 写入窗口。 */
bool active{}; /* 提交成功后所有权转交异步完成回调。 */
~Trace_Scope() {
if (active) aethera::detail::finish_taskflow_trace(*frame);
}
} trace_scope{frame, trace_started};
struct Active_Frame_Scope {
Frame_3D*& target; /* 最终 Private 的同步 process 帧槽位。 */
Frame_3D* previous{}; /* 嵌套调用前的帧;析构时恢复。 */
~Active_Frame_Scope() { target = previous; }
} active_frame_scope{active_frame, active_frame};
active_frame = frame;
object->process([&] { submitted = true; frame->mark(Frame_Trace_Marker::scene_render_finished); });
if (submitted) {
trace_scope.active = false;
return Render_Result::submitted;
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;
}
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;
return Render_Result::backend_unavailable;
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>(); }