3D优化完成
This commit is contained in:
@@ -8,6 +8,7 @@ namespace aethera::render_3d {
|
||||
|
||||
enum struct Datoviz_Frame_Path : std::uint8_t {
|
||||
recorded,
|
||||
updated,
|
||||
reused
|
||||
};
|
||||
|
||||
@@ -24,12 +25,12 @@ struct Datoviz_Gpu_Timing {
|
||||
*/
|
||||
struct Datoviz_Frame_Observation {
|
||||
std::uint64_t render_sequence{}; /* Scene 分配的帧序号。 */
|
||||
Datoviz_Frame_Path path{Datoviz_Frame_Path::recorded}; /* 本帧录制命令或复用既有命令。 */
|
||||
Datoviz_Frame_Path path{Datoviz_Frame_Path::recorded}; /* 本帧录制、更新动态绑定或直接复用命令。 */
|
||||
bool gpu_timing_requested{}; /* 本帧是否读取已录制的 GPU 时间戳。 */
|
||||
bool readback_requested{}; /* 本帧是否请求最终 RGBA 读回。 */
|
||||
bool controller_input_applied{}; /* 本帧是否把已提交输入应用到 Datoviz 控制器。 */
|
||||
bool prepare_released_after_submission{}; /* 本帧实际提交后是否立即允许下一帧 Prepare。 */
|
||||
std::uint64_t render_domain_queue_wait_ns{}; /* 唯一 GPU 提交域排队耗时。 */
|
||||
std::uint64_t queue_submit_wait_ns{}; /* 获取共享 VkQueue 提交权的墙钟耗时。 */
|
||||
std::uint64_t apply_ns{}; /* 应用本帧 Visual 数据耗时。 */
|
||||
std::uint64_t emit_ns{}; /* 生成 Datoviz 帧计划耗时。 */
|
||||
std::uint64_t execute_ns{}; /* 执行 Datoviz 帧计划耗时。 */
|
||||
|
||||
@@ -1,520 +0,0 @@
|
||||
#include "Async_Render_Backend.hpp"
|
||||
#include "Datoviz_Visual_Backend.hpp"
|
||||
#include "Gpu_Completion_Service.hpp"
|
||||
#include "Render_Domain.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
float wheel_step(double pixel, double angle) {
|
||||
if (angle != 0.0) return static_cast<float>(angle / 120.0);
|
||||
return static_cast<float>(pixel / 100.0);
|
||||
}
|
||||
Mouse_Button held_button(Mouse_Button_Mask buttons) {
|
||||
if ((buttons & 1U) != 0) return Mouse_Button::left;
|
||||
if ((buttons & 2U) != 0) return Mouse_Button::right;
|
||||
if ((buttons & 4U) != 0) return Mouse_Button::middle;
|
||||
return Mouse_Button::none;
|
||||
}
|
||||
void publish_datoviz_observation(
|
||||
not_null<Frame_3D*> frame, Datoviz_Frame_Observation observation) {
|
||||
if (observation.apply_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_apply_ns,
|
||||
observation.apply_ns);
|
||||
if (observation.emit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_plan_ns,
|
||||
observation.emit_ns);
|
||||
if (observation.execute_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_execute_ns,
|
||||
observation.execute_ns);
|
||||
if (observation.submit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_submit_ns,
|
||||
observation.submit_ns);
|
||||
if (observation.readback_ns)
|
||||
frame->record(Frame_Trace_Measurement::readback_ns,
|
||||
observation.readback_ns);
|
||||
if (observation.gpu_fence_wait_ns)
|
||||
frame->record(Frame_Trace_Measurement::gpu_fence_wait_ns,
|
||||
observation.gpu_fence_wait_ns);
|
||||
if (observation.gpu) {
|
||||
frame->record(Frame_Trace_Measurement::gpu_render_ns,
|
||||
observation.gpu->render_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_transition_ns,
|
||||
observation.gpu->transition_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_copy_ns,
|
||||
observation.gpu->copy_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_total_ns,
|
||||
observation.gpu->total_ns);
|
||||
}
|
||||
Frame_3D_Access::assign_datoviz_observation(
|
||||
frame, std::move(observation));
|
||||
}
|
||||
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) {
|
||||
std::string result;
|
||||
append_exception_description(error, result);
|
||||
return result;
|
||||
}
|
||||
catch (...) {
|
||||
return "non-standard exception";
|
||||
}
|
||||
return "empty exception";
|
||||
}
|
||||
}
|
||||
struct Async_Render_Backend::Implementation
|
||||
: std::enable_shared_from_this<Implementation> {
|
||||
struct Submission {
|
||||
Shared_Prepared_Visual_Batch visuals{}; /* Scene CPU Prepare 发布的本帧数据句柄。 */
|
||||
Scene_3D_Parameters parameters{}; /* 本帧 Scene、Camera 与轴参数。 */
|
||||
Scene::Event_Batch events{}; /* Prepare 边界已交换的完整事件批次。 */
|
||||
};
|
||||
struct Pending {
|
||||
Datoviz_Visual_Backend::Pending_Frame backend_frame{}; /* 三缓冲目标对应的已录制提交。 */
|
||||
not_null<Frame_3D*> output; /* 调用方拥有;后端借用到 completed 返回同一地址。 */
|
||||
std::optional<Gpu_Completion_Service::Reservation> completion_reservation{};
|
||||
Async_Render_Backend::Submitted submitted_callback{};
|
||||
Async_Render_Backend::Completed completed_callback{};
|
||||
bool prepared{};
|
||||
std::chrono::steady_clock::time_point domain_queued_at{}; /* 进入 GPU 单写者队列的时刻。 */
|
||||
std::uint64_t domain_queue_wait_ns{}; /* Render Domain 消费入口一次写入。 */
|
||||
std::atomic_bool submitted{}; /* 区分提交前取消与 GPU 已拥有资源后的终止。 */
|
||||
std::atomic_bool completion_enqueued{}; /* reservation 与提交失败只允许一个退休事实。 */
|
||||
|
||||
explicit Pending(not_null<Frame_3D*> frame) : output(frame) {}
|
||||
};
|
||||
struct Gpu_Completion {
|
||||
std::shared_ptr<Pending> pending{}; /* 完成前保持后台目标、借用地址和回调集合。 */
|
||||
std::optional<Gpu_Completion_Service::Result> result{}; /* fence 正常交付时的结果。 */
|
||||
std::exception_ptr failure{}; /* 提交域或完成服务的 Unknown Failure。 */
|
||||
};
|
||||
std::shared_ptr<Render_Domain> render_domain; /* 同 GPU 唯一的 Datoviz/Vulkan 写入域。 */
|
||||
std::unique_ptr<Datoviz_Visual_Backend> backend{}; /* Builder build() 创建;随后由本 Scene 串行使用。 */
|
||||
bool overlaps_gpu{}; /* 全部 Visual 是否拥有逐目标独立上传资源。 */
|
||||
std::atomic_bool available{true}; /* 是否仍接受新帧;停止或故障后永久为 false。 */
|
||||
|
||||
Implementation(std::uint32_t gpu_index_value,
|
||||
bool validation_enabled_value,
|
||||
std::vector<Visual_Registration> visuals,
|
||||
const Scene_3D_Parameters& initial_scene)
|
||||
: render_domain(Render_Domain::acquire(gpu_index_value)) {
|
||||
overlaps_gpu = std::ranges::all_of(visuals, [](const auto& visual) {
|
||||
switch (visual.family) {
|
||||
case Visual_Family::point:
|
||||
case Visual_Family::splat:
|
||||
case Visual_Family::pixel:
|
||||
case Visual_Family::sphere:
|
||||
case Visual_Family::primitive:
|
||||
case Visual_Family::mesh:
|
||||
case Visual_Family::path:
|
||||
return true;
|
||||
case Visual_Family::marker:
|
||||
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;
|
||||
});
|
||||
auto built = Datoviz_Visual_Backend::Builder<
|
||||
Datoviz_Visual_Backend>{
|
||||
gpu_index_value, validation_enabled_value,
|
||||
std::move(visuals), initial_scene}.build();
|
||||
if (!built)
|
||||
throw std::logic_error(
|
||||
"Datoviz backend dependency graph is invalid");
|
||||
backend = std::move(built).value();
|
||||
}
|
||||
~Implementation();
|
||||
|
||||
void stop() noexcept;
|
||||
void fail(std::exception_ptr value) noexcept;
|
||||
void render(
|
||||
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
not_null<Frame_3D*> frame, Scene::Event_Batch events,
|
||||
Async_Render_Backend::Submitted submitted_callback,
|
||||
Async_Render_Backend::Completed completed_callback);
|
||||
void queue_prepare(Submission submission, std::shared_ptr<Pending> pending);
|
||||
void queue_submit(std::shared_ptr<Pending> pending);
|
||||
void prepare_and_submit(Submission submission,
|
||||
std::shared_ptr<Pending> pending);
|
||||
void submit_prepared(std::shared_ptr<Pending> pending);
|
||||
void mark_domain_queued(const std::shared_ptr<Pending>& pending);
|
||||
void mark_domain_entered(const std::shared_ptr<Pending>& pending);
|
||||
void queue_finish(std::shared_ptr<Pending> pending,
|
||||
std::optional<Gpu_Completion_Service::Result> result,
|
||||
std::exception_ptr failure);
|
||||
void finish(Gpu_Completion completion);
|
||||
void resolve(std::shared_ptr<Pending> pending,
|
||||
Datoviz_Visual_Backend::Completed_Frame completed);
|
||||
void dispatch(const std::shared_ptr<Event>& event, Extent viewport);
|
||||
};
|
||||
|
||||
Async_Render_Backend::Async_Render_Backend(
|
||||
std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals,
|
||||
const Scene_3D_Parameters& initial_scene)
|
||||
: implementation_(std::make_shared<Implementation>(
|
||||
gpu_index, validation_enabled, std::move(visuals), initial_scene)) {}
|
||||
Async_Render_Backend::~Async_Render_Backend() {
|
||||
implementation_->stop();
|
||||
}
|
||||
|
||||
Async_Render_Backend::Implementation::~Implementation() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void Async_Render_Backend::Implementation::stop() noexcept {
|
||||
available.store(false, std::memory_order_release);
|
||||
}
|
||||
void Async_Render_Backend::Implementation::fail(
|
||||
std::exception_ptr value) noexcept {
|
||||
available.store(false, std::memory_order_release);
|
||||
try {
|
||||
const auto description = failure_description(value);
|
||||
std::fprintf(stderr, "Aethera 3D backend unavailable: %s\n",
|
||||
description.c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::render(
|
||||
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
not_null<Frame_3D*> frame, Scene::Event_Batch events,
|
||||
Async_Render_Backend::Submitted submitted_callback,
|
||||
Async_Render_Backend::Completed completed_callback) {
|
||||
if (!visuals || visuals->empty())
|
||||
throw std::invalid_argument("3D backend requires prepared Visual data");
|
||||
if (!submitted_callback || !completed_callback)
|
||||
throw std::invalid_argument("3D backend requires a completion callback");
|
||||
if (!available.load(std::memory_order_acquire))
|
||||
throw std::runtime_error("3D backend is unavailable");
|
||||
auto pending = std::make_shared<Pending>(frame);
|
||||
pending->submitted_callback = std::move(submitted_callback);
|
||||
pending->completed_callback = std::move(completed_callback);
|
||||
auto self = shared_from_this();
|
||||
auto reservation = Gpu_Completion_Service::instance().prepare(
|
||||
[self, pending](Gpu_Completion_Service::Result result) {
|
||||
self->queue_finish(pending, std::move(result), {});
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
self->queue_finish(pending, {}, std::move(value));
|
||||
}, frame->taskflow_trace_requested());
|
||||
if (!reservation) {
|
||||
if (reservation.result ==
|
||||
Gpu_Completion_Service::Admission_Result::capacity_exhausted)
|
||||
throw std::runtime_error("GPU completion capacity is exhausted");
|
||||
throw std::runtime_error("GPU completion service is unavailable");
|
||||
}
|
||||
pending->completion_reservation.emplace(std::move(reservation.reservation));
|
||||
Submission submission{std::move(visuals), std::move(parameters),
|
||||
std::move(events)};
|
||||
const auto sequence = frame->identity().sequence;
|
||||
const bool observe = frame->taskflow_trace_requested();
|
||||
const bool readback = frame->output() == Frame_3D_Output::pixels;
|
||||
if (submission.events.empty()) {
|
||||
frame->mark(Frame_Trace_Marker::backend_prepare_started);
|
||||
try {
|
||||
if (auto prepared = backend->try_prepare_reused(
|
||||
submission.parameters, *submission.visuals, sequence,
|
||||
observe, readback)) {
|
||||
pending->backend_frame = std::move(*prepared);
|
||||
pending->prepared = true;
|
||||
frame->mark(Frame_Trace_Marker::backend_prepare_finished);
|
||||
queue_submit(std::move(pending));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
auto failure_value = std::current_exception();
|
||||
fail(failure_value);
|
||||
pending->completion_reservation.reset();
|
||||
pending->completed_callback(pending->output,
|
||||
std::move(failure_value));
|
||||
return;
|
||||
}
|
||||
}
|
||||
queue_prepare(std::move(submission), std::move(pending));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::mark_domain_queued(
|
||||
const std::shared_ptr<Pending>& pending) {
|
||||
pending->domain_queued_at = std::chrono::steady_clock::now();
|
||||
pending->output->mark(Frame_Trace_Marker::backend_queue_entered);
|
||||
}
|
||||
void Async_Render_Backend::Implementation::mark_domain_entered(
|
||||
const std::shared_ptr<Pending>& pending) {
|
||||
const auto entered = std::chrono::steady_clock::now();
|
||||
pending->domain_queue_wait_ns = static_cast<std::uint64_t>(
|
||||
std::max<std::int64_t>(0, std::chrono::duration_cast<
|
||||
std::chrono::nanoseconds>(
|
||||
entered - pending->domain_queued_at).count()));
|
||||
pending->output->mark(Frame_Trace_Marker::backend_queue_left);
|
||||
}
|
||||
void Async_Render_Backend::Implementation::queue_prepare(
|
||||
Submission submission, std::shared_ptr<Pending> pending) {
|
||||
auto self = shared_from_this();
|
||||
Render_Domain::Post_Result queued{};
|
||||
try {
|
||||
mark_domain_queued(pending);
|
||||
queued = render_domain->post(
|
||||
[self, submission = std::move(submission), pending]() mutable {
|
||||
self->mark_domain_entered(pending);
|
||||
self->prepare_and_submit(std::move(submission), pending);
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
self->queue_finish(pending, {}, std::move(value));
|
||||
});
|
||||
} catch (...) {
|
||||
auto failure_value = std::current_exception();
|
||||
fail(failure_value);
|
||||
pending->completion_reservation.reset();
|
||||
pending->completed_callback(pending->output, std::move(failure_value));
|
||||
return;
|
||||
}
|
||||
if (queued == Render_Domain::Post_Result::queued) return;
|
||||
auto failure_value = std::make_exception_ptr(std::runtime_error(
|
||||
"render domain stopped before 3D frame preparation"));
|
||||
pending->completion_reservation.reset();
|
||||
pending->completed_callback(pending->output, std::move(failure_value));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::queue_submit(
|
||||
std::shared_ptr<Pending> pending) {
|
||||
auto self = shared_from_this();
|
||||
Render_Domain::Post_Result queued{};
|
||||
try {
|
||||
mark_domain_queued(pending);
|
||||
queued = render_domain->post(
|
||||
[self, pending] {
|
||||
self->mark_domain_entered(pending);
|
||||
self->submit_prepared(pending);
|
||||
},
|
||||
[self, pending](std::exception_ptr value) {
|
||||
self->queue_finish(pending, {}, std::move(value));
|
||||
});
|
||||
}
|
||||
catch (...) {
|
||||
queue_finish(pending, {}, std::current_exception());
|
||||
return;
|
||||
}
|
||||
if (queued == Render_Domain::Post_Result::queued) return;
|
||||
queue_finish(pending, {}, std::make_exception_ptr(std::runtime_error(
|
||||
"render domain stopped before 3D frame submission")));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::prepare_and_submit(
|
||||
Submission submission, std::shared_ptr<Pending> pending) {
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> prepared;
|
||||
const auto sequence = pending->output->identity().sequence;
|
||||
for (const auto& event : submission.events) {
|
||||
if (event) event->mark_dispatch_started(sequence);
|
||||
dispatch(event, submission.parameters.viewport);
|
||||
}
|
||||
submission.events.clear();
|
||||
pending->output->mark(Frame_Trace_Marker::backend_prepare_started);
|
||||
const bool readback = pending->output->output() == Frame_3D_Output::pixels;
|
||||
const bool observe = pending->output->taskflow_trace_requested();
|
||||
/*
|
||||
* 只有结构 Prepare 才到达这里。Datoviz 对同一 GPU context 的 Scene/runtime
|
||||
* 修改与 VkQueue submit 保持 Render Domain 单写者;稳定命令的映射上传已经在
|
||||
* 调用 worker 完成,fence 后 collect 也不会回到本域阻塞后续提交。
|
||||
*/
|
||||
prepared = backend->prepare(submission.parameters, *submission.visuals,
|
||||
sequence, observe, readback);
|
||||
if (!prepared) {
|
||||
throw std::logic_error(
|
||||
"Datoviz did not provide a frame target after Scene admission");
|
||||
}
|
||||
prepared->observation.render_domain_queue_wait_ns =
|
||||
pending->domain_queue_wait_ns;
|
||||
pending->output->mark(Frame_Trace_Marker::backend_prepare_finished);
|
||||
pending->backend_frame = std::move(*prepared);
|
||||
pending->prepared = true;
|
||||
submit_prepared(std::move(pending));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::submit_prepared(
|
||||
std::shared_ptr<Pending> pending) {
|
||||
pending->backend_frame.observation.render_domain_queue_wait_ns =
|
||||
pending->domain_queue_wait_ns;
|
||||
pending->backend_frame.observation.prepare_released_after_submission =
|
||||
overlaps_gpu;
|
||||
pending->output->mark(Frame_Trace_Marker::backend_submit_queued);
|
||||
backend->submit(pending->backend_frame);
|
||||
pending->submitted.store(true, std::memory_order_release);
|
||||
pending->output->mark(Frame_Trace_Marker::gpu_submitted);
|
||||
pending->completion_reservation->watch(
|
||||
pending->backend_frame.device, pending->backend_frame.fence);
|
||||
pending->completion_reservation.reset();
|
||||
pending->submitted_callback(pending->output, overlaps_gpu);
|
||||
}
|
||||
void Async_Render_Backend::Implementation::queue_finish(
|
||||
std::shared_ptr<Pending> pending,
|
||||
std::optional<Gpu_Completion_Service::Result> result,
|
||||
std::exception_ptr failure_value) {
|
||||
if (pending->completion_enqueued.exchange(true, std::memory_order_acq_rel))
|
||||
return;
|
||||
auto self = shared_from_this();
|
||||
try {
|
||||
schedule_task(
|
||||
"render_3d.backend.collect",
|
||||
[self, pending, result = std::move(result),
|
||||
failure_value = std::move(failure_value)]() mutable {
|
||||
std::exception_ptr completion_failure = std::move(failure_value);
|
||||
try {
|
||||
self->finish(Gpu_Completion{
|
||||
pending, std::move(result), completion_failure});
|
||||
}
|
||||
catch (...) {
|
||||
completion_failure = std::current_exception();
|
||||
self->fail(completion_failure);
|
||||
}
|
||||
pending->completed_callback(pending->output,
|
||||
std::move(completion_failure));
|
||||
});
|
||||
}
|
||||
catch (...) {
|
||||
auto value = std::current_exception();
|
||||
fail(value);
|
||||
if (pending->submitted.load(std::memory_order_acquire))
|
||||
backend->quarantine(std::move(pending->backend_frame));
|
||||
else if (pending->prepared)
|
||||
backend->discard(std::move(pending->backend_frame));
|
||||
pending->completed_callback(pending->output, std::move(value));
|
||||
}
|
||||
}
|
||||
void Async_Render_Backend::Implementation::resolve(
|
||||
std::shared_ptr<Pending> pending,
|
||||
Datoviz_Visual_Backend::Completed_Frame completed) {
|
||||
pending->output->mark(Frame_Trace_Marker::readback_finished);
|
||||
publish_datoviz_observation(
|
||||
pending->output, std::move(completed.observation));
|
||||
std::array outputs{pending->output};
|
||||
Frame_3D_Access::assign_pixels(outputs, completed.extent,
|
||||
std::move(completed.pixels),
|
||||
pending->output->identity());
|
||||
}
|
||||
void Async_Render_Backend::Implementation::finish(Gpu_Completion completion) {
|
||||
completion.pending->output->mark(Frame_Trace_Marker::gpu_completed);
|
||||
if (completion.result)
|
||||
completion.pending->backend_frame.observation.gpu_fence_wait_ns =
|
||||
completion.result->wait_duration_ns;
|
||||
completion.pending->output->mark(Frame_Trace_Marker::readback_started);
|
||||
if (completion.failure) {
|
||||
if (completion.pending->submitted.load(std::memory_order_acquire)) {
|
||||
backend->quarantine(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
else if (completion.pending->prepared) {
|
||||
backend->discard(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
std::rethrow_exception(std::move(completion.failure));
|
||||
}
|
||||
if (!completion.result || completion.result->error !=
|
||||
Gpu_Completion_Service::Completion_Error::none) {
|
||||
const auto code = completion.result
|
||||
? static_cast<unsigned>(completion.result->error)
|
||||
: std::numeric_limits<unsigned>::max();
|
||||
const auto vulkan = completion.result
|
||||
? static_cast<int>(completion.result->vulkan_result)
|
||||
: static_cast<int>(VK_ERROR_UNKNOWN);
|
||||
auto failure = std::make_exception_ptr(std::runtime_error(
|
||||
"Datoviz GPU completion failed: error=" +
|
||||
std::to_string(code) + ", VkResult=" + std::to_string(vulkan)));
|
||||
if (completion.pending->submitted.load(std::memory_order_acquire)) {
|
||||
backend->quarantine(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
else if (completion.pending->prepared) {
|
||||
backend->discard(std::move(completion.pending->backend_frame));
|
||||
}
|
||||
std::rethrow_exception(std::move(failure));
|
||||
}
|
||||
auto completed = backend->collect(
|
||||
std::move(completion.pending->backend_frame));
|
||||
completion.pending->prepared = false;
|
||||
resolve(std::move(completion.pending), std::move(completed));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::dispatch(
|
||||
const std::shared_ptr<Event>& event, Extent viewport) {
|
||||
if (!event) return;
|
||||
const auto* pointer =
|
||||
dynamic_cast<const Pointer_Event_Capability*>(event.get());
|
||||
const auto* wheel =
|
||||
dynamic_cast<const Wheel_Event_Capability*>(event.get());
|
||||
const auto* key = dynamic_cast<const Key_Event*>(event.get());
|
||||
const bool pointer_valid = pointer &&
|
||||
std::isfinite(pointer->position_x()) &&
|
||||
std::isfinite(pointer->position_y()) &&
|
||||
pointer->position_x() >= 0.0 && pointer->position_y() >= 0.0 &&
|
||||
pointer->position_x() <= viewport.width &&
|
||||
pointer->position_y() <= viewport.height;
|
||||
if (wheel && pointer_valid) {
|
||||
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(), viewport,
|
||||
event->occurred_at.nanoseconds);
|
||||
}
|
||||
else if (pointer_valid &&
|
||||
(event->type == Event_Type::pointer_move ||
|
||||
event->type == Event_Type::pointer_press ||
|
||||
event->type == Event_Type::pointer_release)) {
|
||||
backend->dispatch_pointer(
|
||||
event->type,
|
||||
static_cast<float>(pointer->position_x()),
|
||||
static_cast<float>(pointer->position_y()),
|
||||
event->type == Event_Type::pointer_move
|
||||
? held_button(pointer->pointer_buttons())
|
||||
: pointer->pointer_button(),
|
||||
pointer->keyboard_modifiers(), viewport,
|
||||
event->occurred_at.nanoseconds);
|
||||
}
|
||||
else if (key) backend->dispatch_key(*key);
|
||||
if ((wheel && pointer_valid) || pointer_valid || key) event->accept();
|
||||
event->mark_dispatch_completed();
|
||||
}
|
||||
void Async_Render_Backend::render(
|
||||
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
not_null<Frame_3D*> frame, Scene::Event_Batch events,
|
||||
Submitted submitted, Completed completed) {
|
||||
implementation_->render(std::move(visuals), parameters, frame,
|
||||
std::move(events), std::move(submitted),
|
||||
std::move(completed));
|
||||
}
|
||||
bool Async_Render_Backend::available() const noexcept {
|
||||
return implementation_->available.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
#include "../base/Frame_3D.hpp"
|
||||
#include "Backend_Types.hpp"
|
||||
#include <scene.hpp>
|
||||
#include <ownership.hpp>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
namespace aethera::render_3d::detail {
|
||||
struct Async_Render_Backend final {
|
||||
public:
|
||||
Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
~Async_Render_Backend();
|
||||
Async_Render_Backend(const Async_Render_Backend&) = delete;
|
||||
Async_Render_Backend& operator=(const Async_Render_Backend&) = delete;
|
||||
using Submitted = std::function<void(not_null<Frame_3D*>, bool)>;
|
||||
using Completed =
|
||||
std::function<void(not_null<Frame_3D*>, std::exception_ptr)>;
|
||||
/*
|
||||
* 3D 核心线程模型:
|
||||
* 1. Scene Taskflow worker 准备不可变业务数据;已录制命令的热路径还可并行
|
||||
* 写本 Scene 独占的三缓冲映射属性区,不访问共享 Datoviz 结构或 VkQueue。
|
||||
* 2. 同一 GPU 只有 Render Domain 能修改 Datoviz Scene/runtime、录制结构命令和
|
||||
* 调用 vkQueueSubmit;这是跨 Scene 的确定单写者序列。
|
||||
* 3. GPU completion 只登记 fence;fence 已完成后的目标读回由普通 Taskflow
|
||||
* worker 并行完成,因为每个目标槽和映射读回区只属于对应 Scene。
|
||||
* 4. 所有跨边界动作都是投递和回调,不引入 join、future.get 或条件变量等待。
|
||||
*/
|
||||
void render(
|
||||
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
|
||||
not_null<Frame_3D*> frame, Scene::Event_Batch events,
|
||||
Submitted submitted, Completed completed);
|
||||
[[nodiscard]] bool available() const noexcept;
|
||||
private:
|
||||
struct Implementation;
|
||||
std::shared_ptr<Implementation> implementation_; /* 已入队闭包共享实现生命周期。 */
|
||||
};
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base/Datoviz_Frame_Observation.hpp"
|
||||
#include "Backend_Types.hpp"
|
||||
#include <render_common.hpp>
|
||||
#include <ownership.hpp>
|
||||
#include <volk.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace aethera::render_3d::detail {
|
||||
|
||||
struct Datoviz_Render_Context;
|
||||
|
||||
struct Datoviz_Visual_Backend final
|
||||
: Def<Datoviz_Visual_Backend, Root> {
|
||||
struct Prop : Prev_Prop {
|
||||
bool operator==(const Prop&) const;
|
||||
};
|
||||
|
||||
struct State : Prev_State {
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
|
||||
struct Private;
|
||||
|
||||
template <typename Object>
|
||||
struct Builder : Prev_Builder<Object> {
|
||||
using Base = Prev_Builder<Object>;
|
||||
|
||||
Builder(std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals,
|
||||
Scene_3D_Parameters initial_scene);
|
||||
|
||||
[[nodiscard]] std::expected<std::unique_ptr<Object>,
|
||||
Dependency_Graph_Error>
|
||||
build();
|
||||
|
||||
private:
|
||||
std::uint32_t gpu_index{}; /* Datoviz 使用的物理 GPU 下标。 */
|
||||
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
|
||||
std::vector<Visual_Registration> visuals; /* 构造期稳定 Visual 注册表。 */
|
||||
Scene_3D_Parameters initial_scene{}; /* 首次创建 Figure 所需场景参数。 */
|
||||
};
|
||||
|
||||
struct Pending_Frame {
|
||||
VkDevice device{VK_NULL_HANDLE}; /* 提交所属 Vulkan Device。 */
|
||||
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_Observation observation{}; /* 与外部 Frame_3D 同帧的原始诊断。 */
|
||||
};
|
||||
|
||||
struct Completed_Frame {
|
||||
Extent extent{}; /* 已完成 GPU 读回的像素尺寸。 */
|
||||
std::vector<std::byte> pixels{}; /* 已完成 GPU 读回的连续 RGBA8 像素。 */
|
||||
Datoviz_Frame_Observation observation{}; /* 已补全 GPU 和读回阶段的原始诊断。 */
|
||||
};
|
||||
|
||||
Datoviz_Visual_Backend();
|
||||
~Datoviz_Visual_Backend() noexcept;
|
||||
Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete;
|
||||
Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete;
|
||||
|
||||
[[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);
|
||||
|
||||
[[nodiscard]] std::optional<Pending_Frame> try_prepare_reused(
|
||||
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);
|
||||
void quarantine(Pending_Frame pending) noexcept;
|
||||
|
||||
void dispatch_pointer(Event_Type type, float x, float y,
|
||||
Mouse_Button button,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_wheel(float x, float y, float delta_x, float delta_y,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_key(const Key_Event& event);
|
||||
};
|
||||
|
||||
} // namespace aethera::render_3d::detail
|
||||
|
||||
#include "Datoviz_Visual_Backend.ipp"
|
||||
@@ -1,165 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <datoviz/drp2/runtime.h>
|
||||
#include <datoviz/input/router.h>
|
||||
#include <datoviz/scene.h>
|
||||
#include <datoviz/stream/frame_stream.h>
|
||||
#include <datoviz/vk/gpu_ctx.h>
|
||||
#include <datoviz/vklite.h>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace aethera::render_3d::detail {
|
||||
|
||||
struct Datoviz_Visual_Backend::Private : Prev_Private {
|
||||
struct Frame_Target;
|
||||
struct Frame_Targets;
|
||||
|
||||
struct External_Attribute {
|
||||
std::string name{}; /* Datoviz attribute semantic owned by this binding. */
|
||||
std::uint32_t stride{}; /* One planar attribute item in bytes. */
|
||||
std::uint32_t capacity{}; /* Items per independently writable frame region. */
|
||||
not_null<DvzSceneBuffer*> scene_buffer; /* Scene-side stable resource label; Scene owns it. */
|
||||
owner<DvzBuffer*> gpu_buffer{}; /* Three-region runtime buffer owned by this backend. */
|
||||
std::uint8_t registered_targets{}; /* Runtime slots that have borrowed gpu_buffer. */
|
||||
};
|
||||
|
||||
struct Visual_Instance {
|
||||
Visual_Identity identity{}; /* Scene 注册的稳定身份。 */
|
||||
Visual_Family family{Visual_Family::point}; /* 原生 Visual 的确定 family。 */
|
||||
not_null<DvzVisual*> visual; /* 由 Datoviz Scene 拥有。 */
|
||||
DvzSampledField* field{}; /* 仅纹理和体数据 family 使用的 Scene 字段资源。 */
|
||||
DvzFont* font{}; /* Glyph/Text 使用的 Scene 字体与可增长 atlas 来源。 */
|
||||
DvzText* coordinate_text{}; /* Marker 的数据坐标 XYZ 标注。 */
|
||||
std::array<std::uint32_t, 3> field_extent{}; /* field 当前样本尺寸。 */
|
||||
std::uint64_t applied_revision{}; /* 此 Visual 已上传的 Prepare 版本。 */
|
||||
std::array<std::uint64_t, 3> uploaded_data_revisions{}; /* 各物理帧区已写入的数据版本。 */
|
||||
std::optional<Prepared_Visual> applied{}; /* 最近应用的元数据与不可变载荷所有权。 */
|
||||
std::vector<External_Attribute> attributes{}; /* 每字段各自拥有三段动态载荷区域。 */
|
||||
};
|
||||
|
||||
struct Runtime_Slot {
|
||||
owner<DvzDrp2Runtime*> runtime{}; /* 后端拥有的目标槽命令运行时。 */
|
||||
owner<DvzFramePlanEmitter*> emitter{}; /* 后端拥有、与 runtime 一一对应的发射器。 */
|
||||
};
|
||||
|
||||
Private();
|
||||
~Private() override;
|
||||
|
||||
void initialize(std::uint32_t gpu_index, bool validation_enabled,
|
||||
const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
void create_scene(const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
void apply_camera(const Camera_Descriptor& camera);
|
||||
void apply_axes(const Scene_3D_Parameters& scene);
|
||||
[[nodiscard]] bool matches_command_structure(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals) const;
|
||||
[[nodiscard]] std::uint64_t apply(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint8_t target_index, bool bind_target);
|
||||
[[nodiscard]] std::uint64_t apply_visual(
|
||||
Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index, bool bind_target);
|
||||
void ensure_external_attributes(Visual_Instance& target,
|
||||
const Prepared_Visual& visual);
|
||||
[[nodiscard]] std::uint64_t upload_external_attributes(
|
||||
Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index);
|
||||
void bind_external_attributes(Visual_Instance& target,
|
||||
std::uint8_t target_index,
|
||||
std::uint32_t item_count);
|
||||
void register_external_attributes(const DvzDrp2CommandStream* stream,
|
||||
std::uint8_t target_index);
|
||||
[[nodiscard]] std::optional<Pending_Frame> reuse_recorded_target(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
[[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);
|
||||
[[nodiscard]] std::optional<Pending_Frame> try_prepare_reused(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
[[nodiscard]] DvzSceneFrameArtifact* emit(
|
||||
const Scene_3D_Parameters& scene, std::uint8_t target_index);
|
||||
[[nodiscard]] std::optional<std::uint8_t> acquire_target(Extent extent);
|
||||
[[nodiscard]] Frame_Target& target(const Pending_Frame& pending);
|
||||
void submit(Pending_Frame& pending);
|
||||
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
||||
void discard(Pending_Frame pending);
|
||||
void quarantine(Pending_Frame pending) noexcept;
|
||||
void dispatch_pointer(Event_Type type, float x, float y,
|
||||
Mouse_Button button,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_wheel(float x, float y, float delta_x, float delta_y,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_key(const Key_Event& event);
|
||||
void abandon_resources() noexcept;
|
||||
void destroy();
|
||||
|
||||
std::shared_ptr<Datoviz_Render_Context> render_context_{}; /* 同一 GPU 上所有后端共享的 Device 与分配器。 */
|
||||
mutable std::mutex target_mutex_{}; /* 当前 Scene 三个目标槽的唯一生命周期同步源。 */
|
||||
std::array<Runtime_Slot, 3> runtime_slots_{}; /* 三套独立 runtime/emitter 状态机。 */
|
||||
owner<DvzScene*> scene_{}; /* 本后端唯一拥有的 Datoviz Scene。 */
|
||||
DvzFigure* figure_{}; /* 当前离屏 Figure。 */
|
||||
DvzPanel* panel_{}; /* 承载全部业务 Visual 的全屏 Panel。 */
|
||||
std::vector<Visual_Instance> visuals_{}; /* 按 Scene 稳定身份管理的原生 Visual。 */
|
||||
std::vector<owner<DvzBuffer*>> external_buffers_{}; /* 后端拥有;runtime 销毁后释放。 */
|
||||
DvzVisual* axes_visual_{}; /* 三维主轴、刻度和网格 Segment Visual。 */
|
||||
DvzText* axes_text_{}; /* 随相机变换的三维刻度与轴标题。 */
|
||||
owner<DvzItemInteraction*> item_interaction_{}; /* 后端显式销毁的图元交互器。 */
|
||||
owner<DvzPinnedReadout*> hover_readout_{}; /* 后端显式销毁的悬停提示。 */
|
||||
owner<DvzController*> camera_controller_{}; /* 后端显式销毁的相机控制器。 */
|
||||
owner<DvzInputRouter*> input_router_{}; /* 后端显式销毁的输入路由器。 */
|
||||
owner<DvzPointerGestureHandler*> gesture_handler_{}; /* 后端显式销毁的手势处理器。 */
|
||||
std::unique_ptr<Frame_Targets> targets_{}; /* 三个可并行处于准备、GPU、读回阶段的目标。 */
|
||||
Extent figure_extent_{}; /* Figure 当前应用的像素尺寸。 */
|
||||
std::uint64_t target_generation_{}; /* 每次重建目标时递增的资源代次。 */
|
||||
std::uint64_t command_revision_{1}; /* 结构变化后递增的命令录制版本。 */
|
||||
std::optional<Camera_Descriptor> applied_camera_{}; /* 已应用到 Panel 的 Camera 配置。 */
|
||||
std::optional<std::array<plot::Axis_Descriptor, 3>> applied_axes_{}; /* 已应用到轴 Visual 的描述。 */
|
||||
bool input_changed_{}; /* 输入是否产生尚未录入命令的资源变化。 */
|
||||
std::atomic_bool quarantined_{}; /* GPU 异常后是否永久放弃销毁与复用。 */
|
||||
};
|
||||
|
||||
template <typename Object>
|
||||
Datoviz_Visual_Backend::Builder<Object>::Builder(
|
||||
std::uint32_t gpu_index_value, bool validation_enabled_value,
|
||||
std::vector<Visual_Registration> visuals_value,
|
||||
Scene_3D_Parameters initial_scene_value)
|
||||
: Base(),
|
||||
gpu_index(gpu_index_value),
|
||||
validation_enabled(validation_enabled_value),
|
||||
visuals(std::move(visuals_value)),
|
||||
initial_scene(std::move(initial_scene_value)) {}
|
||||
|
||||
template <typename Object>
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error>
|
||||
Datoviz_Visual_Backend::Builder<Object>::build() {
|
||||
if (visuals.empty())
|
||||
throw std::invalid_argument(
|
||||
"Datoviz backend requires at least one Visual registration");
|
||||
auto result = Base::build();
|
||||
if (!result) return std::unexpected(result.error());
|
||||
auto backend = std::move(result).value();
|
||||
auto& private_data = Base::private_access(backend.get())
|
||||
.template get<Datoviz_Visual_Backend::Base_Tag>();
|
||||
private_data.initialize(gpu_index, validation_enabled, visuals,
|
||||
initial_scene);
|
||||
return backend;
|
||||
}
|
||||
|
||||
} // namespace aethera::render_3d::detail
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Gpu_Completion_Service.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include <volk.h>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
@@ -62,9 +63,9 @@ Gpu_Completion_Service::Prepare_Result::operator bool() const noexcept {
|
||||
return result == Admission_Result::none;
|
||||
}
|
||||
|
||||
void Gpu_Completion_Service::Reservation::watch(VkDevice device,
|
||||
VkFence fence) {
|
||||
if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE)
|
||||
void Gpu_Completion_Service::Reservation::watch(std::uintptr_t device,
|
||||
std::uintptr_t fence) {
|
||||
if (!pending_ || device == 0 || fence == 0)
|
||||
throw std::logic_error(
|
||||
"GPU completion reservation or fence is invalid");
|
||||
pending_->service->watch(pending_, device, fence);
|
||||
@@ -85,7 +86,7 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
|
||||
|
||||
void Gpu_Completion_Service::watch(
|
||||
const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending,
|
||||
VkDevice device, VkFence fence) {
|
||||
std::uintptr_t device, std::uintptr_t fence) {
|
||||
static_cast<Private&>(*d).watch(pending, device, fence);
|
||||
}
|
||||
|
||||
@@ -149,7 +150,7 @@ Gpu_Completion_Service::Private::prepare(
|
||||
|
||||
void Gpu_Completion_Service::Private::watch(
|
||||
const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending,
|
||||
VkDevice device, VkFence fence) {
|
||||
std::uintptr_t device, std::uintptr_t fence) {
|
||||
{
|
||||
std::lock_guard lock(service_mutex);
|
||||
if (!pending || pending->service != object ||
|
||||
@@ -239,7 +240,7 @@ void Gpu_Completion_Service::Private::poll() noexcept {
|
||||
completion = std::move(pending->completion);
|
||||
on_exception = std::move(pending->on_exception);
|
||||
result.error = error;
|
||||
result.vulkan_result = vulkan_result;
|
||||
result.vulkan_result = static_cast<std::int32_t>(vulkan_result);
|
||||
if (pending->observe)
|
||||
result.wait_duration_ns = elapsed_nanoseconds(pending->watched_at);
|
||||
pending->status = Gpu_Completion_Pending_Fence::Status::canceled;
|
||||
@@ -286,8 +287,8 @@ void Gpu_Completion_Service::Private::poll() noexcept {
|
||||
std::size_t reserved_count{};
|
||||
bool needs_more{};
|
||||
for (auto iterator = active.begin(); iterator != active.end();) {
|
||||
VkDevice device{VK_NULL_HANDLE};
|
||||
VkFence fence{VK_NULL_HANDLE};
|
||||
std::uintptr_t device{};
|
||||
std::uintptr_t fence{};
|
||||
std::chrono::steady_clock::time_point watched_at{};
|
||||
Gpu_Completion_Pending_Fence::Status status{};
|
||||
{
|
||||
@@ -324,7 +325,9 @@ void Gpu_Completion_Service::Private::poll() noexcept {
|
||||
[](State_Access<State> states) {
|
||||
++states.template get<Base_Tag>().fence_probe_count;
|
||||
});
|
||||
const VkResult result = vkGetFenceStatus(device, fence);
|
||||
const VkResult result = vkGetFenceStatus(
|
||||
reinterpret_cast<VkDevice>(device),
|
||||
reinterpret_cast<VkFence>(fence));
|
||||
if (result == VK_NOT_READY) {
|
||||
needs_more = true;
|
||||
++iterator;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include "../Gpu_Completion_State.hpp"
|
||||
#include <render_common.hpp>
|
||||
#include <volk.h>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
@@ -50,7 +49,7 @@ struct Gpu_Completion_Service : Def<Gpu_Completion_Service, Root,
|
||||
};
|
||||
struct Result {
|
||||
Completion_Error error{}; /* 归一化完成结果。 */
|
||||
VkResult vulkan_result{VK_SUCCESS}; /* Vulkan 原始结果码。 */
|
||||
std::int32_t vulkan_result{}; /* Native backend result code. */
|
||||
std::uint64_t wait_duration_ns{}; /* fence 等待时间,单位为纳秒。 */
|
||||
};
|
||||
using Completion = std::function<void(Result)>;
|
||||
@@ -63,7 +62,7 @@ struct Gpu_Completion_Service : Def<Gpu_Completion_Service, Root,
|
||||
Reservation& operator=(const Reservation&) = delete;
|
||||
Reservation(Reservation&& other) noexcept;
|
||||
Reservation& operator=(Reservation&&) = delete;
|
||||
void watch(VkDevice device, VkFence fence);
|
||||
void watch(std::uintptr_t device, std::uintptr_t fence);
|
||||
private:
|
||||
explicit Reservation(
|
||||
std::shared_ptr<Gpu_Completion_Pending_Fence> pending) noexcept;
|
||||
@@ -87,7 +86,7 @@ struct Gpu_Completion_Service : Def<Gpu_Completion_Service, Root,
|
||||
[[nodiscard]] Gpu_Completion_State state() const;
|
||||
private:
|
||||
void watch(const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending,
|
||||
VkDevice device, VkFence fence);
|
||||
std::uintptr_t device, std::uintptr_t fence);
|
||||
void cancel(const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ struct Gpu_Completion_Pending_Fence {
|
||||
watched,
|
||||
canceled
|
||||
};
|
||||
VkDevice device{VK_NULL_HANDLE}; /* fence 所属 Vulkan Device。 */
|
||||
VkFence fence{VK_NULL_HANDLE}; /* 受监视的 Vulkan fence。 */
|
||||
std::uintptr_t device{};
|
||||
std::uintptr_t fence{};
|
||||
Gpu_Completion_Service::Completion completion{}; /* 完成或隔离后的交付回调。 */
|
||||
Gpu_Completion_Service::Exception_Handler on_exception{}; /* 回调异常的隔离入口。 */
|
||||
std::chrono::steady_clock::time_point watched_at{}; /* 开始监视的单调时钟时刻。 */
|
||||
@@ -46,7 +46,7 @@ struct Gpu_Completion_Service::Private : Prev_Private {
|
||||
Exception_Handler on_exception,
|
||||
bool observe);
|
||||
void watch(const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending,
|
||||
VkDevice device, VkFence fence);
|
||||
std::uintptr_t device, std::uintptr_t fence);
|
||||
void cancel(const std::shared_ptr<Gpu_Completion_Pending_Fence>& pending);
|
||||
[[nodiscard]] Gpu_Completion_State state() const;
|
||||
void request_poll(std::chrono::nanoseconds delay = probe_interval) noexcept;
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#include "Render_Domain.hpp"
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
struct Render_Domain_Registry {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<std::uint32_t, std::weak_ptr<Render_Domain>> domains;
|
||||
};
|
||||
Render_Domain_Registry& registry() {
|
||||
static Render_Domain_Registry value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Render_Domain::Task::Task(
|
||||
std::function<void()> function_value,
|
||||
std::function<void(std::exception_ptr)> exception_handler_value)
|
||||
: function(std::move(function_value)),
|
||||
on_exception(std::move(exception_handler_value)) {}
|
||||
std::shared_ptr<Render_Domain> Render_Domain::acquire(std::uint32_t gpu_index) {
|
||||
auto& storage = registry();
|
||||
std::lock_guard lock(storage.mutex);
|
||||
auto& entry = storage.domains[gpu_index];
|
||||
if (auto domain = entry.lock()) return domain;
|
||||
auto domain = std::shared_ptr<Render_Domain>(new Render_Domain(),
|
||||
&Render_Domain::destroy);
|
||||
entry = domain;
|
||||
return domain;
|
||||
}
|
||||
Render_Domain::Render_Domain() : thread_([this] { run(); }) {}
|
||||
Render_Domain::~Render_Domain() {
|
||||
request_stop();
|
||||
if (thread_.joinable()) thread_.detach();
|
||||
}
|
||||
void Render_Domain::destroy(owner<Render_Domain*> domain) noexcept {
|
||||
if (!domain) return;
|
||||
/*
|
||||
* 最后一个外部引用只关闭准入,不等待线程。run() 会消费完停止边界前已经
|
||||
* 入队的命令,再由域线程 detach 并 delete 自身;因此调用方析构路径没有
|
||||
* join、自旋或同步栅栏,Datoviz/Vulkan 对象也始终在其唯一写者线程销毁。
|
||||
*/
|
||||
domain->request_stop();
|
||||
domain->destroy_on_exit_.store(true, std::memory_order_release);
|
||||
}
|
||||
void Render_Domain::request_stop() noexcept {
|
||||
bool expected = false;
|
||||
if (!stopping_.compare_exchange_strong(
|
||||
expected, true, std::memory_order_acq_rel)) return;
|
||||
}
|
||||
Render_Domain::Post_Result Render_Domain::post(
|
||||
std::function<void()> function, Exception_Handler on_exception) {
|
||||
if (!function) throw std::invalid_argument("render domain task is empty");
|
||||
if (!on_exception)
|
||||
throw std::invalid_argument("render domain exception handler is empty");
|
||||
if (stopping_.load(std::memory_order_acquire)) return Post_Result::stopping;
|
||||
auto task = std::make_unique<Task>(std::move(function), std::move(on_exception));
|
||||
active_posters_.fetch_add(1, std::memory_order_acq_rel);
|
||||
if (stopping_.load(std::memory_order_acquire)) {
|
||||
active_posters_.fetch_sub(1, std::memory_order_release);
|
||||
return Post_Result::stopping;
|
||||
}
|
||||
const bool enqueued = tasks_.enqueue(std::move(task));
|
||||
if (!enqueued) {
|
||||
active_posters_.fetch_sub(1, std::memory_order_release);
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
active_posters_.fetch_sub(1, std::memory_order_release);
|
||||
return Post_Result::queued;
|
||||
}
|
||||
void Render_Domain::run() noexcept {
|
||||
current_domain_ = not_null{this};
|
||||
for (;;) {
|
||||
std::unique_ptr<Task> task;
|
||||
bool received = tasks_.wait_dequeue_timed(
|
||||
task, std::chrono::milliseconds(1));
|
||||
if (!received && stopping_.load(std::memory_order_acquire) &&
|
||||
active_posters_.load(std::memory_order_acquire) == 0) {
|
||||
/* stop 边界关闭生产者后再真实探测队列,不能用
|
||||
* size_approx 决定生命周期。 */
|
||||
received = tasks_.try_dequeue(task);
|
||||
}
|
||||
if (received && task) {
|
||||
try {
|
||||
task->function();
|
||||
}
|
||||
catch (...) {
|
||||
try {
|
||||
task->on_exception(
|
||||
contextual_exception("executing render domain task",
|
||||
std::current_exception()));
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
task.reset();
|
||||
}
|
||||
// 停止后不再接受新生产者;一次真实的 timed dequeue 为空,才说明
|
||||
// 已经消费完所有先于停止边界进入的任务。size_approx() 只能诊断,
|
||||
// 不能参与线程退出正确性。
|
||||
if (!received && stopping_.load(std::memory_order_acquire) &&
|
||||
active_posters_.load(std::memory_order_acquire) == 0) {
|
||||
current_domain_ = nullptr;
|
||||
if (destroy_on_exit_.load(std::memory_order_acquire)) {
|
||||
if (thread_.joinable()) thread_.detach();
|
||||
delete this;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
#include "Exception.hpp"
|
||||
#include <ownership.hpp>
|
||||
#include <concurrentqueue-1.0.5/blockingconcurrentqueue.h>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
namespace aethera::render_3d::detail {
|
||||
struct Render_Domain final : public std::enable_shared_from_this<Render_Domain> {
|
||||
struct Task {
|
||||
Task(std::function<void()> function_value,
|
||||
std::function<void(std::exception_ptr)> exception_handler_value);
|
||||
std::function<void()> function; /* 仅在所属 GPU Render Domain 线程执行的闭包。 */
|
||||
std::function<void(std::exception_ptr)> on_exception; /* 闭包 Unknown Failure 的任务隔离出口。 */
|
||||
};
|
||||
public:
|
||||
static std::shared_ptr<Render_Domain> acquire(std::uint32_t gpu_index);
|
||||
~Render_Domain();
|
||||
Render_Domain(const Render_Domain&) = delete;
|
||||
Render_Domain& operator=(const Render_Domain&) = delete;
|
||||
using Exception_Handler = std::function<void(std::exception_ptr)>;
|
||||
enum struct Post_Result : std::uint8_t { queued, stopping };
|
||||
/* 把闭包所有权无等待转移到所属 GPU 的单消费者线程。 */
|
||||
[[nodiscard]] Post_Result post(std::function<void()> function,
|
||||
Exception_Handler on_exception);
|
||||
private:
|
||||
Render_Domain();
|
||||
static void destroy(owner<Render_Domain*> domain) noexcept;
|
||||
void request_stop() noexcept;
|
||||
void run() noexcept;
|
||||
static constexpr std::size_t initial_capacity = 64;
|
||||
inline static thread_local Render_Domain*
|
||||
current_domain_{}; /* 当前线程正在执行的 GPU 域。 */
|
||||
moodycamel::BlockingConcurrentQueue<std::unique_ptr<Task>> tasks_{initial_capacity}; /* 多生产者闭包入口与唯一消费队列。 */
|
||||
std::atomic_size_t active_posters_{}; /* 停止准入边界内仍可能完成入队的生产者数量。 */
|
||||
std::atomic_bool stopping_{}; /* 拒绝新闭包并准备退出。 */
|
||||
std::atomic_bool destroy_on_exit_{}; /* 最后一个引用在域线程释放时由该线程自销毁。 */
|
||||
std::thread thread_; /* 唯一允许访问 Datoviz/Vulkan 对象的线程。 */
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
#pragma once
|
||||
#include "../detail/Async_Render_Backend.hpp"
|
||||
#include "Scene_Datoviz_State.hpp"
|
||||
#include "../detail/Gpu_Completion_Service.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -12,14 +18,70 @@ namespace aethera::render_3d {
|
||||
namespace detail {
|
||||
template <Attached Object>
|
||||
struct Scene_Paint_Context {
|
||||
std::shared_ptr<Async_Render_Backend> backend{}; /* Scene 拥有、异步命令延长生命周期的后端。 */
|
||||
not_null<Object*> scene; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */
|
||||
Scene_3D_Parameters parameters{}; /* 当前 Prepare 读取的 Scene 参数。 */
|
||||
std::shared_ptr<Prepared_Visual_Batch> visuals{ /* 当前 Prepare 发布的数据句柄集合。 */
|
||||
std::make_shared<Prepared_Visual_Batch>()};
|
||||
not_null<Object*> scene; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */
|
||||
Scene_3D_Parameters parameters{}; /* 当前 Prepare 读取的 Scene 参数。 */
|
||||
std::shared_ptr<Prepared_Visual_Batch> visuals{
|
||||
/* 当前 Prepare 发布的数据句柄集合。 */
|
||||
std::make_shared<Prepared_Visual_Batch>()
|
||||
};
|
||||
};
|
||||
inline float wheel_step(double pixel, double angle) {
|
||||
if (angle != 0.0) return static_cast<float>(angle / 120.0);
|
||||
return static_cast<float>(pixel / 100.0);
|
||||
}
|
||||
struct Render_Scene_3D::Private : Prev_Private {
|
||||
inline Mouse_Button held_button(Mouse_Button_Mask buttons) {
|
||||
if ((buttons & 1U) != 0) return Mouse_Button::left;
|
||||
if ((buttons & 2U) != 0) return Mouse_Button::right;
|
||||
if ((buttons & 4U) != 0) return Mouse_Button::middle;
|
||||
return Mouse_Button::none;
|
||||
}
|
||||
inline void publish_datoviz_observation(
|
||||
not_null<Frame_3D*> frame, Datoviz_Frame_Observation observation) {
|
||||
if (observation.apply_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_apply_ns,
|
||||
observation.apply_ns);
|
||||
if (observation.emit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_plan_ns,
|
||||
observation.emit_ns);
|
||||
if (observation.execute_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_execute_ns,
|
||||
observation.execute_ns);
|
||||
if (observation.submit_ns)
|
||||
frame->record(Frame_Trace_Measurement::backend_submit_ns,
|
||||
observation.submit_ns);
|
||||
if (observation.readback_ns)
|
||||
frame->record(Frame_Trace_Measurement::readback_ns,
|
||||
observation.readback_ns);
|
||||
if (observation.gpu_fence_wait_ns)
|
||||
frame->record(Frame_Trace_Measurement::gpu_fence_wait_ns,
|
||||
observation.gpu_fence_wait_ns);
|
||||
if (observation.gpu) {
|
||||
frame->record(Frame_Trace_Measurement::gpu_render_ns,
|
||||
observation.gpu->render_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_transition_ns,
|
||||
observation.gpu->transition_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_copy_ns,
|
||||
observation.gpu->copy_ns);
|
||||
frame->record(Frame_Trace_Measurement::gpu_total_ns,
|
||||
observation.gpu->total_ns);
|
||||
}
|
||||
Frame_3D_Access::assign_datoviz_observation(frame, std::move(observation));
|
||||
}
|
||||
inline std::string failure_description(const std::exception_ptr& failure) {
|
||||
try {
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
return error.what();
|
||||
}
|
||||
catch (...) {
|
||||
return "non-standard exception";
|
||||
}
|
||||
return "empty exception";
|
||||
}
|
||||
}
|
||||
struct Render_Scene_3D::Private : Prev_Private,
|
||||
private detail::Scene_Datoviz_State {
|
||||
enum struct Frame_Phase : std::uint8_t {
|
||||
available,
|
||||
preparing,
|
||||
@@ -36,6 +98,8 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
std::atomic_bool gpu_submitted{};
|
||||
std::atomic_bool gpu_completed{};
|
||||
std::exception_ptr failure{};
|
||||
std::optional<detail::Scene_Datoviz_State::Pending_Frame>
|
||||
datoviz_frame{};
|
||||
};
|
||||
/*
|
||||
* 3D 核心线程模型:
|
||||
@@ -58,10 +122,11 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
std::atomic_bool completion_busy{};
|
||||
std::atomic<std::shared_ptr<const Frame_Callback>> frame_callback{};
|
||||
std::atomic<std::shared_ptr<const Submitted_Frame_Callback>> submitted_callback{};
|
||||
std::shared_ptr<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
|
||||
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
|
||||
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
|
||||
Root* axes_component{}; /* Builder 绑定的三轴组件。 */
|
||||
bool overlaps_gpu{};
|
||||
std::atomic_bool backend_available{true};
|
||||
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
|
||||
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
|
||||
Root* axes_component{}; /* Builder 绑定的三轴组件。 */
|
||||
Camera_Descriptor (*read_camera)(not_null<const Root*>){}; /* 读取 Camera 当前配置。 */
|
||||
std::array<plot::Axis_Descriptor, 3> (*read_axes)(not_null<const Root*>){}; /* 读取三轴当前配置。 */
|
||||
Frame_Context* active_context{}; /* 只由唯一 CPU Prepare 图访问。 */
|
||||
@@ -73,102 +138,127 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
not_null<Root*> camera, not_null<Root*> axes,
|
||||
Camera_Descriptor (*camera_reader)(not_null<const Root*>),
|
||||
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(not_null<const Root*>));
|
||||
template <Attached Object> [[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const;
|
||||
template <Attached Object>
|
||||
[[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const;
|
||||
[[nodiscard]] Frame_Context* context_for(
|
||||
not_null<Frame_3D*> frame) noexcept;
|
||||
template <Attached Object> void try_release_prepare(
|
||||
template <Attached Object>
|
||||
void try_release_prepare(
|
||||
Object* object, not_null<Frame_Context*> context);
|
||||
template <Attached Object> void arm_completion(Object* object);
|
||||
template <Attached Object> void consume_completion(Object* object);
|
||||
template <Attached Object> void complete_frame(
|
||||
template <Attached Object>
|
||||
void arm_completion(Object* object);
|
||||
template <Attached Object>
|
||||
void consume_completion(Object* object);
|
||||
template <Attached Object>
|
||||
void complete_frame(
|
||||
Object* object, not_null<Frame_Context*> context);
|
||||
template <Attached Object>
|
||||
void render_datoviz(
|
||||
Object* object, not_null<Frame_Context*> context,
|
||||
detail::Shared_Prepared_Visual_Batch visuals,
|
||||
detail::Scene_3D_Parameters parameters);
|
||||
template <Attached Object>
|
||||
void collect_datoviz(
|
||||
Object* object, not_null<Frame_Context*> context,
|
||||
std::optional<detail::Gpu_Completion_Service::Result> result,
|
||||
std::exception_ptr failure) noexcept;
|
||||
void dispatch_datoviz(const std::shared_ptr<Event>& event,
|
||||
Extent viewport);
|
||||
void fail_datoviz(std::exception_ptr failure) noexcept;
|
||||
/* CRTP 覆盖:绑定 Scene 机制和 Render_Scene_3D 公开薄壳。 */
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
template <Attached Object> void ensure_frame_taskflow(Object* object);
|
||||
template <Attached Object> [[nodiscard]] Render_Result render(
|
||||
template <Attached Object>
|
||||
void bind_private_crtp(Object* object);
|
||||
template <Attached Object>
|
||||
void ensure_frame_taskflow(Object* object);
|
||||
template <Attached Object>
|
||||
[[nodiscard]] Render_Result render(
|
||||
Object* object, not_null<Frame_3D*> frame);
|
||||
template <Attached Object> void set_frame_callback(Object* object, Frame_Callback callback);
|
||||
template <Attached Object> void set_submitted_frame_callback(Object* object, Submitted_Frame_Callback callback);
|
||||
template <Attached Object> void set_view_active(Object* object, bool active);
|
||||
template <Attached Object> void reset_diagnostics(Object* object);
|
||||
template <Attached Object>
|
||||
void set_frame_callback(Object* object, Frame_Callback callback);
|
||||
template <Attached Object>
|
||||
void set_submitted_frame_callback(Object* object, Submitted_Frame_Callback callback);
|
||||
template <Attached Object>
|
||||
void set_view_active(Object* object, bool active);
|
||||
template <Attached Object>
|
||||
void reset_diagnostics(Object* object);
|
||||
};
|
||||
template <typename Object>
|
||||
Render_Scene_3D::Builder<Object>::Builder() : Base() {}
|
||||
template <typename Object> template <Attached Visual_Object>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_renderable(
|
||||
template <typename Object>
|
||||
template <Attached Visual_Object>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder& Render_Scene_3D::Builder<Object>::add_renderable(
|
||||
not_null<Visual_Object*> visual_value) {
|
||||
if (std::ranges::any_of(visuals, [visual_value](const Visual_Binding& value) {
|
||||
return value.object == visual_value;
|
||||
}))
|
||||
return value.object == visual_value;
|
||||
}))
|
||||
throw std::logic_error("Render_Scene_3D cannot attach the same Visual twice");
|
||||
Visual_Binding binding{
|
||||
visual_value,
|
||||
Visual_Object::Attached_Object::Specification::family,
|
||||
[](not_null<Root*> root, std::shared_ptr<void> context) {
|
||||
using Definition = typename Visual_Object::Attached_Object;
|
||||
using Prepared = typename Definition::Prepared_Visual;
|
||||
const not_null object{static_cast<Visual_Object*>(root.get())};
|
||||
Base::private_access(object.get()).template get<typename Definition::Base_Tag>()
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
|
||||
const Prepared& prepared) {
|
||||
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
|
||||
const auto visual_identity = reinterpret_cast<detail::Visual_Identity>(identity);
|
||||
auto erased = detail::erase_prepared_visual(prepared);
|
||||
auto& visuals = *submission.visuals;
|
||||
const auto found = std::ranges::find(
|
||||
visuals, visual_identity,
|
||||
&detail::Prepared_Visual_Instance::identity);
|
||||
if (found == visuals.end())
|
||||
throw std::logic_error(
|
||||
"3D Visual published outside its Scene registration");
|
||||
/* Builder 已经为每个 Visual 建立稳定槽位。并行 Submit 节点只写各自
|
||||
* 的 visual 成员,不扩容容器,也不互相读写同一对象。 */
|
||||
found->visual = std::move(erased);
|
||||
});
|
||||
}};
|
||||
using Definition = typename Visual_Object::Attached_Object;
|
||||
using Prepared = typename Definition::Prepared_Visual;
|
||||
const not_null object{static_cast<Visual_Object*>(root.get())};
|
||||
Base::private_access(object.get()).template get<typename Definition::Base_Tag>()
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
|
||||
const Prepared& prepared) {
|
||||
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
|
||||
const auto visual_identity = reinterpret_cast<detail::Visual_Identity>(identity);
|
||||
auto erased = detail::erase_prepared_visual(prepared);
|
||||
auto& visuals = *submission.visuals;
|
||||
const auto found = std::ranges::find(
|
||||
visuals, visual_identity,
|
||||
&detail::Prepared_Visual_Instance::identity);
|
||||
if (found == visuals.end())
|
||||
throw std::logic_error(
|
||||
"3D Visual published outside its Scene registration");
|
||||
/* Builder 已经为每个 Visual 建立稳定槽位。并行 Submit 节点只写各自
|
||||
* 的 visual 成员,不扩容容器,也不互相读写同一对象。 */
|
||||
found->visual = std::move(erased);
|
||||
});
|
||||
}
|
||||
};
|
||||
visuals.push_back(binding);
|
||||
this->template add_dependency_node<Render_Graph_Tag>(visual_value.get());
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object> template <Attached Camera_Object>
|
||||
requires std::derived_from<Camera_Object, Camera_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_camera(
|
||||
template <typename Object>
|
||||
template <Attached Camera_Object> requires std::derived_from<Camera_Object, Camera_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder& Render_Scene_3D::Builder<Object>::add_camera(
|
||||
not_null<Camera_Object*> camera_value) {
|
||||
if (camera) throw std::logic_error("Render_Scene_3D accepts one Camera component");
|
||||
camera = camera_value;
|
||||
read_camera = [](not_null<const Root*> root) {
|
||||
const not_null object{static_cast<const Camera_Object*>(root.get())};
|
||||
const Camera_3D::Prop& prop =
|
||||
Base::template current_prop<Camera_3D::Base_Tag>(object.get());
|
||||
return Camera_Descriptor{prop.initial_view, prop.projection, prop.controller,
|
||||
prop.turntable_control, prop.arcball_control,
|
||||
prop.fly_control, prop.panzoom_control,
|
||||
prop.vertical_field_of_view_degrees,
|
||||
prop.near_plane, prop.far_plane};
|
||||
Base::template current_prop<Camera_3D::Base_Tag>(object.get());
|
||||
return Camera_Descriptor{
|
||||
prop.initial_view, prop.projection, prop.controller,
|
||||
prop.turntable_control, prop.arcball_control,
|
||||
prop.fly_control, prop.panzoom_control,
|
||||
prop.vertical_field_of_view_degrees,
|
||||
prop.near_plane, prop.far_plane
|
||||
};
|
||||
};
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object> template <Attached Axes_Object>
|
||||
requires std::derived_from<Axes_Object, Axes_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_axes(
|
||||
template <typename Object>
|
||||
template <Attached Axes_Object> requires std::derived_from<Axes_Object, Axes_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder& Render_Scene_3D::Builder<Object>::add_axes(
|
||||
not_null<Axes_Object*> axes_value) {
|
||||
if (axes) throw std::logic_error("Render_Scene_3D accepts one Axes component");
|
||||
axes = axes_value;
|
||||
read_axes = [](not_null<const Root*> root) {
|
||||
const not_null object{static_cast<const Axes_Object*>(root.get())};
|
||||
const Axes_3D::Prop& prop =
|
||||
Base::template current_prop<Axes_3D::Base_Tag>(object.get());
|
||||
Base::template current_prop<Axes_3D::Base_Tag>(object.get());
|
||||
return std::array<plot::Axis_Descriptor, 3>{prop.x_axis, prop.y_axis, prop.z_axis};
|
||||
};
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::use_gpu(std::uint32_t gpu_index_value,
|
||||
bool validation_enabled_value) {
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder& Render_Scene_3D::Builder<Object>::use_gpu(std::uint32_t gpu_index_value,
|
||||
bool validation_enabled_value) {
|
||||
gpu_index = gpu_index_value;
|
||||
validation_enabled = validation_enabled_value;
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
@@ -187,7 +277,8 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::
|
||||
for (const auto& visual : visuals)
|
||||
registrations.push_back({
|
||||
reinterpret_cast<detail::Visual_Identity>(visual.object.get()),
|
||||
visual.family});
|
||||
visual.family
|
||||
});
|
||||
private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, std::move(registrations),
|
||||
camera, axes, read_camera,
|
||||
read_axes);
|
||||
@@ -199,8 +290,239 @@ detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object)
|
||||
const Prop& prop = static_cast<const Prop&>(
|
||||
double_buffer::detail::Internal_Access::current_prop(object));
|
||||
const auto axis = read_axes(axes_component);
|
||||
return {prop.viewport, prop.clear_color,
|
||||
read_camera(camera_component), axis[0], axis[1], axis[2]};
|
||||
return {
|
||||
prop.viewport, prop.clear_color,
|
||||
read_camera(camera_component), axis[0], axis[1], axis[2]
|
||||
};
|
||||
}
|
||||
inline void Render_Scene_3D::Private::fail_datoviz(
|
||||
std::exception_ptr failure) noexcept {
|
||||
backend_available.store(false, std::memory_order_release);
|
||||
try {
|
||||
const auto description = detail::failure_description(failure);
|
||||
std::fprintf(stderr, "Aethera 3D Scene unavailable: %s\n",
|
||||
description.c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
inline void Render_Scene_3D::Private::dispatch_datoviz(
|
||||
const std::shared_ptr<Event>& event, Extent viewport) {
|
||||
if (!event) return;
|
||||
const auto* pointer =
|
||||
dynamic_cast<const Pointer_Event_Capability*>(event.get());
|
||||
const auto* wheel =
|
||||
dynamic_cast<const Wheel_Event_Capability*>(event.get());
|
||||
const auto* key = dynamic_cast<const Key_Event*>(event.get());
|
||||
const bool pointer_valid = pointer &&
|
||||
std::isfinite(pointer->position_x()) &&
|
||||
std::isfinite(pointer->position_y()) &&
|
||||
pointer->position_x() >= 0.0 && pointer->position_y() >= 0.0 &&
|
||||
pointer->position_x() <= viewport.width &&
|
||||
pointer->position_y() <= viewport.height;
|
||||
if (wheel && pointer_valid) {
|
||||
dispatch_wheel(
|
||||
static_cast<float>(pointer->position_x()),
|
||||
static_cast<float>(pointer->position_y()),
|
||||
detail::wheel_step(wheel->pixel_delta_x_value(),
|
||||
wheel->angle_delta_x_value()),
|
||||
detail::wheel_step(wheel->pixel_delta_y_value(),
|
||||
wheel->angle_delta_y_value()),
|
||||
pointer->keyboard_modifiers(), viewport,
|
||||
event->occurred_at.nanoseconds);
|
||||
}
|
||||
else if (pointer_valid &&
|
||||
(event->type == Event_Type::pointer_move ||
|
||||
event->type == Event_Type::pointer_press ||
|
||||
event->type == Event_Type::pointer_release)) {
|
||||
dispatch_pointer(
|
||||
event->type,
|
||||
static_cast<float>(pointer->position_x()),
|
||||
static_cast<float>(pointer->position_y()),
|
||||
event->type == Event_Type::pointer_move
|
||||
? detail::held_button(pointer->pointer_buttons())
|
||||
: pointer->pointer_button(),
|
||||
pointer->keyboard_modifiers(), viewport,
|
||||
event->occurred_at.nanoseconds);
|
||||
}
|
||||
else if (key) {
|
||||
dispatch_key(*key);
|
||||
}
|
||||
if ((wheel && pointer_valid) || pointer_valid || key) event->accept();
|
||||
event->mark_dispatch_completed();
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::render_datoviz(
|
||||
Object* object, not_null<Frame_Context*> context,
|
||||
detail::Shared_Prepared_Visual_Batch visuals,
|
||||
detail::Scene_3D_Parameters scene) {
|
||||
if (!visuals || visuals->empty()) throw std::invalid_argument("3D Scene requires prepared Visual data");
|
||||
if (!backend_available.load(std::memory_order_acquire)) throw std::runtime_error("3D Scene resources are unavailable");
|
||||
const not_null frame{context->frame};
|
||||
auto reservation = detail::Gpu_Completion_Service::instance().prepare(
|
||||
[this, object, context](detail::Gpu_Completion_Service::Result result) {
|
||||
collect_datoviz(object, context, std::move(result), {});
|
||||
},
|
||||
[this, object, context](std::exception_ptr failure) {
|
||||
collect_datoviz(object, context, {}, std::move(failure));
|
||||
},
|
||||
frame->taskflow_trace_requested());
|
||||
if (!reservation) {
|
||||
if (reservation.result == detail::Gpu_Completion_Service::
|
||||
Admission_Result::capacity_exhausted)
|
||||
throw std::runtime_error(
|
||||
"GPU completion capacity is exhausted");
|
||||
throw std::runtime_error("GPU completion service is unavailable");
|
||||
}
|
||||
try {
|
||||
const auto sequence = frame->identity().sequence;
|
||||
const bool observe = frame->taskflow_trace_requested();
|
||||
const bool readback = frame->output() == Frame_3D_Output::pixels;
|
||||
std::optional<detail::Scene_Datoviz_State::Pending_Frame> prepared;
|
||||
if (context->events.empty()) {
|
||||
frame->mark(Frame_Trace_Marker::backend_prepare_started);
|
||||
prepared = try_prepare_reused(
|
||||
scene, *visuals, sequence, observe, readback);
|
||||
}
|
||||
if (!prepared) {
|
||||
for (const auto& event : context->events) {
|
||||
if (event) event->mark_dispatch_started(sequence);
|
||||
dispatch_datoviz(event, scene.viewport);
|
||||
}
|
||||
context->events.clear();
|
||||
frame->mark(Frame_Trace_Marker::backend_prepare_started);
|
||||
prepared = prepare(
|
||||
scene, *visuals, sequence, observe, readback);
|
||||
}
|
||||
if (!prepared)
|
||||
throw std::logic_error(
|
||||
"Datoviz did not provide a frame target after Scene admission");
|
||||
frame->mark(Frame_Trace_Marker::backend_prepare_finished);
|
||||
context->datoviz_frame.emplace(std::move(*prepared));
|
||||
context->datoviz_frame->observation.prepare_released_after_submission =
|
||||
overlaps_gpu;
|
||||
frame->mark(Frame_Trace_Marker::backend_queue_entered);
|
||||
frame->mark(Frame_Trace_Marker::backend_submit_queued);
|
||||
auto completion_reservation = std::make_shared<
|
||||
detail::Gpu_Completion_Service::Reservation>(
|
||||
std::move(reservation.reservation));
|
||||
submit(
|
||||
*context->datoviz_frame,
|
||||
[this, object, context, frame,
|
||||
completion_reservation = std::move(completion_reservation)](
|
||||
std::exception_ptr submission_failure) mutable {
|
||||
try {
|
||||
const bool submission_failed =
|
||||
static_cast<bool>(submission_failure);
|
||||
frame->mark(Frame_Trace_Marker::backend_queue_left);
|
||||
if (submission_failed) {
|
||||
fail_datoviz(submission_failure);
|
||||
if (context->datoviz_frame) {
|
||||
discard(
|
||||
std::move(*context->datoviz_frame));
|
||||
context->datoviz_frame.reset();
|
||||
}
|
||||
context->failure = std::move(submission_failure);
|
||||
context->overlaps_gpu = false;
|
||||
context->gpu_submitted.store(
|
||||
true, std::memory_order_release);
|
||||
context->gpu_completed.store(
|
||||
true, std::memory_order_release);
|
||||
}
|
||||
else {
|
||||
frame->mark(Frame_Trace_Marker::gpu_submitted);
|
||||
context->overlaps_gpu = overlaps_gpu;
|
||||
context->gpu_submitted.store(
|
||||
true, std::memory_order_release);
|
||||
completion_reservation->watch(
|
||||
context->datoviz_frame->device,
|
||||
context->datoviz_frame->fence);
|
||||
}
|
||||
try_release_prepare(object, context);
|
||||
if (context->phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted && submission_failed)
|
||||
arm_completion(object);
|
||||
}
|
||||
catch (...) {
|
||||
collect_datoviz(
|
||||
object, context, {}, std::current_exception());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (...) {
|
||||
auto failure = std::current_exception();
|
||||
fail_datoviz(failure);
|
||||
if (context->datoviz_frame) {
|
||||
if (context->gpu_submitted.load(std::memory_order_acquire)) quarantine(std::move(*context->datoviz_frame));
|
||||
else discard(std::move(*context->datoviz_frame));
|
||||
context->datoviz_frame.reset();
|
||||
}
|
||||
context->failure = std::move(failure);
|
||||
context->overlaps_gpu = false;
|
||||
context->gpu_submitted.store(true, std::memory_order_release);
|
||||
context->gpu_completed.store(true, std::memory_order_release);
|
||||
try_release_prepare(object, context);
|
||||
if (context->phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted)
|
||||
arm_completion(object);
|
||||
}
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::collect_datoviz(
|
||||
Object* object, not_null<Frame_Context*> context,
|
||||
std::optional<detail::Gpu_Completion_Service::Result> result,
|
||||
std::exception_ptr failure) noexcept {
|
||||
try {
|
||||
const not_null frame{context->frame};
|
||||
frame->mark(Frame_Trace_Marker::gpu_completed);
|
||||
frame->mark(Frame_Trace_Marker::readback_started);
|
||||
if (!context->datoviz_frame) throw std::logic_error("3D completion lost its Datoviz target");
|
||||
if (result)
|
||||
context->datoviz_frame->observation.gpu_fence_wait_ns =
|
||||
result->wait_duration_ns;
|
||||
if (failure) std::rethrow_exception(std::move(failure));
|
||||
if (!result || result->error !=
|
||||
detail::Gpu_Completion_Service::Completion_Error::none) {
|
||||
const auto code = result
|
||||
? static_cast<unsigned>(result->error)
|
||||
: std::numeric_limits<unsigned>::max();
|
||||
const auto vulkan = result
|
||||
? static_cast<int>(result->vulkan_result)
|
||||
: -1;
|
||||
throw std::runtime_error(
|
||||
"Datoviz GPU completion failed: error=" +
|
||||
std::to_string(code) + ", VkResult=" +
|
||||
std::to_string(vulkan));
|
||||
}
|
||||
auto completed = collect(
|
||||
std::move(*context->datoviz_frame));
|
||||
context->datoviz_frame.reset();
|
||||
frame->mark(Frame_Trace_Marker::readback_finished);
|
||||
detail::publish_datoviz_observation(
|
||||
frame, std::move(completed.observation));
|
||||
std::array outputs{frame};
|
||||
detail::Frame_3D_Access::assign_pixels(
|
||||
outputs, completed.extent, std::move(completed.pixels),
|
||||
frame->identity());
|
||||
}
|
||||
catch (...) {
|
||||
context->failure = std::current_exception();
|
||||
fail_datoviz(context->failure);
|
||||
if (context->datoviz_frame) {
|
||||
quarantine(std::move(*context->datoviz_frame));
|
||||
context->datoviz_frame.reset();
|
||||
}
|
||||
}
|
||||
try {
|
||||
context->gpu_completed.store(true, std::memory_order_release);
|
||||
try_release_prepare(object, context);
|
||||
if (context->phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted)
|
||||
arm_completion(object);
|
||||
}
|
||||
catch (...) {
|
||||
fail_datoviz(std::current_exception());
|
||||
}
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::complete_frame(
|
||||
@@ -208,92 +530,89 @@ void Render_Scene_3D::Private::complete_frame(
|
||||
const auto frame = context->frame;
|
||||
const auto callback = frame_callback.load(std::memory_order_acquire);
|
||||
frame->mark(Frame_Trace_Marker::frame_ready);
|
||||
if (context->trace_started)
|
||||
aethera::detail::finish_taskflow_trace(*frame);
|
||||
|
||||
if (context->trace_started) aethera::detail::finish_taskflow_trace(*frame);
|
||||
/* Scene 仅借用外部 Frame。完成通知前必须先清除所有借用引用并开放
|
||||
* Frame_Context;回调之后是否发布、统计或复用只由调用方帧策略决定。 */
|
||||
context->frame = nullptr;
|
||||
context->events.clear();
|
||||
context->failure = {};
|
||||
context->datoviz_frame.reset();
|
||||
context->trace_started = false;
|
||||
context->overlaps_gpu = false;
|
||||
context->cpu_finished.store(false, std::memory_order_relaxed);
|
||||
context->gpu_submitted.store(false, std::memory_order_relaxed);
|
||||
context->gpu_completed.store(false, std::memory_order_relaxed);
|
||||
context->phase.store(Frame_Phase::available, std::memory_order_release);
|
||||
if (callback && *callback) (*callback)(frame);
|
||||
if (callback && *callback) {
|
||||
try {
|
||||
(*callback)(frame);
|
||||
}
|
||||
catch (...) {
|
||||
fail_datoviz(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline Render_Scene_3D::Private::Frame_Context*
|
||||
Render_Scene_3D::Private::context_for(not_null<Frame_3D*> frame) noexcept {
|
||||
inline Render_Scene_3D::Private::Frame_Context* Render_Scene_3D::Private::context_for(not_null<Frame_3D*> frame) noexcept {
|
||||
const auto found = std::ranges::find(frame_contexts, frame.get(),
|
||||
&Frame_Context::frame);
|
||||
&Frame_Context::frame);
|
||||
return found == frame_contexts.end()
|
||||
? nullptr
|
||||
: &*found;
|
||||
? nullptr
|
||||
: &*found;
|
||||
}
|
||||
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::try_release_prepare(
|
||||
Object* object, not_null<Frame_Context*> context) {
|
||||
if (!context->cpu_finished.load(std::memory_order_acquire) ||
|
||||
!context->gpu_submitted.load(std::memory_order_acquire) ||
|
||||
(!context->overlaps_gpu &&
|
||||
!context->gpu_completed.load(std::memory_order_acquire)))
|
||||
!context->gpu_completed.load(std::memory_order_acquire)))
|
||||
return;
|
||||
auto expected = Frame_Phase::preparing;
|
||||
if (!context->phase.compare_exchange_strong(
|
||||
expected, Frame_Phase::submitted, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
expected, Frame_Phase::submitted, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
return;
|
||||
prepare_active.store(false, std::memory_order_release);
|
||||
if (const auto callback =
|
||||
submitted_callback.load(std::memory_order_acquire);
|
||||
callback && *callback)
|
||||
submitted_callback.load(std::memory_order_acquire); callback && *callback)
|
||||
(*callback)(context->frame, context->overlaps_gpu);
|
||||
if (context->gpu_completed.load(std::memory_order_acquire))
|
||||
arm_completion(object);
|
||||
if (context->gpu_completed.load(std::memory_order_acquire)) arm_completion(object);
|
||||
}
|
||||
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::arm_completion(Object* object) {
|
||||
bool expected{};
|
||||
if (!completion_busy.compare_exchange_strong(
|
||||
expected, true, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
expected, true, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
return;
|
||||
aethera::schedule_task("render_3d.frame.retire", [this, object] {
|
||||
consume_completion(object);
|
||||
});
|
||||
consume_completion(object);
|
||||
}
|
||||
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::consume_completion(Object* object) {
|
||||
Frame_Context* selected{};
|
||||
for (auto& context : frame_contexts) {
|
||||
if (context.phase.load(std::memory_order_acquire) !=
|
||||
Frame_Phase::submitted ||
|
||||
Frame_Phase::submitted ||
|
||||
!context.gpu_completed.load(std::memory_order_acquire))
|
||||
continue;
|
||||
if (!selected || context.frame->identity().sequence <
|
||||
selected->frame->identity().sequence)
|
||||
selected->frame->identity().sequence)
|
||||
selected = &context;
|
||||
}
|
||||
if (!selected) {
|
||||
completion_busy.store(false, std::memory_order_release);
|
||||
if (std::ranges::any_of(frame_contexts, [](const auto& context) {
|
||||
return context.phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted &&
|
||||
context.gpu_completed.load(std::memory_order_acquire);
|
||||
}))
|
||||
return context.phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted &&
|
||||
context.gpu_completed.load(std::memory_order_acquire);
|
||||
}))
|
||||
arm_completion(object);
|
||||
return;
|
||||
}
|
||||
auto expected = Frame_Phase::submitted;
|
||||
if (!selected->phase.compare_exchange_strong(
|
||||
expected, Frame_Phase::completing, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire)) {
|
||||
expected, Frame_Phase::completing, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire)) {
|
||||
completion_busy.store(false, std::memory_order_release);
|
||||
arm_completion(object);
|
||||
return;
|
||||
@@ -313,8 +632,7 @@ void Render_Scene_3D::Private::consume_completion(Object* object) {
|
||||
if (frame->taskflow_trace_requested())
|
||||
aethera::detail::run_taskflow(
|
||||
completion_graph, *frame, "render_3d.completion", std::move(done));
|
||||
else
|
||||
aethera::detail::run_taskflow(completion_graph, std::move(done));
|
||||
else aethera::detail::run_taskflow(completion_graph, std::move(done));
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::initialize_backend(
|
||||
@@ -330,48 +648,63 @@ void Render_Scene_3D::Private::initialize_backend(
|
||||
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);
|
||||
for (const auto& visual : visuals) prepared_visuals->push_back({visual.identity, {}});
|
||||
overlaps_gpu = std::ranges::all_of(visuals, [](const auto& visual) {
|
||||
switch (visual.family) {
|
||||
case Visual_Family::point:
|
||||
case Visual_Family::splat:
|
||||
case Visual_Family::pixel:
|
||||
case Visual_Family::sphere:
|
||||
case Visual_Family::primitive:
|
||||
case Visual_Family::mesh:
|
||||
case Visual_Family::path: return true;
|
||||
case Visual_Family::marker:
|
||||
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;
|
||||
});
|
||||
detail::Scene_Datoviz_State::initialize(
|
||||
gpu_index, validation_enabled, visuals, initial);
|
||||
paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(
|
||||
detail::Scene_Paint_Context<Object>{
|
||||
backend, object, initial,
|
||||
std::move(prepared_visuals)});
|
||||
object, initial, std::move(prepared_visuals)
|
||||
});
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
|
||||
if (frame_taskflow) return;
|
||||
if (!runtime->taskflow)
|
||||
runtime->taskflow = std::make_unique<Task_Graph>("scene.render");
|
||||
|
||||
if (!runtime->taskflow) runtime->taskflow = std::make_unique<Task_Graph>("scene.render");
|
||||
frame_taskflow = std::make_unique<Task_Graph>("render_3d.frame");
|
||||
auto& graph = *frame_taskflow;
|
||||
graph.describe("owner_kind", "scene")
|
||||
.describe("owner_component", "scene");
|
||||
auto begin = graph.add("scene.begin", [this, object] {
|
||||
if (!active_context || !active_context->frame)
|
||||
throw std::logic_error("3D frame DAG lost its active frame");
|
||||
if (!active_context || !active_context->frame) throw std::logic_error("3D frame DAG lost its active frame");
|
||||
const auto frame = active_context->frame;
|
||||
frame->mark(Frame_Trace_Marker::scene_render_started);
|
||||
frame->mark(Frame_Trace_Marker::event_dispatch_started);
|
||||
active_context->events = 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);
|
||||
if (!detail::prepared_visual_batch_complete(*context->visuals)) {
|
||||
double_buffer::detail::Internal_Access::
|
||||
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
|
||||
[](Renderable* renderable, Renderable::Private&) {
|
||||
double_buffer::detail::Internal_Access::
|
||||
mark_dirty<Render_Graph_Tag>(renderable);
|
||||
});
|
||||
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
|
||||
[](Renderable* renderable, Renderable::Private&) {
|
||||
double_buffer::detail::Internal_Access::
|
||||
mark_dirty<Render_Graph_Tag>(renderable);
|
||||
});
|
||||
}
|
||||
if (context->visuals.use_count() != 1)
|
||||
context->visuals =
|
||||
std::make_shared<detail::Prepared_Visual_Batch>(*context->visuals);
|
||||
std::make_shared<detail::Prepared_Visual_Batch>(*context->visuals);
|
||||
camera_component->advance_object();
|
||||
axes_component->advance_object();
|
||||
frame->mark(Frame_Trace_Marker::prepare_started);
|
||||
@@ -379,16 +712,13 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
|
||||
frame->mark(Frame_Trace_Marker::paint_started);
|
||||
});
|
||||
begin.describe("dimension", "3D").describe("stage", "frame setup");
|
||||
|
||||
auto renderables = graph.compose("scene.renderables", *runtime->taskflow);
|
||||
renderables.describe("dimension", "3D")
|
||||
.describe("stage", "visual graph")
|
||||
.describe("execution_domain", "Taskflow workers");
|
||||
|
||||
auto submit = graph.add("scene.backend.submit", [this, object] {
|
||||
const auto execution = active_context;
|
||||
if (!execution || !execution->frame)
|
||||
throw std::logic_error("3D submit lost its frame context");
|
||||
if (!execution || !execution->frame) throw std::logic_error("3D submit lost its frame context");
|
||||
auto context = std::static_pointer_cast<
|
||||
detail::Scene_Paint_Context<Object>>(paint_context);
|
||||
if (!detail::prepared_visual_batch_complete(*context->visuals))
|
||||
@@ -397,40 +727,8 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
|
||||
auto& state = static_cast<State&>(
|
||||
double_buffer::detail::Internal_Access::pending_state(object));
|
||||
state.event_statistics = event_statistics.state();
|
||||
context->backend->render(
|
||||
context->visuals, context->parameters,
|
||||
execution->frame, std::move(execution->events),
|
||||
[this, object](not_null<Frame_3D*> submitted, bool overlaps_gpu) {
|
||||
const auto frame_context = context_for(submitted);
|
||||
if (!frame_context)
|
||||
throw std::logic_error(
|
||||
"3D backend submitted a frame not borrowed by Scene");
|
||||
frame_context->overlaps_gpu = overlaps_gpu;
|
||||
frame_context->gpu_submitted.store(
|
||||
true, std::memory_order_release);
|
||||
try_release_prepare(object, frame_context);
|
||||
},
|
||||
[this, object](not_null<Frame_3D*> completed,
|
||||
std::exception_ptr failure) {
|
||||
const auto frame_context = context_for(completed);
|
||||
if (!frame_context)
|
||||
throw std::logic_error(
|
||||
"3D backend completed a frame not borrowed by Scene");
|
||||
frame_context->failure = std::move(failure);
|
||||
if (frame_context->failure &&
|
||||
!frame_context->gpu_submitted.load(
|
||||
std::memory_order_acquire)) {
|
||||
frame_context->overlaps_gpu = false;
|
||||
frame_context->gpu_submitted.store(
|
||||
true, std::memory_order_release);
|
||||
}
|
||||
frame_context->gpu_completed.store(
|
||||
true, std::memory_order_release);
|
||||
try_release_prepare(object, frame_context);
|
||||
if (frame_context->phase.load(std::memory_order_acquire) ==
|
||||
Frame_Phase::submitted)
|
||||
arm_completion(object);
|
||||
});
|
||||
render_datoviz(object, execution, context->visuals,
|
||||
context->parameters);
|
||||
});
|
||||
submit.describe("dimension", "3D")
|
||||
.describe("backend", "Datoviz")
|
||||
@@ -448,15 +746,15 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
"Render_Scene_3D requires a frame callback before render");
|
||||
bool inactive{};
|
||||
if (!prepare_active.compare_exchange_strong(
|
||||
inactive, true, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
inactive, true, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
return Render_Result::frame_pipeline_busy;
|
||||
Frame_Context* execution{};
|
||||
for (auto& context : frame_contexts) {
|
||||
auto expected = Frame_Phase::available;
|
||||
if (context.phase.compare_exchange_strong(
|
||||
expected, Frame_Phase::preparing,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire)) {
|
||||
expected, Frame_Phase::preparing,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire)) {
|
||||
execution = &context;
|
||||
break;
|
||||
}
|
||||
@@ -468,6 +766,7 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
execution->frame = frame;
|
||||
execution->events.clear();
|
||||
execution->failure = {};
|
||||
execution->datoviz_frame.reset();
|
||||
execution->trace_started = false;
|
||||
execution->overlaps_gpu = false;
|
||||
execution->cpu_finished.store(false, std::memory_order_relaxed);
|
||||
@@ -494,7 +793,7 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
double_buffer::detail::Internal_Access::current_prop(object));
|
||||
if (!prop.view_active) return reject_frame(Render_Result::view_inactive);
|
||||
if (prop.viewport.empty()) return reject_frame(Render_Result::empty_viewport);
|
||||
if (!backend || !backend->available())
|
||||
if (!backend_available.load(std::memory_order_acquire))
|
||||
return reject_frame(Render_Result::backend_unavailable);
|
||||
ensure_frame_taskflow(object);
|
||||
}
|
||||
@@ -504,14 +803,14 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
}
|
||||
frame->mark(Frame_Trace_Marker::scene_render_requested);
|
||||
execution->trace_started =
|
||||
aethera::detail::begin_taskflow_trace(*frame);
|
||||
aethera::detail::begin_taskflow_trace(*frame);
|
||||
try {
|
||||
auto completion = [this, object, execution] {
|
||||
double_buffer::detail::Internal_Access::
|
||||
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
|
||||
[](Renderable* renderable, Renderable::Private& data) {
|
||||
data.publish_renderable_state(renderable);
|
||||
});
|
||||
current_dependency_graph<Render_Graph_Tag>(object).for_each_bound(
|
||||
[](Renderable* renderable, Renderable::Private& data) {
|
||||
data.publish_renderable_state(renderable);
|
||||
});
|
||||
double_buffer::detail::Internal_Access::publish_state<Render_Scene_3D::Base_Tag>(object);
|
||||
active_context = nullptr;
|
||||
execution->cpu_finished.store(true, std::memory_order_release);
|
||||
@@ -520,8 +819,7 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
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));
|
||||
else aethera::detail::run_taskflow(*frame_taskflow, std::move(completion));
|
||||
}
|
||||
catch (...) {
|
||||
if (execution->trace_started) {
|
||||
@@ -536,17 +834,18 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::reset_diagnostics(Object* object) {
|
||||
double_buffer::detail::Internal_Access::publish_state<Scene::Base_Tag, &Scene::State::event_statistics>(object,
|
||||
[this](State_Access<typename Object::State> states) {
|
||||
event_statistics.reset();
|
||||
auto& state = states.template get<Scene::Base_Tag>();
|
||||
state.event_statistics = {};
|
||||
});
|
||||
[this](State_Access<typename Object::State> states) {
|
||||
event_statistics.reset();
|
||||
auto& state = states.template get<Scene::Base_Tag>();
|
||||
state.event_statistics = {};
|
||||
});
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::set_frame_callback(Object*, Frame_Callback callback) {
|
||||
frame_callback.store(
|
||||
callback ? std::make_shared<const Frame_Callback>(std::move(callback))
|
||||
: std::shared_ptr<const Frame_Callback>{},
|
||||
callback
|
||||
? std::make_shared<const Frame_Callback>(std::move(callback))
|
||||
: std::shared_ptr<const Frame_Callback>{},
|
||||
std::memory_order_release);
|
||||
}
|
||||
template <Attached Object>
|
||||
@@ -555,7 +854,7 @@ void Render_Scene_3D::Private::set_submitted_frame_callback(
|
||||
submitted_callback.store(
|
||||
callback
|
||||
? std::make_shared<const Submitted_Frame_Callback>(
|
||||
std::move(callback))
|
||||
std::move(callback))
|
||||
: std::shared_ptr<const Submitted_Frame_Callback>{},
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
+450
-306
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include "../base/Datoviz_Frame_Observation.hpp"
|
||||
#include "../detail/Backend_Types.hpp"
|
||||
#include <render_common.hpp>
|
||||
#include <ownership.hpp>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct DvzBuffer;
|
||||
struct DvzController;
|
||||
struct DvzDrp2CommandStream;
|
||||
struct DvzDrp2Runtime;
|
||||
struct DvzFigure;
|
||||
struct DvzFont;
|
||||
struct DvzFramePlanEmitter;
|
||||
struct DvzInputRouter;
|
||||
struct DvzItemInteraction;
|
||||
struct DvzPanel;
|
||||
struct DvzPinnedReadout;
|
||||
struct DvzPointerGestureHandler;
|
||||
struct DvzSampledField;
|
||||
struct DvzScene;
|
||||
struct DvzSceneBuffer;
|
||||
struct DvzSceneFrameArtifact;
|
||||
struct DvzText;
|
||||
struct DvzVisual;
|
||||
|
||||
namespace aethera { namespace render_3d { namespace detail {
|
||||
|
||||
struct Datoviz_Render_Context;
|
||||
|
||||
struct Scene_Datoviz_State {
|
||||
struct Pending_Frame {
|
||||
std::uintptr_t device{};
|
||||
std::uintptr_t fence{};
|
||||
Extent extent{};
|
||||
std::uint64_t sequence{};
|
||||
std::uint8_t target_index{};
|
||||
std::uint64_t target_generation{};
|
||||
Datoviz_Frame_Observation observation{};
|
||||
};
|
||||
|
||||
struct Completed_Frame {
|
||||
Extent extent{};
|
||||
std::vector<std::byte> pixels{};
|
||||
Datoviz_Frame_Observation observation{};
|
||||
};
|
||||
struct Frame_Target;
|
||||
struct Frame_Targets;
|
||||
|
||||
struct External_Attribute {
|
||||
std::string name{};
|
||||
std::uint32_t stride{};
|
||||
std::uint32_t capacity{};
|
||||
not_null<DvzSceneBuffer*> scene_buffer;
|
||||
owner<DvzBuffer*> gpu_buffer{};
|
||||
std::uint8_t registered_targets{};
|
||||
};
|
||||
|
||||
struct Visual_Instance {
|
||||
Visual_Identity identity{};
|
||||
Visual_Family family{Visual_Family::point};
|
||||
not_null<DvzVisual*> visual;
|
||||
DvzSampledField* field{};
|
||||
DvzFont* font{};
|
||||
DvzText* coordinate_text{};
|
||||
std::array<std::uint32_t, 3> field_extent{};
|
||||
std::uint64_t applied_revision{};
|
||||
std::array<std::uint64_t, 3> uploaded_data_revisions{};
|
||||
std::optional<Prepared_Visual> applied{};
|
||||
std::vector<External_Attribute> attributes{};
|
||||
};
|
||||
|
||||
struct Runtime_Slot {
|
||||
owner<DvzDrp2Runtime*> runtime{};
|
||||
owner<DvzFramePlanEmitter*> emitter{};
|
||||
};
|
||||
|
||||
Scene_Datoviz_State();
|
||||
~Scene_Datoviz_State();
|
||||
Scene_Datoviz_State(const Scene_Datoviz_State&) = delete;
|
||||
Scene_Datoviz_State& operator=(const Scene_Datoviz_State&) = delete;
|
||||
|
||||
void initialize(std::uint32_t gpu_index, bool validation_enabled,
|
||||
const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
void create_scene(const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
void apply_camera(const Camera_Descriptor& camera);
|
||||
void apply_axes(const Scene_3D_Parameters& scene);
|
||||
[[nodiscard]] bool matches_command_structure(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals) const;
|
||||
[[nodiscard]] std::uint64_t apply(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint8_t target_index, bool bind_target);
|
||||
[[nodiscard]] std::uint64_t apply_visual(
|
||||
Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index, bool bind_target);
|
||||
void ensure_external_attributes(Visual_Instance& target,
|
||||
const Prepared_Visual& visual);
|
||||
[[nodiscard]] std::uint64_t upload_external_attributes(
|
||||
Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index);
|
||||
void bind_external_attributes(Visual_Instance& target,
|
||||
std::uint8_t target_index,
|
||||
std::uint32_t item_count);
|
||||
void register_external_attributes(const DvzDrp2CommandStream* stream,
|
||||
std::uint8_t target_index);
|
||||
[[nodiscard]] bool target_runtime_resources_current(
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint8_t target_index) const;
|
||||
[[nodiscard]] std::vector<DvzVisual*> stale_runtime_visuals(
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint8_t target_index) const;
|
||||
void mark_target_runtime_resources_uploaded(std::uint8_t target_index);
|
||||
[[nodiscard]] std::optional<Pending_Frame> reuse_recorded_target(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
[[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);
|
||||
[[nodiscard]] std::optional<Pending_Frame> try_prepare_reused(
|
||||
const Scene_3D_Parameters& scene,
|
||||
const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
[[nodiscard]] DvzSceneFrameArtifact* emit(
|
||||
const Scene_3D_Parameters& scene, std::uint8_t target_index);
|
||||
[[nodiscard]] std::optional<std::uint8_t> acquire_target(Extent extent);
|
||||
[[nodiscard]] Frame_Target& target(const Pending_Frame& pending);
|
||||
void submit(Pending_Frame& pending,
|
||||
std::function<void(std::exception_ptr)> completion);
|
||||
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
||||
void discard(Pending_Frame pending);
|
||||
void quarantine(Pending_Frame pending) noexcept;
|
||||
void mark_controller_input_applied() noexcept;
|
||||
void dispatch_pointer(Event_Type type, float x, float y,
|
||||
Mouse_Button button,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_wheel(float x, float y, float delta_x, float delta_y,
|
||||
Keyboard_Modifier modifiers,
|
||||
Extent viewport,
|
||||
std::uint64_t occurred_at_ns);
|
||||
void dispatch_key(const Key_Event& event);
|
||||
void abandon_resources() noexcept;
|
||||
void destroy();
|
||||
|
||||
std::shared_ptr<Datoviz_Render_Context> render_context_{};
|
||||
std::uintptr_t command_pool_{};
|
||||
std::uintptr_t descriptor_pool_{};
|
||||
mutable std::mutex target_mutex_{};
|
||||
std::array<Runtime_Slot, 3> runtime_slots_{};
|
||||
owner<DvzScene*> scene_{};
|
||||
DvzFigure* figure_{};
|
||||
DvzPanel* panel_{};
|
||||
std::vector<Visual_Instance> visuals_{};
|
||||
std::vector<owner<DvzBuffer*>> external_buffers_{};
|
||||
DvzVisual* axes_visual_{};
|
||||
DvzText* axes_text_{};
|
||||
owner<DvzItemInteraction*> item_interaction_{};
|
||||
owner<DvzPinnedReadout*> hover_readout_{};
|
||||
owner<DvzController*> camera_controller_{};
|
||||
owner<DvzInputRouter*> input_router_{};
|
||||
owner<DvzPointerGestureHandler*> gesture_handler_{};
|
||||
std::unique_ptr<Frame_Targets> targets_{};
|
||||
Extent figure_extent_{};
|
||||
std::uint64_t target_generation_{};
|
||||
std::uint64_t command_revision_{1};
|
||||
std::uint64_t controller_revision_{1};
|
||||
std::optional<Camera_Descriptor> applied_camera_{};
|
||||
std::optional<std::array<plot::Axis_Descriptor, 3>> applied_axes_{};
|
||||
bool input_changed_{};
|
||||
std::atomic_bool quarantined_{};
|
||||
};
|
||||
|
||||
}}} // namespace aethera::render_3d::detail
|
||||
@@ -54,6 +54,8 @@ struct DvzDrp2RuntimeConfig
|
||||
uint32_t flags;
|
||||
DvzDevice* device;
|
||||
DvzVma* allocator;
|
||||
uintptr_t command_pool;
|
||||
uintptr_t descriptor_pool;
|
||||
bool semantic_only;
|
||||
};
|
||||
|
||||
@@ -83,6 +85,9 @@ struct DvzDrp2ExternalBufferDesc
|
||||
DVZ_EXPORT DvzDrp2RuntimeConfig
|
||||
dvz_drp2_runtime_vklite_config(DvzDevice* device, DvzVma* allocator);
|
||||
|
||||
DVZ_EXPORT void dvz_drp2_runtime_vklite_pools(
|
||||
DvzDrp2RuntimeConfig* config, uintptr_t command_pool, uintptr_t descriptor_pool);
|
||||
|
||||
|
||||
/**
|
||||
* Return a default external-buffer descriptor.
|
||||
@@ -213,6 +218,24 @@ DVZ_EXPORT bool dvz_drp2_runtime_copy_texture_to_frame(
|
||||
DvzDrp2Runtime* runtime, uint64_t texture_id, const DvzStreamFrame* frame);
|
||||
|
||||
|
||||
/**
|
||||
* Upload bytes directly into a live mapped DRP2 buffer.
|
||||
*
|
||||
* The requested byte range must fit in a live buffer created with
|
||||
* `DVZ_DRP2_BUFFER_USAGE_MAP_WRITE`. This updates an existing runtime resource and does not execute
|
||||
* a command stream or alter semantic object state.
|
||||
*
|
||||
* @param runtime the vklite runtime
|
||||
* @param buffer_id the DRP2 buffer id used in the stream
|
||||
* @param offset byte offset within the buffer
|
||||
* @param size number of bytes to write
|
||||
* @param src source CPU buffer containing at least size bytes
|
||||
* @return true when the live mapped buffer and byte range are valid and the upload was issued
|
||||
*/
|
||||
DVZ_EXPORT bool dvz_drp2_runtime_upload_buffer(
|
||||
DvzDrp2Runtime* runtime, uint64_t buffer_id, uint64_t offset, uint64_t size, const void* src);
|
||||
|
||||
|
||||
/**
|
||||
* Download bytes from a DRP2 buffer into CPU memory.
|
||||
*
|
||||
|
||||
@@ -518,6 +518,55 @@ DVZ_EXPORT DvzSceneFrameArtifact* dvz_figure_emit_frame_with_emitter(
|
||||
const DvzFramePlanEmitConfig* cfg);
|
||||
|
||||
|
||||
/**
|
||||
* Upload the current panel-controller transforms into an already emitted runtime.
|
||||
*
|
||||
* This updates only dynamic common MVP buffers retained by the emitter. Existing resources,
|
||||
* descriptor bindings, and recorded command buffers remain unchanged. The caller must ensure the
|
||||
* destination runtime is not being consumed by GPU work while its mapped buffers are written.
|
||||
*
|
||||
* @param figure the figure whose panel controllers are authoritative
|
||||
* @param emitter the persistent emitter paired with the destination runtime
|
||||
* @param runtime the destination DRP2 runtime
|
||||
* @param uploaded_bytes output number of bytes uploaded, or NULL
|
||||
* @return true when every retained dynamic MVP buffer was resolved and updated
|
||||
*/
|
||||
DVZ_EXPORT bool dvz_figure_update_mvp_buffers(
|
||||
DvzFigure* figure, DvzFramePlanEmitter* emitter, DvzDrp2Runtime* runtime,
|
||||
uint64_t* uploaded_bytes);
|
||||
|
||||
|
||||
/**
|
||||
* Realize dirty retained visual resources into an upload-only command stream.
|
||||
*
|
||||
* This path performs Scene lowering and stream emission only. The caller executes the returned
|
||||
* owned stream on its destination runtime and commits the Figure only after execution succeeds.
|
||||
* `replay_visuals` identifies the Visual payloads missing from that runtime, avoiding a Scene-wide
|
||||
* dirty replay when independent runtimes consume the same retained Figure.
|
||||
*
|
||||
* @param figure the retained figure whose visual data is authoritative
|
||||
* @param emitter the persistent emitter paired with the destination runtime
|
||||
* @param replay_visuals borrowed Visual array whose payloads must be replayed for this runtime
|
||||
* @param replay_visual_count number of entries in `replay_visuals`
|
||||
* @param uploaded_bytes output number of payload bytes uploaded, or NULL
|
||||
* @param command_recording_valid output whether existing draw command recording remains valid
|
||||
* @param report output diagnostic report, or NULL
|
||||
* @return owned upload-only stream, or NULL when lowering or emission failed
|
||||
*/
|
||||
DVZ_EXPORT DvzDrp2CommandStream* dvz_figure_prepare_runtime_resources(
|
||||
DvzFigure* figure, DvzFramePlanEmitter* emitter, DvzVisual* const* replay_visuals,
|
||||
uint32_t replay_visual_count, uint64_t* uploaded_bytes, bool* command_recording_valid,
|
||||
DvzDiagnosticReport* report);
|
||||
|
||||
|
||||
/**
|
||||
* Commit successful upload-only runtime execution to the retained Figure.
|
||||
*
|
||||
* @param figure the Figure that produced the executed resource stream
|
||||
*/
|
||||
DVZ_EXPORT void dvz_figure_commit_runtime_resources(DvzFigure* figure);
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a frame artifact.
|
||||
*
|
||||
|
||||
@@ -87,6 +87,11 @@ DVZ_EXPORT void dvz_commands_free(DvzCommands* cmds);
|
||||
DVZ_EXPORT void
|
||||
dvz_commands(DvzDevice* device, DvzQueue* queue, uint32_t count, DvzCommands* cmds);
|
||||
|
||||
/** Allocate command buffers from an explicitly-owned command pool. */
|
||||
DVZ_EXPORT void dvz_commands_pool(
|
||||
DvzDevice* device, DvzQueue* queue, VkCommandPool command_pool, uint32_t count,
|
||||
DvzCommands* cmds);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,10 @@ DVZ_EXPORT DvzDescriptors* dvz_descriptors_create_wrapper(void);
|
||||
*/
|
||||
DVZ_EXPORT void dvz_descriptors(DvzSlots* slots, DvzDescriptors* descriptors);
|
||||
|
||||
/** Allocate descriptor sets from an explicitly-owned descriptor pool. */
|
||||
DVZ_EXPORT void dvz_descriptors_pool(
|
||||
DvzSlots* slots, VkDescriptorPool descriptor_pool, DvzDescriptors* descriptors);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+3
-1
@@ -104,6 +104,8 @@ struct DvzDrp2Runtime
|
||||
{
|
||||
DvzDevice* device;
|
||||
DvzVma* allocator;
|
||||
VkCommandPool command_pool;
|
||||
VkDescriptorPool descriptor_pool;
|
||||
bool semantic_only;
|
||||
Drp2RuntimeState* semantic_state;
|
||||
#if DVZ_DRP2_HAS_VKLITE
|
||||
@@ -318,7 +320,7 @@ bool _vklite_defer_destroy_object(
|
||||
Drp2VkliteState* state, Drp2VkliteObject* object, VkCommandBuffer command_buffer);
|
||||
void _vklite_flush_deferred_for_command_buffer(
|
||||
Drp2VkliteState* state, VkCommandBuffer command_buffer);
|
||||
DvzCommands* _vklite_owned_commands_create(DvzDevice* device);
|
||||
DvzCommands* _vklite_owned_commands_create(DvzDrp2Runtime* runtime);
|
||||
void _vklite_owned_commands_destroy(DvzCommands* cmds);
|
||||
DvzCommands* _vklite_borrowed_frame_commands_create(
|
||||
DvzDevice* device, VkCommandBuffer command_buffer);
|
||||
|
||||
+7
-2
@@ -426,8 +426,10 @@ bool _vklite_attach_frame_target(
|
||||
* @param device the borrowed Vulkan device wrapper
|
||||
* @return owned command-buffer wrapper, or NULL on failure
|
||||
*/
|
||||
DvzCommands* _vklite_owned_commands_create(DvzDevice* device)
|
||||
DvzCommands* _vklite_owned_commands_create(DvzDrp2Runtime* runtime)
|
||||
{
|
||||
ANN(runtime);
|
||||
DvzDevice* device = runtime->device;
|
||||
ANN(device);
|
||||
|
||||
DvzQueue* queue = dvz_device_queue(device, DVZ_QUEUE_MAIN);
|
||||
@@ -438,7 +440,10 @@ DvzCommands* _vklite_owned_commands_create(DvzDevice* device)
|
||||
if (cmds == NULL)
|
||||
return NULL;
|
||||
|
||||
dvz_commands(device, queue, 1, cmds);
|
||||
if (runtime->command_pool != VK_NULL_HANDLE)
|
||||
dvz_commands_pool(device, queue, runtime->command_pool, 1, cmds);
|
||||
else
|
||||
dvz_commands(device, queue, 1, cmds);
|
||||
if (dvz_commands_count(cmds) == 0 || dvz_commands_handle(cmds) == VK_NULL_HANDLE)
|
||||
{
|
||||
dvz_commands_free(cmds);
|
||||
|
||||
+3
-3
@@ -678,7 +678,7 @@ DvzDrp2ValidationResult _vklite_begin_render_pass(
|
||||
}
|
||||
else
|
||||
{
|
||||
cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _vklite_fail_destroy_object(
|
||||
pass, DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
@@ -1022,7 +1022,7 @@ DvzDrp2ValidationResult _vklite_begin_compute_pass(
|
||||
if (pass == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _vklite_fail_destroy_object(
|
||||
pass, DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
@@ -1406,7 +1406,7 @@ DvzDrp2ValidationResult _vklite_resource_barrier(
|
||||
if (size == 0)
|
||||
size = VK_WHOLE_SIZE;
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
if (dvz_cmd_begin_result(cmds) != 0)
|
||||
|
||||
+13
-4
@@ -83,9 +83,14 @@ static ShadercSyms g_shaderc = {
|
||||
.result_release = shaderc_result_release,
|
||||
};
|
||||
#else
|
||||
static ShadercSyms g_shaderc = {0};
|
||||
static bool g_shaderc_loaded = false;
|
||||
static bool g_shaderc_available = false;
|
||||
#if defined(_MSC_VER)
|
||||
#define DVZ_DRP2_THREAD_LOCAL __declspec(thread)
|
||||
#else
|
||||
#define DVZ_DRP2_THREAD_LOCAL _Thread_local
|
||||
#endif
|
||||
static DVZ_DRP2_THREAD_LOCAL ShadercSyms g_shaderc = {0};
|
||||
static DVZ_DRP2_THREAD_LOCAL bool g_shaderc_loaded = false;
|
||||
static DVZ_DRP2_THREAD_LOCAL bool g_shaderc_available = false;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -675,7 +680,11 @@ DvzDrp2ValidationResult _vklite_build_bind_group_descriptors(
|
||||
DvzDescriptors* descriptors = dvz_descriptors_create_wrapper();
|
||||
if (descriptors == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
dvz_descriptors(layout->slots, descriptors);
|
||||
if (state->runtime->descriptor_pool != VK_NULL_HANDLE)
|
||||
dvz_descriptors_pool(
|
||||
layout->slots, state->runtime->descriptor_pool, descriptors);
|
||||
else
|
||||
dvz_descriptors(layout->slots, descriptors);
|
||||
|
||||
for (uint32_t i = 0; i < bind_group->bind_group_entry_count; i++)
|
||||
{
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
/*************************************************************************************************/
|
||||
|
||||
#if DVZ_DRP2_HAS_VKLITE
|
||||
bool _dvz_drp2_runtime_vklite_upload_buffer(
|
||||
DvzDrp2Runtime* runtime, uint64_t buffer_id, uint64_t offset, uint64_t size, const void* data);
|
||||
|
||||
bool _dvz_drp2_runtime_vklite_download_buffer(
|
||||
DvzDrp2Runtime* runtime, uint64_t buffer_id, uint64_t offset, uint64_t size, void* data);
|
||||
#endif
|
||||
@@ -206,6 +209,14 @@ DvzDrp2RuntimeConfig dvz_drp2_runtime_vklite_config(DvzDevice* device, DvzVma* a
|
||||
return cfg;
|
||||
}
|
||||
|
||||
void dvz_drp2_runtime_vklite_pools(
|
||||
DvzDrp2RuntimeConfig* config, uintptr_t command_pool, uintptr_t descriptor_pool)
|
||||
{
|
||||
ANN(config);
|
||||
config->command_pool = command_pool;
|
||||
config->descriptor_pool = descriptor_pool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DvzDrp2ExternalBufferDesc dvz_drp2_external_buffer_desc(void)
|
||||
@@ -238,6 +249,8 @@ DvzDrp2Runtime* dvz_drp2_runtime_vklite(const DvzDrp2RuntimeConfig* cfg)
|
||||
ANN(runtime);
|
||||
runtime->device = cfg->device;
|
||||
runtime->allocator = cfg->allocator;
|
||||
runtime->command_pool = (VkCommandPool)cfg->command_pool;
|
||||
runtime->descriptor_pool = (VkDescriptorPool)cfg->descriptor_pool;
|
||||
runtime->semantic_only = cfg->semantic_only;
|
||||
return runtime;
|
||||
}
|
||||
@@ -257,6 +270,8 @@ DvzDrp2RuntimeConfig dvz_drp2_runtime_get_config(const DvzDrp2Runtime* runtime)
|
||||
return cfg;
|
||||
cfg.device = runtime->device;
|
||||
cfg.allocator = runtime->allocator;
|
||||
cfg.command_pool = (uintptr_t)runtime->command_pool;
|
||||
cfg.descriptor_pool = (uintptr_t)runtime->descriptor_pool;
|
||||
cfg.semantic_only = runtime->semantic_only;
|
||||
return cfg;
|
||||
}
|
||||
@@ -385,6 +400,44 @@ bool dvz_drp2_runtime_register_external_buffer(
|
||||
|
||||
|
||||
#if DVZ_DRP2_HAS_VKLITE
|
||||
/**
|
||||
* Upload bytes into a live host-writable vklite buffer owned by a DRP2 runtime.
|
||||
*
|
||||
* @param runtime the DRP2 runtime
|
||||
* @param buffer_id the DRP2 buffer id
|
||||
* @param offset the byte offset
|
||||
* @param size the byte count to upload
|
||||
* @param data the source buffer
|
||||
* @return true when the buffer exists and the upload was issued
|
||||
*/
|
||||
bool _dvz_drp2_runtime_vklite_upload_buffer(
|
||||
DvzDrp2Runtime* runtime, uint64_t buffer_id, uint64_t offset, uint64_t size, const void* data)
|
||||
{
|
||||
if (runtime == NULL || runtime->vklite_state == NULL || data == NULL || size == 0)
|
||||
return false;
|
||||
|
||||
Drp2VkliteObject* object = _vklite_find(runtime->vklite_state, buffer_id);
|
||||
if (object == NULL || object->buffer == NULL || runtime->semantic_state == NULL)
|
||||
return false;
|
||||
|
||||
Drp2Object* semantic = _drp2_find_any_object(runtime->semantic_state, buffer_id);
|
||||
if (semantic == NULL || semantic->kind != DRP2_OBJECT_BUFFER ||
|
||||
(semantic->usage & DVZ_DRP2_BUFFER_USAGE_MAP_WRITE) == 0)
|
||||
return false;
|
||||
if (_drp2_range_overflows(offset, size, semantic->size))
|
||||
{
|
||||
log_error(
|
||||
"runtime buffer upload [%" PRIu64 ", %" PRIu64 ") exceeds buffer %" PRIu64
|
||||
" size %" PRIu64,
|
||||
offset, offset + size, buffer_id, semantic->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
dvz_buffer_upload(object->buffer, offset, size, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download bytes from a live vklite buffer owned by a DRP2 runtime.
|
||||
*
|
||||
@@ -675,3 +728,19 @@ bool dvz_drp2_runtime_download_buffer(
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool dvz_drp2_runtime_upload_buffer(
|
||||
DvzDrp2Runtime* runtime, uint64_t buffer_id, uint64_t offset, uint64_t size, const void* src)
|
||||
{
|
||||
#if DVZ_DRP2_HAS_VKLITE
|
||||
return _dvz_drp2_runtime_vklite_upload_buffer(runtime, buffer_id, offset, size, src);
|
||||
#else
|
||||
(void)runtime;
|
||||
(void)buffer_id;
|
||||
(void)offset;
|
||||
(void)size;
|
||||
(void)src;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
+5
-5
@@ -375,7 +375,7 @@ DvzDrp2ValidationResult _vklite_write_texture(
|
||||
dvz_buffer_upload(staging, 0, size, upload_src);
|
||||
dvz_free(decoded);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
{
|
||||
dvz_buffer_destroy(staging);
|
||||
@@ -429,7 +429,7 @@ DvzDrp2ValidationResult _vklite_copy_buffer_to_buffer(
|
||||
if (src == NULL || src->buffer == NULL || dst == NULL || dst->buffer == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
@@ -470,7 +470,7 @@ DvzDrp2ValidationResult _vklite_copy_buffer_to_texture(
|
||||
if (!_drp2_texture_format_bytes_per_texel(dst->format, &bytes_per_texel))
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_USAGE, command_index);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
@@ -520,7 +520,7 @@ DvzDrp2ValidationResult _vklite_copy_texture_to_buffer(
|
||||
if (!_drp2_texture_format_bytes_per_texel(src->format, &bytes_per_texel))
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_USAGE, command_index);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
@@ -563,7 +563,7 @@ DvzDrp2ValidationResult _vklite_copy_texture_to_texture(
|
||||
if (src == NULL || src->images == NULL || dst == NULL || dst->images == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime->device);
|
||||
DvzCommands* cmds = _vklite_owned_commands_create(state->runtime);
|
||||
if (cmds == NULL)
|
||||
return _drp2_fail(DVZ_DRP2_VALIDATION_INVALID_STATE, command_index);
|
||||
|
||||
|
||||
@@ -385,6 +385,7 @@ DvzId _scene_next_id(DvzScene* scene);
|
||||
bool _scene_runtime_emitter_reset(DvzScene* scene);
|
||||
|
||||
void _scene_mark_runtime_payloads_dirty(DvzScene* scene);
|
||||
void _scene_mark_visual_runtime_dirty(DvzVisual* visual);
|
||||
|
||||
struct DvzPanelView2DResolved
|
||||
{
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "_assertions.h"
|
||||
#include "_compat.h"
|
||||
#include "frame_plan/emit.h"
|
||||
#include "frame_plan/frame_plan.h"
|
||||
#include "_log.h"
|
||||
#include "scene_emit/scene_emit.h"
|
||||
@@ -35,6 +37,7 @@
|
||||
#include "core/frame_trace_internal.h"
|
||||
#include "domain/field_internal.h"
|
||||
#include "plot/internal.h"
|
||||
#include "runtime/_frame_plan_runtime_upload.h"
|
||||
#include "_visual_internal.h"
|
||||
#include "datoviz/scene.h"
|
||||
|
||||
@@ -792,6 +795,25 @@ DvzDrp2CommandStream* _scene_figure_emit_stream_with_emitter_ex(
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy one MVP into a deterministic uniform payload with zeroed padding.
|
||||
*
|
||||
* @param dst the destination MVP
|
||||
* @param src the source MVP
|
||||
*/
|
||||
static void _scene_mvp_uniform_copy(DvzMVP* dst, const DvzMVP* src)
|
||||
{
|
||||
ANN(dst);
|
||||
ANN(src);
|
||||
dvz_memset(dst, sizeof(DvzMVP), 0, sizeof(DvzMVP));
|
||||
dvz_memcpy(dst->model, sizeof(dst->model), src->model, sizeof(src->model));
|
||||
dvz_memcpy(dst->view, sizeof(dst->view), src->view, sizeof(src->view));
|
||||
dvz_memcpy(dst->proj, sizeof(dst->proj), src->proj, sizeof(src->proj));
|
||||
dst->time = src->time;
|
||||
dst->flags = src->flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DvzDrp2CommandStream* _scene_figure_emit_stream_ex(
|
||||
DvzFigure* figure, const DvzCapabilitySnapshot* caps, DvzDiagnosticReport* report,
|
||||
@@ -854,6 +876,213 @@ DvzSceneFrameArtifact* dvz_figure_emit_frame_with_emitter(
|
||||
|
||||
|
||||
|
||||
bool dvz_figure_update_mvp_buffers(
|
||||
DvzFigure* figure, DvzFramePlanEmitter* emitter, DvzDrp2Runtime* runtime,
|
||||
uint64_t* uploaded_bytes)
|
||||
{
|
||||
if (uploaded_bytes != NULL)
|
||||
*uploaded_bytes = 0;
|
||||
if (figure == NULL || emitter == NULL || runtime == NULL)
|
||||
return false;
|
||||
|
||||
char figure_id[64];
|
||||
_scene_figure_id(figure, figure_id, sizeof(figure_id));
|
||||
uint64_t total = 0;
|
||||
for (uint32_t panel_index = 0; panel_index < figure->panel_count; panel_index++)
|
||||
{
|
||||
char panel_id[DVZ_SCENE_LABEL_SIZE];
|
||||
dvz_snprintf(panel_id, sizeof(panel_id), "%s_p%u", figure_id, panel_index);
|
||||
size_t panel_id_size = strlen(panel_id);
|
||||
DvzMVP panel_mvp = {0};
|
||||
_scene_panel_apply_mvp(&figure->panels[panel_index], &panel_mvp);
|
||||
|
||||
for (uint32_t slot_index = 0; slot_index < emitter->mvp_panel_count; slot_index++)
|
||||
{
|
||||
const char* slot_key = emitter->mvp_panel_ids[slot_index];
|
||||
if (!emitter->mvp_dynamic[slot_index] ||
|
||||
strncmp(slot_key, panel_id, panel_id_size) != 0 ||
|
||||
slot_key[panel_id_size] != '_')
|
||||
continue;
|
||||
|
||||
DvzMVP updated = {0};
|
||||
_scene_mvp_uniform_copy(&updated, &panel_mvp);
|
||||
if (emitter->mvp_preserve_model[slot_index])
|
||||
{
|
||||
dvz_memcpy(
|
||||
updated.model, sizeof(updated.model), emitter->mvp_cache[slot_index].model,
|
||||
sizeof(emitter->mvp_cache[slot_index].model));
|
||||
}
|
||||
updated.flags |= emitter->mvp_cache[slot_index].flags;
|
||||
|
||||
char buffer_key[2 * DVZ_SCENE_LABEL_SIZE];
|
||||
int key_size = dvz_snprintf(
|
||||
buffer_key, sizeof(buffer_key), "_common_mvp_buf_%s", slot_key);
|
||||
if (key_size < 0 || (size_t)key_size >= sizeof(buffer_key))
|
||||
return false;
|
||||
uint64_t buffer_id = dvz_frame_plan_emitter_object_id(emitter, buffer_key);
|
||||
if (buffer_id == 0 ||
|
||||
!dvz_drp2_runtime_upload_buffer(
|
||||
runtime, buffer_id, 0, sizeof(DvzMVP), &updated))
|
||||
return false;
|
||||
emitter->mvp_cache[slot_index] = updated;
|
||||
total += sizeof(DvzMVP);
|
||||
}
|
||||
}
|
||||
if (uploaded_bytes != NULL)
|
||||
*uploaded_bytes = total;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Emit only the upload nodes of one retained visual FramePlan.
|
||||
*
|
||||
* The runtime-mode emitter normally requires a render node because its public conversion path
|
||||
* records a complete frame. Resource-only updates deliberately bypass that render conversion and
|
||||
* reuse the same persistent resource-id map.
|
||||
*/
|
||||
static DvzDrp2CommandStream* _scene_emit_runtime_resource_updates(
|
||||
DvzFramePlanEmitter* emitter, const DvzFramePlan* plan, DvzDiagnosticReport* report)
|
||||
{
|
||||
ANN(emitter);
|
||||
ANN(plan);
|
||||
if (!emitter->handshake_sent)
|
||||
{
|
||||
(void)dvz_diagnostic_report_add(
|
||||
report, "runtime resource update requires an already emitted runtime");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DvzDrp2CommandStream* stream = dvz_drp2_stream();
|
||||
if (stream == NULL)
|
||||
return NULL;
|
||||
bool ok = true;
|
||||
for (uint32_t i = 0; ok && i < dvz_frame_plan_node_count(plan); i++)
|
||||
{
|
||||
const DvzFramePlanNode* node = dvz_frame_plan_node_get(plan, i);
|
||||
if (node == NULL || dvz_frame_plan_node_type(node) != DVZ_FRAME_PLAN_NODE_UPLOAD)
|
||||
continue;
|
||||
uint64_t resource_id = 0;
|
||||
ok = _emitter_emit_upload(emitter, stream, node, &resource_id);
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
(void)dvz_diagnostic_report_add(report, "failed to emit runtime resource updates");
|
||||
dvz_drp2_stream_destroy(stream);
|
||||
return NULL;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DvzDrp2CommandStream* dvz_figure_prepare_runtime_resources(
|
||||
DvzFigure* figure, DvzFramePlanEmitter* emitter, DvzVisual* const* replay_visuals,
|
||||
uint32_t replay_visual_count, uint64_t* uploaded_bytes, bool* command_recording_valid,
|
||||
DvzDiagnosticReport* report)
|
||||
{
|
||||
if (uploaded_bytes != NULL)
|
||||
*uploaded_bytes = 0;
|
||||
if (command_recording_valid != NULL)
|
||||
*command_recording_valid = false;
|
||||
if (
|
||||
figure == NULL || figure->scene == NULL || emitter == NULL ||
|
||||
(replay_visual_count > 0 && replay_visuals == NULL))
|
||||
return NULL;
|
||||
|
||||
DvzDiagnosticReport local_report;
|
||||
if (report == NULL)
|
||||
{
|
||||
dvz_diagnostic_report_init(&local_report);
|
||||
report = &local_report;
|
||||
}
|
||||
for (uint32_t i = 0; i < replay_visual_count; i++)
|
||||
_scene_mark_visual_runtime_dirty(replay_visuals[i]);
|
||||
|
||||
if (!_scene_figure_resolve_layouts(figure))
|
||||
{
|
||||
(void)dvz_diagnostic_report_add(report, "scene grid layout resolution failed");
|
||||
return NULL;
|
||||
}
|
||||
_scene_prepare_guide_visuals(figure);
|
||||
_scene_prepare_bars_visuals(figure);
|
||||
_scene_prepare_band_visuals(figure);
|
||||
|
||||
char figure_id[64];
|
||||
_scene_figure_id(figure, figure_id, sizeof(figure_id));
|
||||
DvzFramePlan* plan = dvz_frame_plan(figure_id, 0);
|
||||
if (plan == NULL)
|
||||
return NULL;
|
||||
_scene_emit_visual_uploads(figure, plan, report);
|
||||
|
||||
DvzDrp2CommandStream* stream =
|
||||
_scene_emit_runtime_resource_updates(emitter, plan, report);
|
||||
if (stream == NULL)
|
||||
{
|
||||
dvz_frame_plan_destroy(plan);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool recording_valid = true;
|
||||
uint64_t total = 0;
|
||||
bool command_set_valid = true;
|
||||
for (uint32_t i = 0; i < dvz_drp2_stream_count(stream); i++)
|
||||
{
|
||||
const DvzDrp2Command* command = dvz_drp2_stream_get(stream, i);
|
||||
if (command == NULL)
|
||||
{
|
||||
command_set_valid = false;
|
||||
break;
|
||||
}
|
||||
switch (dvz_drp2_command_type(command))
|
||||
{
|
||||
case DVZ_DRP2_COMMAND_CREATE_BUFFER:
|
||||
case DVZ_DRP2_COMMAND_CREATE_TEXTURE:
|
||||
recording_valid = false;
|
||||
break;
|
||||
case DVZ_DRP2_COMMAND_WRITE_BUFFER:
|
||||
total += command->u.write_buffer.size;
|
||||
break;
|
||||
case DVZ_DRP2_COMMAND_WRITE_TEXTURE:
|
||||
total += (uint64_t)command->u.write_texture.bytes_per_row *
|
||||
(uint64_t)command->u.write_texture.rows_per_image *
|
||||
(uint64_t)command->u.write_texture.depth;
|
||||
break;
|
||||
default:
|
||||
command_set_valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!command_set_valid)
|
||||
(void)dvz_diagnostic_report_add(
|
||||
report, "runtime resource update emitted a non-resource command");
|
||||
if (command_set_valid)
|
||||
{
|
||||
if (uploaded_bytes != NULL)
|
||||
*uploaded_bytes = total;
|
||||
if (command_recording_valid != NULL)
|
||||
*command_recording_valid = recording_valid;
|
||||
}
|
||||
dvz_frame_plan_destroy(plan);
|
||||
if (!command_set_valid)
|
||||
{
|
||||
dvz_drp2_stream_destroy(stream);
|
||||
return NULL;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
void dvz_figure_commit_runtime_resources(DvzFigure* figure)
|
||||
{
|
||||
ANN(figure);
|
||||
_scene_commit_emit_success(figure);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void dvz_scene_frame_artifact_destroy(DvzSceneFrameArtifact* artifact)
|
||||
{
|
||||
_scene_frame_artifact_destroy(artifact);
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ uint64_t _scene_next_request_serial(DvzScene* scene)
|
||||
*
|
||||
* @param visual the visual
|
||||
*/
|
||||
static void _scene_mark_visual_runtime_dirty(DvzVisual* visual)
|
||||
void _scene_mark_visual_runtime_dirty(DvzVisual* visual)
|
||||
{
|
||||
if (visual == NULL || visual->scene == NULL)
|
||||
return;
|
||||
|
||||
@@ -150,6 +150,8 @@ struct DvzFramePlanEmitter
|
||||
/* Common cache: APPLY and FIXED slots are panel-specific once viewport is part of set 0. */
|
||||
char mvp_panel_ids[DVZ_SCENE_COMMON_CACHE_CAPACITY][DVZ_SCENE_LABEL_SIZE];
|
||||
DvzMVP mvp_cache[DVZ_SCENE_COMMON_CACHE_CAPACITY];
|
||||
bool mvp_dynamic[DVZ_SCENE_COMMON_CACHE_CAPACITY];
|
||||
bool mvp_preserve_model[DVZ_SCENE_COMMON_CACHE_CAPACITY];
|
||||
uint32_t mvp_panel_count;
|
||||
char viewport_panel_ids[DVZ_SCENE_COMMON_CACHE_CAPACITY][DVZ_SCENE_LABEL_SIZE];
|
||||
DvzSceneViewportUniform viewport_cache[DVZ_SCENE_COMMON_CACHE_CAPACITY];
|
||||
|
||||
@@ -278,6 +278,9 @@ static bool _resolve_common_set(
|
||||
DvzMVP* mvp_slot = _emitter_mvp_slot(emitter, mvp_slot_key);
|
||||
if (mvp_slot == NULL)
|
||||
return false;
|
||||
uint32_t mvp_slot_index = (uint32_t)(mvp_slot - emitter->mvp_cache);
|
||||
emitter->mvp_dynamic[mvp_slot_index] = !fixed;
|
||||
emitter->mvp_preserve_model[mvp_slot_index] = override_mvp != NULL;
|
||||
if (override_mvp != NULL)
|
||||
mvp_src = override_mvp;
|
||||
else if (fixed)
|
||||
|
||||
@@ -30,6 +30,7 @@ struct DvzCommands
|
||||
DvzObject obj;
|
||||
DvzDevice* device;
|
||||
DvzQueue* queue;
|
||||
VkCommandPool command_pool;
|
||||
|
||||
uint32_t count;
|
||||
uint32_t current;
|
||||
|
||||
+11
-2
@@ -84,7 +84,7 @@ static void _commands_release(DvzCommands* cmds)
|
||||
|
||||
VkDevice vkd = dvz_device_handle(cmds->device);
|
||||
ANNVK(vkd);
|
||||
VkCommandPool cpool = dvz_device_command_pool(cmds->device, dvz_queue_family(cmds->queue));
|
||||
VkCommandPool cpool = cmds->command_pool;
|
||||
if (cpool == VK_NULL_HANDLE)
|
||||
{
|
||||
log_warn(
|
||||
@@ -103,6 +103,14 @@ static void _commands_release(DvzCommands* cmds)
|
||||
}
|
||||
|
||||
void dvz_commands(DvzDevice* device, DvzQueue* queue, uint32_t count, DvzCommands* cmds)
|
||||
{
|
||||
dvz_commands_pool(
|
||||
device, queue, dvz_device_command_pool(device, dvz_queue_family(queue)), count, cmds);
|
||||
}
|
||||
|
||||
void dvz_commands_pool(
|
||||
DvzDevice* device, DvzQueue* queue, VkCommandPool command_pool, uint32_t count,
|
||||
DvzCommands* cmds)
|
||||
{
|
||||
ANN(cmds);
|
||||
ANN(device);
|
||||
@@ -113,11 +121,12 @@ void dvz_commands(DvzDevice* device, DvzQueue* queue, uint32_t count, DvzCommand
|
||||
|
||||
cmds->device = device;
|
||||
cmds->queue = queue;
|
||||
cmds->command_pool = command_pool;
|
||||
cmds->count = count;
|
||||
|
||||
VkCommandBufferAllocateInfo info = {0};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
info.commandPool = dvz_device_command_pool(device, dvz_queue_family(queue));
|
||||
info.commandPool = command_pool;
|
||||
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
info.commandBufferCount = count;
|
||||
VkResult res = vkAllocateCommandBuffers(dvz_device_handle(device), &info, cmds->cmds);
|
||||
|
||||
+9
-1
@@ -64,6 +64,14 @@ DvzDescriptors* dvz_descriptors_create_wrapper(void)
|
||||
|
||||
|
||||
void dvz_descriptors(DvzSlots* slots, DvzDescriptors* descriptors)
|
||||
{
|
||||
ANN(slots);
|
||||
dvz_descriptors_pool(
|
||||
slots, dvz_device_descriptor_pool(dvz_slots_device(slots)), descriptors);
|
||||
}
|
||||
|
||||
void dvz_descriptors_pool(
|
||||
DvzSlots* slots, VkDescriptorPool descriptor_pool, DvzDescriptors* descriptors)
|
||||
{
|
||||
ANN(slots);
|
||||
ANN(descriptors);
|
||||
@@ -81,7 +89,7 @@ void dvz_descriptors(DvzSlots* slots, DvzDescriptors* descriptors)
|
||||
descriptors->device = device;
|
||||
descriptors->slots = slots;
|
||||
VkDevice vkd = dvz_device_handle(device);
|
||||
VkDescriptorPool dpool = dvz_device_descriptor_pool(device);
|
||||
VkDescriptorPool dpool = descriptor_pool;
|
||||
ANNVK(vkd);
|
||||
ANNVK(dpool);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user