三维频谱marker
This commit is contained in:
@@ -9,7 +9,11 @@ struct Camera_3D : Def<Camera_3D, Root> {
|
||||
struct Prop : Prev_Prop {
|
||||
plot::Camera_View initial_view{};
|
||||
plot::Camera_Projection projection{plot::Camera_Projection::perspective};
|
||||
plot::Camera_Control control{};
|
||||
plot::Camera_Controller controller{plot::Camera_Controller::turntable};
|
||||
plot::Camera_Turntable_Control turntable_control{};
|
||||
plot::Camera_Arcball_Control arcball_control{};
|
||||
plot::Camera_Fly_Control fly_control{};
|
||||
plot::Camera_Panzoom_Control panzoom_control{};
|
||||
double vertical_field_of_view_degrees{45.0};
|
||||
double near_plane{0.01};
|
||||
double far_plane{100.0};
|
||||
|
||||
@@ -34,12 +34,15 @@ struct Async_Render_Backend::Implementation : std::enable_shared_from_this<Imple
|
||||
Frame_3D* output{}; /* 调用方拥有且保证存活到完成回调的输出帧。 */
|
||||
};
|
||||
struct Submission {
|
||||
Prepared_Visual visual{}; /* 共享 Prepare 发布的不可变 GPU 字段快照。 */
|
||||
Prepared_Visual_Batch visuals{}; /* 同一 Scene 帧的全部不可变 Visual 快照。 */
|
||||
Scene_3D_Parameters parameters{}; /* 本帧 viewport 与清屏参数。 */
|
||||
Frame_3D* output{}; /* 调用方拥有的最终输出帧。 */
|
||||
};
|
||||
std::shared_ptr<Render_Domain> render_domain; /* GPU 索引对应的单线程 Datoviz 域。 */
|
||||
std::unique_ptr<Datoviz_Visual_Backend> backend; /* 只允许 render_domain 线程访问。 */
|
||||
std::uint32_t gpu_index{}; /* 首次使用时创建 Datoviz 后端所需 GPU。 */
|
||||
bool validation_enabled{}; /* 首次使用时采用的 Vulkan 验证配置。 */
|
||||
std::vector<Visual_Registration> visual_registrations{}; /* Scene Builder 发布的稳定 Visual 注册。 */
|
||||
mutable std::mutex render_mutex; /* 保护合并帧意图、在途标记和完成回调。 */
|
||||
std::deque<Submission> pending_submissions{}; /* 唯一 Frame Target 忙碌时等待的外部帧 FIFO。 */
|
||||
Frame_Callback frame_callback{}; /* 完成帧的唯一发布出口。 */
|
||||
@@ -47,38 +50,61 @@ struct Async_Render_Backend::Implementation : std::enable_shared_from_this<Imple
|
||||
mutable std::mutex failure_mutex; /* 保护最后一次 Unknown Failure。 */
|
||||
std::exception_ptr failure{}; /* 后端隔离边界捕获的最后一次 Unknown Failure。 */
|
||||
std::atomic_bool available{}; /* 后端是否可接受事件和绘制。 */
|
||||
Implementation(std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family, Scene_3D_Parameters initial) : render_domain(Render_Domain::acquire(gpu_index)) { const auto initialized = render_domain->invoke([this, gpu_index, validation_enabled, visual_family, initial] { backend = std::make_unique<Datoviz_Visual_Backend>(gpu_index, validation_enabled, visual_family, initial); }); if (!initialized) throw std::runtime_error("render domain stopped during 3D backend initialization"); available.store(true, std::memory_order_release); }
|
||||
Implementation(std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals)
|
||||
: render_domain(Render_Domain::acquire(gpu_index)), gpu_index(gpu_index),
|
||||
validation_enabled(validation_enabled), visual_registrations(std::move(visuals)) {
|
||||
available.store(true, std::memory_order_release);
|
||||
}
|
||||
~Implementation() { available.store(false, std::memory_order_release); if (!backend) return; const auto destroyed = render_domain->invoke([this] { backend.reset(); }); if (!destroyed) backend.release(); }
|
||||
void fail(std::exception_ptr value) noexcept;
|
||||
void render(const Prepared_Visual& visual, Scene_3D_Parameters parameters, Frame_3D* frame);
|
||||
void initialize(const Scene_3D_Parameters& parameters);
|
||||
void render(Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters, Frame_3D* frame);
|
||||
void submit(Submission submission);
|
||||
void finish(std::shared_ptr<Pending> pending, Gpu_Completion_Service::Result completion) noexcept;
|
||||
void complete(Frame_3D* frame);
|
||||
};
|
||||
Async_Render_Backend::Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family, Scene_3D_Parameters parameters) : implementation_(std::make_shared<Implementation>(gpu_index, validation_enabled, visual_family, parameters)) {}
|
||||
Async_Render_Backend::Async_Render_Backend(
|
||||
std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals)
|
||||
: implementation_(std::make_shared<Implementation>(
|
||||
gpu_index, validation_enabled, std::move(visuals))) {}
|
||||
Async_Render_Backend::~Async_Render_Backend() = default;
|
||||
void Async_Render_Backend::Implementation::fail(std::exception_ptr value) noexcept { { std::lock_guard lock(failure_mutex); failure = std::move(value); } available.store(false, std::memory_order_release); std::lock_guard lock(render_mutex); frame_in_flight = false; pending_submissions.clear(); }
|
||||
void Async_Render_Backend::Implementation::render(const Prepared_Visual& visual, Scene_3D_Parameters parameters, Frame_3D* frame) {
|
||||
void Async_Render_Backend::Implementation::initialize(
|
||||
const Scene_3D_Parameters& parameters) {
|
||||
if (backend) return;
|
||||
backend = std::make_unique<Datoviz_Visual_Backend>(
|
||||
gpu_index, validation_enabled, visual_registrations, parameters);
|
||||
visual_registrations.clear();
|
||||
visual_registrations.shrink_to_fit();
|
||||
}
|
||||
void Async_Render_Backend::Implementation::render(
|
||||
Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters, Frame_3D* frame) {
|
||||
if (!frame) throw std::invalid_argument("3D backend requires a non-null external frame");
|
||||
if (!available.load(std::memory_order_acquire)) return;
|
||||
frame->mark(Frame_Trace_Marker::backend_queue_entered);
|
||||
std::optional<Submission> submission;
|
||||
{
|
||||
std::lock_guard lock(render_mutex);
|
||||
if (frame_in_flight) { pending_submissions.push_back(Submission{visual, parameters, frame}); return; }
|
||||
if (frame_in_flight) {
|
||||
pending_submissions.push_back(Submission{std::move(visuals), parameters, frame});
|
||||
return;
|
||||
}
|
||||
frame_in_flight = true;
|
||||
submission = Submission{visual, parameters, frame};
|
||||
submission = Submission{std::move(visuals), parameters, frame};
|
||||
}
|
||||
submit(std::move(*submission));
|
||||
}
|
||||
void Async_Render_Backend::Implementation::submit(Submission submission) {
|
||||
auto self = shared_from_this();
|
||||
auto prepared = std::move(submission.visual);
|
||||
auto prepared = std::move(submission.visuals);
|
||||
auto pending = std::make_shared<Pending>(Pending{std::nullopt, submission.output});
|
||||
const auto parameters = submission.parameters;
|
||||
const auto sequence = submission.output->identity().sequence;
|
||||
const auto queued = render_domain->try_post([self, parameters, sequence, prepared, pending] {
|
||||
pending->output->mark(Frame_Trace_Marker::backend_queue_left);
|
||||
self->initialize(parameters);
|
||||
auto reservation = Gpu_Completion_Service::instance().prepare([self, pending](Gpu_Completion_Service::Result result) { self->finish(pending, std::move(result)); }, [self](std::exception_ptr value) { self->fail(std::move(value)); }, true);
|
||||
if (!reservation) { self->complete(pending->output); return; }
|
||||
pending->backend_frame = self->backend->submit(parameters, prepared, sequence, true, pending->output->output() == Frame_3D_Output::pixels);
|
||||
@@ -129,7 +155,10 @@ void Async_Render_Backend::Implementation::complete(Frame_3D* frame) {
|
||||
if (frame && callback) { frame->mark(Frame_Trace_Marker::callback_started); callback(frame); frame->mark(Frame_Trace_Marker::callback_finished); }
|
||||
if (next) submit(std::move(*next));
|
||||
}
|
||||
void Async_Render_Backend::render(const Prepared_Visual& visual, Scene_3D_Parameters parameters, Frame_3D* frame) { implementation_->render(visual, parameters, frame); }
|
||||
void Async_Render_Backend::render(
|
||||
Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters, Frame_3D* frame) {
|
||||
implementation_->render(std::move(visuals), parameters, frame);
|
||||
}
|
||||
void Async_Render_Backend::set_frame_callback(Frame_Callback callback) { std::lock_guard lock(implementation_->render_mutex); implementation_->frame_callback = std::move(callback); }
|
||||
bool Async_Render_Backend::available() const noexcept { return implementation_->available.load(std::memory_order_acquire); }
|
||||
Async_Render_Backend::Dispatch_Event_Result Async_Render_Backend::dispatch_event(std::shared_ptr<const Event> event, Extent viewport) {
|
||||
@@ -146,6 +175,7 @@ Async_Render_Backend::Dispatch_Event_Result Async_Render_Backend::dispatch_event
|
||||
}
|
||||
else if (!key) return Dispatch_Event_Result::ignored;
|
||||
const auto queued = self->render_domain->try_post([self, event = std::move(event), viewport] {
|
||||
if (!self->backend) return;
|
||||
if (const auto* event_wheel = dynamic_cast<const Wheel_Event_Capability*>(event.get())) {
|
||||
const auto* event_pointer = dynamic_cast<const Pointer_Event_Capability*>(event.get());
|
||||
self->backend->dispatch_wheel(static_cast<float>(event_pointer->position_x()), static_cast<float>(event_pointer->position_y()), wheel_step(event_wheel->pixel_delta_x_value(), event_wheel->angle_delta_x_value()), wheel_step(event_wheel->pixel_delta_y_value(), event_wheel->angle_delta_y_value()), event_pointer->keyboard_modifiers(), viewport);
|
||||
|
||||
@@ -7,12 +7,13 @@ namespace aethera::render_3d::detail {
|
||||
class Async_Render_Backend final {
|
||||
public:
|
||||
using Frame_Callback = std::function<void(Frame_3D*)>;
|
||||
Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family, Scene_3D_Parameters parameters);
|
||||
Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<Visual_Registration> visuals);
|
||||
~Async_Render_Backend();
|
||||
Async_Render_Backend(const Async_Render_Backend&) = delete;
|
||||
Async_Render_Backend& operator=(const Async_Render_Backend&) = delete;
|
||||
/* 将绘制意图无等待提交到调用方拥有的帧;frame 必须存活到完成回调返回。 */
|
||||
void render(const Prepared_Visual& visual, Scene_3D_Parameters parameters, Frame_3D* frame);
|
||||
void render(Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters, Frame_3D* frame);
|
||||
/* 设置完成帧出口;回调在 Datoviz Render Domain 线程执行。 */
|
||||
void set_frame_callback(Frame_Callback callback);
|
||||
[[nodiscard]] bool available() const noexcept;
|
||||
|
||||
@@ -3,7 +3,20 @@
|
||||
#include <event.hpp>
|
||||
#include <plot/Axis.hpp>
|
||||
#include <plot/Camera.hpp>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d::detail {
|
||||
using Visual_Identity = std::uintptr_t;
|
||||
struct Visual_Registration {
|
||||
Visual_Identity identity{}; /* Scene 内稳定对象身份,仅供后端匹配原生 Visual。 */
|
||||
Visual_Family family{Visual_Family::point}; /* 创建原生 Visual 所需的编译期 family。 */
|
||||
bool operator==(const Visual_Registration&) const = default;
|
||||
};
|
||||
struct Prepared_Visual_Instance {
|
||||
Visual_Identity identity{}; /* 与 Builder 注册身份一一对应。 */
|
||||
Prepared_Visual visual{}; /* Prepare 发布的不可变字段快照。 */
|
||||
};
|
||||
using Prepared_Visual_Batch = std::vector<Prepared_Visual_Instance>;
|
||||
struct Scene_3D_Parameters {
|
||||
Extent viewport{560, 320}; /* 离屏渲染目标尺寸,单位为像素。 */
|
||||
Linear_Color clear_color{}; /* 每帧开始时写入的线性背景色。 */
|
||||
|
||||
@@ -604,7 +604,8 @@ private:
|
||||
};
|
||||
Datoviz_Visual_Backend::Datoviz_Visual_Backend(
|
||||
std::uint32_t gpu_index, bool validation_enabled,
|
||||
Visual_Family visual_family, const Scene_3D_Parameters& initial_scene) : domain_thread_(std::this_thread::get_id()) {
|
||||
const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene) : domain_thread_(std::this_thread::get_id()) {
|
||||
try {
|
||||
render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled);
|
||||
auto* gpu_context = render_context_->gpu_context();
|
||||
@@ -612,7 +613,7 @@ Datoviz_Visual_Backend::Datoviz_Visual_Backend(
|
||||
dvz_gpu_ctx_device(gpu_context), dvz_gpu_ctx_alloc(gpu_context));
|
||||
runtime_ = dvz_drp2_runtime_vklite(&runtime_configuration);
|
||||
if (runtime_ == nullptr) throw std::runtime_error("failed to create Datoviz DRP2 runtime");
|
||||
create_scene(visual_family, initial_scene);
|
||||
create_scene(visuals, initial_scene);
|
||||
}
|
||||
catch (...) {
|
||||
destroy();
|
||||
@@ -625,7 +626,11 @@ Datoviz_Visual_Backend::~Datoviz_Visual_Backend() noexcept(false) {
|
||||
void Datoviz_Visual_Backend::require_domain() const {
|
||||
if (std::this_thread::get_id() != domain_thread_) throw std::logic_error("Datoviz objects may only be used on the render domain");
|
||||
}
|
||||
void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_Parameters& initial_scene) {
|
||||
void Datoviz_Visual_Backend::create_scene(
|
||||
const std::vector<Visual_Registration>& registrations,
|
||||
const Scene_3D_Parameters& initial_scene) {
|
||||
if (registrations.empty())
|
||||
throw std::invalid_argument("Datoviz backend requires at least one Visual registration");
|
||||
scene_ = dvz_scene();
|
||||
if (scene_ == nullptr) throw std::runtime_error("failed to create Datoviz scene");
|
||||
const auto capabilities = offscreen_capabilities();
|
||||
@@ -633,46 +638,56 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
figure_ = dvz_figure(scene_, initial_scene.viewport.width,
|
||||
initial_scene.viewport.height, 0);
|
||||
panel_ = figure_ != nullptr ? dvz_panel_full(figure_) : nullptr;
|
||||
if (panel_ != nullptr) {
|
||||
if (figure_ == nullptr || panel_ == nullptr)
|
||||
throw std::runtime_error("failed to create Datoviz figure and panel");
|
||||
bool wants_item_interaction{};
|
||||
visuals_.reserve(registrations.size());
|
||||
for (const auto& registration : registrations) {
|
||||
if (registration.identity == 0 ||
|
||||
std::ranges::any_of(visuals_, [&](const Visual_Instance& value) {
|
||||
return value.identity == registration.identity;
|
||||
}))
|
||||
throw std::invalid_argument("Datoviz backend received duplicate Visual identity");
|
||||
const auto family = registration.family;
|
||||
DvzVisual* visual{};
|
||||
switch (family) {
|
||||
case Visual_Family::point: visual_ = dvz_point(scene_, 0);
|
||||
case Visual_Family::point: visual = dvz_point(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::splat: visual_ = dvz_splat(scene_, 0);
|
||||
case Visual_Family::splat: visual = dvz_splat(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::pixel: visual_ = dvz_pixel(scene_, 0);
|
||||
case Visual_Family::pixel: visual = dvz_pixel(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::marker: visual_ = dvz_marker(scene_, 0);
|
||||
case Visual_Family::marker: visual = dvz_marker(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::sphere: visual_ = dvz_sphere(scene_, 0);
|
||||
case Visual_Family::sphere: visual = dvz_sphere(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::segment: visual_ = dvz_segment(scene_, 0);
|
||||
case Visual_Family::segment: visual = dvz_segment(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::vector: visual_ = dvz_vector(scene_, 0);
|
||||
case Visual_Family::vector: visual = dvz_vector(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::primitive: visual_ = dvz_primitive(
|
||||
case Visual_Family::primitive: visual = dvz_primitive(
|
||||
scene_, DVZ_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0);
|
||||
break;
|
||||
case Visual_Family::mesh: visual_ = dvz_mesh(scene_, 0);
|
||||
case Visual_Family::mesh: visual = dvz_mesh(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::path: visual_ = dvz_path(scene_, 0);
|
||||
case Visual_Family::path: visual = dvz_path(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::image: visual_ = dvz_image(scene_, 0);
|
||||
case Visual_Family::image: visual = dvz_image(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::labels: visual_ = dvz_labels(scene_, 0);
|
||||
case Visual_Family::labels: visual = dvz_labels(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::glyph:
|
||||
case Visual_Family::text: visual_ = dvz_glyph(scene_, 0);
|
||||
case Visual_Family::text: visual = dvz_glyph(scene_, 0);
|
||||
break;
|
||||
case Visual_Family::volume: visual_ = dvz_volume(scene_, 0);
|
||||
case Visual_Family::volume: visual = dvz_volume(scene_, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (visual_ != nullptr &&
|
||||
dvz_visual_set_alpha_mode(visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK)
|
||||
if (visual != nullptr &&
|
||||
dvz_visual_set_alpha_mode(visual, DVZ_ALPHA_OPAQUE) != DVZ_OK)
|
||||
throw std::runtime_error("failed to configure Datoviz visual alpha mode");
|
||||
if (visual_ != nullptr && family == Visual_Family::glyph) configure_glyph_text(scene_, visual_, "GLYPH");
|
||||
if (visual_ != nullptr && family == Visual_Family::text) configure_glyph_text(scene_, visual_, "TEXT");
|
||||
if (visual_ != nullptr &&
|
||||
if (visual != nullptr && family == Visual_Family::glyph) configure_glyph_text(scene_, visual, "GLYPH");
|
||||
if (visual != nullptr && family == Visual_Family::text) configure_glyph_text(scene_, visual, "TEXT");
|
||||
if (visual != nullptr &&
|
||||
(family == Visual_Family::image || family == Visual_Family::glyph ||
|
||||
family == Visual_Family::text)) {
|
||||
constexpr std::uint32_t width = 32;
|
||||
@@ -702,10 +717,10 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
view.bytes_per_row = width * sizeof(pixels.front());
|
||||
view.rows_per_image = height;
|
||||
if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
|
||||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK)
|
||||
dvz_visual_set_field(visual, "field", field) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz 2D sampled field");
|
||||
}
|
||||
if (visual_ != nullptr && family == Visual_Family::labels) {
|
||||
if (visual != nullptr && family == Visual_Family::labels) {
|
||||
constexpr std::uint32_t width = 8;
|
||||
constexpr std::uint32_t height = 8;
|
||||
std::array<std::int32_t, width * height> labels{};
|
||||
@@ -737,13 +752,13 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
};
|
||||
if (field == nullptr || scale == nullptr ||
|
||||
dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
|
||||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK ||
|
||||
dvz_visual_set_field(visual, "field", field) != DVZ_OK ||
|
||||
dvz_scale_set_categories(scale, categories.data(),
|
||||
static_cast<std::uint32_t>(categories.size())) != DVZ_OK ||
|
||||
dvz_visual_set_scale(visual_, "labels", scale) != DVZ_OK)
|
||||
dvz_visual_set_scale(visual, "labels", scale) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz label field");
|
||||
}
|
||||
if (visual_ != nullptr && family == Visual_Family::volume) {
|
||||
if (visual != nullptr && family == Visual_Family::volume) {
|
||||
constexpr std::uint32_t side = 16;
|
||||
std::vector<std::uint8_t> voxels(side * side * side);
|
||||
for (std::uint32_t z = 0; z < side; ++z) {
|
||||
@@ -772,18 +787,22 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
view.bytes_per_row = side;
|
||||
view.rows_per_image = side;
|
||||
if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
|
||||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK ||
|
||||
dvz_volume_set_render_mode(visual_, DVZ_VOLUME_RENDER_MIP) != DVZ_OK ||
|
||||
dvz_volume_set_step_count(visual_, 48) != DVZ_OK)
|
||||
dvz_visual_set_field(visual, "field", field) != DVZ_OK ||
|
||||
dvz_volume_set_render_mode(visual, DVZ_VOLUME_RENDER_MIP) != DVZ_OK ||
|
||||
dvz_volume_set_step_count(visual, 48) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz volume field");
|
||||
}
|
||||
if (figure_ == nullptr || panel_ == nullptr || visual_ == nullptr ||
|
||||
dvz_panel_add_visual(panel_, visual_, nullptr) != DVZ_OK)
|
||||
if (visual == nullptr || dvz_panel_add_visual(panel_, visual, nullptr) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz visual family");
|
||||
if (supports_item_interaction(family)) {
|
||||
if (dvz_visual_set_query_capabilities(
|
||||
visual_, DVZ_QUERY_CAPABILITY_ITEM) != DVZ_OK)
|
||||
visual, DVZ_QUERY_CAPABILITY_ITEM) != DVZ_OK)
|
||||
throw std::runtime_error("failed to enable Datoviz item queries");
|
||||
wants_item_interaction = true;
|
||||
}
|
||||
visuals_.push_back({registration.identity, family, visual, 0});
|
||||
}
|
||||
if (wants_item_interaction) {
|
||||
item_interaction_ = dvz_item_interaction(panel_, nullptr);
|
||||
if (item_interaction_ == nullptr)
|
||||
throw std::runtime_error("failed to create Datoviz item interaction");
|
||||
@@ -944,11 +963,9 @@ void Datoviz_Visual_Backend::apply_axes(const Scene_3D_Parameters& scene) {
|
||||
|
||||
void Datoviz_Visual_Backend::apply_camera(const plot::Camera_Descriptor& source) {
|
||||
if (applied_camera_ && *applied_camera_ == source) return;
|
||||
if (turntable_ != nullptr) {
|
||||
if (input_router_ != nullptr)
|
||||
(void)dvz_turntable_disconnect(turntable_, input_router_);
|
||||
dvz_turntable_destroy(turntable_);
|
||||
turntable_ = nullptr;
|
||||
if (camera_controller_ != nullptr) {
|
||||
dvz_controller_destroy(camera_controller_);
|
||||
camera_controller_ = nullptr;
|
||||
}
|
||||
DvzCameraDesc camera = dvz_camera_desc();
|
||||
const auto assign = [](vec3 target, plot::Spatial_Point value) {
|
||||
@@ -975,31 +992,79 @@ void Datoviz_Visual_Backend::apply_camera(const plot::Camera_Descriptor& source)
|
||||
std::tan(source.vertical_field_of_view_degrees * std::numbers::pi / 360.0));
|
||||
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz Camera component");
|
||||
DvzTurntableDesc turntable = dvz_turntable_desc();
|
||||
turntable.initial_view = camera.view;
|
||||
turntable.yaw_speed = static_cast<float>(source.control.yaw_speed);
|
||||
turntable.pitch_speed = static_cast<float>(source.control.pitch_speed);
|
||||
turntable.zoom_speed = static_cast<float>(source.control.zoom_speed);
|
||||
turntable.pan_speed = static_cast<float>(source.control.pan_speed);
|
||||
turntable.min_pitch = static_cast<float>(source.control.minimum_pitch);
|
||||
turntable.max_pitch = static_cast<float>(source.control.maximum_pitch);
|
||||
turntable.min_distance = static_cast<float>(source.control.minimum_distance);
|
||||
turntable.max_distance = static_cast<float>(source.control.maximum_distance);
|
||||
turntable.controller_flags = DVZ_TURNTABLE_FLAGS_WRAP_YAW |
|
||||
DVZ_TURNTABLE_FLAGS_CLAMP_DISTANCE;
|
||||
if (source.control.pan_enabled)
|
||||
turntable.controller_flags |= DVZ_TURNTABLE_FLAGS_ALLOW_PAN;
|
||||
turntable_ = dvz_turntable_create(&turntable);
|
||||
if (turntable_ == nullptr ||
|
||||
dvz_turntable_set_camera(turntable_, dvz_panel_camera(panel_)) != DVZ_OK ||
|
||||
dvz_turntable_connect(turntable_, input_router_) != DVZ_OK)
|
||||
throw std::runtime_error("failed to connect Datoviz Turntable to Panel Camera");
|
||||
DvzDimMask dimensions = DVZ_DIM_MASK_XYZ;
|
||||
switch (source.controller) {
|
||||
case plot::Camera_Controller::turntable: {
|
||||
DvzTurntableDesc descriptor = dvz_turntable_desc();
|
||||
descriptor.initial_view = camera.view;
|
||||
descriptor.yaw_speed = static_cast<float>(source.turntable_control.yaw_speed);
|
||||
descriptor.pitch_speed = static_cast<float>(source.turntable_control.pitch_speed);
|
||||
descriptor.zoom_speed = static_cast<float>(source.turntable_control.zoom_speed);
|
||||
descriptor.pan_speed = static_cast<float>(source.turntable_control.pan_speed);
|
||||
descriptor.min_pitch = static_cast<float>(source.turntable_control.minimum_pitch);
|
||||
descriptor.max_pitch = static_cast<float>(source.turntable_control.maximum_pitch);
|
||||
descriptor.min_distance = static_cast<float>(source.turntable_control.minimum_distance);
|
||||
descriptor.max_distance = static_cast<float>(source.turntable_control.maximum_distance);
|
||||
descriptor.controller_flags = DVZ_TURNTABLE_FLAGS_WRAP_YAW |
|
||||
DVZ_TURNTABLE_FLAGS_CLAMP_DISTANCE;
|
||||
if (source.turntable_control.pan_enabled)
|
||||
descriptor.controller_flags |= DVZ_TURNTABLE_FLAGS_ALLOW_PAN;
|
||||
if (source.turntable_control.invert_y)
|
||||
descriptor.controller_flags |= DVZ_TURNTABLE_FLAGS_INVERT_Y;
|
||||
camera_controller_ = dvz_turntable(scene_, &descriptor);
|
||||
break;
|
||||
}
|
||||
case plot::Camera_Controller::arcball: {
|
||||
DvzArcballDesc descriptor = dvz_arcball_desc();
|
||||
if (source.arcball_control.constrain_rotation)
|
||||
descriptor.controller_flags |= DVZ_ARCBALL_FLAGS_CONSTRAIN;
|
||||
camera_controller_ = dvz_arcball(scene_, &descriptor);
|
||||
if (camera_controller_ != nullptr && source.arcball_control.constrain_rotation) {
|
||||
auto* arcball = dvz_controller_arcball(camera_controller_);
|
||||
vec3 axis{
|
||||
static_cast<float>(source.arcball_control.constraint_axis.x),
|
||||
static_cast<float>(source.arcball_control.constraint_axis.y),
|
||||
static_cast<float>(source.arcball_control.constraint_axis.z)};
|
||||
if (arcball == nullptr || dvz_arcball_constrain(arcball, axis) != DVZ_OK)
|
||||
throw std::runtime_error("failed to configure Datoviz Arcball constraint");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case plot::Camera_Controller::fly: {
|
||||
DvzFlyDesc descriptor = dvz_fly_desc();
|
||||
descriptor.initial_view = camera.view;
|
||||
descriptor.mode = source.fly_control.mode == plot::Camera_Fly_Mode::plane
|
||||
? DVZ_FLY_MODE_PLANE
|
||||
: DVZ_FLY_MODE_FREE;
|
||||
descriptor.speed = static_cast<float>(source.fly_control.movement_speed);
|
||||
descriptor.fast_multiplier = static_cast<float>(source.fly_control.fast_multiplier);
|
||||
descriptor.slow_multiplier = static_cast<float>(source.fly_control.slow_multiplier);
|
||||
descriptor.look_speed = static_cast<float>(source.fly_control.look_speed);
|
||||
descriptor.wheel_speed = static_cast<float>(source.fly_control.wheel_speed);
|
||||
if (source.fly_control.invert_y) descriptor.controller_flags |= DVZ_FLY_FLAGS_INVERT_Y;
|
||||
if (source.fly_control.fixed_up) descriptor.controller_flags |= DVZ_FLY_FLAGS_FIXED_UP;
|
||||
if (source.fly_control.disable_roll) descriptor.controller_flags |= DVZ_FLY_FLAGS_DISABLE_ROLL;
|
||||
camera_controller_ = dvz_fly(scene_, &descriptor);
|
||||
break;
|
||||
}
|
||||
case plot::Camera_Controller::panzoom: {
|
||||
DvzPanzoomDesc descriptor = dvz_panzoom_desc();
|
||||
if (source.panzoom_control.fixed_x) descriptor.controller_flags |= DVZ_PANZOOM_FLAGS_FIXED_X;
|
||||
if (source.panzoom_control.fixed_y) descriptor.controller_flags |= DVZ_PANZOOM_FLAGS_FIXED_Y;
|
||||
if (source.panzoom_control.keep_aspect) descriptor.controller_flags |= DVZ_PANZOOM_FLAGS_KEEP_ASPECT;
|
||||
camera_controller_ = dvz_panzoom(scene_, &descriptor);
|
||||
dimensions = DVZ_DIM_MASK_XY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (camera_controller_ == nullptr ||
|
||||
dvz_panel_bind_controller(panel_, camera_controller_, dimensions) != DVZ_OK)
|
||||
throw std::runtime_error("failed to bind Datoviz camera controller");
|
||||
applied_camera_ = source;
|
||||
}
|
||||
void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepared_Visual& point) {
|
||||
void Datoviz_Visual_Backend::apply(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared) {
|
||||
require_domain();
|
||||
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
|
||||
const auto& data = *point.data;
|
||||
if (target_extent_ != scene.viewport) {
|
||||
if (dvz_figure_resize(figure_, scene.viewport.width,
|
||||
scene.viewport.height) != DVZ_OK)
|
||||
@@ -1012,7 +1077,25 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
}
|
||||
apply_camera(scene.camera);
|
||||
apply_axes(scene);
|
||||
if (point.revision == applied_visual_revision_) return;
|
||||
if (prepared.size() != visuals_.size())
|
||||
throw std::logic_error("3D frame did not publish every registered Visual");
|
||||
for (auto& target : visuals_) {
|
||||
const auto found = std::ranges::find_if(prepared, [&](const Prepared_Visual_Instance& value) {
|
||||
return value.identity == target.identity;
|
||||
});
|
||||
if (found == prepared.end())
|
||||
throw std::logic_error("3D frame contains an unknown or missing Visual identity");
|
||||
apply_visual(target, found->visual);
|
||||
}
|
||||
}
|
||||
void Datoviz_Visual_Backend::apply_visual(
|
||||
Visual_Instance& target, const Prepared_Visual& point) {
|
||||
if (point.family != target.family)
|
||||
throw std::logic_error("3D Visual family changed after Scene construction");
|
||||
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
|
||||
if (point.revision == target.applied_revision) return;
|
||||
const auto& data = *point.data;
|
||||
auto* visual = target.visual;
|
||||
{
|
||||
mat4 transform{};
|
||||
for (std::size_t row = 0; row < 4; ++row)
|
||||
@@ -1025,17 +1108,17 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
style.edge_color.a = point.point_style.edge_color.alpha;
|
||||
style.stroke_width_px = point.point_style.stroke_width_px;
|
||||
style.aspect = aspect(point.point_style.aspect);
|
||||
if (dvz_point_set_style(visual_, &style) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz point style");
|
||||
if (dvz_point_set_style(visual, &style) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz point style");
|
||||
}
|
||||
if (dvz_visual_set_transform(visual_, transform) != DVZ_OK ||
|
||||
dvz_visual_set_depth_test(visual_, point.depth_test) != DVZ_OK ||
|
||||
if (dvz_visual_set_transform(visual, transform) != DVZ_OK ||
|
||||
dvz_visual_set_depth_test(visual, point.depth_test) != DVZ_OK ||
|
||||
dvz_visual_set_visible(
|
||||
visual_, point.visible && !data.positions.empty()) != DVZ_OK)
|
||||
visual, point.visible && !data.positions.empty()) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz visual state");
|
||||
}
|
||||
if (data.positions.empty()) {
|
||||
if (dvz_visual_set_visible(visual_, false) != DVZ_OK) throw std::runtime_error("failed to hide empty Datoviz point visual");
|
||||
applied_visual_revision_ = point.revision;
|
||||
if (dvz_visual_set_visible(visual, false) != DVZ_OK) throw std::runtime_error("failed to hide empty Datoviz point visual");
|
||||
target.applied_revision = point.revision;
|
||||
return;
|
||||
}
|
||||
const auto count = static_cast<std::uint32_t>(data.positions.size());
|
||||
@@ -1049,7 +1132,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"diameter_px", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::splat: {
|
||||
@@ -1061,7 +1144,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"angle", data.angles.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 4);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::pixel: {
|
||||
@@ -1072,7 +1155,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"pixel_size_px", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::marker: {
|
||||
@@ -1085,7 +1168,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"shape", data.shapes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 5);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 5);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::sphere: {
|
||||
@@ -1096,7 +1179,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"radius", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::segment: {
|
||||
@@ -1108,7 +1191,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 4);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::vector: {
|
||||
@@ -1120,7 +1203,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 4);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::primitive:
|
||||
@@ -1132,7 +1215,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"normal", data.normals.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::path: {
|
||||
@@ -1143,7 +1226,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::image:
|
||||
@@ -1154,7 +1237,7 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
{"extent", data.extents.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual_, updates.data(), 2);
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 2);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::glyph:
|
||||
@@ -1162,21 +1245,21 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
case Visual_Family::volume: break;
|
||||
}
|
||||
if (result != DVZ_OK ||
|
||||
dvz_visual_set_visible(visual_, point.visible) != DVZ_OK)
|
||||
dvz_visual_set_visible(visual, point.visible) != DVZ_OK)
|
||||
throw std::runtime_error("failed to upload Datoviz visual payload");
|
||||
applied_visual_revision_ = point.revision;
|
||||
target.applied_revision = point.revision;
|
||||
}
|
||||
void Datoviz_Visual_Backend::dispatch_pointer(
|
||||
::aethera::Event_Type event, float x, float y,
|
||||
::aethera::Mouse_Button mouse_button,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
if (applied_camera_) {
|
||||
if (applied_camera_ && applied_camera_->controller == plot::Camera_Controller::turntable) {
|
||||
if (mouse_button == ::aethera::Mouse_Button::left &&
|
||||
!applied_camera_->control.rotate_enabled) return;
|
||||
!applied_camera_->turntable_control.rotate_enabled) return;
|
||||
if ((mouse_button == ::aethera::Mouse_Button::middle ||
|
||||
mouse_button == ::aethera::Mouse_Button::right) &&
|
||||
!applied_camera_->control.pan_enabled) return;
|
||||
!applied_camera_->turntable_control.pan_enabled) return;
|
||||
}
|
||||
const float width = static_cast<float>(viewport.width);
|
||||
const float height = static_cast<float>(viewport.height);
|
||||
@@ -1195,7 +1278,9 @@ void Datoviz_Visual_Backend::dispatch_wheel(
|
||||
float x, float y, float delta_x, float delta_y,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
if (applied_camera_ && !applied_camera_->control.zoom_enabled) return;
|
||||
if (applied_camera_ &&
|
||||
applied_camera_->controller == plot::Camera_Controller::turntable &&
|
||||
!applied_camera_->turntable_control.zoom_enabled) return;
|
||||
dvz_pointer_emit_wheel(
|
||||
input_router_, x, y, static_cast<float>(viewport.width),
|
||||
static_cast<float>(viewport.height), delta_x, delta_y,
|
||||
@@ -1205,8 +1290,24 @@ void Datoviz_Visual_Backend::dispatch_key(
|
||||
const ::aethera::Key_Event& event) {
|
||||
require_domain();
|
||||
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
|
||||
if (turntable_ == nullptr || dvz_turntable_reset(turntable_) != DVZ_OK)
|
||||
throw std::runtime_error("failed to reset Datoviz Turntable Camera");
|
||||
DvzResult reset = DVZ_ERROR;
|
||||
switch (dvz_controller_type(camera_controller_)) {
|
||||
case DVZ_CONTROLLER_TYPE_TURNTABLE:
|
||||
reset = dvz_turntable_reset(dvz_controller_turntable(camera_controller_));
|
||||
break;
|
||||
case DVZ_CONTROLLER_TYPE_ARCBALL:
|
||||
reset = dvz_arcball_reset(dvz_controller_arcball(camera_controller_));
|
||||
break;
|
||||
case DVZ_CONTROLLER_TYPE_FLY:
|
||||
reset = dvz_fly_reset(dvz_controller_fly(camera_controller_));
|
||||
break;
|
||||
case DVZ_CONTROLLER_TYPE_PANZOOM:
|
||||
reset = dvz_panzoom_reset(dvz_controller_panzoom(camera_controller_));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (reset != DVZ_OK)
|
||||
throw std::runtime_error("failed to reset Datoviz camera controller");
|
||||
return;
|
||||
}
|
||||
const DvzKeyboardEventType type =
|
||||
@@ -1248,7 +1349,7 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(
|
||||
return artifact;
|
||||
}
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::submit(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual& point,
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback) {
|
||||
require_domain();
|
||||
if (scene.viewport.empty()) return std::nullopt;
|
||||
@@ -1256,7 +1357,7 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
trace.render_sequence = frame_sequence;
|
||||
trace.observed = observe;
|
||||
std::uint64_t phase_started = observe ? trace_now_ns() : 0;
|
||||
apply(scene, point);
|
||||
apply(scene, visuals);
|
||||
if (item_interaction_ != nullptr) {
|
||||
static_cast<void>(dvz_figure_process_queries(figure_, runtime_, nullptr));
|
||||
DvzQueryResult query{};
|
||||
@@ -1392,11 +1493,9 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
runtime_ = nullptr;
|
||||
}
|
||||
target_.reset();
|
||||
if (turntable_ != nullptr) {
|
||||
if (input_router_ != nullptr)
|
||||
(void)dvz_turntable_disconnect(turntable_, input_router_);
|
||||
dvz_turntable_destroy(turntable_);
|
||||
turntable_ = nullptr;
|
||||
if (camera_controller_ != nullptr) {
|
||||
dvz_controller_destroy(camera_controller_);
|
||||
camera_controller_ = nullptr;
|
||||
}
|
||||
if (item_interaction_ != nullptr) {
|
||||
dvz_item_interaction_destroy(item_interaction_);
|
||||
@@ -1415,7 +1514,7 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
dvz_input_router_destroy(input_router_);
|
||||
input_router_ = nullptr;
|
||||
}
|
||||
visual_ = nullptr;
|
||||
visuals_.clear();
|
||||
axes_visual_ = nullptr;
|
||||
axes_text_ = nullptr;
|
||||
applied_camera_.reset();
|
||||
|
||||
@@ -32,12 +32,13 @@ public:
|
||||
Datoviz_Frame_Trace trace; /* 已补全 GPU 和读回阶段的原始诊断数据。 */
|
||||
};
|
||||
Datoviz_Visual_Backend(std::uint32_t gpu_index, bool validation_enabled,
|
||||
Visual_Family visual_family, const Scene_3D_Parameters& initial_scene);
|
||||
const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
~Datoviz_Visual_Backend() noexcept(false);
|
||||
Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete;
|
||||
Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete;
|
||||
[[nodiscard]] std::optional<Pending_Frame> submit(
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual& visual,
|
||||
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals,
|
||||
std::uint64_t frame_sequence, bool observe, bool readback);
|
||||
[[nodiscard]] Completed_Frame collect(Pending_Frame pending);
|
||||
void discard(Pending_Frame pending);
|
||||
@@ -51,11 +52,20 @@ public:
|
||||
void dispatch_key(const Key_Event& event);
|
||||
private:
|
||||
class Frame_Target;
|
||||
struct Visual_Instance {
|
||||
Visual_Identity identity{}; /* Scene 注册的稳定身份。 */
|
||||
Visual_Family family{Visual_Family::point}; /* 原生 Visual 的确定 family。 */
|
||||
DvzVisual* visual{}; /* 由 Datoviz Scene 拥有。 */
|
||||
std::uint64_t applied_revision{}; /* 此 Visual 已上传的 Prepare 版本。 */
|
||||
};
|
||||
void require_domain() const;
|
||||
void create_scene(Visual_Family visual_family, const Scene_3D_Parameters& initial_scene);
|
||||
void create_scene(const std::vector<Visual_Registration>& visuals,
|
||||
const Scene_3D_Parameters& initial_scene);
|
||||
[[nodiscard]] DvzVisual* create_visual(Visual_Family family);
|
||||
void apply_camera(const plot::Camera_Descriptor& camera);
|
||||
void apply_axes(const Scene_3D_Parameters& scene);
|
||||
void apply(const Scene_3D_Parameters& scene, const Prepared_Visual& visual);
|
||||
void apply(const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals);
|
||||
void apply_visual(Visual_Instance& target, const Prepared_Visual& visual);
|
||||
[[nodiscard]] DvzSceneFrameArtifact* emit(const Scene_3D_Parameters& scene);
|
||||
void destroy();
|
||||
std::thread::id domain_thread_; /* 唯一允许访问 Datoviz 对象的线程。 */
|
||||
@@ -63,19 +73,18 @@ private:
|
||||
DvzDrp2Runtime* runtime_{}; /* DRP2 Vulkan 运行时;由本类拥有。 */
|
||||
DvzScene* scene_{}; /* Datoviz Scene;由本类拥有。 */
|
||||
DvzFigure* figure_{}; /* 当前离屏 Figure。 */
|
||||
DvzPanel* panel_{}; /* 承载唯一 Visual 的全屏 Panel。 */
|
||||
DvzVisual* visual_{}; /* Builder 指定 family 的唯一 Visual。 */
|
||||
DvzPanel* panel_{}; /* 承载全部业务 Visual 的全屏 Panel。 */
|
||||
std::vector<Visual_Instance> visuals_{}; /* 按 Scene 稳定身份管理的原生 Visual。 */
|
||||
DvzVisual* axes_visual_{}; /* 三维主轴、刻度和网格 Segment Visual。 */
|
||||
DvzText* axes_text_{}; /* 随相机变换的三维刻度与轴标题。 */
|
||||
DvzItemInteraction* item_interaction_{}; /* Datoviz 原生图元悬停与选择控制器。 */
|
||||
DvzPinnedReadout* hover_readout_{}; /* 最近一次命中的 Datoviz 数据提示卡。 */
|
||||
DvzTurntable* turntable_{}; /* 直接驱动 Panel Camera 的独立 Turntable。 */
|
||||
DvzController* camera_controller_{}; /* 当前绑定到 Panel 的 Datoviz 原生相机控制器。 */
|
||||
DvzInputRouter* input_router_{}; /* Scene 输入事件路由器。 */
|
||||
DvzPointerGestureHandler* gesture_handler_{}; /* 指针手势解析器。 */
|
||||
std::unique_ptr<Frame_Target> target_; /* 当前尺寸对应的离屏提交和读回目标。 */
|
||||
Extent target_extent_{}; /* target_ 当前适配的像素尺寸。 */
|
||||
std::uint64_t target_generation_{}; /* 每次重建 target_ 时递增的资源代次。 */
|
||||
std::uint64_t applied_visual_revision_{}; /* 已上传到 Datoviz 的 Prepared 版本。 */
|
||||
std::optional<plot::Camera_Descriptor> applied_camera_{}; /* 已应用到 Panel 的 Camera 配置。 */
|
||||
std::optional<std::array<plot::Axis_Descriptor, 3>> applied_axes_{}; /* 已生成 Visual 的轴描述快照。 */
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d {
|
||||
/* 三维 Prepare 完成后的异步后端提交阶段标签。 */
|
||||
struct Submit_Tag {};
|
||||
@@ -40,9 +41,12 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
|
||||
Final_Builder& use_gpu(std::uint32_t gpu_index, bool validation_enabled = false);
|
||||
[[nodiscard]] std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> build();
|
||||
private:
|
||||
Root* visual{}; /* 不拥有的唯一 Visual;生命周期必须覆盖 Scene。 */
|
||||
Visual_Family visual_family{Visual_Family::point}; /* Visual 编译期 Spec 对应的后端 family。 */
|
||||
void (*bind_visual)(Root*, std::shared_ptr<void>){}; /* 把 Scene 拥有的弱提交上下文绑定到最终 Visual Private。 */
|
||||
struct Visual_Binding {
|
||||
Root* object{}; /* 不拥有;生命周期必须覆盖 Scene。 */
|
||||
Visual_Family family{Visual_Family::point}; /* 创建对应 Datoviz Visual 的 family。 */
|
||||
void (*bind)(Root*, std::shared_ptr<void>){}; /* 绑定 Scene 拥有的发布上下文。 */
|
||||
};
|
||||
std::vector<Visual_Binding> visuals{}; /* 本 Scene 的全部 Visual,注册顺序保持稳定。 */
|
||||
Root* camera{}; /* 不拥有的 Camera 组件;生命周期必须覆盖 Scene。 */
|
||||
Root* axes{}; /* 不拥有的三轴组件;生命周期必须覆盖 Scene。 */
|
||||
using Camera_Read = plot::Camera_Descriptor (*)(const Root*);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "../detail/Async_Render_Backend.hpp"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
namespace aethera::render_3d {
|
||||
namespace detail {
|
||||
@@ -9,6 +10,7 @@ struct Scene_Paint_Context {
|
||||
Object* scene{}; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */
|
||||
Frame_3D* frame{}; /* 当前 Submit 图对应的外部帧;process 返回后清空。 */
|
||||
Scene_3D_Parameters parameters{}; /* 本帧从 Scene 组件读取的一致快照。 */
|
||||
Prepared_Visual_Batch visuals{}; /* Submit 图发布的本帧 Visual 快照集合。 */
|
||||
};
|
||||
}
|
||||
struct Render_Scene_3D::Private : Prev_Private {
|
||||
@@ -33,7 +35,7 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
/* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */
|
||||
template <Attached Object>
|
||||
void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled,
|
||||
Visual_Family visual_family, Root* camera, Root* axes,
|
||||
std::vector<detail::Visual_Registration> visuals, Root* camera, Root* axes,
|
||||
plot::Camera_Descriptor (*camera_reader)(const Root*),
|
||||
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(const Root*));
|
||||
template <Attached Object> [[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const;
|
||||
@@ -52,17 +54,23 @@ template <typename Object> template <Attached Visual_Object>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
|
||||
if (!visual_value) throw std::invalid_argument("Render_Scene_3D cannot attach a null Visual");
|
||||
if (visual) throw std::logic_error("Render_Scene_3D currently accepts one primary Visual");
|
||||
visual = visual_value;
|
||||
visual_family = Visual_Object::Attached_Object::Specification::family;
|
||||
bind_visual = [](Root* root, std::shared_ptr<void> context) {
|
||||
if (std::ranges::any_of(visuals, [visual_value](const Visual_Binding& value) {
|
||||
return value.object == visual_value;
|
||||
}))
|
||||
throw std::logic_error("Render_Scene_3D cannot attach the same Visual twice");
|
||||
Visual_Binding binding{};
|
||||
binding.object = visual_value;
|
||||
binding.family = Visual_Object::Attached_Object::Specification::family;
|
||||
binding.bind = [](Root* root, std::shared_ptr<void> context) {
|
||||
auto* object = static_cast<Visual_Object*>(root);
|
||||
Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>()
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const detail::Prepared_Visual& prepared) {
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
|
||||
const detail::Prepared_Visual& prepared) {
|
||||
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
|
||||
submission.backend->render(prepared, submission.parameters, submission.frame);
|
||||
submission.visuals.push_back({reinterpret_cast<detail::Visual_Identity>(identity), prepared});
|
||||
});
|
||||
};
|
||||
visuals.push_back(binding);
|
||||
this->template add_dependency_node<Prepare_Data_Tag>(visual_value);
|
||||
this->template add_dependency_node<Submit_Tag>(visual_value);
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
@@ -76,7 +84,9 @@ Render_Scene_3D::Builder<Object>::add_camera(Camera_Object* camera_value) {
|
||||
camera = camera_value;
|
||||
read_camera = [](const Root* root) {
|
||||
const auto& prop = static_cast<const Camera_Object*>(root)->template read_prop<Camera_3D::Base_Tag>();
|
||||
return plot::Camera_Descriptor{prop.initial_view, prop.projection, prop.control,
|
||||
return plot::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};
|
||||
};
|
||||
@@ -105,16 +115,20 @@ Render_Scene_3D::Builder<Object>::use_gpu(std::uint32_t gpu_index_value,
|
||||
}
|
||||
template <typename Object>
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::build() {
|
||||
if (!visual || !bind_visual) throw std::invalid_argument("Render_Scene_3D requires one Visual");
|
||||
if (visuals.empty()) throw std::invalid_argument("Render_Scene_3D requires at least one Visual");
|
||||
if (!camera || !read_camera) throw std::invalid_argument("Render_Scene_3D requires one Camera component");
|
||||
if (!axes || !read_axes) throw std::invalid_argument("Render_Scene_3D requires one Axes component");
|
||||
auto result = Base::build();
|
||||
if (!result) return std::unexpected(result.error());
|
||||
auto scene = std::move(result).value();
|
||||
auto& private_data = Base::private_access(scene.get()).template get<Render_Scene_3D::Base_Tag>();
|
||||
private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, visual_family,
|
||||
std::vector<detail::Visual_Registration> registrations;
|
||||
registrations.reserve(visuals.size());
|
||||
for (const auto& visual : visuals)
|
||||
registrations.push_back({reinterpret_cast<detail::Visual_Identity>(visual.object), visual.family});
|
||||
private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, std::move(registrations),
|
||||
camera, axes, read_camera, read_axes);
|
||||
bind_visual(visual, private_data.paint_context);
|
||||
for (const auto& visual : visuals) visual.bind(visual.object, private_data.paint_context);
|
||||
return scene;
|
||||
}
|
||||
template <Attached Object>
|
||||
@@ -125,7 +139,8 @@ detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object)
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::initialize_backend(
|
||||
Object* object, std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family,
|
||||
Object* object, std::uint32_t gpu_index, bool validation_enabled,
|
||||
std::vector<detail::Visual_Registration> visuals,
|
||||
Root* camera, Root* axes, plot::Camera_Descriptor (*camera_reader)(const Root*),
|
||||
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(const Root*)) {
|
||||
camera_component = camera;
|
||||
@@ -134,9 +149,9 @@ void Render_Scene_3D::Private::initialize_backend(
|
||||
read_axes = axes_reader;
|
||||
const auto initial = parameters(object);
|
||||
backend = std::make_shared<detail::Async_Render_Backend>(gpu_index, validation_enabled,
|
||||
visual_family, initial);
|
||||
std::move(visuals));
|
||||
paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(
|
||||
detail::Scene_Paint_Context<Object>{backend, object, nullptr, initial});
|
||||
detail::Scene_Paint_Context<Object>{backend, object, nullptr, initial, {}});
|
||||
}
|
||||
inline void Render_Scene_3D::Private::dispatch_events(Extent viewport) { this->consume_events([&](const std::shared_ptr<Event>& event) { const auto result = backend->dispatch_event(event, viewport); return result != detail::Async_Render_Backend::Dispatch_Event_Result::queue_full && result != detail::Async_Render_Backend::Dispatch_Event_Result::backend_unavailable; }); }
|
||||
template <Attached Object, typename Callback>
|
||||
@@ -153,6 +168,7 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
|
||||
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);
|
||||
context->visuals.clear();
|
||||
struct Frame_Context_Scope {
|
||||
Frame_3D*& target; /* Visual Submit 回调读取的当前外部帧槽位。 */
|
||||
Frame_3D* previous{}; /* 嵌套调用前的帧;析构时恢复。 */
|
||||
@@ -188,6 +204,9 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
|
||||
dispatch->state.notify(root);
|
||||
});
|
||||
if (!result) throw std::logic_error("render scene submit graph became invalid during submission");
|
||||
if (context->visuals.empty())
|
||||
throw std::logic_error("render scene submit graph published no Visual snapshots");
|
||||
context->backend->render(context->visuals, context->parameters, context->frame);
|
||||
frame->mark(Frame_Trace_Marker::paint_finished);
|
||||
});
|
||||
std::invoke(std::forward<Callback>(callback));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
namespace aethera::render_3d {
|
||||
template <typename Spec>
|
||||
struct Basic_Visual<Spec>::Private : Basic_Visual::Prev_Private {
|
||||
using Enqueue_Paint = void (*)(void*, const detail::Prepared_Visual&);
|
||||
using Enqueue_Paint = void (*)(void*, const Root*, const detail::Prepared_Visual&);
|
||||
struct Paint_Target {
|
||||
std::weak_ptr<void> context{}; /* Scene 拥有的异步提交上下文;Scene 销毁后自动失效。 */
|
||||
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
|
||||
@@ -42,7 +42,7 @@ template <typename Spec> bool Basic_Visual<Spec>::State::operator==(const State&
|
||||
template <typename Spec> bool Basic_Visual<Spec>::Private::valid_items(const std::vector<Item>& items) { return std::ranges::all_of(items, [](const Item& item) { return Spec::valid(item); }); }
|
||||
template <typename Spec> void Basic_Visual<Spec>::Private::bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue) { paint_target = {std::move(context), enqueue}; }
|
||||
template <typename Spec> template <Attached Object> void Basic_Visual<Spec>::Private::prepare_data(Object* object) { using Visual_Tag = typename Basic_Visual::Base_Tag; const auto& prop = object->template read_prop<Visual_Tag>(); if (!valid_items(prop.items) || !detail::finite(prop.transform)) throw std::invalid_argument("3D visual property contains invalid data"); auto data = std::make_shared<detail::Prepared_Visual_Data>(); Spec::prepare(prop.items, *data); auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>(); output = {}; output.family = Spec::family; output.transform = prop.transform; output.visible = prop.visible; output.depth_test = prop.depth_test; output.revision = next_revision++; output.data = std::move(data); if constexpr (requires { prop.style; }) output.point_style = prop.style; object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) { auto& state = states.template get<Visual_Tag>(); state.prepared_item_count = prop.items.size(); state.prepared_revision = output.revision; }); }
|
||||
template <typename Spec> template <Attached Object> tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { if (auto context = paint_target.context.lock(); context && paint_target.enqueue) { const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>(); const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>(); paint_target.enqueue(context.get(), pending.revision >= current.revision ? pending : current); } }).name("render_3d.paint.enqueue"); return graph; }
|
||||
template <typename Spec> template <Attached Object> tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { if (auto context = paint_target.context.lock(); context && paint_target.enqueue) { const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>(); const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>(); paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current); } }).name("render_3d.paint.publish"); return graph; }
|
||||
template <typename Spec> template <Attached Object> bool Basic_Visual<Spec>::Private::should_paint(Object*, const State&, bool) { return paint_target.enqueue != nullptr && !paint_target.context.expired(); }
|
||||
template <typename Spec> template <typename Object, typename Owner, typename Member, typename Prop_Type> void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||
template <typename Spec> template <typename Object, typename Prop_Type, typename State_Type> void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { using Visual_Tag = typename Basic_Visual::Base_Tag; pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size(); }
|
||||
|
||||
Reference in New Issue
Block a user