性能优化
This commit is contained in:
@@ -15,6 +15,9 @@ enum class Frame_Trace_Marker : std::uint8_t {
|
||||
paint_started,
|
||||
paint_finished,
|
||||
backend_queue_entered,
|
||||
backend_prepare_started,
|
||||
backend_prepare_finished,
|
||||
backend_submit_queued,
|
||||
backend_queue_left,
|
||||
gpu_submitted,
|
||||
gpu_completed,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Datoviz_Visual_Backend.hpp"
|
||||
#include "Gpu_Completion_Service.hpp"
|
||||
#include "Render_Domain.hpp"
|
||||
#include <concurrentqueue-1.0.5/blockingconcurrentqueue.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
@@ -9,7 +10,9 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
@@ -24,83 +27,127 @@ Mouse_Button held_button(Mouse_Button_Mask buttons) {
|
||||
return Mouse_Button::none;
|
||||
}
|
||||
void record_datoviz_trace(Frame_3D* frame, const Datoviz_Frame_Trace& trace) {
|
||||
if (trace.apply_ns) frame->record(Frame_Trace_Measurement::backend_apply_ns, trace.apply_ns);
|
||||
if (trace.emit_ns) frame->record(Frame_Trace_Measurement::backend_plan_ns, trace.emit_ns);
|
||||
if (trace.execute_ns) frame->record(Frame_Trace_Measurement::backend_execute_ns, trace.execute_ns);
|
||||
if (trace.submit_ns) frame->record(Frame_Trace_Measurement::backend_submit_ns, trace.submit_ns);
|
||||
if (trace.readback_ns) frame->record(Frame_Trace_Measurement::readback_ns, trace.readback_ns);
|
||||
if (trace.apply_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_apply_ns, trace.apply_ns);
|
||||
if (trace.emit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_plan_ns, trace.emit_ns);
|
||||
if (trace.execute_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_execute_ns, trace.execute_ns);
|
||||
if (trace.submit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_submit_ns, trace.submit_ns);
|
||||
if (trace.readback_ns)
|
||||
frame->record(Frame_Trace_Measurement::readback_ns, trace.readback_ns);
|
||||
if (!trace.gpu) return;
|
||||
frame->record(Frame_Trace_Measurement::gpu_render_ns, trace.gpu->render_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_transition_ns, trace.gpu->transition_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_transition_ns,
|
||||
trace.gpu->transition_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_copy_ns, trace.gpu->copy_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_total_ns, trace.gpu->total_ns);
|
||||
}
|
||||
}
|
||||
struct Async_Render_Backend::Implementation : std::enable_shared_from_this<Implementation> {
|
||||
struct Async_Render_Backend::Implementation
|
||||
: std::enable_shared_from_this<Implementation> {
|
||||
struct Completion {
|
||||
Frame_3D* output{}; /* 调用方持有到本项回调结束。 */
|
||||
Frame_Callback callback{}; /* 提交时捕获,避免后来替换出口改变历史语义。 */
|
||||
};
|
||||
struct Pending {
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> backend_frame{};
|
||||
std::vector<Completion> completions{}; /* 一次实际 GPU 图需要结清的全部逻辑提交。 */
|
||||
Extent extent{};
|
||||
Frame_3D* output{}; /* 调用方持有到本项回调返回。 */
|
||||
Frame_Callback callback{}; /* render 入队时捕获的完成出口。 */
|
||||
};
|
||||
struct Submission {
|
||||
Prepared_Visual_Batch visuals{}; /* 最新提交的大批量不可变 Visual 快照。 */
|
||||
Scene_3D_Parameters parameters{}; /* 小型 Scene 参数按值复制进闭包。 */
|
||||
std::vector<Completion> completions{}; /* 被最新画面合并的历史完成项。 */
|
||||
Prepared_Visual_Batch visuals{}; /* Scene CPU Prepare 发布的大批量不可变快照。 */
|
||||
Scene_3D_Parameters parameters{}; /* 本帧 Scene、Camera 与轴参数快照。 */
|
||||
std::vector<Completion> completions{}; /* 合并到同一实际画面的逻辑帧。 */
|
||||
};
|
||||
std::shared_ptr<Render_Domain> render_domain;
|
||||
std::unique_ptr<Datoviz_Visual_Backend> backend;
|
||||
std::uint32_t gpu_index{};
|
||||
bool validation_enabled{};
|
||||
std::vector<Visual_Registration> visual_registrations{};
|
||||
mutable std::mutex render_mutex;
|
||||
std::optional<Submission> pending_submission{}; /* 唯一待提交画面;旧大数据由新快照直接替换。 */
|
||||
Frame_Callback frame_callback{};
|
||||
bool frame_in_flight{};
|
||||
mutable std::mutex failure_mutex;
|
||||
std::exception_ptr failure{};
|
||||
std::atomic_bool available{};
|
||||
Implementation(std::uint32_t gpu_index_value, bool validation_enabled_value,
|
||||
struct Pending {
|
||||
Datoviz_Visual_Backend::Pending_Frame backend_frame{}; /* 三缓冲目标对应的已录制提交。 */
|
||||
std::vector<Completion> completions{}; /* GPU 完成时一起结清的逻辑帧。 */
|
||||
Extent extent{}; /* 失败时生成空结果所需的尺寸。 */
|
||||
};
|
||||
struct Gpu_Completion {
|
||||
std::shared_ptr<Pending> pending{}; /* 完成前保持帧目标和回调集合存活。 */
|
||||
std::optional<Gpu_Completion_Service::Result> result{}; /* fence 正常交付时的结果。 */
|
||||
std::exception_ptr failure{}; /* 提交域或完成服务的 Unknown Failure。 */
|
||||
};
|
||||
struct Event_Command {
|
||||
std::shared_ptr<const Event> event{}; /* Scene 转移给准备域的输入事件。 */
|
||||
Extent viewport{}; /* 事件产生时的物理像素视口。 */
|
||||
};
|
||||
struct Stop_Command {};
|
||||
using Command = std::variant<Submission, Gpu_Completion,
|
||||
Event_Command, Stop_Command>;
|
||||
|
||||
static constexpr std::size_t command_capacity = 64;
|
||||
std::shared_ptr<Render_Domain> render_domain; /* 同 GPU 唯一的轻量 Queue Submit 域。 */
|
||||
std::unique_ptr<Datoviz_Visual_Backend> backend{}; /* 仅准备线程构造、准备、收集和销毁。 */
|
||||
std::uint32_t gpu_index{}; /* Datoviz 共享 GPU Context 的设备下标。 */
|
||||
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
|
||||
std::vector<Visual_Registration> visual_registrations{}; /* 首帧初始化后释放的稳定 Visual 注册表。 */
|
||||
moodycamel::BlockingConcurrentQueue<Command> commands{command_capacity}; /* 多生产者到单准备线程的命令入口。 */
|
||||
std::thread preparation_thread{}; /* 每 Scene 独立的 Datoviz CPU 准备域。 */
|
||||
std::optional<Submission> deferred_submission{}; /* 三槽占满时仅保留的最新画面和历史回调。 */
|
||||
std::mutex callback_mutex{}; /* 保护完成回调替换与 render 捕获。 */
|
||||
Frame_Callback frame_callback{}; /* 后续 render 捕获的完成出口。 */
|
||||
std::mutex failure_mutex{}; /* 保护跨线程传播的最后 Unknown Failure。 */
|
||||
std::exception_ptr failure{}; /* 后端不可用的根因。 */
|
||||
std::atomic_bool available{true}; /* 对 Scene 发布的后端可接收状态。 */
|
||||
std::atomic_bool stop_requested{}; /* 拒绝新命令并等待在途槽结清。 */
|
||||
|
||||
Implementation(std::uint32_t gpu_index_value,
|
||||
bool validation_enabled_value,
|
||||
std::vector<Visual_Registration> visuals)
|
||||
: render_domain(Render_Domain::acquire(gpu_index_value)),
|
||||
gpu_index(gpu_index_value), validation_enabled(validation_enabled_value),
|
||||
visual_registrations(std::move(visuals)) {
|
||||
available.store(true, std::memory_order_release);
|
||||
}
|
||||
~Implementation() {
|
||||
available.store(false, std::memory_order_release);
|
||||
if (!backend) return;
|
||||
const auto destroyed = render_domain->invoke([this] { backend.reset(); });
|
||||
if (!destroyed) backend.release();
|
||||
}
|
||||
gpu_index(gpu_index_value),
|
||||
validation_enabled(validation_enabled_value),
|
||||
visual_registrations(std::move(visuals)),
|
||||
preparation_thread([this] { run(); }) {}
|
||||
~Implementation() { stop(); }
|
||||
|
||||
void stop() noexcept;
|
||||
void run() noexcept;
|
||||
void fail(std::exception_ptr value) noexcept;
|
||||
void enqueue(Command command);
|
||||
void initialize(const Scene_3D_Parameters& parameters);
|
||||
void render(Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
Frame_3D* frame);
|
||||
void submit(Submission submission);
|
||||
void finish(std::shared_ptr<Pending> pending,
|
||||
Gpu_Completion_Service::Result completion) noexcept;
|
||||
void render(Prepared_Visual_Batch visuals,
|
||||
Scene_3D_Parameters parameters, Frame_3D* frame);
|
||||
void accept(Submission submission);
|
||||
void merge_deferred(Submission submission);
|
||||
void prepare(Submission submission);
|
||||
void submit(std::shared_ptr<Pending> pending);
|
||||
void finish(Gpu_Completion completion);
|
||||
void resolve(std::shared_ptr<Pending> pending,
|
||||
std::optional<Datoviz_Visual_Backend::Completed_Frame> completed);
|
||||
void complete(std::shared_ptr<Pending> pending);
|
||||
static std::vector<Frame_3D*> frames(const Pending& pending);
|
||||
void complete(std::vector<Completion> completions);
|
||||
void dispatch(Event_Command command);
|
||||
};
|
||||
|
||||
Async_Render_Backend::Async_Render_Backend(
|
||||
std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals)
|
||||
: implementation_(std::make_shared<Implementation>(
|
||||
gpu_index, validation_enabled, std::move(visuals))) {}
|
||||
Async_Render_Backend::~Async_Render_Backend() = default;
|
||||
void Async_Render_Backend::Implementation::fail(std::exception_ptr value) noexcept {
|
||||
|
||||
void Async_Render_Backend::Implementation::stop() noexcept {
|
||||
available.store(false, std::memory_order_release);
|
||||
if (!stop_requested.exchange(true, std::memory_order_acq_rel)) {
|
||||
try {
|
||||
enqueue(Stop_Command{});
|
||||
}
|
||||
catch (...) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
if (preparation_thread.joinable()) preparation_thread.join();
|
||||
}
|
||||
void Async_Render_Backend::Implementation::fail(
|
||||
std::exception_ptr value) noexcept {
|
||||
{
|
||||
std::lock_guard lock(failure_mutex);
|
||||
failure = std::move(value);
|
||||
}
|
||||
available.store(false, std::memory_order_release);
|
||||
}
|
||||
void Async_Render_Backend::Implementation::enqueue(Command command) {
|
||||
if (!commands.enqueue(std::move(command))) throw std::bad_alloc{};
|
||||
}
|
||||
void Async_Render_Backend::Implementation::initialize(
|
||||
const Scene_3D_Parameters& parameters) {
|
||||
if (backend) return;
|
||||
@@ -109,149 +156,154 @@ void Async_Render_Backend::Implementation::initialize(
|
||||
visual_registrations.clear();
|
||||
visual_registrations.shrink_to_fit();
|
||||
}
|
||||
std::vector<Frame_3D*> Async_Render_Backend::Implementation::frames(
|
||||
const Pending& pending) {
|
||||
std::vector<Frame_3D*> result;
|
||||
result.reserve(pending.completions.size());
|
||||
for (const auto& completion : pending.completions)
|
||||
result.push_back(completion.output);
|
||||
return result;
|
||||
}
|
||||
void Async_Render_Backend::Implementation::render(
|
||||
Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
Frame_3D* frame) {
|
||||
if (!frame)
|
||||
throw std::invalid_argument("3D backend requires a non-null external frame");
|
||||
if (!available.load(std::memory_order_acquire)) return;
|
||||
frame->mark(Frame_Trace_Marker::backend_queue_entered);
|
||||
std::optional<Submission> submission;
|
||||
Frame_Callback callback;
|
||||
{
|
||||
std::lock_guard lock(render_mutex);
|
||||
Completion completion{frame, frame_callback};
|
||||
if (frame_in_flight) {
|
||||
Submission latest{std::move(visuals), parameters, {}};
|
||||
if (pending_submission)
|
||||
latest.completions = std::move(pending_submission->completions);
|
||||
latest.completions.push_back(std::move(completion));
|
||||
pending_submission = std::move(latest);
|
||||
std::lock_guard lock(callback_mutex);
|
||||
callback = frame_callback;
|
||||
}
|
||||
frame->mark(Frame_Trace_Marker::backend_queue_entered);
|
||||
enqueue(Submission{std::move(visuals), parameters,
|
||||
{Completion{frame, std::move(callback)}}});
|
||||
}
|
||||
void Async_Render_Backend::Implementation::merge_deferred(
|
||||
Submission submission) {
|
||||
if (deferred_submission) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
frame_in_flight = true;
|
||||
submission.emplace(Submission{std::move(visuals), parameters,
|
||||
{std::move(completion)}});
|
||||
if (backend &&
|
||||
!backend->can_prepare(submission.parameters, submission.visuals)) {
|
||||
merge_deferred(std::move(submission));
|
||||
return;
|
||||
}
|
||||
submit(std::move(*submission));
|
||||
prepare(std::move(submission));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::submit(Submission submission) {
|
||||
auto self = shared_from_this();
|
||||
auto pending = std::make_shared<Pending>();
|
||||
pending->completions = std::move(submission.completions);
|
||||
pending->extent = submission.parameters.viewport;
|
||||
const auto sequence = pending->completions.back().output->identity().sequence;
|
||||
struct Submission_Task {
|
||||
Prepared_Visual_Batch visuals;
|
||||
Scene_3D_Parameters parameters;
|
||||
std::uint64_t sequence{};
|
||||
std::shared_ptr<Pending> pending;
|
||||
};
|
||||
auto task = std::make_shared<Submission_Task>(Submission_Task{
|
||||
std::move(submission.visuals), submission.parameters, sequence, pending});
|
||||
const auto queued = render_domain->post(
|
||||
[self, task] {
|
||||
for (const auto& completion : task->pending->completions)
|
||||
completion.output->mark(Frame_Trace_Marker::backend_queue_left);
|
||||
self->initialize(task->parameters);
|
||||
void Async_Render_Backend::Implementation::prepare(Submission submission) {
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> prepared;
|
||||
try {
|
||||
initialize(submission.parameters);
|
||||
for (const auto& completion : submission.completions)
|
||||
completion.output->mark(Frame_Trace_Marker::backend_prepare_started);
|
||||
const auto sequence =
|
||||
submission.completions.back().output->identity().sequence;
|
||||
const bool readback = std::ranges::any_of(
|
||||
task->pending->completions,
|
||||
[](const Completion& completion) {
|
||||
submission.completions, [](const Completion& completion) {
|
||||
return completion.output->output() == Frame_3D_Output::pixels;
|
||||
});
|
||||
auto reservation = Gpu_Completion_Service::instance().prepare(
|
||||
[self, pending = task->pending](Gpu_Completion_Service::Result result) {
|
||||
self->finish(pending, std::move(result));
|
||||
},
|
||||
[self, pending = task->pending](std::exception_ptr value) {
|
||||
self->fail(std::move(value));
|
||||
self->resolve(pending, std::nullopt);
|
||||
}, true);
|
||||
if (!reservation) {
|
||||
self->resolve(task->pending, std::nullopt);
|
||||
return;
|
||||
}
|
||||
task->pending->backend_frame = self->backend->submit(
|
||||
task->parameters, task->visuals, task->sequence, true, readback);
|
||||
if (!task->pending->backend_frame) {
|
||||
self->resolve(task->pending, std::nullopt);
|
||||
return;
|
||||
}
|
||||
for (const auto& completion : task->pending->completions) {
|
||||
record_datoviz_trace(completion.output,
|
||||
task->pending->backend_frame->trace);
|
||||
completion.output->mark(Frame_Trace_Marker::gpu_submitted);
|
||||
}
|
||||
reservation.reservation.watch(task->pending->backend_frame->device,
|
||||
task->pending->backend_frame->fence);
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
self->fail(std::move(value));
|
||||
self->resolve(pending, std::nullopt);
|
||||
});
|
||||
if (queued != Render_Domain::Post_Result::queued) {
|
||||
fail(std::make_exception_ptr(
|
||||
std::runtime_error("render domain stopped before 3D submission")));
|
||||
resolve(std::move(pending), std::nullopt);
|
||||
}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::finish(
|
||||
std::shared_ptr<Pending> pending,
|
||||
Gpu_Completion_Service::Result completion) noexcept {
|
||||
for (const auto& item : pending->completions) {
|
||||
item.output->mark(Frame_Trace_Marker::gpu_completed);
|
||||
item.output->record(Frame_Trace_Measurement::gpu_fence_wait_ns,
|
||||
completion.wait_duration_ns);
|
||||
}
|
||||
auto self = shared_from_this();
|
||||
try {
|
||||
const auto result = render_domain->post(
|
||||
[self, pending, completion] {
|
||||
if (!pending->backend_frame) {
|
||||
self->resolve(pending, std::nullopt);
|
||||
return;
|
||||
}
|
||||
if (completion.error !=
|
||||
Gpu_Completion_Service::Completion_Error::none) {
|
||||
self->backend->discard(std::move(*pending->backend_frame));
|
||||
pending->backend_frame.reset();
|
||||
self->resolve(pending, std::nullopt);
|
||||
return;
|
||||
}
|
||||
for (const auto& item : pending->completions)
|
||||
item.output->mark(Frame_Trace_Marker::readback_started);
|
||||
auto completed = self->backend->collect(
|
||||
std::move(*pending->backend_frame));
|
||||
pending->backend_frame.reset();
|
||||
self->resolve(pending, std::move(completed));
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
self->fail(std::move(value));
|
||||
self->resolve(pending, std::nullopt);
|
||||
});
|
||||
if (result != Render_Domain::Post_Result::queued) {
|
||||
fail(std::make_exception_ptr(std::runtime_error(
|
||||
"render domain stopped before GPU completion collection")));
|
||||
resolve(std::move(pending), std::nullopt);
|
||||
}
|
||||
prepared = backend->prepare(
|
||||
submission.parameters, submission.visuals, sequence, true, readback);
|
||||
}
|
||||
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));
|
||||
return;
|
||||
}
|
||||
for (const auto& completion : submission.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);
|
||||
}
|
||||
catch (...) {
|
||||
backend->discard(std::move(pending->backend_frame));
|
||||
fail(std::current_exception());
|
||||
resolve(std::move(pending), std::nullopt);
|
||||
}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::submit(
|
||||
std::shared_ptr<Pending> pending) {
|
||||
auto self = shared_from_this();
|
||||
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());
|
||||
}
|
||||
}, true);
|
||||
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);
|
||||
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);
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
try {
|
||||
self->enqueue(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"))});
|
||||
}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::resolve(
|
||||
std::shared_ptr<Pending> pending,
|
||||
std::optional<Datoviz_Visual_Backend::Completed_Frame> completed) {
|
||||
std::vector<Frame_3D*> outputs;
|
||||
outputs.reserve(pending->completions.size());
|
||||
for (const auto& completion : pending->completions)
|
||||
outputs.push_back(completion.output);
|
||||
if (completed) {
|
||||
auto outputs = frames(*pending);
|
||||
for (Frame_3D* output : outputs) {
|
||||
output->mark(Frame_Trace_Marker::readback_finished);
|
||||
record_datoviz_trace(output, completed->trace);
|
||||
@@ -262,48 +314,14 @@ void Async_Render_Backend::Implementation::resolve(
|
||||
else {
|
||||
const auto pixel_count = static_cast<std::size_t>(pending->extent.width) *
|
||||
pending->extent.height * 4U;
|
||||
auto outputs = frames(*pending);
|
||||
Frame_3D_Access::assign_pixels(outputs, pending->extent,
|
||||
std::vector<std::byte>(pixel_count));
|
||||
Frame_3D_Access::assign_pixels(
|
||||
outputs, pending->extent, std::vector<std::byte>(pixel_count));
|
||||
}
|
||||
complete(std::move(pending));
|
||||
complete(std::move(pending->completions));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::complete(
|
||||
std::shared_ptr<Pending> pending) {
|
||||
std::optional<Submission> next;
|
||||
const std::size_t rendered_count = pending->completions.size();
|
||||
{
|
||||
std::lock_guard lock(render_mutex);
|
||||
frame_in_flight = false;
|
||||
if (pending_submission && available.load(std::memory_order_acquire)) {
|
||||
next = std::move(pending_submission);
|
||||
pending_submission.reset();
|
||||
frame_in_flight = true;
|
||||
}
|
||||
else if (pending_submission) {
|
||||
auto historical = std::move(pending_submission->completions);
|
||||
pending_submission.reset();
|
||||
pending->completions.insert(
|
||||
pending->completions.end(),
|
||||
std::make_move_iterator(historical.begin()),
|
||||
std::make_move_iterator(historical.end()));
|
||||
}
|
||||
}
|
||||
if (pending->completions.size() > rendered_count && rendered_count != 0) {
|
||||
Frame_3D* source = pending->completions.front().output;
|
||||
for (std::size_t index = rendered_count;
|
||||
index < pending->completions.size(); ++index) {
|
||||
try {
|
||||
Frame_3D_Access::share_pixels(
|
||||
source, pending->completions[index].output);
|
||||
}
|
||||
catch (...) {
|
||||
std::lock_guard lock(failure_mutex);
|
||||
failure = std::current_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& item : pending->completions) {
|
||||
std::vector<Completion> completions) {
|
||||
for (auto& item : completions) {
|
||||
if (!item.output || !item.callback) continue;
|
||||
item.output->mark(Frame_Trace_Marker::callback_started);
|
||||
try {
|
||||
@@ -315,15 +333,127 @@ void Async_Render_Backend::Implementation::complete(
|
||||
}
|
||||
item.output->mark(Frame_Trace_Marker::callback_finished);
|
||||
}
|
||||
if (next) submit(std::move(*next));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::finish(Gpu_Completion completion) {
|
||||
for (const auto& item : completion.pending->completions) {
|
||||
item.output->mark(Frame_Trace_Marker::gpu_completed);
|
||||
if (completion.result)
|
||||
item.output->record(Frame_Trace_Measurement::gpu_fence_wait_ns,
|
||||
completion.result->wait_duration_ns);
|
||||
item.output->mark(Frame_Trace_Marker::readback_started);
|
||||
}
|
||||
std::optional<Datoviz_Visual_Backend::Completed_Frame> completed;
|
||||
if (completion.failure) {
|
||||
fail(std::move(completion.failure));
|
||||
backend->discard(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
else if (!completion.result ||
|
||||
completion.result->error !=
|
||||
Gpu_Completion_Service::Completion_Error::none) {
|
||||
backend->discard(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
else {
|
||||
completed = backend->collect(
|
||||
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);
|
||||
}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::dispatch(Event_Command command) {
|
||||
if (!backend || !command.event) return;
|
||||
const auto* pointer =
|
||||
dynamic_cast<const Pointer_Event_Capability*>(command.event.get());
|
||||
const auto* wheel =
|
||||
dynamic_cast<const Wheel_Event_Capability*>(command.event.get());
|
||||
const auto* key = dynamic_cast<const Key_Event*>(command.event.get());
|
||||
if (wheel) {
|
||||
backend->dispatch_wheel(
|
||||
static_cast<float>(pointer->position_x()),
|
||||
static_cast<float>(pointer->position_y()),
|
||||
wheel_step(wheel->pixel_delta_x_value(),
|
||||
wheel->angle_delta_x_value()),
|
||||
wheel_step(wheel->pixel_delta_y_value(),
|
||||
wheel->angle_delta_y_value()),
|
||||
pointer->keyboard_modifiers(), command.viewport);
|
||||
}
|
||||
else if (pointer) {
|
||||
backend->dispatch_pointer(
|
||||
command.event->type,
|
||||
static_cast<float>(pointer->position_x()),
|
||||
static_cast<float>(pointer->position_y()),
|
||||
command.event->type == Event_Type::pointer_move
|
||||
? held_button(pointer->pointer_buttons())
|
||||
: pointer->pointer_button(),
|
||||
pointer->keyboard_modifiers(), command.viewport);
|
||||
}
|
||||
else if (key) backend->dispatch_key(*key);
|
||||
command.event->accept();
|
||||
}
|
||||
void Async_Render_Backend::Implementation::run() noexcept {
|
||||
bool stopping{};
|
||||
for (;;) {
|
||||
Command command;
|
||||
commands.wait_dequeue(command);
|
||||
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 if (auto* event = std::get_if<Event_Command>(&command))
|
||||
dispatch(std::move(*event));
|
||||
else stopping = true;
|
||||
}
|
||||
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 (stopping && (!backend || backend->idle())) {
|
||||
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();
|
||||
try { resolve(std::move(pending), std::nullopt); }
|
||||
catch (...) { fail(std::current_exception()); }
|
||||
}
|
||||
backend.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Async_Render_Backend::render(
|
||||
Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
Frame_3D* frame) {
|
||||
implementation_->render(std::move(visuals), parameters, frame);
|
||||
}
|
||||
void Async_Render_Backend::set_frame_callback(Frame_Callback callback) {
|
||||
std::lock_guard lock(implementation_->render_mutex);
|
||||
std::lock_guard lock(implementation_->callback_mutex);
|
||||
implementation_->frame_callback = std::move(callback);
|
||||
}
|
||||
bool Async_Render_Backend::available() const noexcept {
|
||||
@@ -353,39 +483,13 @@ Async_Render_Backend::dispatch_event(std::shared_ptr<const Event> event,
|
||||
return Dispatch_Event_Result::invalid_event;
|
||||
}
|
||||
else if (!key) return Dispatch_Event_Result::ignored;
|
||||
const auto queued = self->render_domain->post(
|
||||
[self, event = std::move(event), viewport] {
|
||||
if (!self->backend) return;
|
||||
if (const auto* event_wheel =
|
||||
dynamic_cast<const Wheel_Event_Capability*>(event.get())) {
|
||||
const auto* event_pointer =
|
||||
dynamic_cast<const Pointer_Event_Capability*>(event.get());
|
||||
self->backend->dispatch_wheel(
|
||||
static_cast<float>(event_pointer->position_x()),
|
||||
static_cast<float>(event_pointer->position_y()),
|
||||
wheel_step(event_wheel->pixel_delta_x_value(),
|
||||
event_wheel->angle_delta_x_value()),
|
||||
wheel_step(event_wheel->pixel_delta_y_value(),
|
||||
event_wheel->angle_delta_y_value()),
|
||||
event_pointer->keyboard_modifiers(), viewport);
|
||||
try {
|
||||
self->enqueue(Implementation::Event_Command{std::move(event), viewport});
|
||||
}
|
||||
else if (const auto* event_pointer =
|
||||
dynamic_cast<const Pointer_Event_Capability*>(event.get()))
|
||||
self->backend->dispatch_pointer(
|
||||
event->type,
|
||||
static_cast<float>(event_pointer->position_x()),
|
||||
static_cast<float>(event_pointer->position_y()),
|
||||
event->type == Event_Type::pointer_move
|
||||
? held_button(event_pointer->pointer_buttons())
|
||||
: event_pointer->pointer_button(),
|
||||
event_pointer->keyboard_modifiers(), viewport);
|
||||
else if (const auto* event_key = dynamic_cast<const Key_Event*>(event.get()))
|
||||
self->backend->dispatch_key(*event_key);
|
||||
event->accept();
|
||||
},
|
||||
[self](std::exception_ptr value) { self->fail(std::move(value)); });
|
||||
return queued == Render_Domain::Post_Result::queued
|
||||
? Dispatch_Event_Result::queued
|
||||
: Dispatch_Event_Result::backend_unavailable;
|
||||
catch (...) {
|
||||
self->fail(std::current_exception());
|
||||
return Dispatch_Event_Result::backend_unavailable;
|
||||
}
|
||||
return Dispatch_Event_Result::queued;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] DvzGpuCtx* gpu_context() const noexcept { return gpu_context_; }
|
||||
[[nodiscard]] std::mutex& api_mutex() noexcept { return api_mutex_; }
|
||||
|
||||
private:
|
||||
Datoviz_Render_Context(std::uint32_t gpu_index, bool validation_enabled) {
|
||||
@@ -214,6 +215,7 @@ private:
|
||||
}
|
||||
|
||||
DvzGpuCtx* gpu_context_{}; /* 共享 GPU Device 与分配器的唯一所有权。 */
|
||||
std::mutex api_mutex_{}; /* 同 GPU Datoviz/Vulkan API 与 Frame Target 状态的唯一串行化域。 */
|
||||
};
|
||||
|
||||
class Datoviz_Visual_Backend::Frame_Target final {
|
||||
@@ -278,7 +280,8 @@ public:
|
||||
destroy();
|
||||
}
|
||||
void begin(bool observe, bool readback) {
|
||||
if (in_flight_) throw std::logic_error("Datoviz frame target is still in flight");
|
||||
if (recording_ || prepared_ || in_flight_)
|
||||
throw std::logic_error("Datoviz frame target is not available");
|
||||
observing_ = observe;
|
||||
readback_requested_ = readback;
|
||||
if (observing_ && !timestamps_initialized_) {
|
||||
@@ -352,7 +355,7 @@ public:
|
||||
frame.wait_semaphore_fd = -1;
|
||||
return frame;
|
||||
}
|
||||
void submit() {
|
||||
void finish_recording() {
|
||||
if (!recording_) throw std::logic_error("Datoviz frame target is not recording");
|
||||
const VkCommandBuffer command_buffer = dvz_commands_handle(commands_);
|
||||
if (observing_ && timestamps_supported_) {
|
||||
@@ -414,10 +417,16 @@ public:
|
||||
dvz_fence_reset(fence_);
|
||||
dvz_submit(submit_);
|
||||
dvz_submit_command(submit_, dvz_commands_handle(commands_));
|
||||
prepared_ = true;
|
||||
}
|
||||
void submit() {
|
||||
if (!prepared_ || in_flight_)
|
||||
throw std::logic_error("Datoviz frame target has no prepared submission");
|
||||
DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN);
|
||||
if (dvz_submit_send(submit_, dvz_queue_handle(queue),
|
||||
dvz_fence_handle(fence_)) != VK_SUCCESS)
|
||||
throw std::runtime_error("failed to submit Datoviz point frame");
|
||||
prepared_ = false;
|
||||
in_flight_ = true;
|
||||
completed_layout_ = readback_requested_ ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
@@ -443,7 +452,14 @@ public:
|
||||
return result;
|
||||
}
|
||||
void discard_after_completion() {
|
||||
if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame");
|
||||
if (prepared_) {
|
||||
prepared_ = false;
|
||||
observing_ = false;
|
||||
readback_requested_ = false;
|
||||
return;
|
||||
}
|
||||
if (!in_flight_)
|
||||
throw std::logic_error("Datoviz frame target has no pending frame");
|
||||
in_flight_ = false;
|
||||
observing_ = false;
|
||||
readback_requested_ = false;
|
||||
@@ -457,9 +473,14 @@ public:
|
||||
[[nodiscard]] std::uint64_t generation() const noexcept {
|
||||
return generation_;
|
||||
}
|
||||
[[nodiscard]] Extent extent() const noexcept { return extent_; }
|
||||
[[nodiscard]] bool available() const noexcept {
|
||||
return !recording_ && !prepared_ && !in_flight_;
|
||||
}
|
||||
void abort() noexcept {
|
||||
if (recording_ && commands_ != nullptr) dvz_cmd_reset(commands_);
|
||||
recording_ = false;
|
||||
prepared_ = false;
|
||||
observing_ = false;
|
||||
readback_requested_ = false;
|
||||
}
|
||||
@@ -544,9 +565,9 @@ private:
|
||||
return timing;
|
||||
}
|
||||
void destroy() {
|
||||
if (in_flight_)
|
||||
if (recording_ || prepared_ || in_flight_)
|
||||
throw std::logic_error(
|
||||
"Datoviz frame target is still in flight during destruction");
|
||||
"Datoviz frame target is busy during destruction");
|
||||
if (query_pool_ != VK_NULL_HANDLE && gpu_context_ != nullptr) {
|
||||
vkDestroyQueryPool(
|
||||
dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)),
|
||||
@@ -598,18 +619,26 @@ private:
|
||||
float timestamp_period_ns_{};
|
||||
std::uint32_t timestamp_valid_bits_{};
|
||||
bool recording_{};
|
||||
bool prepared_{};
|
||||
bool in_flight_{};
|
||||
bool observing_{};
|
||||
bool readback_requested_{};
|
||||
bool timestamps_initialized_{};
|
||||
bool timestamps_supported_{};
|
||||
};
|
||||
struct Datoviz_Visual_Backend::Frame_Targets {
|
||||
static constexpr std::size_t count = 3;
|
||||
std::array<std::unique_ptr<Frame_Target>, count> values{}; /* 固定三槽,槽地址在后端生命周期内稳定。 */
|
||||
};
|
||||
Datoviz_Visual_Backend::Datoviz_Visual_Backend(
|
||||
std::uint32_t gpu_index, bool validation_enabled,
|
||||
const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene) : domain_thread_(std::this_thread::get_id()) {
|
||||
const Scene_3D_Parameters& initial_scene)
|
||||
: domain_thread_(std::this_thread::get_id()),
|
||||
targets_(std::make_unique<Frame_Targets>()) {
|
||||
try {
|
||||
render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled);
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
auto* gpu_context = render_context_->gpu_context();
|
||||
DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config(
|
||||
dvz_gpu_ctx_device(gpu_context), dvz_gpu_ctx_alloc(gpu_context));
|
||||
@@ -1082,7 +1111,7 @@ void Datoviz_Visual_Backend::apply_camera(const plot::Camera_Descriptor& source)
|
||||
void Datoviz_Visual_Backend::apply(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared) {
|
||||
require_domain();
|
||||
if (target_extent_ != scene.viewport) {
|
||||
if (figure_extent_ != scene.viewport) {
|
||||
if (dvz_figure_resize(figure_, scene.viewport.width,
|
||||
scene.viewport.height) != DVZ_OK)
|
||||
throw std::runtime_error("failed to resize Datoviz point figure");
|
||||
@@ -1091,6 +1120,7 @@ void Datoviz_Visual_Backend::apply(
|
||||
scene.viewport.width, scene.viewport.height, 1.0F, 1.0F
|
||||
};
|
||||
dvz_input_emit_resize(input_router_, &resize);
|
||||
figure_extent_ = scene.viewport;
|
||||
}
|
||||
apply_camera(scene.camera);
|
||||
apply_axes(scene);
|
||||
@@ -1306,6 +1336,8 @@ void Datoviz_Visual_Backend::dispatch_pointer(
|
||||
::aethera::Mouse_Button mouse_button,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
input_changed_ = true;
|
||||
if (applied_camera_ && applied_camera_->controller == plot::Camera_Controller::turntable) {
|
||||
if (mouse_button == ::aethera::Mouse_Button::left &&
|
||||
!applied_camera_->turntable_control.rotate_enabled) return;
|
||||
@@ -1330,6 +1362,8 @@ void Datoviz_Visual_Backend::dispatch_wheel(
|
||||
float x, float y, float delta_x, float delta_y,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
input_changed_ = true;
|
||||
if (applied_camera_ &&
|
||||
applied_camera_->controller == plot::Camera_Controller::turntable &&
|
||||
!applied_camera_->turntable_control.zoom_enabled) return;
|
||||
@@ -1341,6 +1375,8 @@ void Datoviz_Visual_Backend::dispatch_wheel(
|
||||
void Datoviz_Visual_Backend::dispatch_key(
|
||||
const ::aethera::Key_Event& event) {
|
||||
require_domain();
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
input_changed_ = true;
|
||||
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
|
||||
DvzResult reset = DVZ_ERROR;
|
||||
switch (dvz_controller_type(camera_controller_)) {
|
||||
@@ -1400,11 +1436,38 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(
|
||||
}
|
||||
return artifact;
|
||||
}
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::submit(
|
||||
std::optional<std::uint8_t> Datoviz_Visual_Backend::acquire_target(Extent extent) {
|
||||
for (std::uint8_t index = 0; index < targets_->values.size(); ++index) {
|
||||
auto& candidate = targets_->values[index];
|
||||
if (candidate && candidate->available() && candidate->extent() == extent)
|
||||
return index;
|
||||
}
|
||||
for (std::uint8_t index = 0; index < targets_->values.size(); ++index) {
|
||||
auto& candidate = targets_->values[index];
|
||||
if (candidate && !candidate->available()) continue;
|
||||
candidate = std::make_unique<Frame_Target>(
|
||||
render_context_->gpu_context(), extent, ++target_generation_);
|
||||
return index;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
Datoviz_Visual_Backend::Frame_Target& Datoviz_Visual_Backend::target(
|
||||
const Pending_Frame& pending) {
|
||||
if (pending.target_index >= targets_->values.size())
|
||||
throw std::logic_error("Datoviz pending frame target index is invalid");
|
||||
auto& result = targets_->values[pending.target_index];
|
||||
if (!result || result->generation() != pending.target_generation)
|
||||
throw std::logic_error("Datoviz pending frame target no longer exists");
|
||||
return *result;
|
||||
}
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::prepare(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback) {
|
||||
require_domain();
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
if (scene.viewport.empty()) return std::nullopt;
|
||||
const auto target_index = acquire_target(scene.viewport);
|
||||
if (!target_index) return std::nullopt;
|
||||
Datoviz_Frame_Trace trace;
|
||||
trace.render_sequence = frame_sequence;
|
||||
trace.observed = observe;
|
||||
@@ -1442,13 +1505,13 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
}
|
||||
}
|
||||
if (observe) trace.apply_ns = trace_now_ns() - phase_started;
|
||||
if (target_ == nullptr || target_extent_ != scene.viewport) {
|
||||
target_.reset();
|
||||
target_extent_ = scene.viewport;
|
||||
target_ = std::make_unique<Frame_Target>(render_context_->gpu_context(), target_extent_,
|
||||
++target_generation_);
|
||||
}
|
||||
target_->begin(observe, readback);
|
||||
auto& frame_target = *targets_->values[*target_index];
|
||||
frame_target.begin(observe, readback);
|
||||
struct Recording_Scope {
|
||||
Frame_Target& target; /* 异常退出时回收尚未发布的录制槽。 */
|
||||
bool released{}; /* finish_recording 成功后禁止回滚。 */
|
||||
~Recording_Scope() { if (!released) target.abort(); }
|
||||
} recording_scope{frame_target};
|
||||
DvzSceneFrameArtifact* artifact{};
|
||||
try {
|
||||
if (observe) phase_started = trace_now_ns();
|
||||
@@ -1456,7 +1519,7 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
if (observe) trace.emit_ns = trace_now_ns() - phase_started;
|
||||
}
|
||||
catch (...) {
|
||||
target_->abort();
|
||||
frame_target.abort();
|
||||
raise_context("preparing Datoviz frame", std::current_exception());
|
||||
}
|
||||
if (observe) {
|
||||
@@ -1468,7 +1531,7 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
dvz_scene_frame_artifact_frame_index(artifact);
|
||||
}
|
||||
const DvzDrp2CommandStream* stream = dvz_scene_frame_artifact_stream(artifact);
|
||||
const DvzStreamFrame target_frame = target_->stream_frame();
|
||||
const DvzStreamFrame target_frame = frame_target.stream_frame();
|
||||
if (observe) phase_started = trace_now_ns();
|
||||
const bool attached = stream != nullptr &&
|
||||
dvz_drp2_runtime_attach_frame_target(
|
||||
@@ -1481,7 +1544,7 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
trace.validation_ok = attached && result.ok;
|
||||
trace.validation_code = static_cast<std::uint32_t>(result.code);
|
||||
trace.validation_command_index = result.command_index;
|
||||
if (observe || !trace.validation_ok) {
|
||||
if (!trace.validation_ok) {
|
||||
if (char* json = dvz_scene_frame_artifact_json(
|
||||
artifact, "renderive_frame")) {
|
||||
trace.artifact_json = json;
|
||||
@@ -1490,11 +1553,11 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
}
|
||||
dvz_scene_frame_artifact_destroy(artifact);
|
||||
if (!attached) {
|
||||
target_->abort();
|
||||
frame_target.abort();
|
||||
throw std::runtime_error("failed to attach the Datoviz point frame target");
|
||||
}
|
||||
if (!result.ok) {
|
||||
target_->abort();
|
||||
frame_target.abort();
|
||||
std::string message =
|
||||
"failed to execute Datoviz point frame: validation code " +
|
||||
std::to_string(static_cast<unsigned>(result.code)) +
|
||||
@@ -1506,45 +1569,89 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
}
|
||||
throw std::runtime_error(std::move(message));
|
||||
}
|
||||
if (observe) phase_started = trace_now_ns();
|
||||
target_->submit();
|
||||
if (observe) trace.submit_ns = trace_now_ns() - phase_started;
|
||||
frame_target.finish_recording();
|
||||
recording_scope.released = true;
|
||||
input_changed_ = false;
|
||||
return Pending_Frame{
|
||||
target_->device(), target_->fence(), scene.viewport,
|
||||
frame_sequence, target_->generation(),
|
||||
frame_target.device(), frame_target.fence(), scene.viewport,
|
||||
frame_sequence, *target_index, frame_target.generation(),
|
||||
std::move(trace)
|
||||
};
|
||||
}
|
||||
void Datoviz_Visual_Backend::submit(Pending_Frame& pending) {
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
const std::uint64_t started = pending.trace.observed ? trace_now_ns() : 0;
|
||||
target(pending).submit();
|
||||
if (pending.trace.observed)
|
||||
pending.trace.submit_ns = trace_now_ns() - started;
|
||||
}
|
||||
Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect(
|
||||
Pending_Frame pending) {
|
||||
require_domain();
|
||||
if (target_ == nullptr ||
|
||||
target_->generation() != pending.target_generation)
|
||||
throw std::logic_error("Datoviz pending frame target no longer exists");
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
const std::uint64_t readback_started = pending.trace.observed
|
||||
? trace_now_ns()
|
||||
: 0;
|
||||
auto collection = target_->collect();
|
||||
auto collection = target(pending).collect();
|
||||
if (pending.trace.observed) pending.trace.readback_ns = trace_now_ns() - readback_started;
|
||||
pending.trace.gpu = std::move(collection.gpu_timing);
|
||||
return {pending.extent, std::move(collection.pixels), std::move(pending.trace)};
|
||||
}
|
||||
void Datoviz_Visual_Backend::discard(Pending_Frame pending) {
|
||||
require_domain();
|
||||
if (target_ == nullptr ||
|
||||
target_->generation() != pending.target_generation)
|
||||
throw std::logic_error("Datoviz pending frame target no longer exists");
|
||||
target_->discard_after_completion();
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
target(pending).discard_after_completion();
|
||||
}
|
||||
bool Datoviz_Visual_Backend::can_prepare(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& prepared) const {
|
||||
if (!render_context_) return false;
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
const bool target_available = std::ranges::any_of(
|
||||
targets_->values, [](const auto& target) {
|
||||
return !target || target->available();
|
||||
});
|
||||
if (!target_available) return false;
|
||||
const bool all_targets_available = std::ranges::all_of(
|
||||
targets_->values, [](const auto& target) {
|
||||
return !target || target->available();
|
||||
});
|
||||
if (all_targets_available) return true;
|
||||
if (input_changed_ || figure_extent_ != scene.viewport ||
|
||||
!applied_camera_ || *applied_camera_ != scene.camera ||
|
||||
!applied_axes_ || *applied_axes_ !=
|
||||
std::array{scene.x_axis, scene.y_axis, scene.z_axis})
|
||||
return false;
|
||||
for (const auto& source : prepared) {
|
||||
const auto target = std::ranges::find_if(
|
||||
visuals_, [&](const Visual_Instance& value) {
|
||||
return value.identity == source.identity;
|
||||
});
|
||||
if (target == visuals_.end() ||
|
||||
target->applied_revision != source.visual.revision)
|
||||
return false;
|
||||
}
|
||||
return prepared.size() == visuals_.size();
|
||||
}
|
||||
bool Datoviz_Visual_Backend::idle() const {
|
||||
if (!render_context_) return true;
|
||||
std::lock_guard api_lock(render_context_->api_mutex());
|
||||
return std::ranges::all_of(targets_->values, [](const auto& target) {
|
||||
return !target || target->available();
|
||||
});
|
||||
}
|
||||
void Datoviz_Visual_Backend::destroy() {
|
||||
if (std::this_thread::get_id() != domain_thread_)
|
||||
throw std::logic_error(
|
||||
"Datoviz backend may only be destroyed on the render domain");
|
||||
auto context = render_context_;
|
||||
std::unique_lock<std::mutex> api_lock;
|
||||
if (context) api_lock = std::unique_lock(context->api_mutex());
|
||||
if (runtime_ != nullptr) {
|
||||
dvz_drp2_runtime_destroy(runtime_);
|
||||
runtime_ = nullptr;
|
||||
}
|
||||
target_.reset();
|
||||
for (auto& target : targets_->values) target.reset();
|
||||
if (camera_controller_ != nullptr) {
|
||||
dvz_controller_destroy(camera_controller_);
|
||||
camera_controller_ = nullptr;
|
||||
@@ -1578,6 +1685,8 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
scene_ = nullptr;
|
||||
}
|
||||
render_context_.reset();
|
||||
if (api_lock.owns_lock()) api_lock.unlock();
|
||||
context.reset();
|
||||
}
|
||||
} // namespace aethera::render_3d::detail
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
VkFence fence{VK_NULL_HANDLE}; /* 标记本帧 GPU 完成的 fence。 */
|
||||
Extent extent{}; /* 本帧离屏目标尺寸。 */
|
||||
std::uint64_t sequence{}; /* Scene 分配的帧序号。 */
|
||||
std::uint8_t target_index{}; /* 本帧独占的三缓冲目标槽位。 */
|
||||
std::uint64_t target_generation{}; /* 防止复用过期目标的资源代次。 */
|
||||
Datoviz_Frame_Trace trace; /* 本帧可选诊断数据。 */
|
||||
};
|
||||
@@ -37,11 +38,16 @@ public:
|
||||
~Datoviz_Visual_Backend() noexcept(false);
|
||||
Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete;
|
||||
Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete;
|
||||
[[nodiscard]] std::optional<Pending_Frame> submit(
|
||||
[[nodiscard]] std::optional<Pending_Frame> prepare(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
void submit(Pending_Frame& pending);
|
||||
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
||||
void discard(Pending_Frame pending);
|
||||
[[nodiscard]] bool can_prepare(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals) const;
|
||||
[[nodiscard]] bool idle() const;
|
||||
void dispatch_pointer(Event_Type type, float x, float y,
|
||||
Mouse_Button button,
|
||||
Keyboard_Modifier modifiers,
|
||||
@@ -52,6 +58,7 @@ public:
|
||||
void dispatch_key(const Key_Event& event);
|
||||
private:
|
||||
class Frame_Target;
|
||||
struct Frame_Targets;
|
||||
struct Visual_Instance {
|
||||
Visual_Identity identity{}; /* Scene 注册的稳定身份。 */
|
||||
Visual_Family family{Visual_Family::point}; /* 原生 Visual 的确定 family。 */
|
||||
@@ -68,6 +75,8 @@ private:
|
||||
void apply(const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals);
|
||||
void apply_visual(Visual_Instance& target, const Prepared_Visual& visual);
|
||||
[[nodiscard]] DvzSceneFrameArtifact* emit(const Scene_3D_Parameters& scene);
|
||||
[[nodiscard]] std::optional<std::uint8_t> acquire_target(Extent extent);
|
||||
[[nodiscard]] Frame_Target& target(const Pending_Frame& pending);
|
||||
void destroy();
|
||||
std::thread::id domain_thread_; /* 唯一允许访问 Datoviz 对象的线程。 */
|
||||
std::shared_ptr<Datoviz_Render_Context> render_context_; /* 同一 GPU 上所有后端共享的 Device 与分配器。 */
|
||||
@@ -83,11 +92,12 @@ private:
|
||||
DvzController* camera_controller_{}; /* 当前绑定到 Panel 的 Datoviz 原生相机控制器。 */
|
||||
DvzInputRouter* input_router_{}; /* Scene 输入事件路由器。 */
|
||||
DvzPointerGestureHandler* gesture_handler_{}; /* 指针手势解析器。 */
|
||||
std::unique_ptr<Frame_Target> target_; /* 当前尺寸对应的离屏提交和读回目标。 */
|
||||
Extent target_extent_{}; /* target_ 当前适配的像素尺寸。 */
|
||||
std::unique_ptr<Frame_Targets> targets_; /* 三个可并行处于准备、GPU 和读回阶段的目标。 */
|
||||
Extent figure_extent_{}; /* Datoviz Figure 当前应用的像素尺寸。 */
|
||||
std::uint64_t target_generation_{}; /* 每次重建 target_ 时递增的资源代次。 */
|
||||
std::optional<plot::Camera_Descriptor> applied_camera_{}; /* 已应用到 Panel 的 Camera 配置。 */
|
||||
std::optional<std::array<plot::Axis_Descriptor, 3>> applied_axes_{}; /* 已生成 Visual 的轴描述快照。 */
|
||||
bool input_changed_{}; /* 输入控制器是否产生尚未录入完成帧的资源更新。 */
|
||||
};
|
||||
} // namespace aethera::render_3d::detail
|
||||
|
||||
|
||||
+30
-23
@@ -41,7 +41,7 @@ enum class Frame_Pacing_Mode : std::uint32_t {
|
||||
maximum_rate
|
||||
};
|
||||
struct Frame_Pacing_Properties {
|
||||
Frame_Pacing_Mode mode{Frame_Pacing_Mode::fixed_rate}; /* 控制后继 render 请求节奏的策略。 */
|
||||
Frame_Pacing_Mode mode{Frame_Pacing_Mode::maximum_rate}; /* 控制后继 render 请求节奏的策略。 */
|
||||
double fixed_rate_fps{30.0}; /* 固定频率策略的目标帧率,单位为 FPS。 */
|
||||
double minimum_latency_headroom{1.25}; /* 最低延迟策略相对 P95 生成耗时的安全系数。 */
|
||||
};
|
||||
@@ -278,10 +278,9 @@ struct Plot::Private {
|
||||
std::unordered_map<const void*, Frame_Handler> handlers; /* 以连接身份索引的完成帧订阅。 */
|
||||
std::uint64_t next_frame_sequence{1}; /* 下一外部帧使用的单调序号;只在 strand 访问。 */
|
||||
Frame_Policy frame_policy{}; /* 仅保存可编辑刷新策略,不保存衍生统计。 */
|
||||
std::optional<Managed_Frame> active_frame{}; /* 当前由 Scene/异步后端借用指针的外部帧。 */
|
||||
std::unordered_map<Render_Frame*, Managed_Frame> active_frames{}; /* Scene 借用中的外部帧,以回调原地址为唯一索引。 */
|
||||
std::deque<const void*> pending_order{}; /* 按首次等待顺序保存连接身份,避免连接间饥饿。 */
|
||||
std::unordered_map<const void*, Managed_Frame> pending_frames{}; /* 每个连接只保留最新一个尚未提交 Scene 的外部帧。 */
|
||||
bool frame_in_flight{}; /* Scene 是否已有一次尚未完成回调的帧。 */
|
||||
template <typename Scene_Object>
|
||||
Private(asio::any_io_executor executor,
|
||||
std::unique_ptr<Scene_Object> value_scene,
|
||||
@@ -301,7 +300,8 @@ struct Plot::Private {
|
||||
void request_frame(Frame_Submission submission);
|
||||
void render_frame(Managed_Frame frame);
|
||||
void publish_completed_frame(Render_Frame* frame);
|
||||
void frame_completed();
|
||||
void frame_completed(Render_Frame* frame);
|
||||
[[nodiscard]] std::size_t frame_capacity() const noexcept;
|
||||
};
|
||||
nlohmann::json Plot::Private::schema() const {
|
||||
auto result = view->schema();
|
||||
@@ -329,7 +329,7 @@ Plot::Private::Managed_Frame Plot::Private::make_frame(Frame_Submission submissi
|
||||
}
|
||||
void Plot::Private::request_frame(Frame_Submission submission) {
|
||||
auto frame = make_frame(std::move(submission));
|
||||
if (frame_in_flight) {
|
||||
if (active_frames.size() >= frame_capacity()) {
|
||||
const auto found = pending_frames.find(frame.owner);
|
||||
if (found != pending_frames.end()) found->second = std::move(frame);
|
||||
else {
|
||||
@@ -342,47 +342,54 @@ void Plot::Private::request_frame(Frame_Submission submission) {
|
||||
render_frame(std::move(frame));
|
||||
}
|
||||
void Plot::Private::render_frame(Managed_Frame frame) {
|
||||
frame_in_flight = true;
|
||||
active_frame = std::move(frame);
|
||||
auto& request = active_frame->request;
|
||||
Render_Frame* identity = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); }, frame.frame);
|
||||
auto [active, inserted] = active_frames.emplace(identity, std::move(frame));
|
||||
if (!inserted) throw std::logic_error("Plot received a duplicate frame address");
|
||||
auto& managed = active->second;
|
||||
auto& request = managed.request;
|
||||
request.width = std::clamp(request.width, 160U, 1920U);
|
||||
request.height = std::clamp(request.height, 120U, 1080U);
|
||||
view->update(request);
|
||||
if (auto* scene_2d = std::get_if<std::unique_ptr<Scene_2D>>(&scene)) {
|
||||
(*scene_2d)->set<&Render_Scene_2D::Prop::viewport>(Size{static_cast<int>(request.width), static_cast<int>(request.height)});
|
||||
const auto result = (*scene_2d)->render(std::get<std::unique_ptr<Frame_2D>>(active_frame->frame).get());
|
||||
const auto result = (*scene_2d)->render(std::get<std::unique_ptr<Frame_2D>>(managed.frame).get());
|
||||
if (result != Render_Scene_2D::Render_Result::completed)
|
||||
asio::post(strand, [this] {
|
||||
frame_completed();
|
||||
asio::post(strand, [this, identity] {
|
||||
frame_completed(identity);
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(scene);
|
||||
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{request.width, request.height});
|
||||
const auto result = scene_3d->render(std::get<std::unique_ptr<Frame_3D>>(active_frame->frame).get());
|
||||
const auto result = scene_3d->render(std::get<std::unique_ptr<Frame_3D>>(managed.frame).get());
|
||||
if (result != Render_Scene_3D::Render_Result::submitted)
|
||||
asio::post(strand, [this] {
|
||||
frame_completed();
|
||||
asio::post(strand, [this, identity] {
|
||||
frame_completed(identity);
|
||||
});
|
||||
}
|
||||
void Plot::Private::publish_completed_frame(Render_Frame* frame) {
|
||||
if (!active_frame) throw std::logic_error("frame callback has no externally owned active frame");
|
||||
const auto active = active_frames.find(frame);
|
||||
if (active == active_frames.end())
|
||||
throw std::logic_error("frame callback has no externally owned active frame");
|
||||
auto& managed = active->second;
|
||||
const auto pacing = frame_policy.snapshot();
|
||||
if (auto* frame_2d = std::get_if<std::unique_ptr<Frame_2D>>(&active_frame->frame); frame_2d && frame_2d->get() == frame) publish(active_frame->owner, encode_frame(frame_2d->get(), active_frame->request.delivery, pacing));
|
||||
else if (auto* frame_3d = std::get_if<std::unique_ptr<Frame_3D>>(&active_frame->frame); frame_3d && frame_3d->get() == frame) publish(active_frame->owner, encode_frame(frame_3d->get(), active_frame->request.delivery, pacing));
|
||||
if (auto* frame_2d = std::get_if<std::unique_ptr<Frame_2D>>(&managed.frame); frame_2d && frame_2d->get() == frame) publish(managed.owner, encode_frame(frame_2d->get(), managed.request.delivery, pacing));
|
||||
else if (auto* frame_3d = std::get_if<std::unique_ptr<Frame_3D>>(&managed.frame); frame_3d && frame_3d->get() == frame) publish(managed.owner, encode_frame(frame_3d->get(), managed.request.delivery, pacing));
|
||||
else throw std::logic_error("frame callback does not match the externally owned active frame");
|
||||
frame_completed();
|
||||
frame_completed(frame);
|
||||
}
|
||||
void Plot::Private::frame_completed() {
|
||||
active_frame.reset();
|
||||
frame_in_flight = false;
|
||||
while (!pending_order.empty()) {
|
||||
std::size_t Plot::Private::frame_capacity() const noexcept {
|
||||
return std::holds_alternative<std::unique_ptr<Scene_3D>>(scene) ? 3U : 1U;
|
||||
}
|
||||
void Plot::Private::frame_completed(Render_Frame* frame) {
|
||||
active_frames.erase(frame);
|
||||
while (active_frames.size() < frame_capacity() && !pending_order.empty()) {
|
||||
const auto owner = pending_order.front();
|
||||
pending_order.pop_front();
|
||||
auto next = pending_frames.extract(owner);
|
||||
if (next.empty()) continue;
|
||||
render_frame(std::move(next.mapped()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
Plot::Plot(asio::any_io_executor executor,
|
||||
|
||||
+36
-23
@@ -281,16 +281,24 @@ function pipeline_stage_values(values: Frame_Stage_Values, dimension: Plot["dime
|
||||
stages.pipeline_3d_scene_coordination_ms = take(Math.max(0, scene - event - prepare - paint));
|
||||
const scene_finished = values.marker_scene_render_finished_ms ?? 0;
|
||||
const queue_entered = values.marker_backend_queue_entered_ms ?? scene_finished;
|
||||
const queue_left = values.marker_backend_queue_left_ms ?? scene_finished;
|
||||
stages.pipeline_3d_backend_queue_ms = take(Math.max(0, queue_left - Math.max(scene_finished, queue_entered)));
|
||||
const prepare_started = values.marker_backend_prepare_started_ms ?? queue_entered;
|
||||
const prepare_finished = values.marker_backend_prepare_finished_ms ?? prepare_started;
|
||||
const submit_queued = values.marker_backend_submit_queued_ms ?? prepare_finished;
|
||||
const queue_left = values.marker_backend_queue_left_ms ?? submit_queued;
|
||||
stages.pipeline_3d_prepare_queue_ms = take(Math.max(0, prepare_started - Math.max(scene_finished, queue_entered)));
|
||||
let preparation_window = Math.max(0, prepare_finished - prepare_started);
|
||||
const take_preparation = (key: string) => { const value = Math.min(preparation_window, Math.max(0, values[key] ?? 0)); preparation_window -= value; return take(value); };
|
||||
stages.pipeline_3d_backend_apply_ms = take_preparation("backend_apply_ms");
|
||||
stages.pipeline_3d_backend_plan_ms = take_preparation("backend_plan_ms");
|
||||
stages.pipeline_3d_backend_execute_ms = take_preparation("backend_execute_ms");
|
||||
stages.pipeline_3d_backend_commands_ms = take(preparation_window);
|
||||
stages.pipeline_3d_backend_queue_ms = take(Math.max(0, queue_left - submit_queued));
|
||||
const gpu_submitted = values.marker_gpu_submitted_ms ?? queue_left;
|
||||
let backend_window = Math.max(0, gpu_submitted - queue_left);
|
||||
const take_backend = (key: string) => { const value = Math.min(backend_window, Math.max(0, values[key] ?? 0)); backend_window -= value; return take(value); };
|
||||
stages.pipeline_3d_backend_apply_ms = take_backend("backend_apply_ms");
|
||||
stages.pipeline_3d_backend_plan_ms = take_backend("backend_plan_ms");
|
||||
stages.pipeline_3d_backend_execute_ms = take_backend("backend_execute_ms");
|
||||
stages.pipeline_3d_backend_submit_ms = take_backend("backend_submit_ms");
|
||||
stages.pipeline_3d_backend_commands_ms = take(backend_window);
|
||||
let submit_window = Math.max(0, gpu_submitted - queue_left);
|
||||
const measured_submit = Math.min(submit_window, Math.max(0, values.backend_submit_ms ?? 0));
|
||||
stages.pipeline_3d_backend_submit_ms = take(measured_submit);
|
||||
submit_window -= measured_submit;
|
||||
stages.pipeline_3d_submit_handoff_ms = take(submit_window);
|
||||
let gpu_window = interval("gpu_submitted", "gpu_completed");
|
||||
const take_gpu = (key: string) => { const value = Math.min(gpu_window, Math.max(0, values[key] ?? 0)); gpu_window -= value; return take(value); };
|
||||
stages.pipeline_3d_gpu_render_ms = take_gpu("gpu_render_ms");
|
||||
@@ -386,7 +394,8 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
let stopped = false;
|
||||
let timer = 0;
|
||||
let diagnostics_timer = 0;
|
||||
let frame_pending = false;
|
||||
const maximum_in_flight = plot.dimension === "3D" ? 3 : 1;
|
||||
const in_flight_requests = new Set<number>();
|
||||
let manual_frame_pending = false;
|
||||
let next_request_id = 1;
|
||||
let previous_request_time = 0;
|
||||
@@ -397,7 +406,7 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
let samples: Frame_Sample[] = [];
|
||||
const request_started_at = new Map<number, number>();
|
||||
const presentation_callbacks = new Set<number>();
|
||||
const pacing = {mode: "fixed_rate" as Frame_Pacing_Mode, fixed_rate_fps: 30, minimum_latency_headroom: 1.25};
|
||||
const pacing = {mode: "maximum_rate" as Frame_Pacing_Mode, fixed_rate_fps: 30, minimum_latency_headroom: 1.25};
|
||||
const local_policy_overrides = new Set<Frame_Policy_Event["key"]>();
|
||||
const socket = new ReconnectingWebSocket(socket_url(plot.websocket), [], {
|
||||
minReconnectionDelay: 300,
|
||||
@@ -428,7 +437,7 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
const request_frame = (manual: boolean) => {
|
||||
clear_timer();
|
||||
if (stopped) return;
|
||||
if (frame_pending) { if (manual) manual_frame_pending = true; return; }
|
||||
if (in_flight_requests.size >= maximum_in_flight) { if (manual) manual_frame_pending = true; return; }
|
||||
const retry = () => { if (manual) manual_frame_pending = true; timer = window.setTimeout(() => request_frame(manual), 250); };
|
||||
if (socket.readyState !== WebSocket.OPEN) { retry(); return; }
|
||||
const canvas = canvas_ref.current;
|
||||
@@ -446,9 +455,10 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
socket.send(JSON.stringify({...message, request_id, delivery,
|
||||
pixel_format: requested_pixel_format}));
|
||||
request_started_at.set(request_id, started_at);
|
||||
frame_pending = true;
|
||||
in_flight_requests.add(request_id);
|
||||
manual_frame_pending = false;
|
||||
previous_request_time = started_at;
|
||||
schedule_next();
|
||||
};
|
||||
const synchronize_pacing = (metadata: Frame_Metadata) => {
|
||||
const next = metadata.pacing;
|
||||
@@ -511,7 +521,7 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
const complete_frame = (pair: {value: Frame_Metadata; received_at: number}, completed_at: number, canvas_upload_ms: number, presentation: boolean) => {
|
||||
const started_at = request_started_at.get(pair.value.correlation_id);
|
||||
if (started_at === undefined) return;
|
||||
frame_pending = false;
|
||||
in_flight_requests.delete(pair.value.correlation_id);
|
||||
request_started_at.delete(pair.value.correlation_id);
|
||||
latest_metadata = pair.value;
|
||||
synchronize_pacing(pair.value);
|
||||
@@ -536,7 +546,7 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
const canvas_upload_ms = renderer.draw(bytes, pair.value);
|
||||
if (canvas_upload_ms !== null) complete_frame(pair, completed_at, canvas_upload_ms, true);
|
||||
else {
|
||||
frame_pending = false;
|
||||
in_flight_requests.delete(pair.value.correlation_id);
|
||||
request_started_at.delete(pair.value.correlation_id);
|
||||
schedule_next();
|
||||
}
|
||||
@@ -575,15 +585,15 @@ function use_plot_stream(plot: Plot, canvas_ref: React.RefObject<HTMLCanvasEleme
|
||||
};
|
||||
const on_delivery_change = (event: Event) => {
|
||||
const detail = (event as CustomEvent<{plot_id: string}>).detail;
|
||||
if (detail?.plot_id === plot.id && !frame_pending) schedule_next(true);
|
||||
if (detail?.plot_id === plot.id && in_flight_requests.size === 0) schedule_next(true);
|
||||
};
|
||||
window.addEventListener("aethera-frame-policy", on_policy_change);
|
||||
window.addEventListener("aethera-manual-frame", on_manual_frame);
|
||||
window.addEventListener("aethera-reset-camera", on_camera_reset);
|
||||
window.addEventListener("aethera-reset-frame-diagnostics", on_diagnostics_reset);
|
||||
window.addEventListener("aethera-frame-delivery", on_delivery_change);
|
||||
socket.onopen = () => { frame_pending = false; pending_metadata = null; request_started_at.clear(); set_status("LIVE"); request_frame(true); };
|
||||
socket.onclose = () => { clear_timer(); frame_pending = false; pending_metadata = null; request_started_at.clear(); if (!stopped) set_status("CONNECTING"); };
|
||||
socket.onopen = () => { in_flight_requests.clear(); pending_metadata = null; request_started_at.clear(); set_status("LIVE"); request_frame(true); };
|
||||
socket.onclose = () => { clear_timer(); in_flight_requests.clear(); pending_metadata = null; request_started_at.clear(); if (!stopped) set_status("CONNECTING"); };
|
||||
socket.onmessage = event => {
|
||||
if (typeof event.data === "string") {
|
||||
try {
|
||||
@@ -894,16 +904,18 @@ const pipeline_2d_definitions: Pipeline_Stage_Definition[] = [
|
||||
];
|
||||
const pipeline_3d_definitions: Pipeline_Stage_Definition[] = [
|
||||
pipeline_common_start,
|
||||
["pipeline_3d_event_ms", "3D 事件入队", "Scene 将输入事件提交到三维 Render Domain 的耗时。"],
|
||||
["pipeline_3d_event_ms", "3D 事件入队", "Scene 将输入事件提交到本 Scene 独立 Datoviz 准备域的耗时。"],
|
||||
["pipeline_3d_prepare_ms", "3D Visual Prepare", "将 Visual items 转换为不可变 Prepared_Visual GPU 字段的耗时;数据变化时执行。"],
|
||||
["pipeline_3d_submit_graph_ms", "3D Submit 图", "Submit 依赖图把 Prepared_Visual 入队到异步后端的耗时。"],
|
||||
["pipeline_3d_scene_coordination_ms", "3D Scene 编排", "三维 Scene 同步阶段中除事件、Prepare、Submit 外的依赖图编排耗时。"],
|
||||
["pipeline_3d_backend_queue_ms", "Render Domain 排队", "Scene 提交结束后等待单线程 Datoviz Render Domain 接管的耗时。"],
|
||||
["pipeline_3d_prepare_queue_ms", "准备域排队", "Scene 发布不可变 Visual 快照后,等待本 Scene 独立 Datoviz CPU 准备线程的耗时。"],
|
||||
["pipeline_3d_backend_apply_ms", "Datoviz Apply", "把本帧 Visual 与 Scene 参数应用到 Datoviz 对象的 CPU 耗时。"],
|
||||
["pipeline_3d_backend_plan_ms", "Datoviz Plan", "Datoviz 生成本帧 GPU 命令计划的 CPU 耗时。"],
|
||||
["pipeline_3d_backend_execute_ms", "Datoviz Execute", "Datoviz 执行命令构建的 CPU 耗时。"],
|
||||
["pipeline_3d_backend_submit_ms", "GPU 提交", "将已构建命令提交到 GPU 队列的 CPU 耗时。"],
|
||||
["pipeline_3d_backend_commands_ms", "后端命令衔接", "Render Domain 接管到 GPU 提交之间未落在四个 Datoviz trace 字段中的命令衔接时间。"],
|
||||
["pipeline_3d_backend_commands_ms", "准备域其余工作", "每 Scene 准备域中除 Apply、Plan、Execute 外的命令录制和交互处理耗时。"],
|
||||
["pipeline_3d_backend_queue_ms", "GPU 提交域排队", "命令已录制完成后,等待同 GPU 单线程 Queue Submit 域的耗时。"],
|
||||
["pipeline_3d_backend_submit_ms", "GPU Queue Submit", "共享提交域执行已经录制好的 Vulkan Queue Submit 的 CPU 耗时。"],
|
||||
["pipeline_3d_submit_handoff_ms", "提交衔接", "共享提交域接管命令到 fence 开始监视之间未落入 Queue Submit 测量的轻量衔接。"],
|
||||
["pipeline_3d_gpu_render_ms", "GPU Render", "GPU 执行渲染通道的设备时间。"],
|
||||
["pipeline_3d_gpu_transition_ms", "GPU 资源转换", "GPU 图像布局和资源状态转换的设备时间。"],
|
||||
["pipeline_3d_gpu_copy_ms", "GPU 回读复制", "GPU 将渲染结果复制到可回读资源的设备时间。"],
|
||||
@@ -959,7 +971,8 @@ function Frame_Timeline_Chart({diagnostics, dimension, paused, on_context_menu}:
|
||||
] : [
|
||||
total_series,
|
||||
["pipeline_3d_prepare_ms", "Visual Prepare", "#62a8ff"],
|
||||
["pipeline_3d_backend_queue_ms", "后端排队", "#f4bd63"],
|
||||
["pipeline_3d_prepare_queue_ms", "准备域排队", "#f4bd63"],
|
||||
["pipeline_3d_backend_queue_ms", "GPU 提交排队", "#ef9f55"],
|
||||
["pipeline_3d_gpu_render_ms", "GPU Render", "#ff7d9c"],
|
||||
pixel_delivery ? ["pipeline_3d_readback_ms", "CPU 回读", "#b998ff"] : ["pipeline_3d_gpu_sync_ms", "GPU 同步", "#b998ff"]
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user