优化了 还是卡
This commit is contained in:
@@ -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_; /* 保护完成线程的条件等待。 */
|
||||
|
||||
@@ -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
|
||||
* 留给模块内部 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;
|
||||
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>(); }
|
||||
|
||||
Reference in New Issue
Block a user