3D优化完成

This commit is contained in:
2026-08-28 21:11:39 +08:00
parent 93d3f93774
commit 647ad6a66c
36 changed files with 1974 additions and 1548 deletions
@@ -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 只登记 fencefence 已完成后的目标读回由普通 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 对象的线程。 */
};
}
+480 -181
View File
@@ -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);
}
@@ -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.
*
+49
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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++)
{
+69
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -30,6 +30,7 @@ struct DvzCommands
DvzObject obj;
DvzDevice* device;
DvzQueue* queue;
VkCommandPool command_pool;
uint32_t count;
uint32_t current;
+11 -2
View File
@@ -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
View File
@@ -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);