diff --git a/kernel/main.cmake b/kernel/main.cmake index c341b76..3cc23cc 100644 --- a/kernel/main.cmake +++ b/kernel/main.cmake @@ -3,7 +3,11 @@ set(Aethera_Kernel_dependencies aethera_kernel::taskflow global::magic_enum glob set(Aethera_BUILD_TESTS TRUE) if (Aethera_BUILD_TESTS) set(Aethera_Kernel_test_targets) - list(APPEND Aethera_Kernel_dependencies global::GTest global::benchmark) + list(APPEND Aethera_Kernel_dependencies + global::GTest + global::benchmark + global::libwebsockets + ) endif () rcl_add_dependency_action_targets(Aethera_Kernel_env ${Aethera_Kernel_dependencies}) set_target_properties(Aethera_Kernel_env PROPERTIES FOLDER Aethera_Kernel) diff --git a/kernel/src/kernel/frame.cpp b/kernel/src/kernel/frame.cpp index 9df51e8..e99794d 100644 --- a/kernel/src/kernel/frame.cpp +++ b/kernel/src/kernel/frame.cpp @@ -124,7 +124,8 @@ Frame_Statistics_Sample Render_Frame::statistics() const { Frame_Statistic::backend_plan_ms, Frame_Statistic::backend_execute_ms, Frame_Statistic::backend_submit_ms, - Frame_Statistic::gpu_fence_wait_ms, + Frame_Statistic::gpu_completion_observation_ms, + Frame_Statistic::completion_task_queue_ms, Frame_Statistic::gpu_render_ms, Frame_Statistic::gpu_transition_ms, Frame_Statistic::gpu_copy_ms, @@ -135,7 +136,8 @@ Frame_Statistics_Sample Render_Frame::statistics() const { Frame_Trace_Measurement::backend_plan_ns, Frame_Trace_Measurement::backend_execute_ns, Frame_Trace_Measurement::backend_submit_ns, - Frame_Trace_Measurement::gpu_fence_wait_ns, + Frame_Trace_Measurement::gpu_completion_observation_ns, + Frame_Trace_Measurement::completion_task_queue_ns, Frame_Trace_Measurement::gpu_render_ns, Frame_Trace_Measurement::gpu_transition_ns, Frame_Trace_Measurement::gpu_copy_ns, diff --git a/kernel/src/kernel/frame.hpp b/kernel/src/kernel/frame.hpp index 7062311..cfeaf8d 100644 --- a/kernel/src/kernel/frame.hpp +++ b/kernel/src/kernel/frame.hpp @@ -54,7 +54,8 @@ enum struct Frame_Trace_Measurement : std::uint8_t { backend_plan_ns, backend_execute_ns, backend_submit_ns, - gpu_fence_wait_ns, + gpu_completion_observation_ns, + completion_task_queue_ns, gpu_render_ns, gpu_transition_ns, gpu_copy_ns, diff --git a/kernel/src/kernel/frame_statistics.hpp b/kernel/src/kernel/frame_statistics.hpp index 38d07f0..b7e74c9 100644 --- a/kernel/src/kernel/frame_statistics.hpp +++ b/kernel/src/kernel/frame_statistics.hpp @@ -20,7 +20,8 @@ enum struct Frame_Statistic : std::uint8_t { backend_plan_ms, backend_execute_ms, backend_submit_ms, - gpu_fence_wait_ms, + gpu_completion_observation_ms, + completion_task_queue_ms, gpu_render_ms, gpu_transition_ms, gpu_copy_ms, diff --git a/mcp/core/runtime/Plot.cpp b/mcp/core/runtime/Plot.cpp index de5017a..e20d289 100644 --- a/mcp/core/runtime/Plot.cpp +++ b/mcp/core/runtime/Plot.cpp @@ -308,7 +308,10 @@ nlohmann::json datoviz_observation_json( {"emit", milliseconds(value.emit_ns)}, {"execute", milliseconds(value.execute_ns)}, {"submit", milliseconds(value.submit_ns)}, - {"gpu_fence_wait", milliseconds(value.gpu_fence_wait_ns)}, + {"gpu_completion_observation", + milliseconds(value.gpu_completion_observation_ns)}, + {"completion_task_queue", + milliseconds(value.completion_task_queue_ns)}, {"readback", milliseconds(value.readback_ns)}}}, {"traffic", { {"uploaded_bytes", value.uploaded_bytes}, @@ -1156,7 +1159,26 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) { record_plot_measurements(output); auto& scene_3d = std::get>(scene); scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{tick.width, tick.height}); - const auto result = scene_3d->render(&output); + const auto weak = lifetime; + const auto result = scene_3d->render( + &output, + Render_Scene_3D::Frame_Callbacks{ + .submitted = [weak](not_null, bool) { + if (auto owner = weak.lock()) { + try { owner->d->release_render_admission(weak); } + catch (...) { owner->d->fail(std::current_exception()); } + } + }, + .completed = [weak](not_null frame) { + if (auto owner = weak.lock()) { + try { + owner->d->publish_completed_frame(frame); + owner->d->consume_completed_frame(frame); + owner->d->retire_completed_frame(frame); + } + catch (...) { owner->d->fail(std::current_exception()); } + } + }}); if (result == Render_Scene_3D::Render_Result::submitted) { taskflow_trace_claimed = false; managed->submitted_at = std::chrono::steady_clock::now(); @@ -1526,32 +1548,6 @@ void Plot::ensure_started() { .source = Frame_Request_Source::periodic}); } }); - /* 3D Scene 的提交/完成回调只表示借用阶段变化;Frame 的发布、 - * 统计和复用始终由本帧策略决定。2D 回调与每次 render 直接绑定。 */ - if (std::holds_alternative>(d->scene)) { - auto& scene_3d = std::get>(d->scene); - scene_3d->set_submitted_frame_callback( - [weak](not_null, bool) { - if (auto owner = weak.lock()) { - try { - owner->d->release_render_admission(weak); - } - catch (...) { owner->d->fail(std::current_exception()); } - } - }); - scene_3d->set_frame_callback( - [weak](not_null frame) { - if (auto owner = weak.lock()) { - try { - owner->d->publish_completed_frame(frame); - owner->d->consume_completed_frame(frame); - owner->d->retire_completed_frame(frame); - } - catch (...) { owner->d->fail(std::current_exception()); } - } - }); - } - /* 3D callback 必须先于周期时钟安装,避免首帧在初始化窗口进入 Scene。 */ d->refresh_schedule(); }); } diff --git a/render_3D/render_3D/base/Datoviz_Frame_Observation.hpp b/render_3D/render_3D/base/Datoviz_Frame_Observation.hpp index 5e7fbf9..6b621ea 100644 --- a/render_3D/render_3D/base/Datoviz_Frame_Observation.hpp +++ b/render_3D/render_3D/base/Datoviz_Frame_Observation.hpp @@ -35,7 +35,8 @@ struct Datoviz_Frame_Observation { std::uint64_t emit_ns{}; /* 生成 Datoviz 帧计划耗时。 */ std::uint64_t execute_ns{}; /* 执行 Datoviz 帧计划耗时。 */ std::uint64_t submit_ns{}; /* Vulkan 队列提交耗时。 */ - std::uint64_t gpu_fence_wait_ns{}; /* 从 watch 到 fence 完成的墙钟耗时。 */ + std::uint64_t gpu_completion_observation_ns{}; /* 从 watch 到探测到 fence 完成的墙钟耗时。 */ + std::uint64_t completion_task_queue_ns{}; /* 探测到 fence 完成后,Scene completion task 的排队耗时。 */ std::uint64_t readback_ns{}; /* 已完成 GPU 目标的 CPU 读回耗时。 */ std::uint64_t uploaded_bytes{}; /* 本帧写入映射属性存储的有效字节数。 */ std::uint64_t readback_bytes{}; /* 本帧产出的连续像素字节数。 */ diff --git a/render_3D/render_3D/base/Types.hpp b/render_3D/render_3D/base/Types.hpp index b651a36..8c58c82 100644 --- a/render_3D/render_3D/base/Types.hpp +++ b/render_3D/render_3D/base/Types.hpp @@ -3,8 +3,6 @@ #include #include #include -#include -#include namespace aethera::render_3d { using Coordinate_3D = float; using Pixel_Distance = float; @@ -47,36 +45,9 @@ struct Linear_Color { Coordinate_3D alpha{1.0F}; /* 线性不透明度通道,范围为 0 到 1。 */ bool operator==(const Linear_Color&) const = default; }; -enum struct Visual_Family : std::uint8_t { point, splat, pixel, marker, sphere, segment, vector, primitive, mesh, path, image, labels, glyph, text, volume }; -enum struct Marker_Shape : std::uint8_t { disc, square, triangle, diamond, cross }; -enum struct Point_Aspect : std::uint8_t { filled, stroke, outline }; struct Visual_Settings { bool operator==(const Visual_Settings&) const = default; }; -struct Image_Field_Settings { - std::uint32_t field_width{}; /* 纹理字段宽度;零值表示尚未提供纹理。 */ - std::uint32_t field_height{}; /* 纹理字段高度;必须与宽度同时为零或非零。 */ - std::vector field_pixels{}; /* 行优先 RGBA8 像素;数量必须等于宽乘高。 */ - bool operator==(const Image_Field_Settings&) const = default; -}; -struct Labels_Field_Settings { - std::uint32_t field_width{}; /* 分类字段宽度,单位为样本。 */ - std::uint32_t field_height{}; /* 分类字段高度,单位为样本。 */ - std::vector field_labels{}; /* 行优先分类 ID;数量必须等于宽乘高。 */ - bool operator==(const Labels_Field_Settings&) const = default; -}; -struct Volume_Field_Settings { - std::uint32_t field_width{}; /* 体字段宽度,单位为体素。 */ - std::uint32_t field_height{}; /* 体字段高度,单位为体素。 */ - std::uint32_t field_depth{}; /* 体数据深度;三个维度必须同时为零或非零。 */ - bool operator==(const Volume_Field_Settings&) const = default; -}; -struct Point_Style { - Color edge_color{Color::black()}; /* 点边缘颜色。 */ - Pixel_Distance stroke_width_px{}; /* 点边缘宽度,单位为像素。 */ - Point_Aspect aspect{Point_Aspect::filled}; /* 点内部和边缘的组合方式。 */ - bool operator==(const Point_Style&) const = default; -}; namespace detail { [[nodiscard]] bool finite(Coordinate_3D value) noexcept; [[nodiscard]] bool finite(Vec2 value) noexcept; diff --git a/render_3D/render_3D/detail/Backend_Types.hpp b/render_3D/render_3D/detail/Backend_Types.hpp index ca171ad..e077da2 100644 --- a/render_3D/render_3D/detail/Backend_Types.hpp +++ b/render_3D/render_3D/detail/Backend_Types.hpp @@ -1,30 +1,15 @@ #pragma once #include "../visual/Visuals.hpp" +#include "../visual/detail/Datoviz_Visual_Operations.hpp" #include #include #include "../camera/Camera.hpp" +#include #include #include #include -#include #include namespace aethera::render_3d::detail { -using Prepared_Visual_Payload = std::variant< - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr>; /* Scene 到 Backend 的唯一异构边界;Visual 对象内部不保存此信封。 */ struct Prepared_Visual { Matrix4 transform{}; @@ -32,18 +17,19 @@ struct Prepared_Visual { bool depth_test{true}; std::uint64_t revision{}; std::uint64_t data_revision{}; - Prepared_Visual_Payload data{}; + const Datoviz_Visual_Operations* operations{}; /* 可空借用;未发布 payload 时为空。 */ + std::shared_ptr data{}; /* 具体 Visual Prepared_Data 的只读所有权。 */ }; -template -Prepared_Visual erase_prepared_visual(const Prepared_Visual_For& source) { +template +Prepared_Visual erase_prepared_visual( + const Prepared_Visual_For& source) { return {source.transform, source.visible, source.depth_test, source.revision, - source.data_revision, source.data}; + source.data_revision, &Spec::datoviz_operations(), source.data}; } 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; + not_null operations; /* 具体 Visual 自描述的 Datoviz 操作。 */ }; struct Prepared_Visual_Instance { Visual_Identity identity{}; /* 与 Builder 注册身份一一对应。 */ @@ -54,8 +40,7 @@ using Shared_Prepared_Visual_Batch = std::shared_ptr; [[nodiscard]] inline bool prepared_visual_has_payload( const Prepared_Visual& visual) noexcept { - return std::visit([](const auto& data) { return static_cast(data); }, - visual.data); + return visual.operations != nullptr && visual.data != nullptr; } [[nodiscard]] inline bool prepared_visual_batch_complete( const Prepared_Visual_Batch& visuals) noexcept { diff --git a/render_3D/render_3D/scene/Render_Scene_3D.cpp b/render_3D/render_3D/scene/Render_Scene_3D.cpp index f3c8d56..04be731 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.cpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.cpp @@ -3,16 +3,9 @@ namespace aethera::render_3d { bool Render_Scene_3D::Prop::operator==(const Prop&) const = default; Render_Scene_3D::Render_Result Render_Scene_3D::render( - not_null frame) { - return double_buffer::detail::Internal_Access::get(this).render(this, frame); -} -void Render_Scene_3D::set_frame_callback(Frame_Callback callback) { - double_buffer::detail::Internal_Access::get(this).set_frame_callback(this, std::move(callback)); -} -void Render_Scene_3D::set_submitted_frame_callback( - Submitted_Frame_Callback callback) { - double_buffer::detail::Internal_Access::get(this) - .set_submitted_frame_callback(this, std::move(callback)); + not_null frame, Frame_Callbacks callbacks) { + return double_buffer::detail::Internal_Access::get(this).render( + this, frame, std::move(callbacks)); } void Render_Scene_3D::activate_view() { double_buffer::detail::Internal_Access::get(this).set_view_active(this, true); diff --git a/render_3D/render_3D/scene/Render_Scene_3D.hpp b/render_3D/render_3D/scene/Render_Scene_3D.hpp index bed2bbb..d74447b 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.hpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.hpp @@ -39,7 +39,7 @@ struct Render_Scene_3D : Def { private: struct Visual_Binding { not_null object; /* 不拥有;生命周期必须覆盖 Scene。 */ - Visual_Family family{Visual_Family::point}; /* 创建对应 Datoviz Visual 的 family。 */ + not_null operations; /* 具体 Visual 的后端能力。 */ void (*bind)(not_null, std::shared_ptr){}; /* 绑定 Scene 拥有的发布上下文。 */ }; std::vector visuals{}; /* 本 Scene 的全部 Visual,注册顺序保持稳定。 */ @@ -53,19 +53,18 @@ struct Render_Scene_3D : Def { bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */ }; enum struct Render_Result { submitted, frame_pipeline_busy, view_inactive, empty_viewport, backend_unavailable }; - using Frame_Callback = std::function)>; - using Submitted_Frame_Callback = - std::function, bool)>; + struct Frame_Callbacks { + std::function, bool)> submitted{}; /* GPU 已提交并可释放下一次 Prepare 准入。 */ + std::function)> completed{}; /* Scene 清除全部帧借用后返还同一 Frame。 */ + }; using Render_Result_Type = Render_Result; using Render_Frame_Type = Frame_3D; - [[nodiscard]] Render_Result render(not_null frame); - void set_frame_callback(Frame_Callback callback); - void set_submitted_frame_callback(Submitted_Frame_Callback callback); + [[nodiscard]] Render_Result render( + not_null frame, Frame_Callbacks callbacks); /* * 无等待写入调用方拥有的 Frame;Scene 始终只借用地址。最多三个借用可在 * CPU Prepare、GPU 和读回阶段重叠,完成全部写入并清除借用后调用唯一完成回调。 */ - /* GPU 实际提交并释放下一次 Prepare 时调用;仅用于调用方继续驱动帧策略。 */ /* * 返回 GPU 完成并回读像素后、帧回调前执行的直接 Taskflow。 * 只能在 Scene 没有运行时修改该图;禁止在执行期间 emplace/erase/clear。 diff --git a/render_3D/render_3D/scene/Render_Scene_3D.ipp b/render_3D/render_3D/scene/Render_Scene_3D.ipp index ca82367..549fb13 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.ipp +++ b/render_3D/render_3D/scene/Render_Scene_3D.ipp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +53,13 @@ inline void publish_datoviz_observation( 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_completion_observation_ns) + frame->record( + Frame_Trace_Measurement::gpu_completion_observation_ns, + observation.gpu_completion_observation_ns); + if (observation.completion_task_queue_ns) + frame->record(Frame_Trace_Measurement::completion_task_queue_ns, + observation.completion_task_queue_ns); if (observation.gpu) { frame->record(Frame_Trace_Measurement::gpu_render_ns, observation.gpu->render_ns); @@ -89,16 +94,21 @@ struct Render_Scene_3D::Private : Prev_Private, completing }; struct Frame_Context { - std::atomic phase{Frame_Phase::available}; - Frame_3D* frame{}; - Event_Batch events{}; - bool trace_started{}; - bool overlaps_gpu{}; - std::atomic_bool cpu_finished{}; - std::atomic_bool gpu_submitted{}; - std::atomic_bool gpu_completed{}; - std::exception_ptr failure{}; - std::optional + std::atomic phase{Frame_Phase::available}; /* 当前借用帧在 Scene 内的唯一生命周期阶段。 */ + Frame_3D* frame{}; /* 可空、非拥有借用;available 时为空。 */ + Event_Batch events{}; /* 本帧 Prepare 消费的输入批次。 */ + bool trace_started{}; /* 本帧是否已绑定 Taskflow trace。 */ + bool overlaps_gpu{}; /* 本帧资源是否允许 CPU Prepare 与 GPU 重叠。 */ + std::atomic_bool cpu_finished{}; /* frame DAG 已完成 CPU 部分。 */ + std::atomic_bool gpu_submitted{}; /* Vulkan queue submit 已完成。 */ + std::atomic_bool collection_ready{}; /* completion_result/failure 已 release 发布给 Scene completion task。 */ + std::atomic_bool collection_completed{}; /* target collect 与 Frame 数据附加已完成。 */ + std::chrono::steady_clock::time_point completion_observed_at{}; /* fence 被非阻塞探测为完成的时刻。 */ + std::optional completion_result{}; /* 本物理帧唯一 Vulkan 完成结果。 */ + std::exception_ptr completion_failure{}; /* 本物理帧的完成边界 Unknown Failure。 */ + std::exception_ptr failure{}; /* Scene 最终向调用方交付的 Unknown Failure。 */ + Frame_Callbacks callbacks{}; /* 仅绑定本次借用帧;返还前移出并清空。 */ + std::optional datoviz_frame{}; }; /* @@ -120,8 +130,6 @@ struct Render_Scene_3D::Private : Prev_Private, std::array frame_contexts{}; std::atomic_bool prepare_active{}; std::atomic_bool completion_busy{}; - std::atomic> frame_callback{}; - std::atomic> submitted_callback{}; bool overlaps_gpu{}; std::atomic_bool backend_available{true}; std::shared_ptr paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */ @@ -158,10 +166,13 @@ struct Render_Scene_3D::Private : Prev_Private, detail::Shared_Prepared_Visual_Batch visuals, detail::Scene_3D_Parameters parameters); template - void collect_datoviz( + void observe_datoviz_completion( Object* object, not_null context, std::optional result, std::exception_ptr failure) noexcept; + template + void collect_datoviz( + Object* object, not_null context) noexcept; void dispatch_datoviz(const std::shared_ptr& event, Extent viewport); void fail_datoviz(std::exception_ptr failure) noexcept; @@ -172,11 +183,8 @@ struct Render_Scene_3D::Private : Prev_Private, void ensure_frame_taskflow(Object* object); template [[nodiscard]] Render_Result render( - Object* object, not_null frame); - template - void set_frame_callback(Object* object, Frame_Callback callback); - template - void set_submitted_frame_callback(Object* object, Submitted_Frame_Callback callback); + Object* object, not_null frame, + Frame_Callbacks callbacks); template void set_view_active(Object* object, bool active); template @@ -194,7 +202,7 @@ typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Build throw std::logic_error("Render_Scene_3D cannot attach the same Visual twice"); Visual_Binding binding{ visual_value, - Visual_Object::Attached_Object::Specification::family, + &Visual_Object::Attached_Object::Specification::datoviz_operations(), [](not_null root, std::shared_ptr context) { using Definition = typename Visual_Object::Attached_Object; using Prepared = typename Definition::Prepared_Visual; @@ -204,7 +212,8 @@ typename Render_Scene_3D::Builder::Final_Builder& Render_Scene_3D::Build const Prepared& prepared) { auto& submission = *static_cast*>(raw_context); const auto visual_identity = reinterpret_cast(identity); - auto erased = detail::erase_prepared_visual(prepared); + auto erased = detail::erase_prepared_visual< + typename Definition::Specification>(prepared); auto& visuals = *submission.visuals; const auto found = std::ranges::find( visuals, visual_identity, @@ -277,7 +286,7 @@ std::expected, Dependency_Graph_Error> Render_Scene_3D:: for (const auto& visual : visuals) registrations.push_back({ reinterpret_cast(visual.object.get()), - visual.family + visual.operations }); private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, std::move(registrations), camera, axes, read_camera, @@ -361,10 +370,12 @@ void Render_Scene_3D::Private::render_datoviz( 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), {}); + observe_datoviz_completion( + object, context, std::move(result), {}); }, [this, object, context](std::exception_ptr failure) { - collect_datoviz(object, context, {}, std::move(failure)); + observe_datoviz_completion( + object, context, {}, std::move(failure)); }, frame->taskflow_trace_requested()); if (!reservation) { @@ -378,7 +389,7 @@ void Render_Scene_3D::Private::render_datoviz( const auto sequence = frame->identity().sequence; const bool observe = frame->taskflow_trace_requested(); const bool readback = frame->output() == Frame_3D_Output::pixels; - std::optional prepared; + std::optional prepared; if (context->events.empty()) { frame->mark(Frame_Trace_Marker::backend_prepare_started); prepared = try_prepare_reused( @@ -426,7 +437,7 @@ void Render_Scene_3D::Private::render_datoviz( context->overlaps_gpu = false; context->gpu_submitted.store( true, std::memory_order_release); - context->gpu_completed.store( + context->collection_completed.store( true, std::memory_order_release); } else { @@ -444,7 +455,7 @@ void Render_Scene_3D::Private::render_datoviz( arm_completion(object); } catch (...) { - collect_datoviz( + observe_datoviz_completion( object, context, {}, std::current_exception()); } }); @@ -460,7 +471,7 @@ void Render_Scene_3D::Private::render_datoviz( 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); + context->collection_completed.store(true, std::memory_order_release); try_release_prepare(object, context); if (context->phase.load(std::memory_order_acquire) == Frame_Phase::submitted) @@ -468,18 +479,47 @@ void Render_Scene_3D::Private::render_datoviz( } } template -void Render_Scene_3D::Private::collect_datoviz( +void Render_Scene_3D::Private::observe_datoviz_completion( Object* object, not_null context, std::optional result, std::exception_ptr failure) noexcept { try { const not_null frame{context->frame}; frame->mark(Frame_Trace_Marker::gpu_completed); + if (context->collection_ready.load(std::memory_order_acquire)) + throw std::logic_error( + "3D frame received duplicate GPU completion"); + context->completion_result = std::move(result); + context->completion_failure = std::move(failure); + context->completion_observed_at = std::chrono::steady_clock::now(); + context->collection_ready.store(true, std::memory_order_release); + arm_completion(object); + } + catch (...) { + fail_datoviz(std::current_exception()); + } +} +template +void Render_Scene_3D::Private::collect_datoviz( + Object* object, not_null context) noexcept { + try { + const not_null frame{context->frame}; + const auto completion_started = std::chrono::steady_clock::now(); frame->mark(Frame_Trace_Marker::readback_started); if (!context->datoviz_frame) throw std::logic_error("3D completion lost its Datoviz target"); + auto result = std::move(context->completion_result); + auto failure = std::move(context->completion_failure); + context->completion_result.reset(); + context->completion_failure = {}; + auto& observation = context->datoviz_frame->observation; if (result) - context->datoviz_frame->observation.gpu_fence_wait_ns = - result->wait_duration_ns; + observation.gpu_completion_observation_ns = + result->wait_duration_ns; + observation.completion_task_queue_ns = + static_cast(std::max( + 0, std::chrono::duration_cast( + completion_started - context->completion_observed_at) + .count())); if (failure) std::rethrow_exception(std::move(failure)); if (!result || result->error != detail::Gpu_Completion_Service::Completion_Error::none) { @@ -514,11 +554,8 @@ void Render_Scene_3D::Private::collect_datoviz( } } try { - context->gpu_completed.store(true, std::memory_order_release); + context->collection_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()); @@ -528,7 +565,7 @@ template void Render_Scene_3D::Private::complete_frame( Object*, not_null context) { const auto frame = context->frame; - const auto callback = frame_callback.load(std::memory_order_acquire); + auto callback = std::move(context->callbacks.completed); frame->mark(Frame_Trace_Marker::frame_ready); if (context->trace_started) aethera::detail::finish_taskflow_trace(*frame); /* Scene 仅借用外部 Frame。完成通知前必须先清除所有借用引用并开放 @@ -541,11 +578,16 @@ void Render_Scene_3D::Private::complete_frame( 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->collection_ready.store(false, std::memory_order_relaxed); + context->collection_completed.store(false, std::memory_order_relaxed); + context->completion_observed_at = {}; + context->completion_result.reset(); + context->completion_failure = {}; + context->callbacks = {}; context->phase.store(Frame_Phase::available, std::memory_order_release); - if (callback && *callback) { + if (callback) { try { - (*callback)(frame); + callback(frame); } catch (...) { fail_datoviz(std::current_exception()); @@ -565,7 +607,7 @@ void Render_Scene_3D::Private::try_release_prepare( 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->collection_completed.load(std::memory_order_acquire))) return; auto expected = Frame_Phase::preparing; if (!context->phase.compare_exchange_strong( @@ -573,10 +615,10 @@ void Render_Scene_3D::Private::try_release_prepare( 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) - (*callback)(context->frame, context->overlaps_gpu); - if (context->gpu_completed.load(std::memory_order_acquire)) arm_completion(object); + if (context->callbacks.submitted) + context->callbacks.submitted(context->frame, context->overlaps_gpu); + if (context->collection_completed.load(std::memory_order_acquire)) + arm_completion(object); } template void Render_Scene_3D::Private::arm_completion(Object* object) { @@ -585,54 +627,92 @@ void Render_Scene_3D::Private::arm_completion(Object* object) { expected, true, std::memory_order_acq_rel, std::memory_order_acquire)) return; - consume_completion(object); + try { + aethera::schedule_task( + "render_3d.scene.complete", + [this, object] { + try { consume_completion(object); } + catch (...) { + completion_busy.store(false, std::memory_order_release); + fail_datoviz(std::current_exception()); + } + }); + } + catch (...) { + completion_busy.store(false, std::memory_order_release); + throw; + } } template 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 || - !context.gpu_completed.load(std::memory_order_acquire)) + for (;;) { + Frame_Context* collecting{}; + for (auto& context : frame_contexts) { + if (!context.collection_ready.load(std::memory_order_acquire) || + context.frame == nullptr) + continue; + if (!collecting || context.frame->identity().sequence < + collecting->frame->identity().sequence) + collecting = &context; + } + if (collecting) { + if (!collecting->collection_ready.exchange( + false, std::memory_order_acq_rel)) + continue; + collect_datoviz(object, collecting); continue; - if (!selected || context.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); - })) + } + + Frame_Context* selected{}; + for (auto& context : frame_contexts) { + if (context.phase.load(std::memory_order_acquire) != + Frame_Phase::submitted || + !context.collection_completed.load(std::memory_order_acquire)) + continue; + if (!selected || context.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.collection_ready.load( + std::memory_order_acquire) || + (context.phase.load(std::memory_order_acquire) == + Frame_Phase::submitted && + context.collection_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)) + continue; + + const auto frame = not_null{selected->frame}; + frame->mark(Frame_Trace_Marker::paint_finished); + frame->mark(Frame_Trace_Marker::scene_render_finished); + if (completion_graph.empty()) { + complete_frame(object, selected); + continue; + } + auto done = [this, object, selected] { + complete_frame(object, selected); + completion_busy.store(false, std::memory_order_release); arm_completion(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)); 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)) { - completion_busy.store(false, std::memory_order_release); - arm_completion(object); - return; - } - const auto frame = selected->frame; - frame->mark(Frame_Trace_Marker::paint_finished); - frame->mark(Frame_Trace_Marker::scene_render_finished); - auto done = [this, object, selected] { - complete_frame(object, selected); - completion_busy.store(false, std::memory_order_release); - arm_completion(object); - }; - if (completion_graph.empty()) { - done(); - return; - } - 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)); } template void Render_Scene_3D::Private::initialize_backend( @@ -648,26 +728,13 @@ void Render_Scene_3D::Private::initialize_backend( const auto initial = parameters(object); auto prepared_visuals = std::make_shared(); prepared_visuals->reserve(visuals.size()); - for (const auto& visual : visuals) prepared_visuals->push_back({visual.identity, {}}); + for (const auto& visual : visuals) { + detail::Prepared_Visual prepared; + prepared.operations = visual.operations; + prepared_visuals->push_back({visual.identity, std::move(prepared)}); + } 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; + return visual.operations->overlaps_gpu; }); detail::Scene_Datoviz_State::initialize( gpu_index, validation_enabled, visuals, initial); @@ -738,10 +805,10 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { } template Render_Scene_3D::Render_Result Render_Scene_3D::Private::render( - Object* object, not_null frame) { + Object* object, not_null frame, + Frame_Callbacks callbacks) { frame->mark(Frame_Trace_Marker::scene_render_entered); - const auto callback = frame_callback.load(std::memory_order_acquire); - if (!callback || !*callback) + if (!callbacks.completed) throw std::logic_error( "Render_Scene_3D requires a frame callback before render"); bool inactive{}; @@ -771,11 +838,17 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render( execution->overlaps_gpu = false; execution->cpu_finished.store(false, std::memory_order_relaxed); execution->gpu_submitted.store(false, std::memory_order_relaxed); - execution->gpu_completed.store(false, std::memory_order_relaxed); + execution->collection_ready.store(false, std::memory_order_relaxed); + execution->collection_completed.store(false, std::memory_order_relaxed); + execution->completion_observed_at = {}; + execution->completion_result.reset(); + execution->completion_failure = {}; + execution->callbacks = std::move(callbacks); active_context = execution; const auto reject_frame = [this, execution](Render_Result result) { active_context = nullptr; execution->frame = nullptr; + execution->callbacks = {}; execution->phase.store(Frame_Phase::available, std::memory_order_release); prepare_active.store(false, std::memory_order_release); return result; @@ -841,24 +914,6 @@ void Render_Scene_3D::Private::reset_diagnostics(Object* object) { }); } template -void Render_Scene_3D::Private::set_frame_callback(Object*, Frame_Callback callback) { - frame_callback.store( - callback - ? std::make_shared(std::move(callback)) - : std::shared_ptr{}, - std::memory_order_release); -} -template -void Render_Scene_3D::Private::set_submitted_frame_callback( - Object*, Submitted_Frame_Callback callback) { - submitted_callback.store( - callback - ? std::make_shared( - std::move(callback)) - : std::shared_ptr{}, - std::memory_order_release); -} -template void Render_Scene_3D::Private::set_view_active(Object* object, bool active) { double_buffer::detail::Internal_Access::set<&Prop::view_active>(object, active); } diff --git a/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp b/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp index 664c13a..4cbb4f4 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp +++ b/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp @@ -1,2756 +1,8 @@ -#include "Scene_Datoviz_State.hpp" -#include "../detail/Exception.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace aethera::render_3d::detail { -namespace { -constexpr std::uint64_t color_target_id = 0x5256504f494e54ULL; -DvzCapabilitySnapshot offscreen_capabilities() { - auto capabilities = dvz_capability_snapshot(); - capabilities.supports_color_blending = true; - return capabilities; -} -std::uint64_t trace_now_ns() noexcept { - return static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); -} -template -owner allocate_wrapper(Allocate allocate, const char* message) { - owner resource = allocate(); - if (resource == nullptr) throw std::runtime_error(message); - return resource; -} -int modifiers(::aethera::Keyboard_Modifier value) { - const auto bits = static_cast(value); - int result = DVZ_KEY_MODIFIER_NONE; - if ((bits & static_cast(::aethera::Keyboard_Modifier::shift)) != 0) result |= DVZ_KEY_MODIFIER_SHIFT; - if ((bits & static_cast(::aethera::Keyboard_Modifier::control)) != 0) result |= DVZ_KEY_MODIFIER_CONTROL; - if ((bits & static_cast(::aethera::Keyboard_Modifier::alt)) != 0) result |= DVZ_KEY_MODIFIER_ALT; - if ((bits & static_cast(::aethera::Keyboard_Modifier::meta)) != 0) result |= DVZ_KEY_MODIFIER_SUPER; - return result; -} -DvzPointerButton button(::aethera::Mouse_Button value) { - switch (value) { - case ::aethera::Mouse_Button::left: return DVZ_POINTER_BUTTON_LEFT; - case ::aethera::Mouse_Button::middle: return DVZ_POINTER_BUTTON_MIDDLE; - case ::aethera::Mouse_Button::right: return DVZ_POINTER_BUTTON_RIGHT; - case ::aethera::Mouse_Button::none: return DVZ_POINTER_BUTTON_NONE; - } - return DVZ_POINTER_BUTTON_NONE; -} -DvzKeyCode key_code(::aethera::Key key, std::uint32_t native_key) { - switch (key) { - case ::aethera::Key::escape: return DVZ_KEY_ESCAPE; - case ::aethera::Key::enter: return DVZ_KEY_ENTER; - case ::aethera::Key::space: return DVZ_KEY_SPACE; - case ::aethera::Key::delete_key: return DVZ_KEY_DELETE; - case ::aethera::Key::backspace: return DVZ_KEY_BACKSPACE; - case ::aethera::Key::left: return DVZ_KEY_LEFT; - case ::aethera::Key::right: return DVZ_KEY_RIGHT; - case ::aethera::Key::up: return DVZ_KEY_UP; - case ::aethera::Key::down: return DVZ_KEY_DOWN; - case ::aethera::Key::home: return DVZ_KEY_HOME; - case ::aethera::Key::unknown: break; - } - return native_key <= static_cast(DVZ_KEY_LAST) - ? static_cast(native_key) - : DVZ_KEY_UNKNOWN; -} -DvzShapeAspect aspect(Point_Aspect value) { - switch (value) { - case Point_Aspect::filled: return DVZ_SHAPE_ASPECT_FILLED; - case Point_Aspect::stroke: return DVZ_SHAPE_ASPECT_STROKE; - case Point_Aspect::outline: return DVZ_SHAPE_ASPECT_OUTLINE; - } - return DVZ_SHAPE_ASPECT_FILLED; -} -float axis_position(const plot::Axis_Descriptor& axis, double coordinate) { - const auto origin = axis.range.origin; - const auto target = axis.range.target; - if (!std::isfinite(origin) || !std::isfinite(target) || origin == target) throw std::invalid_argument("3D axis range must be finite and non-empty"); - double ratio{}; - if (axis.scale == plot::Axis_Scale::logarithmic) { - if (!(origin > 0.0) || !(target > 0.0) || !(coordinate > 0.0)) throw std::invalid_argument("logarithmic 3D axis coordinates must be positive"); - ratio = (std::log10(coordinate) - std::log10(origin)) / - (std::log10(target) - std::log10(origin)); - } - else { - ratio = (coordinate - origin) / (target - origin); - } - return static_cast(-1.0 + 2.0 * ratio); -} -std::string axis_title(const plot::Axis_Descriptor& axis) { - if (axis.label.empty()) return axis.unit; - if (axis.unit.empty()) return axis.label; - return axis.label + " (" + axis.unit + ")"; -} -DvzFont* configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) { - auto font_descriptor = dvz_font_desc(); - font_descriptor.family = "Roboto"; - font_descriptor.style = "Regular"; - auto* font = dvz_font(scene, &font_descriptor); - auto atlas_specification = - dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); - if (font == nullptr || - !dvz_font_atlas_ensure_string(font, &atlas_specification, text)) - throw std::runtime_error("failed to create Datoviz text atlas"); - const auto* atlas = dvz_font_atlas(font, &atlas_specification); - if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) throw std::runtime_error("failed to bind Datoviz text atlas"); - float line_width = 0.0F; - for (const auto* character = text; *character != '\0'; ++character) { - const auto* glyph = - dvz_text_atlas_glyph(atlas, static_cast(*character)); - if (glyph != nullptr) line_width += glyph->advance; - } - std::vector> positions; - std::vector> bounds; - std::vector> texture_coordinates; - std::vector> colors; - std::vector angles; - const auto atlas_info = dvz_text_atlas_info(atlas); - float cursor_x = 0.0F; - std::size_t glyph_index = 0; - for (const auto* character = text; *character != '\0'; ++character) { - const auto* glyph = - dvz_text_atlas_glyph(atlas, static_cast(*character)); - if (glyph == nullptr) continue; - const float x0 = cursor_x + glyph->xoff - 0.5F * line_width; - const float y0 = 0.5F * atlas_info.ascent + glyph->yoff; - const std::array glyph_bounds{ - x0, y0, x0 + glyph->width, y0 + glyph->height - }; - const std::array glyph_texture{ - glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3] - }; - const std::array glyph_color = - glyph_index % 2 == 0 - ? std::array{40, 235, 205, 255} - : std::array{255, 190, 80, 255}; - for (std::uint32_t vertex = 0; vertex < 6; ++vertex) { - positions.push_back({0.0F, 0.0F, 0.0F}); - bounds.push_back(glyph_bounds); - texture_coordinates.push_back(glyph_texture); - colors.push_back(glyph_color); - angles.push_back(0.0F); - } - cursor_x += glyph->advance; - ++glyph_index; - } - if (positions.empty()) throw std::runtime_error("Datoviz text atlas contains no visible glyphs"); - const auto count = static_cast(positions.size()); - const std::array updates{ - { - {"position", positions.data(), count}, {"bounds", bounds.data(), count}, - {"texcoords", texture_coordinates.data(), count}, - {"color", colors.data(), count}, {"angle", angles.data(), count} - } - }; - if (dvz_visual_set_data_many(visual, updates.data(), - static_cast(updates.size())) != DVZ_OK || - dvz_visual_set_depth_test(visual, false) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz text geometry"); - return font; -} -std::vector utf8_codepoints(std::string_view text) { - std::vector result; - result.reserve(text.size()); - for (std::size_t index = 0; index < text.size();) { - const auto lead = static_cast(text[index]); - std::uint32_t codepoint{}; - std::size_t length{}; - if (lead < 0x80U) { - codepoint = lead; - length = 1; - } - else if ((lead & 0xE0U) == 0xC0U) { - codepoint = lead & 0x1FU; - length = 2; - } - else if ((lead & 0xF0U) == 0xE0U) { - codepoint = lead & 0x0FU; - length = 3; - } - else if ((lead & 0xF8U) == 0xF0U) { - codepoint = lead & 0x07U; - length = 4; - } - else { - throw std::invalid_argument("3D text contains invalid UTF-8"); - } - if (index + length > text.size()) throw std::invalid_argument("3D text contains truncated UTF-8"); - for (std::size_t offset = 1; offset < length; ++offset) { - const auto continuation = - static_cast(text[index + offset]); - if ((continuation & 0xC0U) != 0x80U) throw std::invalid_argument("3D text contains invalid UTF-8"); - codepoint = (codepoint << 6U) | (continuation & 0x3FU); - } - if ((length == 2 && codepoint < 0x80U) || - (length == 3 && codepoint < 0x800U) || - (length == 4 && codepoint < 0x10000U) || - codepoint > 0x10FFFFU || - (codepoint >= 0xD800U && codepoint <= 0xDFFFU)) - throw std::invalid_argument("3D text contains non-canonical UTF-8"); - result.push_back(codepoint); - index += length; - } - return result; -} -void upload_text(DvzVisual* visual, DvzFont* font, - const Text_Prepared_Data& data) { - if (font == nullptr) throw std::logic_error("Datoviz text visual has no font"); - auto atlas_specification = - dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); - for (const auto& text : data.strings) - if (!dvz_font_atlas_ensure_string( - font, &atlas_specification, text.c_str())) - throw std::runtime_error("failed to grow Datoviz text atlas"); - const auto* atlas = dvz_font_atlas(font, &atlas_specification); - if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) throw std::runtime_error("failed to bind Datoviz text atlas"); - std::vector> positions; - std::vector> bounds; - std::vector> texture_coordinates; - std::vector> colors; - std::vector angles; - for (std::size_t item_index = 0; item_index < data.strings.size(); - ++item_index) { - const auto& text = data.strings[item_index]; - const auto codepoints = utf8_codepoints(text); - float line_width{}; - for (const auto codepoint : codepoints) { - const auto* glyph = dvz_text_atlas_glyph(atlas, codepoint); - if (glyph != nullptr) line_width += glyph->advance; - } - const float scale = data.sizes[item_index] / 64.0F; - float cursor_x{}; - for (const auto codepoint : codepoints) { - const auto* glyph = dvz_text_atlas_glyph(atlas, codepoint); - if (glyph == nullptr) continue; - const float x0 = (cursor_x + glyph->xoff - 0.5F * line_width) * scale; - const float y0 = glyph->yoff * scale; - const std::array glyph_bounds{ - x0, y0, x0 + glyph->width * scale, - y0 + glyph->height * scale - }; - const std::array glyph_texture{ - glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3] - }; - for (std::uint32_t vertex = 0; vertex < 6; ++vertex) { - positions.push_back(data.positions[item_index]); - bounds.push_back(glyph_bounds); - texture_coordinates.push_back(glyph_texture); - colors.push_back(data.colors[item_index]); - angles.push_back(0.0F); - } - cursor_x += glyph->advance; - } - } - const auto count = static_cast(positions.size()); - const std::array updates{ - { - {"position", positions.data(), count}, - {"bounds", bounds.data(), count}, - {"texcoords", texture_coordinates.data(), count}, - {"color", colors.data(), count}, - {"angle", angles.data(), count} - } - }; - if (dvz_visual_set_data_many( - visual, updates.data(), - static_cast(updates.size())) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz text payload"); -} -bool supports_item_interaction(Visual_Family family) noexcept { - return family == Visual_Family::point || family == Visual_Family::pixel || - family == Visual_Family::marker; -} -bool uses_external_attributes(Visual_Family family) noexcept { - switch (family) { - case Visual_Family::point: - case Visual_Family::splat: - case Visual_Family::pixel: - case Visual_Family::marker: - case Visual_Family::sphere: - case Visual_Family::primitive: - case Visual_Family::mesh: - case Visual_Family::path: return true; - case Visual_Family::segment: - case Visual_Family::vector: - case Visual_Family::image: - case Visual_Family::labels: - case Visual_Family::glyph: - case Visual_Family::text: - case Visual_Family::volume: return false; - } - return false; -} -template -const Data& prepared_data(const Prepared_Visual& visual) { - const auto* value = std::get_if>(&visual.data); - if (value == nullptr || !*value) throw std::logic_error("3D prepared Visual payload does not match its family"); - return **value; -} -bool has_payload(const Prepared_Visual& visual) noexcept { - return std::visit([](const auto& value) { - return static_cast(value); - }, - visual.data); -} -std::size_t prepared_item_count(const Prepared_Visual& visual) noexcept { - return std::visit([](const auto& value) -> std::size_t { - if (!value) return 0; - if constexpr (requires { value->positions; }) return value->positions.size(); - else if constexpr (requires { value->centers; }) return value->centers.size(); - else if constexpr (requires { value->starts; }) return value->starts.size(); - else if constexpr (requires { value->origins; }) return value->origins.size(); - else return value->values.size(); - }, visual.data); -} -bool has_coordinate_labels(const Prepared_Visual& visual) noexcept { - const auto* payload = std::get_if>( - &visual.data); - if (payload == nullptr || !*payload) return false; - return std::ranges::any_of( - (*payload)->coordinate_label_visibility, - [](std::uint8_t visible) { - return visible != 0; - }); -} -bool same_visual_structure(const Prepared_Visual& applied, - const Prepared_Visual& incoming) noexcept { - if (applied.data.index() != incoming.data.index() || - applied.transform != incoming.transform || - applied.visible != incoming.visible || - applied.depth_test != incoming.depth_test || - !has_payload(applied) || !has_payload(incoming) || - prepared_item_count(applied) != prepared_item_count(incoming)) - return false; - if ((std::holds_alternative>( - incoming.data)) && - (has_coordinate_labels(applied) || has_coordinate_labels(incoming))) - return applied.revision == incoming.revision; - return true; -} -std::string artifact_command_excerpt(const std::string& json, - std::uint32_t command_index) { - constexpr std::string_view marker{"{ \"cmd\":"}; - const auto first_index = command_index > 12 ? command_index - 12 : 0; - std::size_t position{}; - std::size_t first_position{}; - for (std::uint32_t index = 0; index <= command_index; ++index) { - position = json.find(marker, position); - if (position == std::string::npos) return {}; - if (index == first_index) first_position = position; - if (index != command_index) position += marker.size(); - } - const auto end = json.find('\n', position); - return json.substr(first_position, - end == std::string::npos ? 2048 : end - first_position); -} -} // namespace -struct Datoviz_Render_Context final : - std::enable_shared_from_this { -public: - [[nodiscard]] static std::shared_ptr acquire( - std::uint32_t gpu_index, bool validation_enabled) { - return std::shared_ptr( - new Datoviz_Render_Context(gpu_index, validation_enabled)); - } - ~Datoviz_Render_Context() { - if (gpu_context_ != nullptr) dvz_gpu_ctx_destroy(gpu_context_); - } - [[nodiscard]] not_null gpu_context() const noexcept { - return not_null{gpu_context_}; - } - void enqueue_submission(std::function command) { - if (!command) throw std::invalid_argument("empty Datoviz queue command"); - if (!submissions_.enqueue(std::move(command))) throw std::bad_alloc{}; - submission_generation_.fetch_add(1, std::memory_order_release); - arm_submission_drain(); - } -private: - Datoviz_Render_Context(std::uint32_t gpu_index, bool validation_enabled) { - DvzGpuCtxConfig configuration = dvz_gpu_ctx_config(); - dvz_gpu_ctx_config_validation(&configuration, validation_enabled); - dvz_gpu_ctx_config_gpu(&configuration, gpu_index); - dvz_gpu_ctx_config_enable_canvas_extensions(&configuration, false); - gpu_context_ = dvz_gpu_ctx(&configuration); - if (gpu_context_ == nullptr) - throw std::runtime_error("failed to create Scene Datoviz GPU context"); - } - void arm_submission_drain() { - if (submission_drain_active_.exchange(true, std::memory_order_acq_rel)) return; - auto lifetime = shared_from_this(); - aethera::schedule_task("datoviz.queue.submit", [lifetime = std::move(lifetime)] { - lifetime->drain_submissions(); - }); - } - void drain_submissions() noexcept { - const auto observed = submission_generation_.load(std::memory_order_acquire); - std::function command; - while (submissions_.try_dequeue(command)) { - try { command(); } - catch (...) {} - command = {}; - } - submission_drain_active_.store(false, std::memory_order_release); - if (submission_generation_.load(std::memory_order_acquire) != observed) - arm_submission_drain(); - } - owner gpu_context_{}; /* 当前 Scene 独占 GPU Device 与分配器。 */ - moodycamel::ConcurrentQueue> submissions_{}; - std::atomic_uint64_t submission_generation_{}; - std::atomic_bool submission_drain_active_{}; -}; -void retain_quarantined_context( - std::shared_ptr& context) noexcept { - if (!context) return; - static moodycamel::ConcurrentQueue< - std::shared_ptr> quarantined; - if (quarantined.enqueue(context)) - context.reset(); - else - static_cast(new(std::nothrow) - std::shared_ptr(std::move(context))); -} -struct Scene_Datoviz_State::Frame_Target final { -public: - struct Collection { - std::vector pixels; - std::optional gpu_timing; - }; - Frame_Target(not_null gpu_context, VkCommandPool command_pool, - Extent extent, std::uint64_t generation) : - gpu_context_(gpu_context), extent_(extent), generation_(generation) { - if (extent.empty()) throw std::invalid_argument("invalid Datoviz point frame target"); - const std::uint64_t byte_size = static_cast(extent.width) * - extent.height * 4ULL; - if (byte_size > std::numeric_limits::max()) throw std::length_error("Datoviz point frame target is too large"); - byte_size_ = static_cast(byte_size); - DvzDevice* device = dvz_gpu_ctx_device(gpu_context_); - DvzVma* allocator = dvz_gpu_ctx_alloc(gpu_context_); - DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); - if (device == nullptr || allocator == nullptr || queue == nullptr) throw std::runtime_error("Datoviz GPU context is incomplete"); - try { - image_ = allocate_wrapper(dvz_images_create_wrapper, - "failed to allocate Datoviz image"); - dvz_images(device, allocator, VK_IMAGE_TYPE_2D, 1, image_); - dvz_images_format(image_, VK_FORMAT_R8G8B8A8_UNORM); - dvz_images_size(image_, extent.width, extent.height, 1); - dvz_images_tiling(image_, VK_IMAGE_TILING_OPTIMAL); - dvz_images_usage(image_, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT); - dvz_images_alloc_flags(image_, DVZ_ALLOC_FLAGS_NONE); - if (dvz_images_create(image_) != 0) throw std::runtime_error("failed to create Datoviz point image"); - view_ = allocate_wrapper(dvz_image_views_create_wrapper, - "failed to allocate Datoviz image view"); - dvz_image_views(image_, view_); - dvz_image_views_type(view_, VK_IMAGE_VIEW_TYPE_2D); - dvz_image_views_aspect(view_, VK_IMAGE_ASPECT_COLOR_BIT); - dvz_image_views_mip(view_, 0, 1); - dvz_image_views_layers(view_, 0, 1); - if (dvz_image_views_create(view_) != 0) throw std::runtime_error("failed to create Datoviz point image view"); - commands_ = allocate_wrapper(dvz_commands_create_wrapper, - "failed to allocate Datoviz commands"); - dvz_commands_pool(device, queue, command_pool, 1, commands_); - if (dvz_commands_handle(commands_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz command buffer"); - fence_ = allocate_wrapper(dvz_fence_create_wrapper, - "failed to allocate Datoviz fence"); - dvz_fence(device, true, fence_); - if (dvz_fence_handle(fence_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz fence"); - submit_ = allocate_wrapper(dvz_submit_create_wrapper, - "failed to allocate Datoviz submit"); - readback_ = allocate_wrapper(dvz_buffer_create_wrapper, - "failed to allocate Datoviz readback"); - dvz_buffer(device, allocator, readback_); - dvz_buffer_size(readback_, byte_size_); - dvz_buffer_flags(readback_, DVZ_ALLOC_HOST_ACCESS_RANDOM | DVZ_ALLOC_MAPPED); - dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT); - if (dvz_buffer_create(readback_) != 0) throw std::runtime_error("failed to create Datoviz readback buffer"); - initialize_timestamps(device, queue); - } - catch (...) { - destroy(); - raise_context("creating Datoviz frame target", std::current_exception()); - } - } - ~Frame_Target() noexcept { - try { - if (quarantined_) release_without_destroy(); - else destroy(); - } - catch (...) { - release_without_destroy(); - } - } - [[nodiscard]] bool can_reuse_commands( - std::uint64_t command_revision, bool readback) const noexcept { - return available() && recorded_ && - recorded_command_revision_ == command_revision && - recorded_readback_ == readback; - } - [[nodiscard]] bool can_reuse( - std::uint64_t command_revision, std::uint64_t controller_revision, - bool readback) const noexcept { - return can_reuse_commands(command_revision, readback) && - recorded_controller_revision_ == controller_revision; - } - void mark_mvp_uploaded(std::uint64_t controller_revision) { - if (!available() || !recorded_) - throw std::logic_error( - "Datoviz frame target cannot publish an MVP revision"); - recorded_controller_revision_ = controller_revision; - } - void invalidate_recording() { - if (!available()) - throw std::logic_error( - "Datoviz frame target cannot invalidate a busy recording"); - recorded_ = false; - } - void reuse(std::uint64_t command_revision, - std::uint64_t controller_revision, bool observe, - bool readback) { - if (!can_reuse(command_revision, controller_revision, readback)) - throw std::logic_error( - "Datoviz frame target command recording cannot be reused"); - observing_ = observe; - readback_requested_ = readback; - dvz_fence_reset(fence_); - prepared_ = true; - } - void begin(bool observe, bool readback, std::uint64_t command_revision, - std::uint64_t controller_revision) { - if (recording_ || prepared_ || in_flight_) throw std::logic_error("Datoviz frame target is not available"); - observing_ = observe; - readback_requested_ = readback; - recording_command_revision_ = command_revision; - recording_controller_revision_ = controller_revision; - /* Reset invalidates the previous recording immediately. Only - * finish_recording() may publish the new cache identity. */ - recorded_ = false; - try { - dvz_cmd_reset(commands_); - if (dvz_cmd_begin_result(commands_) != 0) - throw std::runtime_error( - "failed to begin Datoviz command buffer"); - recording_ = true; - DvzBarriers barriers{}; - dvz_barriers(&barriers); - auto* image_barrier = dvz_barriers_image( - &barriers, dvz_image_handle(image_, 0)); - if (image_barrier == nullptr) - throw std::runtime_error( - "failed to allocate Datoviz image barrier"); - if (completed_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) { - dvz_barrier_image_stage( - image_barrier, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); - dvz_barrier_image_access( - image_barrier, 0, - VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); - } - else if (completed_layout_ == - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - dvz_barrier_image_stage( - image_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); - dvz_barrier_image_access( - image_barrier, VK_ACCESS_2_TRANSFER_READ_BIT, - VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); - } - else { - dvz_barrier_image_stage( - image_barrier, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); - dvz_barrier_image_access( - image_barrier, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); - } - dvz_barrier_image_layout( - image_barrier, completed_layout_, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT); - dvz_barrier_image_mip(image_barrier, 0, 1); - dvz_barrier_image_layers(image_barrier, 0, 1); - dvz_cmd_barriers(commands_, &barriers); - if (timestamps_supported_) { - const VkCommandBuffer command_buffer = - dvz_commands_handle(commands_); - vkCmdResetQueryPool(command_buffer, query_pool_, 0, 4); - vkCmdWriteTimestamp( - command_buffer, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - query_pool_, 0); - } - } - catch (...) { - abort(); - throw; - } - } - [[nodiscard]] DvzStreamFrame stream_frame() const { - DvzStreamFrame frame{}; - frame.image = dvz_image_handle(image_, 0); - frame.command_buffer = dvz_commands_handle(commands_); - frame.image_view = dvz_image_views_handle(view_, 0); - frame.extent = {extent_.width, extent_.height}; - frame.color_format = VK_FORMAT_R8G8B8A8_UNORM; - frame.image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - frame.usage = DVZ_STREAM_FRAME_USAGE_RENDER_TARGET | DVZ_STREAM_FRAME_USAGE_COPY_SRC; - frame.command_buffer_recording = recording_; - frame.image_borrowed = true; - frame.image_view_borrowed = true; - frame.command_buffer_borrowed = true; - frame.handles_dirty = true; - frame.resource_generation = generation_; - frame.image_valid = true; - frame.memory_fd = -1; - frame.wait_semaphore_fd = -1; - return frame; - } - void finish_recording() { - if (!recording_) throw std::logic_error("Datoviz frame target is not recording"); - const VkCommandBuffer command_buffer = dvz_commands_handle(commands_); - if (timestamps_supported_) { - vkCmdWriteTimestamp( - command_buffer, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - query_pool_, 1); - } - if (readback_requested_) { - DvzBarriers image_barriers{}; - dvz_barriers(&image_barriers); - auto* image_barrier = - dvz_barriers_image(&image_barriers, dvz_image_handle(image_, 0)); - dvz_barrier_image_stage(image_barrier, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_2_TRANSFER_BIT); - dvz_barrier_image_access(image_barrier, - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_2_TRANSFER_READ_BIT); - dvz_barrier_image_layout(image_barrier, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT); - dvz_barrier_image_mip(image_barrier, 0, 1); - dvz_barrier_image_layers(image_barrier, 0, 1); - dvz_cmd_barriers(commands_, &image_barriers); - if (timestamps_supported_) { - vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, - query_pool_, 2); - } - DvzImageRegion region{}; - dvz_image_region(®ion); - dvz_image_region_extent(®ion, extent_.width, extent_.height, 1); - dvz_cmd_copy_image_to_buffer( - commands_, dvz_image_handle(image_, 0), - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, ®ion, - dvz_buffer_handle(readback_), 0); - DvzBarriers buffer_barriers{}; - dvz_barriers(&buffer_barriers); - auto* buffer_barrier = dvz_barriers_buffer( - &buffer_barriers, dvz_buffer_handle(readback_), 0, byte_size_); - dvz_barrier_buffer_stage(buffer_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT, - VK_PIPELINE_STAGE_2_HOST_BIT); - dvz_barrier_buffer_access(buffer_barrier, VK_ACCESS_2_TRANSFER_WRITE_BIT, - VK_ACCESS_2_HOST_READ_BIT); - dvz_cmd_barriers(commands_, &buffer_barriers); - } - else if (timestamps_supported_) { - vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - query_pool_, 2); - } - if (timestamps_supported_) { - vkCmdWriteTimestamp(command_buffer, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - query_pool_, 3); - } - if (dvz_cmd_end_result(commands_) != 0) throw std::runtime_error("failed to end Datoviz command buffer"); - recording_ = false; - dvz_fence_reset(fence_); - dvz_submit(submit_); - dvz_submit_command(submit_, dvz_commands_handle(commands_)); - recorded_command_revision_ = recording_command_revision_; - recorded_controller_revision_ = recording_controller_revision_; - recorded_readback_ = readback_requested_; - recorded_ = true; - prepared_ = true; - } - void submit() { - if (!prepared_ || in_flight_) throw std::logic_error("Datoviz frame target has no prepared submission"); - DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); - if (dvz_submit_send(submit_, dvz_queue_handle(queue), - dvz_fence_handle(fence_)) != VK_SUCCESS) - throw std::runtime_error("failed to submit Datoviz point frame"); - prepared_ = false; - in_flight_ = true; - completed_layout_ = readback_requested_ ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - } - [[nodiscard]] Collection collect() { - if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame"); - Collection result; - try { - if (readback_requested_) { - result.pixels.resize(static_cast(byte_size_)); - dvz_buffer_download(readback_, 0, byte_size_, result.pixels.data()); - } - result.gpu_timing = collect_gpu_timing(); - } - catch (...) { - in_flight_ = false; - observing_ = false; - readback_requested_ = false; - raise_context("submitting Datoviz frame target", std::current_exception()); - } - in_flight_ = false; - observing_ = false; - readback_requested_ = false; - return result; - } - void discard_after_completion() { - if (prepared_) { - prepared_ = false; - observing_ = false; - readback_requested_ = false; - return; - } - if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame"); - in_flight_ = false; - observing_ = false; - readback_requested_ = false; - } - [[nodiscard]] VkDevice device() const { - return dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)); - } - [[nodiscard]] VkFence fence() const { - return dvz_fence_handle(fence_); - } - [[nodiscard]] std::uint64_t generation() const noexcept { - return generation_; - } - void quarantine_after_submission() noexcept { - recording_ = false; - prepared_ = false; - in_flight_ = false; - observing_ = false; - readback_requested_ = false; - recorded_ = false; - quarantined_ = true; - } - [[nodiscard]] Extent extent() const noexcept { - return extent_; - } - [[nodiscard]] bool available() const noexcept { - return !quarantined_ && !recording_ && !prepared_ && !in_flight_; - } - [[nodiscard]] bool quiescent() const noexcept { - return !recording_ && !prepared_ && !in_flight_; - } - void abort() noexcept { - if (recording_ && commands_ != nullptr) dvz_cmd_reset(commands_); - recording_ = false; - prepared_ = false; - observing_ = false; - readback_requested_ = false; - } -private: - void release_without_destroy() noexcept { - query_pool_ = VK_NULL_HANDLE; - readback_ = nullptr; - fence_ = nullptr; - submit_ = nullptr; - commands_ = nullptr; - view_ = nullptr; - image_ = nullptr; - } - void initialize_timestamps(not_null device, - not_null queue) noexcept { - timestamps_initialized_ = true; - if (vkGetPhysicalDeviceQueueFamilyProperties == nullptr || - vkGetPhysicalDeviceProperties == nullptr || - vkCreateQueryPool == nullptr || - vkCmdResetQueryPool == nullptr || - vkCmdWriteTimestamp == nullptr || - vkGetQueryPoolResults == nullptr) - return; - const VkPhysicalDevice physical = - dvz_device_physical_device(device); - const VkDevice logical = dvz_device_handle(device); - if (physical == VK_NULL_HANDLE || logical == VK_NULL_HANDLE) return; - std::uint32_t family_count{}; - vkGetPhysicalDeviceQueueFamilyProperties( - physical, &family_count, nullptr); - if (family_count == 0) return; - std::vector families(family_count); - vkGetPhysicalDeviceQueueFamilyProperties( - physical, &family_count, families.data()); - const std::uint32_t family = dvz_queue_family(queue); - if (family >= family_count || - families[family].timestampValidBits == 0) - return; - VkPhysicalDeviceProperties properties{}; - vkGetPhysicalDeviceProperties(physical, &properties); - VkQueryPoolCreateInfo configuration{ - VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO - }; - configuration.queryType = VK_QUERY_TYPE_TIMESTAMP; - configuration.queryCount = 4; - if (vkCreateQueryPool(logical, &configuration, nullptr, - &query_pool_) != VK_SUCCESS) { - query_pool_ = VK_NULL_HANDLE; - return; - } - timestamp_period_ns_ = properties.limits.timestampPeriod; - timestamp_valid_bits_ = families[family].timestampValidBits; - timestamps_supported_ = timestamp_period_ns_ > 0.0F; - } - [[nodiscard]] std::optional collect_gpu_timing() const noexcept { - if (!observing_ || !timestamps_supported_ || - query_pool_ == VK_NULL_HANDLE) - return std::nullopt; - const VkDevice device = - dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)); - std::array timestamps{}; - if (vkGetQueryPoolResults( - device, query_pool_, 0, - static_cast(timestamps.size()), - sizeof(timestamps), timestamps.data(), sizeof(std::uint64_t), - VK_QUERY_RESULT_64_BIT) != VK_SUCCESS) - return std::nullopt; - const auto elapsed = [this](std::uint64_t begin, - std::uint64_t end) noexcept { - std::uint64_t ticks = end - begin; - if (timestamp_valid_bits_ < 64) { - const std::uint64_t mask = - (std::uint64_t{1} << timestamp_valid_bits_) - 1; - ticks &= mask; - } - const long double nanoseconds = - static_cast(ticks) * timestamp_period_ns_; - return nanoseconds >= - static_cast( - std::numeric_limits::max()) - ? std::numeric_limits::max() - : static_cast(nanoseconds); - }; - auto timing = Datoviz_Gpu_Timing{ - elapsed(timestamps[0], timestamps[1]), - elapsed(timestamps[1], timestamps[2]), - elapsed(timestamps[2], timestamps[3]), - elapsed(timestamps[0], timestamps[3]) - }; - if (!readback_requested_) timing.copy_ns = 0; - return timing; - } - void destroy() { - if (recording_ || prepared_ || in_flight_) - throw std::logic_error( - "Datoviz frame target is busy during destruction"); - if (query_pool_ != VK_NULL_HANDLE && gpu_context_ != nullptr) { - vkDestroyQueryPool( - dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)), - query_pool_, nullptr); - query_pool_ = VK_NULL_HANDLE; - } - if (readback_ != nullptr) { - dvz_buffer_destroy(readback_); - dvz_buffer_free(readback_); - readback_ = nullptr; - } - if (fence_ != nullptr) { - dvz_fence_destroy(fence_); - dvz_fence_free(fence_); - fence_ = nullptr; - } - if (submit_ != nullptr) { - dvz_submit_free(submit_); - submit_ = nullptr; - } - if (commands_ != nullptr) { - dvz_commands_destroy(commands_); - dvz_commands_free(commands_); - commands_ = nullptr; - } - if (view_ != nullptr) { - dvz_image_views_destroy(view_); - dvz_image_views_free(view_); - view_ = nullptr; - } - if (image_ != nullptr) { - dvz_images_destroy(image_); - dvz_images_free(image_); - image_ = nullptr; - } - } - not_null gpu_context_; - Extent extent_{}; - std::uint64_t generation_{}; - DvzSize byte_size_{}; - owner image_{}; - owner view_{}; - owner commands_{}; - owner fence_{}; - owner submit_{}; - owner readback_{}; - VkQueryPool query_pool_{VK_NULL_HANDLE}; - VkImageLayout completed_layout_{VK_IMAGE_LAYOUT_UNDEFINED}; - float timestamp_period_ns_{}; - std::uint32_t timestamp_valid_bits_{}; - bool recording_{}; - bool prepared_{}; - bool in_flight_{}; - bool observing_{}; - bool readback_requested_{}; - bool timestamps_initialized_{}; - bool timestamps_supported_{}; - std::uint64_t recording_command_revision_{}; - std::uint64_t recorded_command_revision_{}; - std::uint64_t recording_controller_revision_{}; - std::uint64_t recorded_controller_revision_{}; - bool recorded_{}; - bool recorded_readback_{}; - bool quarantined_{}; -}; -struct Scene_Datoviz_State::Frame_Targets { - static constexpr std::size_t count = 3; - std::array, count> values{}; /* 固定三槽,槽地址在后端生命周期内稳定。 */ -}; -Scene_Datoviz_State::Scene_Datoviz_State() : - targets_(std::make_unique()) {} -Scene_Datoviz_State::~Scene_Datoviz_State() { - try { - destroy(); - } - catch (...) { - abandon_resources(); - } -} -void Scene_Datoviz_State::initialize( - std::uint32_t gpu_index, bool validation_enabled, - const std::vector& visuals, - const Scene_3D_Parameters& initial_scene) { - try { - render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled); - const auto gpu_context = render_context_->gpu_context(); - DvzDevice* device = dvz_gpu_ctx_device(gpu_context.get()); - DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context.get(), DVZ_QUEUE_MAIN); - VkDevice vk_device = dvz_device_handle(device); - VkCommandPoolCreateInfo command_pool_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = dvz_queue_family(queue)}; - VkCommandPool command_pool{VK_NULL_HANDLE}; - if (vkCreateCommandPool(vk_device, &command_pool_info, nullptr, - &command_pool) != VK_SUCCESS) - throw std::runtime_error("failed to create Scene command pool"); - command_pool_ = reinterpret_cast(command_pool); - std::array pool_sizes{{ - {VK_DESCRIPTOR_TYPE_SAMPLER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, DVZ_MAX_DESCRIPTOR_SETS}, - {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, DVZ_MAX_DESCRIPTOR_SETS}}}; - VkDescriptorPoolCreateInfo descriptor_pool_info{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - .maxSets = DVZ_MAX_DESCRIPTOR_SETS * - static_cast(pool_sizes.size()), - .poolSizeCount = static_cast(pool_sizes.size()), - .pPoolSizes = pool_sizes.data()}; - VkDescriptorPool descriptor_pool{VK_NULL_HANDLE}; - if (vkCreateDescriptorPool(vk_device, &descriptor_pool_info, nullptr, - &descriptor_pool) != VK_SUCCESS) - throw std::runtime_error("failed to create Scene descriptor pool"); - descriptor_pool_ = reinterpret_cast(descriptor_pool); - DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config( - device, - dvz_gpu_ctx_alloc(gpu_context.get())); - dvz_drp2_runtime_vklite_pools( - &runtime_configuration, - command_pool_, descriptor_pool_); - for (auto& slot : runtime_slots_) { - slot.runtime = dvz_drp2_runtime_vklite(&runtime_configuration); - slot.emitter = dvz_frame_plan_emitter(); - if (slot.runtime == nullptr || slot.emitter == nullptr) throw std::runtime_error("failed to create a Datoviz frame-target runtime"); - } - create_scene(visuals, initial_scene); - } - catch (...) { - const auto failure = std::current_exception(); - try { - destroy(); - } - catch (...) {} - raise_context("creating Datoviz backend", failure); - } -} -void Scene_Datoviz_State::create_scene( - const std::vector& 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(); - if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK) throw std::runtime_error("failed to configure Datoviz scene capabilities"); - auto* figure = dvz_figure(scene_, initial_scene.viewport.width, - initial_scene.viewport.height, 0); - if (figure == nullptr) throw std::runtime_error("failed to create Datoviz figure"); - auto* panel = dvz_panel_full(figure); - if (panel == nullptr) throw std::runtime_error("failed to create Datoviz panel"); - figure_ = not_null{figure}; - panel_ = not_null{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{}; - DvzSampledField* sampled_field{}; - DvzFont* font{}; - switch (family) { - case Visual_Family::point: visual = dvz_point(scene_, 0); - break; - case Visual_Family::splat: visual = dvz_splat(scene_, 0); - break; - case Visual_Family::pixel: visual = dvz_pixel(scene_, 0); - break; - case Visual_Family::marker: visual = dvz_marker(scene_, 0); - break; - case Visual_Family::sphere: visual = dvz_sphere(scene_, 0); - break; - case Visual_Family::segment: visual = dvz_segment(scene_, 0); - break; - case Visual_Family::vector: visual = dvz_vector(scene_, 0); - break; - case Visual_Family::primitive: visual = dvz_primitive( - scene_, DVZ_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0); - break; - case Visual_Family::mesh: visual = dvz_mesh(scene_, 0); - break; - case Visual_Family::path: visual = dvz_path(scene_, 0); - break; - case Visual_Family::image: visual = dvz_image(scene_, 0); - break; - case Visual_Family::labels: visual = dvz_labels(scene_, 0); - break; - case Visual_Family::glyph: - case Visual_Family::text: visual = dvz_glyph(scene_, 0); - break; - case Visual_Family::volume: visual = dvz_volume(scene_, 0); - break; - } - if (visual != nullptr && - dvz_visual_set_alpha_mode(visual, DVZ_ALPHA_BLENDED) != DVZ_OK) - throw std::runtime_error("failed to configure Datoviz visual alpha mode"); - if (visual != nullptr && family == Visual_Family::glyph) font = configure_glyph_text(scene_, visual, "GLYPH"); - if (visual != nullptr && family == Visual_Family::text) font = configure_glyph_text(scene_, visual, "TEXT"); - if (visual != nullptr && family == Visual_Family::image) { - constexpr std::uint32_t width = 32; - constexpr std::uint32_t height = 32; - std::vector> pixels(width * height); - for (std::uint32_t y = 0; y < height; ++y) { - for (std::uint32_t x = 0; x < width; ++x) { - const bool stroke = (x / 8 + y / 8) % 2 == 0; - pixels[y * width + x] = stroke - ? std::array{40, 235, 205, 255} - : std::array{18, 42, 72, 255}; - } - } - auto descriptor = dvz_sampled_field_desc(); - descriptor.dim = DVZ_FIELD_DIM_2D; - descriptor.format = DVZ_FIELD_FORMAT_RGBA8_UNORM; - descriptor.semantic = DVZ_FIELD_SEMANTIC_COLOR; - descriptor.color_role = DVZ_COLOR_ROLE_SRGB_COLOR; - descriptor.width = width; - descriptor.height = height; - descriptor.depth = 1; - sampled_field = dvz_sampled_field(scene_, &descriptor); - auto view = dvz_field_data_view(); - view.data = pixels.data(); - view.bytes_per_row = width * sizeof(pixels.front()); - view.rows_per_image = height; - if (sampled_field == nullptr || - dvz_sampled_field_set_data(sampled_field, &view) != DVZ_OK || - dvz_visual_set_field(visual, "field", sampled_field) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz 2D sampled field"); - } - if (visual != nullptr && family == Visual_Family::labels) { - constexpr std::uint32_t width = 8; - constexpr std::uint32_t height = 8; - std::array labels{}; - for (std::uint32_t y = 0; y < height; ++y) for (std::uint32_t x = 0; x < width; ++x) labels[y * width + x] = static_cast((x / 4) + 2 * (y / 4)); - auto descriptor = dvz_sampled_field_desc(); - descriptor.dim = DVZ_FIELD_DIM_2D; - descriptor.format = DVZ_FIELD_FORMAT_R32_SINT; - descriptor.semantic = DVZ_FIELD_SEMANTIC_LABEL; - descriptor.color_role = DVZ_COLOR_ROLE_DATA; - descriptor.width = width; - descriptor.height = height; - descriptor.depth = 1; - sampled_field = dvz_sampled_field(scene_, &descriptor); - auto view = dvz_field_data_view(); - view.data = labels.data(); - view.bytes_per_row = width * sizeof(labels.front()); - view.rows_per_image = height; - auto scale_descriptor = dvz_scale_desc(); - scale_descriptor.kind = DVZ_SCALE_CATEGORICAL; - auto* scale = dvz_scale(scene_, &scale_descriptor); - const std::array categories{ - { - {.category_id = 0, .order = 0, .label = "north west", .color = {235, 70, 70, 255}}, - {.category_id = 1, .order = 1, .label = "north east", .color = {70, 220, 100, 255}}, - {.category_id = 2, .order = 2, .label = "south west", .color = {70, 120, 245, 255}}, - {.category_id = 3, .order = 3, .label = "south east", .color = {245, 210, 55, 255}}, - } - }; - if (sampled_field == nullptr || scale == nullptr || - dvz_sampled_field_set_data(sampled_field, &view) != DVZ_OK || - dvz_visual_set_field(visual, "field", sampled_field) != DVZ_OK || - dvz_scale_set_categories(scale, categories.data(), - static_cast(categories.size())) != 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) { - constexpr std::uint32_t side = 16; - std::vector voxels(side * side * side); - for (std::uint32_t z = 0; z < side; ++z) { - for (std::uint32_t y = 0; y < side; ++y) { - for (std::uint32_t x = 0; x < side; ++x) { - const float dx = static_cast(x) - 7.5F; - const float dy = static_cast(y) - 7.5F; - const float dz = static_cast(z) - 7.5F; - const float distance = std::sqrt(dx * dx + dy * dy + dz * dz); - voxels[(z * side + y) * side + x] = - distance < 6.5F ? 1.0F - distance / 6.5F : 0.0F; - } - } - } - auto descriptor = dvz_sampled_field_desc(); - descriptor.dim = DVZ_FIELD_DIM_3D; - descriptor.format = DVZ_FIELD_FORMAT_R32_FLOAT; - descriptor.semantic = DVZ_FIELD_SEMANTIC_SCALAR; - descriptor.color_role = DVZ_COLOR_ROLE_DATA; - descriptor.width = side; - descriptor.height = side; - descriptor.depth = side; - sampled_field = dvz_sampled_field(scene_, &descriptor); - auto view = dvz_field_data_view(); - view.data = voxels.data(); - view.bytes_per_row = side * sizeof(voxels.front()); - view.rows_per_image = side; - if (sampled_field == nullptr || - dvz_sampled_field_set_data(sampled_field, &view) != DVZ_OK || - dvz_visual_set_field(visual, "field", sampled_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 (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) - throw std::runtime_error("failed to enable Datoviz item queries"); - wants_item_interaction = true; - } - DvzText* coordinate_text{}; - if (family == Visual_Family::marker) { - coordinate_text = dvz_text(panel, 0); - DvzTextPlacement placement = dvz_text_placement(); - placement.mode = DVZ_TEXT_PLACEMENT_DATA; - placement.anchor = DVZ_SCENE_ANCHOR_DATA; - placement.depth_test = false; - DvzTextStyle style = dvz_text_style(); - style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS; - style.size_px = 12.0F; - if (coordinate_text == nullptr || - dvz_text_set_placement(coordinate_text, &placement) != DVZ_OK || - dvz_text_set_style(coordinate_text, &style) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz marker coordinate labels"); - } - DvzSampledField* field_borrow{}; - if (sampled_field != nullptr) field_borrow = not_null{sampled_field}; - DvzFont* font_borrow{}; - if (font != nullptr) font_borrow = not_null{font}; - DvzText* coordinate_text_borrow{}; - if (coordinate_text != nullptr) coordinate_text_borrow = not_null{coordinate_text}; - visuals_.push_back( - { - registration.identity, family, not_null{visual}, field_borrow, - font_borrow, coordinate_text_borrow, - family == Visual_Family::volume - ? std::array{16, 16, 16} - : family == Visual_Family::labels - ? std::array{8, 8, 1} - : family == Visual_Family::image - ? std::array{32, 32, 1} - : std::array{}, - 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"); - } - auto* axes_visual = dvz_segment(scene_, 0); - auto* axes_text = dvz_text(panel, 0); - if (axes_visual == nullptr || axes_text == nullptr || - dvz_segment_set_caps(axes_visual, DVZ_SEGMENT_CAP_BUTT, - DVZ_SEGMENT_CAP_BUTT) != DVZ_OK || - dvz_visual_set_alpha_mode(axes_visual, DVZ_ALPHA_OPAQUE) != DVZ_OK || - dvz_panel_add_visual(panel, axes_visual, nullptr) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz 3D axes visuals"); - axes_visual_ = not_null{axes_visual}; - axes_text_ = not_null{axes_text}; - DvzTextPlacement axes_placement = dvz_text_placement(); - axes_placement.mode = DVZ_TEXT_PLACEMENT_DATA; - axes_placement.anchor = DVZ_SCENE_ANCHOR_DATA; - axes_placement.depth_test = false; - DvzTextStyle axes_style = dvz_text_style(); - axes_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS; - axes_style.size_px = 12.0F; - if (dvz_text_set_placement(axes_text, &axes_placement) != DVZ_OK || - dvz_text_set_style(axes_text, &axes_style) != DVZ_OK) - throw std::runtime_error("failed to configure Datoviz 3D axes text"); - apply_axes(initial_scene); - input_router_ = dvz_input_router(); - gesture_handler_ = input_router_ != nullptr - ? dvz_pointer_gesture_handler(input_router_) - : nullptr; - if (input_router_ == nullptr || gesture_handler_ == nullptr || - dvz_panel_connect_input(panel, input_router_) != DVZ_OK) - throw std::runtime_error("failed to connect Datoviz point input"); - apply_camera(initial_scene.camera); - DvzInputResizeEvent resize{ - initial_scene.viewport.width, initial_scene.viewport.height, - initial_scene.viewport.width, initial_scene.viewport.height, 1.0F, 1.0F - }; - dvz_input_emit_resize(input_router_, &resize); -} -void Scene_Datoviz_State::apply_axes( - const Scene_3D_Parameters& scene) { - const std::array descriptors{scene.x_axis, scene.y_axis, scene.z_axis}; - if (applied_axes_ && *applied_axes_ == descriptors) return; - using Position = std::array; - std::vector starts; - std::vector ends; - std::vector colors; - std::vector widths; - const DvzColor axis_color{190, 207, 226, 255}; - const DvzColor grid_color{58, 70, 86, 255}; - const DvzColor tick_color{145, 164, 188, 255}; - const auto segment = [&](Position start, Position end, DvzColor color, - float width) { - starts.push_back(start); - ends.push_back(end); - colors.push_back(color); - widths.push_back(width); - }; - std::vector strings; - std::vector text_items; - const auto text = [&](std::string value, Position position, - std::array offset, - std::array anchor, float size, - DvzColor color) { - strings.push_back(std::move(value)); - DvzTextItem item{}; - item.struct_size = sizeof(DvzTextItem); - item.position[0] = position[0]; - item.position[1] = position[1]; - item.position[2] = position[2]; - item.offset[0] = offset[0]; - item.offset[1] = offset[1]; - item.anchor[0] = anchor[0]; - item.anchor[1] = anchor[1]; - item.size_px = size; - item.color = color; - text_items.push_back(item); - }; - const auto append_axis = [&](const plot::Axis_Descriptor& axis, - std::size_t dimension) { - if (!axis.visible) return; - const auto ticks = plot::axis_ticks(axis); - if (dimension == 0) segment({-1, -1, -1}, {1, -1, -1}, axis_color, 2.2F); - else if (dimension == 1) segment({-1, -1, -1}, {-1, 1, -1}, axis_color, 2.2F); - else segment({-1, -1, -1}, {-1, -1, 1}, axis_color, 2.2F); - for (const auto& tick : ticks) { - const auto position = axis_position(axis, tick.coordinate); - if (dimension == 0) { - segment({position, -1, -1}, {position, -1.055F, -1}, - tick_color, 1.4F); - if (axis.grid_visible) - segment({position, -1, -1}, {position, 1, -1}, - grid_color, 1.0F); - if (axis.labels_visible) - text(tick.label, {position, -1, -1}, {0, 12}, {.5F, 0}, - 11, tick_color); - } - else if (dimension == 1) { - segment({-1, position, -1}, {-1.055F, position, -1}, - tick_color, 1.4F); - if (axis.grid_visible) - segment({-1, position, -1}, {1, position, -1}, - grid_color, 1.0F); - if (axis.labels_visible) - text(tick.label, {-1, position, -1}, {-10, 0}, {1, .5F}, - 11, tick_color); - } - else { - segment({-1, -1, position}, {-1.055F, -1, position}, - tick_color, 1.4F); - if (axis.grid_visible) { - segment({-1, -1, position}, {1, -1, position}, - grid_color, 1.0F); - segment({-1, -1, position}, {-1, 1, position}, - grid_color, 1.0F); - } - if (axis.labels_visible) - text(tick.label, {-1, -1, position}, {-10, 0}, {1, .5F}, - 11, tick_color); - } - } - const auto title = axis_title(axis); - if (title.empty()) return; - if (dimension == 0) text(title, {0, -1, -1}, {0, 34}, {.5F, 0}, 14, axis_color); - else if (dimension == 1) text(title, {-1, 0, -1}, {-78, 0}, {1, .5F}, 14, axis_color); - else text(title, {-1, -1, 0}, {-78, 0}, {1, .5F}, 14, axis_color); - }; - append_axis(descriptors[0], 0); - append_axis(descriptors[1], 1); - append_axis(descriptors[2], 2); - const auto segment_count = static_cast(starts.size()); - const std::array updates{ - { - {"position_start", starts.data(), segment_count}, - {"position_end", ends.data(), segment_count}, - {"color", colors.data(), segment_count}, - {"stroke_width_px", widths.data(), segment_count} - } - }; - if (dvz_visual_set_data_many(axes_visual_, updates.data(), - static_cast(updates.size())) != DVZ_OK || - dvz_visual_set_visible(axes_visual_, !starts.empty()) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz 3D axes geometry"); - for (std::size_t index = 0; index < text_items.size(); ++index) text_items[index].string = strings[index].c_str(); - if (dvz_text_set_items( - axes_text_, text_items.empty() ? nullptr : text_items.data(), - static_cast(text_items.size())) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz 3D axes labels"); - applied_axes_ = descriptors; -} -void Scene_Datoviz_State::apply_camera( - const Camera_Descriptor& source) { - if (applied_camera_ && *applied_camera_ == source) return; - if (camera_controller_ != nullptr) { - dvz_controller_destroy(camera_controller_); - camera_controller_ = nullptr; - } - DvzCameraDesc camera = dvz_camera_desc(); - const auto assign = [](vec3 target, Spatial_Point value) { - target[0] = static_cast(value.x); - target[1] = static_cast(value.y); - target[2] = static_cast(value.z); - }; - assign(camera.view.eye, source.initial_view.eye); - assign(camera.view.target, source.initial_view.target); - assign(camera.view.up, source.initial_view.up); - camera.projection.type = source.projection == Camera_Projection::orthographic - ? DVZ_CAMERA_ORTHOGRAPHIC - : DVZ_CAMERA_PERSPECTIVE; - camera.projection.fov_y = static_cast( - source.vertical_field_of_view_degrees * std::numbers::pi / 180.0); - camera.projection.near_clip = static_cast(source.near_plane); - camera.projection.far_clip = static_cast(source.far_plane); - const auto dx = source.initial_view.eye.x - source.initial_view.target.x; - const auto dy = source.initial_view.eye.y - source.initial_view.target.y; - const auto dz = source.initial_view.eye.z - source.initial_view.target.z; - const auto distance = std::sqrt(dx * dx + dy * dy + dz * dz); - camera.projection.ortho_height = static_cast( - 2.0 * distance * - 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"); - DvzDimMask dimensions = DVZ_DIM_MASK_XYZ; - switch (source.controller) { - case Camera_Controller::turntable: { - DvzTurntableDesc descriptor = dvz_turntable_desc(); - descriptor.initial_view = camera.view; - descriptor.yaw_speed = static_cast(source.turntable_control.yaw_speed); - descriptor.pitch_speed = static_cast(source.turntable_control.pitch_speed); - descriptor.zoom_speed = static_cast(source.turntable_control.zoom_speed); - descriptor.pan_speed = static_cast(source.turntable_control.pan_speed); - descriptor.min_pitch = static_cast(source.turntable_control.minimum_pitch); - descriptor.max_pitch = static_cast(source.turntable_control.maximum_pitch); - descriptor.min_distance = static_cast(source.turntable_control.minimum_distance); - descriptor.max_distance = static_cast(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 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(source.arcball_control.constraint_axis.x), - static_cast(source.arcball_control.constraint_axis.y), - static_cast(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 Camera_Controller::fly: { - DvzFlyDesc descriptor = dvz_fly_desc(); - descriptor.initial_view = camera.view; - descriptor.mode = source.fly_control.mode == Camera_Fly_Mode::plane - ? DVZ_FLY_MODE_PLANE - : DVZ_FLY_MODE_FREE; - descriptor.speed = static_cast(source.fly_control.movement_speed); - descriptor.fast_multiplier = static_cast(source.fly_control.fast_multiplier); - descriptor.slow_multiplier = static_cast(source.fly_control.slow_multiplier); - descriptor.look_speed = static_cast(source.fly_control.look_speed); - descriptor.wheel_speed = static_cast(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 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; -} -bool Scene_Datoviz_State::matches_command_structure( - const Scene_3D_Parameters& scene, - const Prepared_Visual_Batch& prepared) const { - if (figure_extent_ != scene.viewport || - !applied_camera_ || *applied_camera_ != scene.camera || - !applied_axes_ || *applied_axes_ != - std::array{scene.x_axis, scene.y_axis, scene.z_axis} || - prepared.size() != visuals_.size()) - return false; - for (const auto& source : prepared) { - const auto target = std::ranges::find_if( - visuals_, [&](const Visual_Instance& value) { - return value.identity == source.identity; - }); - if (target == visuals_.end()) return false; - if (!target->applied || - !same_visual_structure(*target->applied, source.visual)) - return false; - } - return true; -} -bool Scene_Datoviz_State::target_runtime_resources_current( - const Prepared_Visual_Batch& prepared, - std::uint8_t target_index) const { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); - for (const auto& source : prepared) { - const auto target = std::ranges::find_if( - visuals_, [&](const Visual_Instance& value) { - return value.identity == source.identity; - }); - if (target == visuals_.end()) - throw std::logic_error( - "Datoviz runtime update contains an unknown Visual identity"); - if (!uses_external_attributes(target->family) && - target->uploaded_data_revisions[target_index] != - source.visual.data_revision) - return false; - } - return true; -} -std::vector Scene_Datoviz_State::stale_runtime_visuals( - const Prepared_Visual_Batch& prepared, - std::uint8_t target_index) const { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); - std::vector result; - result.reserve(prepared.size()); - for (const auto& source : prepared) { - const auto target = std::ranges::find_if( - visuals_, [&](const Visual_Instance& value) { - return value.identity == source.identity; - }); - if (target == visuals_.end()) - throw std::logic_error( - "Datoviz runtime update contains an unknown Visual identity"); - if (!uses_external_attributes(target->family) && - target->uploaded_data_revisions[target_index] != - source.visual.data_revision) - result.push_back(target->visual); - } - return result; -} -void Scene_Datoviz_State::mark_target_runtime_resources_uploaded( - std::uint8_t target_index) { - if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); - for (auto& visual : visuals_) - if (!uses_external_attributes(visual.family) && visual.applied) - visual.uploaded_data_revisions[target_index] = - visual.applied->data_revision; -} -std::uint64_t Scene_Datoviz_State::apply( - const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared, - std::uint8_t target_index, bool bind_target) { - if (figure_extent_ != scene.viewport) { - if (dvz_figure_resize(figure_, scene.viewport.width, - scene.viewport.height) != DVZ_OK) - throw std::runtime_error("failed to resize Datoviz point figure"); - DvzInputResizeEvent resize{ - scene.viewport.width, scene.viewport.height, - scene.viewport.width, scene.viewport.height, 1.0F, 1.0F - }; - dvz_input_emit_resize(input_router_, &resize); - figure_extent_ = scene.viewport; - } - apply_camera(scene.camera); - apply_axes(scene); - if (prepared.size() != visuals_.size()) throw std::logic_error("3D frame did not publish every registered Visual"); - std::uint64_t uploaded_bytes{}; - 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"); - uploaded_bytes += apply_visual( - target, found->visual, target_index, bind_target); - } - return uploaded_bytes; -} -void Scene_Datoviz_State::ensure_external_attributes( - Visual_Instance& target, const Prepared_Visual& visual) { - const auto item_count = prepared_item_count(visual); - if (item_count == 0) return; - if (item_count > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute item count exceeds uint32"); - std::size_t expected_count{}; - switch (target.family) { - case Visual_Family::point: expected_count = 3; - break; - case Visual_Family::splat: expected_count = 4; - break; - case Visual_Family::pixel: expected_count = 3; - break; - case Visual_Family::marker: expected_count = 5; - break; - case Visual_Family::sphere: expected_count = 3; - break; - case Visual_Family::primitive: expected_count = 3; - break; - case Visual_Family::mesh: expected_count = 4; - break; - case Visual_Family::path: expected_count = 3; - break; - case Visual_Family::image: - case Visual_Family::labels: expected_count = 2; - break; - default: throw std::logic_error( - "external attributes requested for an unsupported Visual family"); - } - if (target.attributes.size() == expected_count && - std::ranges::all_of(target.attributes, [&](const External_Attribute& value) { - return value.capacity >= item_count; - })) - return; - std::uint64_t capacity = 1; - while (capacity < item_count) capacity *= 2; - if (capacity > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute capacity exceeds uint32"); - std::vector attributes; - attributes.reserve(expected_count); - const auto create = [&](const char* name, std::uint32_t stride) { - const std::uint64_t byte_size = capacity * stride * Frame_Targets::count; - if (byte_size == 0 || byte_size > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute buffer is too large"); - auto descriptor = dvz_scene_buffer_desc(); - descriptor.usage = DVZ_SCENE_BUFFER_USAGE_VERTEX; - descriptor.stride = stride; - descriptor.byte_size = byte_size; - auto* scene_buffer = dvz_scene_buffer(scene_, &descriptor); - if (scene_buffer == nullptr) throw std::runtime_error("failed to create Datoviz external scene buffer"); - auto* gpu_buffer = allocate_wrapper( - dvz_buffer_create_wrapper, - "failed to allocate Datoviz external GPU buffer wrapper"); - const auto gpu_context = render_context_->gpu_context(); - dvz_buffer(dvz_gpu_ctx_device(gpu_context.get()), - dvz_gpu_ctx_alloc(gpu_context.get()), - gpu_buffer); - dvz_buffer_size(gpu_buffer, static_cast(byte_size)); - dvz_buffer_usage(gpu_buffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); - dvz_buffer_flags(gpu_buffer, DVZ_ALLOC_HOST_ACCESS_SEQUENTIAL_WRITE); - if (dvz_buffer_create(gpu_buffer) != DVZ_OK) { - dvz_buffer_free(gpu_buffer); - throw std::runtime_error("failed to create Datoviz external GPU buffer"); - } - external_buffers_.push_back(gpu_buffer); - attributes.push_back({ - name, stride, static_cast(capacity), - not_null{scene_buffer}, gpu_buffer, false - }); - }; - switch (target.family) { - case Visual_Family::point: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("diameter_px", sizeof(float)); - break; - case Visual_Family::splat: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("sigma", sizeof(std::array)); - create("angle", sizeof(float)); - break; - case Visual_Family::pixel: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("pixel_size_px", sizeof(float)); - break; - case Visual_Family::marker: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("diameter_px", sizeof(float)); - create("angle", sizeof(float)); - create("shape", sizeof(std::uint32_t)); - break; - case Visual_Family::sphere: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("radius", sizeof(float)); - break; - case Visual_Family::primitive: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("normal", sizeof(Prepared_Position)); - break; - case Visual_Family::mesh: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("normal", sizeof(Prepared_Position)); - create("texcoords", sizeof(std::array)); - break; - case Visual_Family::path: create("position", sizeof(Prepared_Position)); - create("color", sizeof(Prepared_Color)); - create("stroke_width_px", sizeof(float)); - break; - case Visual_Family::image: - case Visual_Family::labels: create("position", sizeof(Prepared_Position)); - create("extent", sizeof(std::array)); - break; - default: throw std::logic_error( - "external attribute layout requested for an unsupported Visual family"); - } - target.attributes = std::move(attributes); - target.uploaded_data_revisions = {}; -} -std::uint64_t Scene_Datoviz_State::upload_external_attributes( - Visual_Instance& target, const Prepared_Visual& visual, - std::uint8_t target_index) { - const auto item_count = prepared_item_count(visual); - if (item_count == 0) return 0; - std::uint64_t uploaded_bytes{}; - const auto upload = [&](External_Attribute& attribute, const void* source, - std::size_t count) { - if (source == nullptr || count != item_count) throw std::logic_error("Datoviz external attribute fields have different item counts"); - const auto byte_count = static_cast(count * attribute.stride); - const auto byte_offset = static_cast(target_index) * - attribute.capacity * attribute.stride; - dvz_buffer_upload(attribute.gpu_buffer, byte_offset, byte_count, source); - uploaded_bytes += byte_count; - }; - auto attribute = target.attributes.begin(); - switch (target.family) { - case Visual_Family::point: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.diameters.data(), data.diameters.size()); - break; - } - case Visual_Family::splat: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.sigmas.data(), data.sigmas.size()); - upload(*attribute++, data.angles.data(), data.angles.size()); - break; - } - case Visual_Family::pixel: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.sizes.data(), data.sizes.size()); - break; - } - case Visual_Family::marker: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.diameters.data(), data.diameters.size()); - upload(*attribute++, data.angles.data(), data.angles.size()); - upload(*attribute++, data.shapes.data(), data.shapes.size()); - break; - } - case Visual_Family::sphere: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.centers.data(), data.centers.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.radii.data(), data.radii.size()); - break; - } - case Visual_Family::primitive: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.normals.data(), data.normals.size()); - break; - } - case Visual_Family::mesh: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.normals.data(), data.normals.size()); - upload(*attribute++, data.texture_coordinates.data(), data.texture_coordinates.size()); - break; - } - case Visual_Family::path: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.colors.data(), data.colors.size()); - upload(*attribute++, data.widths.data(), data.widths.size()); - break; - } - case Visual_Family::image: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.extents.data(), data.extents.size()); - break; - } - case Visual_Family::labels: { - const auto& data = prepared_data(visual); - upload(*attribute++, data.positions.data(), data.positions.size()); - upload(*attribute++, data.extents.data(), data.extents.size()); - break; - } - default: throw std::logic_error( - "external attribute upload requested for an unsupported Visual family"); - } - return uploaded_bytes; -} -void Scene_Datoviz_State::bind_external_attributes( - Visual_Instance& target, std::uint8_t target_index, - std::uint32_t item_count) { - for (auto& attribute : target.attributes) { - const std::uint64_t byte_offset = static_cast(target_index) * - attribute.capacity * attribute.stride; - if (dvz_visual_set_attr_buffer(target.visual.get(), attribute.name.c_str(), - attribute.scene_buffer.get(), byte_offset, - item_count) != DVZ_OK) - throw std::runtime_error("failed to bind Datoviz external visual attribute"); - } -} -void Scene_Datoviz_State::register_external_attributes( - const DvzDrp2CommandStream* stream, std::uint8_t target_index) { - if (stream == nullptr) return; - if (target_index >= runtime_slots_.size()) throw std::logic_error("Datoviz external attribute target is invalid"); - const auto target_bit = static_cast(1U << target_index); - for (auto& visual : visuals_) { - for (auto& attribute : visual.attributes) { - if ((attribute.registered_targets & target_bit) != 0) continue; - char key[DVZ_SCENE_LABEL_SIZE]{}; - if (!dvz_scene_buffer_resource_key(attribute.scene_buffer.get(), key, - sizeof(key))) - throw std::runtime_error("failed to resolve Datoviz external buffer key"); - const auto id = dvz_drp2_stream_label_id(stream, key); - if (id == 0) throw std::runtime_error("Datoviz frame omitted an external buffer label"); - DvzSceneBufferDesc scene_descriptor{}; - if (!dvz_scene_buffer_info(attribute.scene_buffer.get(), - &scene_descriptor)) - throw std::runtime_error("failed to inspect Datoviz external buffer"); - auto descriptor = dvz_drp2_external_buffer_desc(); - descriptor.buffer = attribute.gpu_buffer; - descriptor.size = scene_descriptor.byte_size; - descriptor.usage = DVZ_DRP2_BUFFER_USAGE_VERTEX; - if (!dvz_drp2_runtime_register_external_buffer( - runtime_slots_[target_index].runtime, id, - &descriptor)) - throw std::runtime_error("failed to register Datoviz external GPU buffer"); - attribute.registered_targets |= target_bit; - } - } -} -std::uint64_t Scene_Datoviz_State::apply_visual( - Visual_Instance& target, const Prepared_Visual& point, - std::uint8_t target_index, bool bind_target) { - if (!has_payload(point)) throw std::logic_error("3D prepared visual has no immutable payload"); - if (!uses_external_attributes(target.family) && - point.revision == target.applied_revision) - return 0; - const auto item_count = prepared_item_count(point); - auto* visual = target.visual.get(); - const bool structure_changed = !target.applied || - !same_visual_structure(*target.applied, point); - const bool coordinate_text_changed = target.coordinate_text != nullptr && - (!target.applied || has_coordinate_labels(*target.applied) || - has_coordinate_labels(point)); - if (coordinate_text_changed) { - const auto& data = prepared_data(point); - std::vector strings; - std::vector items; - if (point.visible) { - strings.reserve(data.positions.size()); - items.reserve(data.positions.size()); - for (std::size_t index = 0; index < data.positions.size(); ++index) { - if (index >= data.coordinate_label_visibility.size() || - !data.coordinate_label_visibility[index]) - continue; - const auto& source = data.positions[index]; - const auto& matrix = point.transform.values; - const float x = matrix[0] * source[0] + matrix[1] * source[1] + matrix[2] * source[2] + matrix[3]; - const float y = matrix[4] * source[0] + matrix[5] * source[1] + matrix[6] * source[2] + matrix[7]; - const float z = matrix[8] * source[0] + matrix[9] * source[1] + matrix[10] * source[2] + matrix[11]; - std::ostringstream stream; - stream << std::fixed << std::setprecision(3) - << "X " << x << " Y " << y << " Z " << z; - strings.push_back(std::move(stream).str()); - DvzTextItem item{}; - item.struct_size = sizeof(DvzTextItem); - item.position[0] = x; - item.position[1] = y; - item.position[2] = z; - item.offset[0] = 10.0F; - item.offset[1] = -10.0F; - item.anchor[0] = 0.0F; - item.anchor[1] = 1.0F; - item.size_px = 12.0F; - item.color = {235, 244, 255, 255}; - items.push_back(item); - } - } - for (std::size_t index = 0; index < items.size(); ++index) items[index].string = strings[index].c_str(); - if (dvz_text_set_items(target.coordinate_text, - items.empty() ? nullptr : items.data(), - static_cast(items.size())) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz marker coordinate labels"); - } - if (structure_changed) { - mat4 transform{}; - for (std::size_t row = 0; row < 4; ++row) for (std::size_t column = 0; column < 4; ++column) transform[row][column] = point.transform.values[row * 4 + column]; - if (target.family == Visual_Family::point) { - const auto& data = prepared_data(point); - DvzPointStyleDesc style = dvz_point_style_desc(); - style.edge_color.r = data.style.edge_color.red; - style.edge_color.g = data.style.edge_color.green; - style.edge_color.b = data.style.edge_color.blue; - style.edge_color.a = data.style.edge_color.alpha; - style.stroke_width_px = data.style.stroke_width_px; - style.aspect = aspect(data.style.aspect); - 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 || - dvz_visual_set_visible( - visual, point.visible && item_count != 0) != DVZ_OK) - throw std::runtime_error("failed to apply Datoviz visual state"); - } - if (item_count == 0) { - 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; - target.applied = point; - return 0; - } - const auto count = static_cast(item_count); - if (uses_external_attributes(target.family)) { - /* - * 普通顶点族在第一次录制前即绑定三槽外部属性。Segment/Vector 的 - * typed-stroke 元数据、Glyph/Text 的 atlas 几何以及 Image/Labels/Volume - * 的采样场资源继续使用 Datoviz 结构路径; - * 把 dense 数据先写入再改绑会违反 Datoviz 的单一属性所有权契约。 - */ - std::uint64_t uploaded_bytes{}; - ensure_external_attributes(target, point); - if (bind_target) bind_external_attributes(target, target_index, count); - if (target.uploaded_data_revisions[target_index] != - point.data_revision) { - uploaded_bytes = upload_external_attributes( - target, point, target_index); - target.uploaded_data_revisions[target_index] = - point.data_revision; - } - if (structure_changed && - dvz_visual_set_visible(visual, point.visible) != DVZ_OK) - throw std::runtime_error( - "failed to apply Datoviz external visual visibility"); - target.applied_revision = point.revision; - target.applied = point; - return uploaded_bytes; - } - if (target.applied && target.applied->data_revision == point.data_revision) { - target.applied_revision = point.revision; - target.applied = point; - return 0; - } - DvzResult result = DVZ_OK; - switch (target.family) { - case Visual_Family::point: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"diameter_px", data.diameters.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::splat: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"sigma", data.sigmas.data(), count}, - {"angle", data.angles.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 4); - break; - } - case Visual_Family::pixel: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"pixel_size_px", data.sizes.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::marker: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"diameter_px", data.diameters.data(), count}, - {"angle", data.angles.data(), count}, - {"shape", data.shapes.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 5); - break; - } - case Visual_Family::sphere: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.centers.data(), count}, - {"color", data.colors.data(), count}, - {"radius", data.radii.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::segment: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position_start", data.starts.data(), count}, - {"position_end", data.ends.data(), count}, - {"color", data.colors.data(), count}, - {"stroke_width_px", data.widths.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 4); - break; - } - case Visual_Family::vector: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.origins.data(), count}, - {"vector", data.directions.data(), count}, - {"color", data.colors.data(), count}, - {"stroke_width_px", data.widths.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 4); - break; - } - case Visual_Family::primitive: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"normal", data.normals.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::mesh: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"normal", data.normals.data(), count}, - {"texcoords", data.texture_coordinates.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 4); - break; - } - case Visual_Family::path: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"color", data.colors.data(), count}, - {"stroke_width_px", data.widths.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::image: { - const auto& data = prepared_data(point); - if (!target.field) throw std::logic_error("Datoviz image has no sampled field"); - auto view = dvz_field_data_view(); - view.data = data.field_pixels.data(); - view.bytes_per_row = static_cast(data.field_width) * - sizeof(data.field_pixels.front()); - view.rows_per_image = data.field_height; - const std::array extent{data.field_width, data.field_height, 1U}; - result = extent == target.field_extent - ? dvz_sampled_field_set_data(target.field, &view) - : dvz_sampled_field_resize( - target.field, extent[0], extent[1], extent[2], &view); - if (result != DVZ_OK) break; - target.field_extent = extent; - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"extent", data.extents.data(), count}, - {"tex_rect", data.texture_rectangles.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::labels: { - const auto& data = prepared_data(point); - if (!target.field) throw std::logic_error("Datoviz labels visual has no sampled field"); - auto view = dvz_field_data_view(); - view.data = data.field_labels.data(); - view.bytes_per_row = static_cast(data.field_width) * - sizeof(data.field_labels.front()); - view.rows_per_image = data.field_height; - const std::array extent{data.field_width, data.field_height, 1U}; - result = extent == target.field_extent - ? dvz_sampled_field_set_data(target.field, &view) - : dvz_sampled_field_resize( - target.field, extent[0], extent[1], extent[2], &view); - if (result != DVZ_OK) break; - target.field_extent = extent; - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"extent", data.extents.data(), count}, - {"tex_rect", data.texture_rectangles.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), 3); - break; - } - case Visual_Family::glyph: { - const auto& data = prepared_data(point); - const std::array updates{ - { - {"position", data.positions.data(), count}, - {"bounds", data.bounds.data(), count}, - {"texcoords", data.texture_coordinates.data(), count}, - {"color", data.colors.data(), count}, - {"angle", data.angles.data(), count} - } - }; - result = dvz_visual_set_data_many(visual, updates.data(), - static_cast(updates.size())); - break; - } - case Visual_Family::text: { - if (!target.font) throw std::logic_error("Datoviz text visual has no font"); - upload_text(visual, target.font, - prepared_data(point)); - break; - } - case Visual_Family::volume: { - const auto& data = prepared_data(point); - if (data.field_width == 0 || data.field_height == 0 || - data.field_depth == 0) - throw std::invalid_argument( - "3D volume field dimensions must be non-zero"); - const auto expected = static_cast(data.field_width) * - data.field_height * data.field_depth; - if (expected != data.values.size()) - throw std::invalid_argument( - "3D volume voxel count does not match field dimensions"); - if (!target.field) throw std::logic_error("Datoviz volume has no sampled field"); - auto view = dvz_field_data_view(); - view.data = data.values.data(); - view.bytes_per_row = static_cast(data.field_width) * - sizeof(data.values.front()); - view.rows_per_image = data.field_height; - const std::array extent{ - data.field_width, data.field_height, data.field_depth - }; - result = extent == target.field_extent - ? dvz_sampled_field_set_data(target.field, &view) - : dvz_sampled_field_resize( - target.field, extent[0], extent[1], extent[2], &view); - if (result == DVZ_OK) target.field_extent = extent; - break; - } - } - if (result != DVZ_OK || - dvz_visual_set_visible(visual, point.visible) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz visual payload"); - target.applied_revision = point.revision; - target.applied = point; - return 0; -} -void Scene_Datoviz_State::mark_controller_input_applied() noexcept { - input_changed_ = true; - ++controller_revision_; - if (controller_revision_ == 0) controller_revision_ = 1; -} -void Scene_Datoviz_State::dispatch_pointer( - ::aethera::Event_Type event, float x, float y, - ::aethera::Mouse_Button mouse_button, - ::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport, - std::uint64_t occurred_at_ns) { - if (applied_camera_ && applied_camera_->controller == Camera_Controller::turntable) { - if (mouse_button == ::aethera::Mouse_Button::left && - !applied_camera_->turntable_control.rotate_enabled) - return; - if ((mouse_button == ::aethera::Mouse_Button::middle || - mouse_button == ::aethera::Mouse_Button::right) && - !applied_camera_->turntable_control.pan_enabled) - return; - } - const float width = static_cast(viewport.width); - const float height = static_cast(viewport.height); - const DvzPointerEventType type = - event == ::aethera::Event_Type::pointer_move - ? DVZ_POINTER_EVENT_MOVE - : event == ::aethera::Event_Type::pointer_press - ? DVZ_POINTER_EVENT_PRESS - : DVZ_POINTER_EVENT_RELEASE; - dvz_pointer_emit_position(input_router_, type, x, y, width, height, - button(mouse_button), - modifiers(keyboard_modifiers), 1.0F, - occurred_at_ns, nullptr); - mark_controller_input_applied(); -} -void Scene_Datoviz_State::dispatch_wheel( - float x, float y, float delta_x, float delta_y, - ::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport, - std::uint64_t occurred_at_ns) { - if (applied_camera_ && - applied_camera_->controller == Camera_Controller::turntable && - !applied_camera_->turntable_control.zoom_enabled) - return; - dvz_pointer_emit_wheel( - input_router_, x, y, static_cast(viewport.width), - static_cast(viewport.height), delta_x, delta_y, - modifiers(keyboard_modifiers), 1.0F, occurred_at_ns, nullptr); - mark_controller_input_applied(); -} -void Scene_Datoviz_State::dispatch_key( - const ::aethera::Key_Event& event) { - if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) { - 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"); - mark_controller_input_applied(); - return; - } - const DvzKeyboardEventType type = - event.type == ::aethera::Event_Type::key_release - ? DVZ_KEYBOARD_EVENT_RELEASE - : event.auto_repeat - ? DVZ_KEYBOARD_EVENT_REPEAT - : DVZ_KEYBOARD_EVENT_PRESS; - dvz_keyboard_emit(input_router_, type, - key_code(event.key, event.native_key), - modifiers(event.modifiers), nullptr); - mark_controller_input_applied(); -} -DvzSceneFrameArtifact* Scene_Datoviz_State::emit( - const Scene_3D_Parameters& scene, std::uint8_t target_index) { - if (target_index >= runtime_slots_.size() || - runtime_slots_[target_index].emitter == nullptr) - throw std::logic_error("Datoviz frame target emitter is unavailable"); - DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config(); - configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL; - configuration.external_color_target = true; - configuration.color_target_id = color_target_id; - configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM; - configuration.target_width = scene.viewport.width; - configuration.target_height = scene.viewport.height; - configuration.clear_color[0] = scene.clear_color.red; - configuration.clear_color[1] = scene.clear_color.green; - configuration.clear_color[2] = scene.clear_color.blue; - configuration.clear_color[3] = scene.clear_color.alpha; - const auto capabilities = offscreen_capabilities(); - DvzDiagnosticReport report{}; - dvz_diagnostic_report_init(&report); - /* Every frame target owns a DRP2 runtime and its matching retained emitter. - * A stream is incremental relative to exactly one runtime; sharing an emitter - * across the three targets makes target N receive commands that assume target - * N-1's resource state. Replaying retained payloads also compensates for the - * Scene-wide dirty flags committed by another target's preceding emission. */ - auto* artifact = dvz_figure_emit_frame_with_emitter( - figure_, runtime_slots_[target_index].emitter, true, - &capabilities, &report, &configuration); - if (artifact == nullptr) { - std::string message = "failed to emit Datoviz visual frame"; - const auto count = dvz_diagnostic_report_count(&report); - if (count != 0) { - if (const char* diagnostic = dvz_diagnostic_report_get(&report, 0)) message += ": " + std::string(diagnostic); - } - throw std::runtime_error(message); - } - return artifact; -} -std::optional Scene_Datoviz_State::acquire_target(Extent extent) { - for (std::uint8_t index = 0; index < targets_->values.size(); ++index) { - auto& candidate = targets_->values[index]; - if (candidate && candidate->available() && candidate->extent() == extent) return index; - } - for (std::uint8_t index = 0; index < targets_->values.size(); ++index) { - auto& candidate = targets_->values[index]; - if (candidate && !candidate->available()) continue; - candidate = std::make_unique( - render_context_->gpu_context(), - reinterpret_cast(command_pool_), extent, - ++target_generation_); - return index; - } - return std::nullopt; -} -Scene_Datoviz_State::Frame_Target& Scene_Datoviz_State::target( - const Pending_Frame& pending) { - if (pending.target_index >= targets_->values.size()) throw std::logic_error("Datoviz pending frame target index is invalid"); - auto& result = targets_->values[pending.target_index]; - if (!result || result->generation() != pending.target_generation) throw std::logic_error("Datoviz pending frame target no longer exists"); - return *result; -} -std::optional Scene_Datoviz_State::reuse_recorded_target( - const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared, - std::uint64_t frame_sequence, bool observe, bool readback) { - Datoviz_Frame_Observation observation; - observation.render_sequence = frame_sequence; - observation.path = Datoviz_Frame_Path::reused; - observation.gpu_timing_requested = observe; - observation.readback_requested = readback; - const std::uint64_t started = trace_now_ns(); - std::lock_guard target_lock(target_mutex_); - if (!matches_command_structure(scene, prepared)) return std::nullopt; - for (std::uint8_t target_index = 0; - target_index < targets_->values.size(); ++target_index) { - auto& candidate = targets_->values[target_index]; - if (!candidate || candidate->extent() != scene.viewport || - !candidate->can_reuse( - command_revision_, controller_revision_, readback)) - continue; - if (!target_runtime_resources_current(prepared, target_index)) continue; - /* The command buffer already binds this target's independent region. - * Only immutable frame payload bytes may change on this path. No - * Scene graph, emitter, runtime, allocator, camera or axis object is - * touched, so different Scene preparation workers can write their own - * mapped allocations in parallel without weakening the global API - * serialization used by structural Datoviz operations. */ - for (const auto& source : prepared) { - auto visual = std::ranges::find_if( - visuals_, [&](const Visual_Instance& value) { - return value.identity == source.identity; - }); - if (visual == visuals_.end()) - throw std::logic_error( - "recorded Datoviz frame contains an unknown Visual identity"); - if (!uses_external_attributes(visual->family)) continue; - if (!has_payload(source.visual)) - throw std::logic_error( - "recorded Datoviz Visual has no immutable payload"); - if (prepared_item_count(source.visual) != 0 && - visual->uploaded_data_revisions[target_index] != - source.visual.data_revision) { - observation.uploaded_bytes += upload_external_attributes( - *visual, source.visual, target_index); - visual->uploaded_data_revisions[target_index] = - source.visual.data_revision; - } - visual->applied_revision = source.visual.revision; - visual->applied = source.visual; - } - candidate->reuse( - command_revision_, controller_revision_, observe, readback); - observation.apply_ns = trace_now_ns() - started; - return Pending_Frame{ - reinterpret_cast(candidate->device()), - reinterpret_cast(candidate->fence()), scene.viewport, - frame_sequence, target_index, candidate->generation(), - std::move(observation) - }; - } - return std::nullopt; -} -std::optional Scene_Datoviz_State::try_prepare_reused( - const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals, - std::uint64_t frame_sequence, bool observe, bool readback) { - if (scene.viewport.empty()) return std::nullopt; - return reuse_recorded_target( - scene, visuals, frame_sequence, observe, readback); -} -std::optional Scene_Datoviz_State::prepare( - const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals, - std::uint64_t frame_sequence, bool observe, bool readback) { - if (scene.viewport.empty()) return std::nullopt; - if (auto reused = reuse_recorded_target( - scene, visuals, frame_sequence, observe, readback)) - return reused; - /* - * DRP2 runtime 的结构执行会创建/上传资源,并可能在内部使用共享主 - * VkQueue。它必须和 Render Domain 的显式 submit 使用同一外部同步域。 - * 热路径的已录制目标复用在此锁之前返回,仍可在各 Scene 准备线程并行。 - */ - std::optional target_index; - { - std::lock_guard resource_lock(target_mutex_); - target_index = acquire_target(scene.viewport); - } - if (!target_index) return std::nullopt; - Datoviz_Frame_Observation observation; - observation.render_sequence = frame_sequence; - observation.path = Datoviz_Frame_Path::recorded; - observation.gpu_timing_requested = observe; - observation.readback_requested = readback; - observation.controller_input_applied = input_changed_; - std::uint64_t phase_started = trace_now_ns(); - const bool content_changed = !matches_command_structure(scene, visuals); - if (content_changed) ++command_revision_; - auto& frame_target = *targets_->values[*target_index]; - const bool bind_target = - !frame_target.can_reuse_commands(command_revision_, readback); - observation.uploaded_bytes = apply( - scene, visuals, *target_index, bind_target); - bool query_changed_scene{}; - if (item_interaction_ != nullptr) { - static_cast(dvz_figure_process_queries( - figure_, runtime_slots_[*target_index].runtime, nullptr)); - DvzQueryResult query{}; - bool resolved{}; - while (dvz_scene_poll_query(scene_, &query)) resolved = true; - if (resolved) { - query_changed_scene = true; - if (hover_readout_ != nullptr) { - dvz_pinned_readout_destroy(hover_readout_); - hover_readout_ = nullptr; - } - if (query.hit) { - if (query.value_kind == DVZ_QUERY_VALUE_NONE) { - if (query.has_data_position || query.has_visual_position) { - query.value_kind = DVZ_QUERY_VALUE_VEC3; - const auto& position = query.has_data_position - ? query.data_position - : query.visual_position; - std::ranges::copy(position, query.vector); - constexpr char position_label[] = "Position"; - std::ranges::copy(position_label, query.label); - } - else { - query.value_kind = DVZ_QUERY_VALUE_SCALAR; - query.scalar = static_cast(query.item_id); - constexpr char item_label[] = "Item"; - std::ranges::copy(item_label, query.label); - } - } - hover_readout_ = dvz_pinned_readout_query(panel_, &query); - } - } - } - if (query_changed_scene) ++command_revision_; - observation.apply_ns = trace_now_ns() - phase_started; - bool runtime_resources_updated{}; - if (frame_target.can_reuse_commands(command_revision_, readback) && - !target_runtime_resources_current(visuals, *target_index)) { - DvzDiagnosticReport report{}; - dvz_diagnostic_report_init(&report); - std::uint64_t uploaded_bytes{}; - bool command_recording_valid{}; - phase_started = trace_now_ns(); - const auto replay_visuals = stale_runtime_visuals( - visuals, *target_index); - std::unique_ptr - update_stream{ - dvz_figure_prepare_runtime_resources( - figure_, runtime_slots_[*target_index].emitter, - replay_visuals.data(), - static_cast(replay_visuals.size()), - &uploaded_bytes, &command_recording_valid, &report), - &dvz_drp2_stream_destroy - }; - observation.emit_ns += trace_now_ns() - phase_started; - if (!update_stream) { - std::string message = - "failed to prepare Datoviz retained runtime resources"; - if (dvz_diagnostic_report_count(&report) != 0) - if (const char* diagnostic = - dvz_diagnostic_report_get(&report, 0)) - message += ": " + std::string(diagnostic); - throw std::runtime_error(std::move(message)); - } - phase_started = trace_now_ns(); - DvzDrp2ValidationResult update_result{}; - { - update_result = dvz_drp2_runtime_execute( - runtime_slots_[*target_index].runtime, - update_stream.get()); - } - observation.execute_ns += trace_now_ns() - phase_started; - if (!update_result.ok) - throw std::runtime_error( - "failed to execute Datoviz retained runtime resources"); - dvz_figure_commit_runtime_resources(figure_); - observation.uploaded_bytes += uploaded_bytes; - mark_target_runtime_resources_uploaded(*target_index); - runtime_resources_updated = true; - if (!command_recording_valid) frame_target.invalidate_recording(); - } - /* Controller input changes only mapped common MVP buffers. The selected - * target is available, so its own runtime allocation can be updated without - * rebuilding the Figure artifact or re-recording commands. Structural Scene - * changes keep using command_revision_ and fall through to full recording. */ - bool mvp_updated{}; - if (frame_target.can_reuse_commands(command_revision_, readback) && - !frame_target.can_reuse( - command_revision_, controller_revision_, readback)) { - std::uint64_t mvp_uploaded_bytes{}; - if (dvz_figure_update_mvp_buffers( - figure_, runtime_slots_[*target_index].emitter, - runtime_slots_[*target_index].runtime, - &mvp_uploaded_bytes)) { - frame_target.mark_mvp_uploaded(controller_revision_); - observation.uploaded_bytes += mvp_uploaded_bytes; - mvp_updated = true; - } - } - if (frame_target.can_reuse( - command_revision_, controller_revision_, readback)) { - { - std::lock_guard target_lock(target_mutex_); - frame_target.reuse( - command_revision_, controller_revision_, observe, readback); - } - input_changed_ = false; - observation.path = (runtime_resources_updated || mvp_updated) - ? Datoviz_Frame_Path::updated - : Datoviz_Frame_Path::reused; - return Pending_Frame{ - reinterpret_cast(frame_target.device()), - reinterpret_cast(frame_target.fence()), scene.viewport, - frame_sequence, *target_index, frame_target.generation(), - std::move(observation) - }; - } - { - std::lock_guard recording_lock(target_mutex_); - frame_target.begin( - observe, readback, command_revision_, controller_revision_); - } - struct Recording_Scope { - Frame_Target& target; /* 异常退出时回收尚未发布的录制槽。 */ - std::mutex& target_mutex; /* 本 Scene Frame Target 的生命周期门。 */ - bool released{}; /* finish_recording 成功后禁止回滚。 */ - ~Recording_Scope() { - if (released) return; - try { - std::lock_guard recording_lock(target_mutex); - target.abort(); - } - catch (...) {} - } - } recording_scope{frame_target, target_mutex_}; - std::unique_ptr - artifact{nullptr, &dvz_scene_frame_artifact_destroy}; - try { - phase_started = trace_now_ns(); - artifact.reset(emit(scene, *target_index)); - observation.emit_ns = trace_now_ns() - phase_started; - } - catch (...) { - raise_context("preparing Datoviz frame", std::current_exception()); - } - if (observe) { - observation.artifact_status = static_cast( - dvz_scene_frame_artifact_status(artifact.get())); - observation.artifact_resource_version = - dvz_scene_frame_artifact_resource_version(artifact.get()); - observation.artifact_frame_index = - dvz_scene_frame_artifact_frame_index(artifact.get()); - } - const DvzDrp2CommandStream* stream = - dvz_scene_frame_artifact_stream(artifact.get()); - register_external_attributes(stream, *target_index); - const DvzStreamFrame target_frame = frame_target.stream_frame(); - phase_started = trace_now_ns(); - bool attached{}; - DvzDrp2ValidationResult result{}; - { - attached = stream != nullptr && - dvz_drp2_runtime_attach_frame_target( - runtime_slots_[*target_index].runtime, - color_target_id, &target_frame); - if (attached) - result = dvz_drp2_runtime_execute( - runtime_slots_[*target_index].runtime, stream); - } - observation.execute_ns = trace_now_ns() - phase_started; - observation.validation_performed = true; - observation.validation_ok = attached && result.ok; - observation.validation_code = static_cast(result.code); - observation.validation_command_index = result.command_index; - if (!observation.validation_ok) { - if (char* json = dvz_scene_frame_artifact_json( - artifact.get(), "renderive_frame")) { - observation.artifact_json = json; - dvz_drp2_stream_json_destroy(json); - } - } - artifact.reset(); - if (!attached) { - throw std::runtime_error("failed to attach the Datoviz point frame target"); - } - if (!result.ok) { - std::string message = - "failed to execute Datoviz point frame: validation code " + - std::to_string(static_cast(result.code)) + - ", command " + std::to_string(result.command_index); - if (!observation.artifact_json.empty()) - message += ", failing command " + artifact_command_excerpt( - observation.artifact_json, result.command_index); - throw std::runtime_error(std::move(message)); - } - mark_target_runtime_resources_uploaded(*target_index); - { - std::lock_guard recording_lock(target_mutex_); - frame_target.finish_recording(); - } - recording_scope.released = true; - input_changed_ = false; - return Pending_Frame{ - reinterpret_cast(frame_target.device()), - reinterpret_cast(frame_target.fence()), scene.viewport, - frame_sequence, *target_index, frame_target.generation(), - std::move(observation) - }; -} -void Scene_Datoviz_State::submit( - Pending_Frame& pending, - std::function completion) { - if (!completion) - throw std::invalid_argument("Datoviz submission completion is empty"); - const std::uint64_t queued = trace_now_ns(); - render_context_->enqueue_submission( - [this, &pending, queued, completion = std::move(completion)]() mutable { - try { - pending.observation.queue_submit_wait_ns = - trace_now_ns() - queued; - std::lock_guard target_lock(target_mutex_); - const std::uint64_t started = trace_now_ns(); - target(pending).submit(); - pending.observation.submit_ns = trace_now_ns() - started; - completion({}); - } - catch (...) { completion(std::current_exception()); } - }); -} -Scene_Datoviz_State::Completed_Frame Scene_Datoviz_State::collect( - Pending_Frame pending) { - /* Fence 已完成,读回缓冲与查询池只属于本目标槽;不同 Scene 的映射内存 - * 下载可以在共享 Task_Resource 上并行。 */ - std::lock_guard target_lock(target_mutex_); - const std::uint64_t readback_started = trace_now_ns(); - auto collection = target(pending).collect(); - pending.observation.readback_ns = trace_now_ns() - readback_started; - pending.observation.readback_bytes = collection.pixels.size(); - pending.observation.gpu = std::move(collection.gpu_timing); - return { - pending.extent, std::move(collection.pixels), - std::move(pending.observation) - }; -} -void Scene_Datoviz_State::discard(Pending_Frame pending) { - std::lock_guard target_lock(target_mutex_); - target(pending).discard_after_completion(); -} -void Scene_Datoviz_State::quarantine( - Pending_Frame pending) noexcept { - quarantined_.store(true, std::memory_order_release); - try { - std::lock_guard target_lock(target_mutex_); - if (pending.target_index < targets_->values.size()) { - auto& frame_target = targets_->values[pending.target_index]; - if (frame_target && - frame_target->generation() == pending.target_generation) - frame_target->quarantine_after_submission(); - } - } - catch (...) {} -} -void Scene_Datoviz_State::abandon_resources() noexcept { - quarantined_.store(true, std::memory_order_release); - static_cast(targets_.release()); - retain_quarantined_context(render_context_); -} -void Scene_Datoviz_State::destroy() { - if (quarantined_.load(std::memory_order_acquire)) { - abandon_resources(); - return; - } - auto context = render_context_; - { - std::lock_guard target_lock(target_mutex_); - for (auto& slot : runtime_slots_) { - if (slot.runtime != nullptr) { - dvz_drp2_runtime_destroy(slot.runtime); - slot.runtime = nullptr; - } - if (slot.emitter != nullptr) { - dvz_frame_plan_emitter_destroy(slot.emitter); - slot.emitter = nullptr; - } - } - for (auto& target : targets_->values) target.reset(); - for (auto* buffer : external_buffers_) { - if (buffer == nullptr) continue; - dvz_buffer_destroy(buffer); - dvz_buffer_free(buffer); - } - external_buffers_.clear(); - if (camera_controller_ != nullptr) { - dvz_controller_destroy(camera_controller_); - camera_controller_ = nullptr; - } - if (item_interaction_ != nullptr) { - dvz_item_interaction_destroy(item_interaction_); - item_interaction_ = nullptr; - } - if (hover_readout_ != nullptr) { - dvz_pinned_readout_destroy(hover_readout_); - hover_readout_ = nullptr; - } - if (panel_ && input_router_ != nullptr) (void)dvz_panel_connect_input(panel_, nullptr); - if (gesture_handler_ != nullptr) { - dvz_pointer_gesture_handler_destroy(gesture_handler_); - gesture_handler_ = nullptr; - } - if (input_router_ != nullptr) { - dvz_input_router_destroy(input_router_); - input_router_ = nullptr; - } - visuals_.clear(); - axes_visual_ = nullptr; - axes_text_ = nullptr; - applied_camera_.reset(); - applied_axes_.reset(); - panel_ = nullptr; - figure_ = nullptr; - if (scene_ != nullptr) { - dvz_scene_destroy(scene_); - scene_ = nullptr; - } - if (context) { - VkDevice device = dvz_device_handle( - dvz_gpu_ctx_device(context->gpu_context().get())); - if (descriptor_pool_ != 0) { - vkDestroyDescriptorPool( - device, reinterpret_cast(descriptor_pool_), - nullptr); - descriptor_pool_ = 0; - } - if (command_pool_ != 0) { - vkDestroyCommandPool( - device, reinterpret_cast(command_pool_), nullptr); - command_pool_ = 0; - } - } - } - render_context_.reset(); - context.reset(); -} -} // namespace aethera::render_3d::detail +#include "detail/Datoviz_Scene_Common.ipp" +#include "detail/Datoviz_Frame_Target.ipp" +#include "detail/Datoviz_Visual_Factories.ipp" +#include "detail/Datoviz_Scene_Resources.ipp" +#include "detail/Datoviz_Visual_Operations.ipp" +#include "detail/Datoviz_Visual_Set.ipp" +#include "detail/Datoviz_Interaction.ipp" +#include "detail/Datoviz_Frame_Pipeline.ipp" diff --git a/render_3D/render_3D/scene/Scene_Datoviz_State.hpp b/render_3D/render_3D/scene/Scene_Datoviz_State.hpp index 1952bba..4f54b2a 100644 --- a/render_3D/render_3D/scene/Scene_Datoviz_State.hpp +++ b/render_3D/render_3D/scene/Scene_Datoviz_State.hpp @@ -2,6 +2,9 @@ #include "../base/Datoviz_Frame_Observation.hpp" #include "../detail/Backend_Types.hpp" +#include "detail/Datoviz_Frame_State.hpp" +#include "detail/Datoviz_Scene_Components.hpp" +#include "detail/Datoviz_Visual_State.hpp" #include #include #include @@ -39,53 +42,10 @@ 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 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 scene_buffer; - owner gpu_buffer{}; - std::uint8_t registered_targets{}; - }; - - struct Visual_Instance { - Visual_Identity identity{}; - Visual_Family family{Visual_Family::point}; - not_null visual; - DvzSampledField* field{}; - DvzFont* font{}; - DvzText* coordinate_text{}; - std::array field_extent{}; - std::uint64_t applied_revision{}; - std::array uploaded_data_revisions{}; - std::optional applied{}; - std::vector attributes{}; - }; - - struct Runtime_Slot { - owner runtime{}; - owner emitter{}; - }; - +struct Scene_Datoviz_State : private Datoviz_Native_Scene, + private Datoviz_Visual_Set, + private Datoviz_Interaction_State, + private Datoviz_Frame_Pipeline { Scene_Datoviz_State(); ~Scene_Datoviz_State(); Scene_Datoviz_State(const Scene_Datoviz_State&) = delete; @@ -106,14 +66,14 @@ struct Scene_Datoviz_State { 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, + Datoviz_Visual_Instance& target, const Prepared_Visual& visual, std::uint8_t target_index, bool bind_target); - void ensure_external_attributes(Visual_Instance& target, + void ensure_external_attributes(Datoviz_Visual_Instance& target, const Prepared_Visual& visual); [[nodiscard]] std::uint64_t upload_external_attributes( - Visual_Instance& target, const Prepared_Visual& visual, + Datoviz_Visual_Instance& target, const Prepared_Visual& visual, std::uint8_t target_index); - void bind_external_attributes(Visual_Instance& target, + void bind_external_attributes(Datoviz_Visual_Instance& target, std::uint8_t target_index, std::uint32_t item_count); void register_external_attributes(const DvzDrp2CommandStream* stream, @@ -125,27 +85,27 @@ struct Scene_Datoviz_State { 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 reuse_recorded_target( + [[nodiscard]] std::optional 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 prepare( + [[nodiscard]] std::optional prepare( const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals, std::uint64_t frame_sequence, bool observe, bool readback); - [[nodiscard]] std::optional try_prepare_reused( + [[nodiscard]] std::optional 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 acquire_target(Extent extent); - [[nodiscard]] Frame_Target& target(const Pending_Frame& pending); - void submit(Pending_Frame& pending, + [[nodiscard]] Datoviz_Frame_Target& target(const Datoviz_Pending_Frame& pending); + void submit(Datoviz_Pending_Frame& pending, std::function completion); - [[nodiscard]] Completed_Frame collect(Pending_Frame pending); - void discard(Pending_Frame pending); - void quarantine(Pending_Frame pending) noexcept; + [[nodiscard]] Datoviz_Completed_Frame collect(Datoviz_Pending_Frame pending); + void discard(Datoviz_Pending_Frame pending); + void quarantine(Datoviz_Pending_Frame pending) noexcept; void mark_controller_input_applied() noexcept; void dispatch_pointer(Event_Type type, float x, float y, Mouse_Button button, @@ -160,32 +120,6 @@ struct Scene_Datoviz_State { void abandon_resources() noexcept; void destroy(); - std::shared_ptr render_context_{}; - std::uintptr_t command_pool_{}; - std::uintptr_t descriptor_pool_{}; - mutable std::mutex target_mutex_{}; - std::array runtime_slots_{}; - owner scene_{}; - DvzFigure* figure_{}; - DvzPanel* panel_{}; - std::vector visuals_{}; - std::vector> external_buffers_{}; - DvzVisual* axes_visual_{}; - DvzText* axes_text_{}; - owner item_interaction_{}; - owner hover_readout_{}; - owner camera_controller_{}; - owner input_router_{}; - owner gesture_handler_{}; - std::unique_ptr targets_{}; - Extent figure_extent_{}; - std::uint64_t target_generation_{}; - std::uint64_t command_revision_{1}; - std::uint64_t controller_revision_{1}; - std::optional applied_camera_{}; - std::optional> applied_axes_{}; - bool input_changed_{}; - std::atomic_bool quarantined_{}; }; }}} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp new file mode 100644 index 0000000..e29a987 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp @@ -0,0 +1,513 @@ +Datoviz_Frame_Pipeline::Datoviz_Frame_Pipeline() : + targets_(std::make_unique()) {} + +DvzSceneFrameArtifact* Scene_Datoviz_State::emit( + const Scene_3D_Parameters& scene, std::uint8_t target_index) { + if (target_index >= runtime_slots_.size() || + runtime_slots_[target_index].emitter == nullptr) + throw std::logic_error("Datoviz frame target emitter is unavailable"); + DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config(); + configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL; + configuration.external_color_target = true; + configuration.color_target_id = color_target_id; + configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM; + configuration.target_width = scene.viewport.width; + configuration.target_height = scene.viewport.height; + configuration.clear_color[0] = scene.clear_color.red; + configuration.clear_color[1] = scene.clear_color.green; + configuration.clear_color[2] = scene.clear_color.blue; + configuration.clear_color[3] = scene.clear_color.alpha; + const auto capabilities = offscreen_capabilities(); + DvzDiagnosticReport report{}; + dvz_diagnostic_report_init(&report); + /* Every frame target owns a DRP2 runtime and its matching retained emitter. + * A stream is incremental relative to exactly one runtime; sharing an emitter + * across the three targets makes target N receive commands that assume target + * N-1's resource state. Replaying retained payloads also compensates for the + * Scene-wide dirty flags committed by another target's preceding emission. */ + auto* artifact = dvz_figure_emit_frame_with_emitter( + figure_, runtime_slots_[target_index].emitter, true, + &capabilities, &report, &configuration); + if (artifact == nullptr) { + std::string message = "failed to emit Datoviz visual frame"; + const auto count = dvz_diagnostic_report_count(&report); + if (count != 0) { + if (const char* diagnostic = dvz_diagnostic_report_get(&report, 0)) message += ": " + std::string(diagnostic); + } + throw std::runtime_error(message); + } + return artifact; +} +std::optional Scene_Datoviz_State::acquire_target(Extent extent) { + for (std::uint8_t index = 0; index < targets_->values.size(); ++index) { + auto& candidate = targets_->values[index]; + if (candidate && candidate->available() && candidate->extent() == extent) return index; + } + for (std::uint8_t index = 0; index < targets_->values.size(); ++index) { + auto& candidate = targets_->values[index]; + if (candidate && !candidate->available()) continue; + candidate = std::make_unique( + render_context_->gpu_context(), + reinterpret_cast(command_pool_), extent, + ++target_generation_); + return index; + } + return std::nullopt; +} +Datoviz_Frame_Target& Scene_Datoviz_State::target( + const Datoviz_Pending_Frame& pending) { + if (pending.target_index >= targets_->values.size()) throw std::logic_error("Datoviz pending frame target index is invalid"); + auto& result = targets_->values[pending.target_index]; + if (!result || result->generation() != pending.target_generation) throw std::logic_error("Datoviz pending frame target no longer exists"); + return *result; +} +std::optional Scene_Datoviz_State::reuse_recorded_target( + const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared, + std::uint64_t frame_sequence, bool observe, bool readback) { + Datoviz_Frame_Observation observation; + observation.render_sequence = frame_sequence; + observation.path = Datoviz_Frame_Path::reused; + observation.gpu_timing_requested = observe; + observation.readback_requested = readback; + const std::uint64_t started = trace_now_ns(); + std::lock_guard target_lock(target_mutex_); + if (!matches_command_structure(scene, prepared)) return std::nullopt; + for (std::uint8_t target_index = 0; + target_index < targets_->values.size(); ++target_index) { + auto& candidate = targets_->values[target_index]; + if (!candidate || candidate->extent() != scene.viewport || + !candidate->can_reuse( + command_revision_, controller_revision_, readback)) + continue; + if (!target_runtime_resources_current(prepared, target_index)) continue; + /* The command buffer already binds this target's independent region. + * Only immutable frame payload bytes may change on this path. No + * Scene graph, emitter, runtime, allocator, camera or axis object is + * touched, so different Scene preparation workers can write their own + * mapped allocations in parallel without weakening the global API + * serialization used by structural Datoviz operations. */ + for (const auto& source : prepared) { + auto visual = std::ranges::find_if( + visuals_, [&](const Datoviz_Visual_Instance& value) { + return value.identity == source.identity; + }); + if (visual == visuals_.end()) + throw std::logic_error( + "recorded Datoviz frame contains an unknown Visual identity"); + if (source.visual.operations != visual->operations) + throw std::logic_error( + "recorded Datoviz Visual operations do not match registration"); + if (visual->operations->external_attributes.empty()) continue; + if (!has_payload(source.visual)) + throw std::logic_error( + "recorded Datoviz Visual has no immutable payload"); + if (prepared_item_count(source.visual) != 0 && + visual->uploaded_data_revisions[target_index] != + source.visual.data_revision) { + observation.uploaded_bytes += upload_external_attributes( + *visual, source.visual, target_index); + visual->uploaded_data_revisions[target_index] = + source.visual.data_revision; + } + visual->applied_revision = source.visual.revision; + visual->applied = source.visual; + } + candidate->reuse( + command_revision_, controller_revision_, observe, readback); + observation.apply_ns = trace_now_ns() - started; + return Datoviz_Pending_Frame{ + reinterpret_cast(candidate->device()), + reinterpret_cast(candidate->fence()), scene.viewport, + frame_sequence, target_index, candidate->generation(), + std::move(observation) + }; + } + return std::nullopt; +} +std::optional Scene_Datoviz_State::try_prepare_reused( + const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals, + std::uint64_t frame_sequence, bool observe, bool readback) { + if (scene.viewport.empty()) return std::nullopt; + return reuse_recorded_target( + scene, visuals, frame_sequence, observe, readback); +} +std::optional Scene_Datoviz_State::prepare( + const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals, + std::uint64_t frame_sequence, bool observe, bool readback) { + if (scene.viewport.empty()) return std::nullopt; + if (auto reused = reuse_recorded_target( + scene, visuals, frame_sequence, observe, readback)) + return reused; + /* + * DRP2 runtime 的结构执行会创建/上传资源,并可能在内部使用共享主 + * VkQueue。它必须和 Render Domain 的显式 submit 使用同一外部同步域。 + * 热路径的已录制目标复用在此锁之前返回,仍可在各 Scene 准备线程并行。 + */ + std::optional target_index; + { + std::lock_guard resource_lock(target_mutex_); + target_index = acquire_target(scene.viewport); + } + if (!target_index) return std::nullopt; + Datoviz_Frame_Observation observation; + observation.render_sequence = frame_sequence; + observation.path = Datoviz_Frame_Path::recorded; + observation.gpu_timing_requested = observe; + observation.readback_requested = readback; + observation.controller_input_applied = input_changed_; + std::uint64_t phase_started = trace_now_ns(); + const bool content_changed = !matches_command_structure(scene, visuals); + if (content_changed) ++command_revision_; + auto& frame_target = *targets_->values[*target_index]; + const bool bind_target = + !frame_target.can_reuse_commands(command_revision_, readback); + observation.uploaded_bytes = apply( + scene, visuals, *target_index, bind_target); + bool query_changed_scene{}; + if (item_interaction_ != nullptr) { + static_cast(dvz_figure_process_queries( + figure_, runtime_slots_[*target_index].runtime, nullptr)); + DvzQueryResult query{}; + bool resolved{}; + while (dvz_scene_poll_query(scene_, &query)) resolved = true; + if (resolved) { + query_changed_scene = true; + if (hover_readout_ != nullptr) { + dvz_pinned_readout_destroy(hover_readout_); + hover_readout_ = nullptr; + } + if (query.hit) { + if (query.value_kind == DVZ_QUERY_VALUE_NONE) { + if (query.has_data_position || query.has_visual_position) { + query.value_kind = DVZ_QUERY_VALUE_VEC3; + const auto& position = query.has_data_position + ? query.data_position + : query.visual_position; + std::ranges::copy(position, query.vector); + constexpr char position_label[] = "Position"; + std::ranges::copy(position_label, query.label); + } + else { + query.value_kind = DVZ_QUERY_VALUE_SCALAR; + query.scalar = static_cast(query.item_id); + constexpr char item_label[] = "Item"; + std::ranges::copy(item_label, query.label); + } + } + hover_readout_ = dvz_pinned_readout_query(panel_, &query); + } + } + } + if (query_changed_scene) ++command_revision_; + observation.apply_ns = trace_now_ns() - phase_started; + bool runtime_resources_updated{}; + if (frame_target.can_reuse_commands(command_revision_, readback) && + !target_runtime_resources_current(visuals, *target_index)) { + DvzDiagnosticReport report{}; + dvz_diagnostic_report_init(&report); + std::uint64_t uploaded_bytes{}; + bool command_recording_valid{}; + phase_started = trace_now_ns(); + const auto replay_visuals = stale_runtime_visuals( + visuals, *target_index); + std::unique_ptr + update_stream{ + dvz_figure_prepare_runtime_resources( + figure_, runtime_slots_[*target_index].emitter, + replay_visuals.data(), + static_cast(replay_visuals.size()), + &uploaded_bytes, &command_recording_valid, &report), + &dvz_drp2_stream_destroy + }; + observation.emit_ns += trace_now_ns() - phase_started; + if (!update_stream) { + std::string message = + "failed to prepare Datoviz retained runtime resources"; + if (dvz_diagnostic_report_count(&report) != 0) + if (const char* diagnostic = + dvz_diagnostic_report_get(&report, 0)) + message += ": " + std::string(diagnostic); + throw std::runtime_error(std::move(message)); + } + phase_started = trace_now_ns(); + DvzDrp2ValidationResult update_result{}; + { + update_result = dvz_drp2_runtime_execute( + runtime_slots_[*target_index].runtime, + update_stream.get()); + } + observation.execute_ns += trace_now_ns() - phase_started; + if (!update_result.ok) + throw std::runtime_error( + "failed to execute Datoviz retained runtime resources"); + dvz_figure_commit_runtime_resources(figure_); + observation.uploaded_bytes += uploaded_bytes; + mark_target_runtime_resources_uploaded(*target_index); + runtime_resources_updated = true; + if (!command_recording_valid) frame_target.invalidate_recording(); + } + /* Controller input changes only mapped common MVP buffers. The selected + * target is available, so its own runtime allocation can be updated without + * rebuilding the Figure artifact or re-recording commands. Structural Scene + * changes keep using command_revision_ and fall through to full recording. */ + bool mvp_updated{}; + if (frame_target.can_reuse_commands(command_revision_, readback) && + !frame_target.can_reuse( + command_revision_, controller_revision_, readback)) { + std::uint64_t mvp_uploaded_bytes{}; + if (dvz_figure_update_mvp_buffers( + figure_, runtime_slots_[*target_index].emitter, + runtime_slots_[*target_index].runtime, + &mvp_uploaded_bytes)) { + frame_target.mark_mvp_uploaded(controller_revision_); + observation.uploaded_bytes += mvp_uploaded_bytes; + mvp_updated = true; + } + } + if (frame_target.can_reuse( + command_revision_, controller_revision_, readback)) { + { + std::lock_guard target_lock(target_mutex_); + frame_target.reuse( + command_revision_, controller_revision_, observe, readback); + } + input_changed_ = false; + observation.path = (runtime_resources_updated || mvp_updated) + ? Datoviz_Frame_Path::updated + : Datoviz_Frame_Path::reused; + return Datoviz_Pending_Frame{ + reinterpret_cast(frame_target.device()), + reinterpret_cast(frame_target.fence()), scene.viewport, + frame_sequence, *target_index, frame_target.generation(), + std::move(observation) + }; + } + { + std::lock_guard recording_lock(target_mutex_); + frame_target.begin( + observe, readback, command_revision_, controller_revision_); + } + struct Recording_Scope { + Datoviz_Frame_Target& target; /* 异常退出时回收尚未发布的录制槽。 */ + std::mutex& target_mutex; /* 本 Scene Frame Target 的生命周期门。 */ + bool released{}; /* finish_recording 成功后禁止回滚。 */ + ~Recording_Scope() { + if (released) return; + try { + std::lock_guard recording_lock(target_mutex); + target.abort(); + } + catch (...) {} + } + } recording_scope{frame_target, target_mutex_}; + std::unique_ptr + artifact{nullptr, &dvz_scene_frame_artifact_destroy}; + try { + phase_started = trace_now_ns(); + artifact.reset(emit(scene, *target_index)); + observation.emit_ns = trace_now_ns() - phase_started; + } + catch (...) { + raise_context("preparing Datoviz frame", std::current_exception()); + } + if (observe) { + observation.artifact_status = static_cast( + dvz_scene_frame_artifact_status(artifact.get())); + observation.artifact_resource_version = + dvz_scene_frame_artifact_resource_version(artifact.get()); + observation.artifact_frame_index = + dvz_scene_frame_artifact_frame_index(artifact.get()); + } + const DvzDrp2CommandStream* stream = + dvz_scene_frame_artifact_stream(artifact.get()); + register_external_attributes(stream, *target_index); + const DvzStreamFrame target_frame = frame_target.stream_frame(); + phase_started = trace_now_ns(); + bool attached{}; + DvzDrp2ValidationResult result{}; + { + attached = stream != nullptr && + dvz_drp2_runtime_attach_frame_target( + runtime_slots_[*target_index].runtime, + color_target_id, &target_frame); + if (attached) + result = dvz_drp2_runtime_execute( + runtime_slots_[*target_index].runtime, stream); + } + observation.execute_ns = trace_now_ns() - phase_started; + observation.validation_performed = true; + observation.validation_ok = attached && result.ok; + observation.validation_code = static_cast(result.code); + observation.validation_command_index = result.command_index; + if (!observation.validation_ok) { + if (char* json = dvz_scene_frame_artifact_json( + artifact.get(), "renderive_frame")) { + observation.artifact_json = json; + dvz_drp2_stream_json_destroy(json); + } + } + artifact.reset(); + if (!attached) { + throw std::runtime_error("failed to attach the Datoviz point frame target"); + } + if (!result.ok) { + std::string message = + "failed to execute Datoviz point frame: validation code " + + std::to_string(static_cast(result.code)) + + ", command " + std::to_string(result.command_index); + if (!observation.artifact_json.empty()) + message += ", failing command " + artifact_command_excerpt( + observation.artifact_json, result.command_index); + throw std::runtime_error(std::move(message)); + } + mark_target_runtime_resources_uploaded(*target_index); + { + std::lock_guard recording_lock(target_mutex_); + frame_target.finish_recording(); + } + recording_scope.released = true; + input_changed_ = false; + return Datoviz_Pending_Frame{ + reinterpret_cast(frame_target.device()), + reinterpret_cast(frame_target.fence()), scene.viewport, + frame_sequence, *target_index, frame_target.generation(), + std::move(observation) + }; +} +void Scene_Datoviz_State::submit( + Datoviz_Pending_Frame& pending, + std::function completion) { + if (!completion) + throw std::invalid_argument("Datoviz submission completion is empty"); + const std::uint64_t queued = trace_now_ns(); + render_context_->enqueue_submission( + [this, &pending, queued, completion = std::move(completion)]() mutable { + try { + pending.observation.queue_submit_wait_ns = + trace_now_ns() - queued; + std::lock_guard target_lock(target_mutex_); + const std::uint64_t started = trace_now_ns(); + target(pending).submit(); + pending.observation.submit_ns = trace_now_ns() - started; + completion({}); + } + catch (...) { completion(std::current_exception()); } + }); +} +Datoviz_Completed_Frame Scene_Datoviz_State::collect( + Datoviz_Pending_Frame pending) { + /* Fence 已完成,读回缓冲与查询池只属于本目标槽;不同 Scene 的映射内存 + * 下载可以在共享 Task_Resource 上并行。 */ + std::lock_guard target_lock(target_mutex_); + const std::uint64_t readback_started = trace_now_ns(); + auto collection = target(pending).collect(); + pending.observation.readback_ns = trace_now_ns() - readback_started; + pending.observation.readback_bytes = collection.pixels.size(); + pending.observation.gpu = std::move(collection.gpu_timing); + return { + pending.extent, std::move(collection.pixels), + std::move(pending.observation) + }; +} +void Scene_Datoviz_State::discard(Datoviz_Pending_Frame pending) { + std::lock_guard target_lock(target_mutex_); + target(pending).discard_after_completion(); +} +void Scene_Datoviz_State::quarantine( + Datoviz_Pending_Frame pending) noexcept { + quarantined_.store(true, std::memory_order_release); + try { + std::lock_guard target_lock(target_mutex_); + if (pending.target_index < targets_->values.size()) { + auto& frame_target = targets_->values[pending.target_index]; + if (frame_target && + frame_target->generation() == pending.target_generation) + frame_target->quarantine_after_submission(); + } + } + catch (...) {} +} +void Scene_Datoviz_State::abandon_resources() noexcept { + quarantined_.store(true, std::memory_order_release); + static_cast(targets_.release()); + retain_quarantined_context(render_context_); +} +void Scene_Datoviz_State::destroy() { + if (quarantined_.load(std::memory_order_acquire)) { + abandon_resources(); + return; + } + auto context = render_context_; + { + std::lock_guard target_lock(target_mutex_); + for (auto& slot : runtime_slots_) { + if (slot.runtime != nullptr) { + dvz_drp2_runtime_destroy(slot.runtime); + slot.runtime = nullptr; + } + if (slot.emitter != nullptr) { + dvz_frame_plan_emitter_destroy(slot.emitter); + slot.emitter = nullptr; + } + } + for (auto& target : targets_->values) target.reset(); + for (auto* buffer : external_buffers_) { + if (buffer == nullptr) continue; + dvz_buffer_destroy(buffer); + dvz_buffer_free(buffer); + } + external_buffers_.clear(); + if (camera_controller_ != nullptr) { + dvz_controller_destroy(camera_controller_); + camera_controller_ = nullptr; + } + if (item_interaction_ != nullptr) { + dvz_item_interaction_destroy(item_interaction_); + item_interaction_ = nullptr; + } + if (hover_readout_ != nullptr) { + dvz_pinned_readout_destroy(hover_readout_); + hover_readout_ = nullptr; + } + if (panel_ && input_router_ != nullptr) (void)dvz_panel_connect_input(panel_, nullptr); + if (gesture_handler_ != nullptr) { + dvz_pointer_gesture_handler_destroy(gesture_handler_); + gesture_handler_ = nullptr; + } + if (input_router_ != nullptr) { + dvz_input_router_destroy(input_router_); + input_router_ = nullptr; + } + visuals_.clear(); + axes_visual_ = nullptr; + axes_text_ = nullptr; + applied_camera_.reset(); + applied_axes_.reset(); + panel_ = nullptr; + figure_ = nullptr; + if (scene_ != nullptr) { + dvz_scene_destroy(scene_); + scene_ = nullptr; + } + if (context) { + VkDevice device = dvz_device_handle( + dvz_gpu_ctx_device(context->gpu_context().get())); + if (descriptor_pool_ != 0) { + vkDestroyDescriptorPool( + device, reinterpret_cast(descriptor_pool_), + nullptr); + descriptor_pool_ = 0; + } + if (command_pool_ != 0) { + vkDestroyCommandPool( + device, reinterpret_cast(command_pool_), nullptr); + command_pool_ = 0; + } + } + } + render_context_.reset(); + context.reset(); +} +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp b/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp new file mode 100644 index 0000000..51a8415 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../../base/Datoviz_Frame_Observation.hpp" +#include "../../base/Types.hpp" +#include +#include +#include + +namespace aethera::render_3d::detail { + +struct Datoviz_Pending_Frame { + std::uintptr_t device{}; /* Datoviz Device 的非拥有句柄值。 */ + std::uintptr_t fence{}; /* 本帧完成 Fence 的非拥有句柄值。 */ + Extent extent{}; /* 本帧离屏目标尺寸。 */ + std::uint64_t sequence{}; /* 调用方 Frame 的物理序号。 */ + std::uint8_t target_index{}; /* Scene 三槽中的目标下标。 */ + std::uint64_t target_generation{}; /* 防止槽重建后的陈旧引用命中。 */ + Datoviz_Frame_Observation observation{}; /* 直接附加到本帧的后端事实。 */ +}; + +struct Datoviz_Completed_Frame { + Extent extent{}; /* 已完成帧的像素尺寸。 */ + std::vector pixels{}; /* 返还给调用方 Frame 的 RGBA 像素。 */ + Datoviz_Frame_Observation observation{}; /* 与像素对应的后端事实。 */ +}; + +struct Datoviz_Frame_Target; +struct Datoviz_Frame_Target_Set; + +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_Target.ipp b/render_3D/render_3D/scene/detail/Datoviz_Frame_Target.ipp new file mode 100644 index 0000000..61528d9 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Target.ipp @@ -0,0 +1,506 @@ +struct Datoviz_Frame_Target final { +public: + struct Collection { + std::vector pixels; + std::optional gpu_timing; + }; + Datoviz_Frame_Target(not_null gpu_context, VkCommandPool command_pool, + Extent extent, std::uint64_t generation) : + gpu_context_(gpu_context), extent_(extent), generation_(generation) { + if (extent.empty()) throw std::invalid_argument("invalid Datoviz point frame target"); + const std::uint64_t byte_size = static_cast(extent.width) * + extent.height * 4ULL; + if (byte_size > std::numeric_limits::max()) throw std::length_error("Datoviz point frame target is too large"); + byte_size_ = static_cast(byte_size); + DvzDevice* device = dvz_gpu_ctx_device(gpu_context_); + DvzVma* allocator = dvz_gpu_ctx_alloc(gpu_context_); + DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); + if (device == nullptr || allocator == nullptr || queue == nullptr) throw std::runtime_error("Datoviz GPU context is incomplete"); + try { + image_ = allocate_wrapper(dvz_images_create_wrapper, + "failed to allocate Datoviz image"); + dvz_images(device, allocator, VK_IMAGE_TYPE_2D, 1, image_); + dvz_images_format(image_, VK_FORMAT_R8G8B8A8_UNORM); + dvz_images_size(image_, extent.width, extent.height, 1); + dvz_images_tiling(image_, VK_IMAGE_TILING_OPTIMAL); + dvz_images_usage(image_, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT); + dvz_images_alloc_flags(image_, DVZ_ALLOC_FLAGS_NONE); + if (dvz_images_create(image_) != 0) throw std::runtime_error("failed to create Datoviz point image"); + view_ = allocate_wrapper(dvz_image_views_create_wrapper, + "failed to allocate Datoviz image view"); + dvz_image_views(image_, view_); + dvz_image_views_type(view_, VK_IMAGE_VIEW_TYPE_2D); + dvz_image_views_aspect(view_, VK_IMAGE_ASPECT_COLOR_BIT); + dvz_image_views_mip(view_, 0, 1); + dvz_image_views_layers(view_, 0, 1); + if (dvz_image_views_create(view_) != 0) throw std::runtime_error("failed to create Datoviz point image view"); + commands_ = allocate_wrapper(dvz_commands_create_wrapper, + "failed to allocate Datoviz commands"); + dvz_commands_pool(device, queue, command_pool, 1, commands_); + if (dvz_commands_handle(commands_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz command buffer"); + fence_ = allocate_wrapper(dvz_fence_create_wrapper, + "failed to allocate Datoviz fence"); + dvz_fence(device, true, fence_); + if (dvz_fence_handle(fence_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz fence"); + submit_ = allocate_wrapper(dvz_submit_create_wrapper, + "failed to allocate Datoviz submit"); + readback_ = allocate_wrapper(dvz_buffer_create_wrapper, + "failed to allocate Datoviz readback"); + dvz_buffer(device, allocator, readback_); + dvz_buffer_size(readback_, byte_size_); + dvz_buffer_flags(readback_, DVZ_ALLOC_HOST_ACCESS_RANDOM | DVZ_ALLOC_MAPPED); + dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + if (dvz_buffer_create(readback_) != 0) throw std::runtime_error("failed to create Datoviz readback buffer"); + initialize_timestamps(device, queue); + } + catch (...) { + destroy(); + raise_context("creating Datoviz frame target", std::current_exception()); + } + } + ~Datoviz_Frame_Target() noexcept { + try { + if (quarantined_) release_without_destroy(); + else destroy(); + } + catch (...) { + release_without_destroy(); + } + } + [[nodiscard]] bool can_reuse_commands( + std::uint64_t command_revision, bool readback) const noexcept { + return available() && recorded_ && + recorded_command_revision_ == command_revision && + recorded_readback_ == readback; + } + [[nodiscard]] bool can_reuse( + std::uint64_t command_revision, std::uint64_t controller_revision, + bool readback) const noexcept { + return can_reuse_commands(command_revision, readback) && + recorded_controller_revision_ == controller_revision; + } + void mark_mvp_uploaded(std::uint64_t controller_revision) { + if (!available() || !recorded_) + throw std::logic_error( + "Datoviz frame target cannot publish an MVP revision"); + recorded_controller_revision_ = controller_revision; + } + void invalidate_recording() { + if (!available()) + throw std::logic_error( + "Datoviz frame target cannot invalidate a busy recording"); + recorded_ = false; + } + void reuse(std::uint64_t command_revision, + std::uint64_t controller_revision, bool observe, + bool readback) { + if (!can_reuse(command_revision, controller_revision, readback)) + throw std::logic_error( + "Datoviz frame target command recording cannot be reused"); + observing_ = observe; + readback_requested_ = readback; + dvz_fence_reset(fence_); + prepared_ = true; + } + void begin(bool observe, bool readback, std::uint64_t command_revision, + std::uint64_t controller_revision) { + if (recording_ || prepared_ || in_flight_) throw std::logic_error("Datoviz frame target is not available"); + observing_ = observe; + readback_requested_ = readback; + recording_command_revision_ = command_revision; + recording_controller_revision_ = controller_revision; + /* Reset invalidates the previous recording immediately. Only + * finish_recording() may publish the new cache identity. */ + recorded_ = false; + try { + dvz_cmd_reset(commands_); + if (dvz_cmd_begin_result(commands_) != 0) + throw std::runtime_error( + "failed to begin Datoviz command buffer"); + recording_ = true; + DvzBarriers barriers{}; + dvz_barriers(&barriers); + auto* image_barrier = dvz_barriers_image( + &barriers, dvz_image_handle(image_, 0)); + if (image_barrier == nullptr) + throw std::runtime_error( + "failed to allocate Datoviz image barrier"); + if (completed_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) { + dvz_barrier_image_stage( + image_barrier, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); + dvz_barrier_image_access( + image_barrier, 0, + VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); + } + else if (completed_layout_ == + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + dvz_barrier_image_stage( + image_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); + dvz_barrier_image_access( + image_barrier, VK_ACCESS_2_TRANSFER_READ_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); + } + else { + dvz_barrier_image_stage( + image_barrier, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); + dvz_barrier_image_access( + image_barrier, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); + } + dvz_barrier_image_layout( + image_barrier, completed_layout_, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT); + dvz_barrier_image_mip(image_barrier, 0, 1); + dvz_barrier_image_layers(image_barrier, 0, 1); + dvz_cmd_barriers(commands_, &barriers); + if (timestamps_supported_) { + const VkCommandBuffer command_buffer = + dvz_commands_handle(commands_); + vkCmdResetQueryPool(command_buffer, query_pool_, 0, 4); + vkCmdWriteTimestamp( + command_buffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + query_pool_, 0); + } + } + catch (...) { + abort(); + throw; + } + } + [[nodiscard]] DvzStreamFrame stream_frame() const { + DvzStreamFrame frame{}; + frame.image = dvz_image_handle(image_, 0); + frame.command_buffer = dvz_commands_handle(commands_); + frame.image_view = dvz_image_views_handle(view_, 0); + frame.extent = {extent_.width, extent_.height}; + frame.color_format = VK_FORMAT_R8G8B8A8_UNORM; + frame.image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + frame.usage = DVZ_STREAM_FRAME_USAGE_RENDER_TARGET | DVZ_STREAM_FRAME_USAGE_COPY_SRC; + frame.command_buffer_recording = recording_; + frame.image_borrowed = true; + frame.image_view_borrowed = true; + frame.command_buffer_borrowed = true; + frame.handles_dirty = true; + frame.resource_generation = generation_; + frame.image_valid = true; + frame.memory_fd = -1; + frame.wait_semaphore_fd = -1; + return frame; + } + void finish_recording() { + if (!recording_) throw std::logic_error("Datoviz frame target is not recording"); + const VkCommandBuffer command_buffer = dvz_commands_handle(commands_); + if (timestamps_supported_) { + vkCmdWriteTimestamp( + command_buffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + query_pool_, 1); + } + if (readback_requested_) { + DvzBarriers image_barriers{}; + dvz_barriers(&image_barriers); + auto* image_barrier = + dvz_barriers_image(&image_barriers, dvz_image_handle(image_, 0)); + dvz_barrier_image_stage(image_barrier, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_TRANSFER_BIT); + dvz_barrier_image_access(image_barrier, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_2_TRANSFER_READ_BIT); + dvz_barrier_image_layout(image_barrier, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT); + dvz_barrier_image_mip(image_barrier, 0, 1); + dvz_barrier_image_layers(image_barrier, 0, 1); + dvz_cmd_barriers(commands_, &image_barriers); + if (timestamps_supported_) { + vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + query_pool_, 2); + } + DvzImageRegion region{}; + dvz_image_region(®ion); + dvz_image_region_extent(®ion, extent_.width, extent_.height, 1); + dvz_cmd_copy_image_to_buffer( + commands_, dvz_image_handle(image_, 0), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, ®ion, + dvz_buffer_handle(readback_), 0); + DvzBarriers buffer_barriers{}; + dvz_barriers(&buffer_barriers); + auto* buffer_barrier = dvz_barriers_buffer( + &buffer_barriers, dvz_buffer_handle(readback_), 0, byte_size_); + dvz_barrier_buffer_stage(buffer_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT, + VK_PIPELINE_STAGE_2_HOST_BIT); + dvz_barrier_buffer_access(buffer_barrier, VK_ACCESS_2_TRANSFER_WRITE_BIT, + VK_ACCESS_2_HOST_READ_BIT); + dvz_cmd_barriers(commands_, &buffer_barriers); + } + else if (timestamps_supported_) { + vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + query_pool_, 2); + } + if (timestamps_supported_) { + vkCmdWriteTimestamp(command_buffer, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + query_pool_, 3); + } + if (dvz_cmd_end_result(commands_) != 0) throw std::runtime_error("failed to end Datoviz command buffer"); + recording_ = false; + dvz_fence_reset(fence_); + dvz_submit(submit_); + dvz_submit_command(submit_, dvz_commands_handle(commands_)); + recorded_command_revision_ = recording_command_revision_; + recorded_controller_revision_ = recording_controller_revision_; + recorded_readback_ = readback_requested_; + recorded_ = true; + prepared_ = true; + } + void submit() { + if (!prepared_ || in_flight_) throw std::logic_error("Datoviz frame target has no prepared submission"); + DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); + if (dvz_submit_send(submit_, dvz_queue_handle(queue), + dvz_fence_handle(fence_)) != VK_SUCCESS) + throw std::runtime_error("failed to submit Datoviz point frame"); + prepared_ = false; + in_flight_ = true; + completed_layout_ = readback_requested_ ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + [[nodiscard]] Collection collect() { + if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame"); + Collection result; + try { + if (readback_requested_) { + result.pixels.resize(static_cast(byte_size_)); + dvz_buffer_download(readback_, 0, byte_size_, result.pixels.data()); + } + result.gpu_timing = collect_gpu_timing(); + } + catch (...) { + in_flight_ = false; + observing_ = false; + readback_requested_ = false; + raise_context("submitting Datoviz frame target", std::current_exception()); + } + in_flight_ = false; + observing_ = false; + readback_requested_ = false; + return result; + } + void discard_after_completion() { + if (prepared_) { + prepared_ = false; + observing_ = false; + readback_requested_ = false; + return; + } + if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame"); + in_flight_ = false; + observing_ = false; + readback_requested_ = false; + } + [[nodiscard]] VkDevice device() const { + return dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)); + } + [[nodiscard]] VkFence fence() const { + return dvz_fence_handle(fence_); + } + [[nodiscard]] std::uint64_t generation() const noexcept { + return generation_; + } + void quarantine_after_submission() noexcept { + recording_ = false; + prepared_ = false; + in_flight_ = false; + observing_ = false; + readback_requested_ = false; + recorded_ = false; + quarantined_ = true; + } + [[nodiscard]] Extent extent() const noexcept { + return extent_; + } + [[nodiscard]] bool available() const noexcept { + return !quarantined_ && !recording_ && !prepared_ && !in_flight_; + } + [[nodiscard]] bool quiescent() const noexcept { + return !recording_ && !prepared_ && !in_flight_; + } + void abort() noexcept { + if (recording_ && commands_ != nullptr) dvz_cmd_reset(commands_); + recording_ = false; + prepared_ = false; + observing_ = false; + readback_requested_ = false; + } +private: + void release_without_destroy() noexcept { + query_pool_ = VK_NULL_HANDLE; + readback_ = nullptr; + fence_ = nullptr; + submit_ = nullptr; + commands_ = nullptr; + view_ = nullptr; + image_ = nullptr; + } + void initialize_timestamps(not_null device, + not_null queue) noexcept { + timestamps_initialized_ = true; + if (vkGetPhysicalDeviceQueueFamilyProperties == nullptr || + vkGetPhysicalDeviceProperties == nullptr || + vkCreateQueryPool == nullptr || + vkCmdResetQueryPool == nullptr || + vkCmdWriteTimestamp == nullptr || + vkGetQueryPoolResults == nullptr) + return; + const VkPhysicalDevice physical = + dvz_device_physical_device(device); + const VkDevice logical = dvz_device_handle(device); + if (physical == VK_NULL_HANDLE || logical == VK_NULL_HANDLE) return; + std::uint32_t family_count{}; + vkGetPhysicalDeviceQueueFamilyProperties( + physical, &family_count, nullptr); + if (family_count == 0) return; + std::vector families(family_count); + vkGetPhysicalDeviceQueueFamilyProperties( + physical, &family_count, families.data()); + const std::uint32_t family = dvz_queue_family(queue); + if (family >= family_count || + families[family].timestampValidBits == 0) + return; + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physical, &properties); + VkQueryPoolCreateInfo configuration{ + VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO + }; + configuration.queryType = VK_QUERY_TYPE_TIMESTAMP; + configuration.queryCount = 4; + if (vkCreateQueryPool(logical, &configuration, nullptr, + &query_pool_) != VK_SUCCESS) { + query_pool_ = VK_NULL_HANDLE; + return; + } + timestamp_period_ns_ = properties.limits.timestampPeriod; + timestamp_valid_bits_ = families[family].timestampValidBits; + timestamps_supported_ = timestamp_period_ns_ > 0.0F; + } + [[nodiscard]] std::optional collect_gpu_timing() const noexcept { + if (!observing_ || !timestamps_supported_ || + query_pool_ == VK_NULL_HANDLE) + return std::nullopt; + const VkDevice device = + dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)); + std::array timestamps{}; + if (vkGetQueryPoolResults( + device, query_pool_, 0, + static_cast(timestamps.size()), + sizeof(timestamps), timestamps.data(), sizeof(std::uint64_t), + VK_QUERY_RESULT_64_BIT) != VK_SUCCESS) + return std::nullopt; + const auto elapsed = [this](std::uint64_t begin, + std::uint64_t end) noexcept { + std::uint64_t ticks = end - begin; + if (timestamp_valid_bits_ < 64) { + const std::uint64_t mask = + (std::uint64_t{1} << timestamp_valid_bits_) - 1; + ticks &= mask; + } + const long double nanoseconds = + static_cast(ticks) * timestamp_period_ns_; + return nanoseconds >= + static_cast( + std::numeric_limits::max()) + ? std::numeric_limits::max() + : static_cast(nanoseconds); + }; + auto timing = Datoviz_Gpu_Timing{ + elapsed(timestamps[0], timestamps[1]), + elapsed(timestamps[1], timestamps[2]), + elapsed(timestamps[2], timestamps[3]), + elapsed(timestamps[0], timestamps[3]) + }; + if (!readback_requested_) timing.copy_ns = 0; + return timing; + } + void destroy() { + if (recording_ || prepared_ || in_flight_) + throw std::logic_error( + "Datoviz frame target is busy during destruction"); + if (query_pool_ != VK_NULL_HANDLE && gpu_context_ != nullptr) { + vkDestroyQueryPool( + dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)), + query_pool_, nullptr); + query_pool_ = VK_NULL_HANDLE; + } + if (readback_ != nullptr) { + dvz_buffer_destroy(readback_); + dvz_buffer_free(readback_); + readback_ = nullptr; + } + if (fence_ != nullptr) { + dvz_fence_destroy(fence_); + dvz_fence_free(fence_); + fence_ = nullptr; + } + if (submit_ != nullptr) { + dvz_submit_free(submit_); + submit_ = nullptr; + } + if (commands_ != nullptr) { + dvz_commands_destroy(commands_); + dvz_commands_free(commands_); + commands_ = nullptr; + } + if (view_ != nullptr) { + dvz_image_views_destroy(view_); + dvz_image_views_free(view_); + view_ = nullptr; + } + if (image_ != nullptr) { + dvz_images_destroy(image_); + dvz_images_free(image_); + image_ = nullptr; + } + } + not_null gpu_context_; + Extent extent_{}; + std::uint64_t generation_{}; + DvzSize byte_size_{}; + owner image_{}; + owner view_{}; + owner commands_{}; + owner fence_{}; + owner submit_{}; + owner readback_{}; + VkQueryPool query_pool_{VK_NULL_HANDLE}; + VkImageLayout completed_layout_{VK_IMAGE_LAYOUT_UNDEFINED}; + float timestamp_period_ns_{}; + std::uint32_t timestamp_valid_bits_{}; + bool recording_{}; + bool prepared_{}; + bool in_flight_{}; + bool observing_{}; + bool readback_requested_{}; + bool timestamps_initialized_{}; + bool timestamps_supported_{}; + std::uint64_t recording_command_revision_{}; + std::uint64_t recorded_command_revision_{}; + std::uint64_t recording_controller_revision_{}; + std::uint64_t recorded_controller_revision_{}; + bool recorded_{}; + bool recorded_readback_{}; + bool quarantined_{}; +}; +struct Datoviz_Frame_Target_Set { + static constexpr std::size_t count = 3; + std::array, count> values{}; /* 固定三槽,槽地址在后端生命周期内稳定。 */ +}; diff --git a/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp b/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp new file mode 100644 index 0000000..0d8fb8d --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp @@ -0,0 +1,77 @@ +void Scene_Datoviz_State::mark_controller_input_applied() noexcept { + input_changed_ = true; + ++controller_revision_; + if (controller_revision_ == 0) controller_revision_ = 1; +} +void Scene_Datoviz_State::dispatch_pointer( + ::aethera::Event_Type event, float x, float y, + ::aethera::Mouse_Button mouse_button, + ::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport, + std::uint64_t occurred_at_ns) { + if (applied_camera_ && applied_camera_->controller == Camera_Controller::turntable) { + if (mouse_button == ::aethera::Mouse_Button::left && + !applied_camera_->turntable_control.rotate_enabled) + return; + if ((mouse_button == ::aethera::Mouse_Button::middle || + mouse_button == ::aethera::Mouse_Button::right) && + !applied_camera_->turntable_control.pan_enabled) + return; + } + const float width = static_cast(viewport.width); + const float height = static_cast(viewport.height); + const DvzPointerEventType type = + event == ::aethera::Event_Type::pointer_move + ? DVZ_POINTER_EVENT_MOVE + : event == ::aethera::Event_Type::pointer_press + ? DVZ_POINTER_EVENT_PRESS + : DVZ_POINTER_EVENT_RELEASE; + dvz_pointer_emit_position(input_router_, type, x, y, width, height, + button(mouse_button), + modifiers(keyboard_modifiers), 1.0F, + occurred_at_ns, nullptr); + mark_controller_input_applied(); +} +void Scene_Datoviz_State::dispatch_wheel( + float x, float y, float delta_x, float delta_y, + ::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport, + std::uint64_t occurred_at_ns) { + if (applied_camera_ && + applied_camera_->controller == Camera_Controller::turntable && + !applied_camera_->turntable_control.zoom_enabled) + return; + dvz_pointer_emit_wheel( + input_router_, x, y, static_cast(viewport.width), + static_cast(viewport.height), delta_x, delta_y, + modifiers(keyboard_modifiers), 1.0F, occurred_at_ns, nullptr); + mark_controller_input_applied(); +} +void Scene_Datoviz_State::dispatch_key( + const ::aethera::Key_Event& event) { + if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) { + 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"); + mark_controller_input_applied(); + return; + } + const DvzKeyboardEventType type = + event.type == ::aethera::Event_Type::key_release + ? DVZ_KEYBOARD_EVENT_RELEASE + : event.auto_repeat + ? DVZ_KEYBOARD_EVENT_REPEAT + : DVZ_KEYBOARD_EVENT_PRESS; + dvz_keyboard_emit(input_router_, type, + key_code(event.key, event.native_key), + modifiers(event.modifiers), nullptr); + mark_controller_input_applied(); +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp new file mode 100644 index 0000000..92c3f54 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp @@ -0,0 +1,397 @@ +#include "Scene_Datoviz_State.hpp" +#include "../detail/Exception.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace aethera::render_3d::detail { +namespace { +constexpr std::uint64_t color_target_id = 0x5256504f494e54ULL; +DvzCapabilitySnapshot offscreen_capabilities() { + auto capabilities = dvz_capability_snapshot(); + capabilities.supports_color_blending = true; + return capabilities; +} +std::uint64_t trace_now_ns() noexcept { + return static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} +template +owner allocate_wrapper(Allocate allocate, const char* message) { + owner resource = allocate(); + if (resource == nullptr) throw std::runtime_error(message); + return resource; +} +int modifiers(::aethera::Keyboard_Modifier value) { + const auto bits = static_cast(value); + int result = DVZ_KEY_MODIFIER_NONE; + if ((bits & static_cast(::aethera::Keyboard_Modifier::shift)) != 0) result |= DVZ_KEY_MODIFIER_SHIFT; + if ((bits & static_cast(::aethera::Keyboard_Modifier::control)) != 0) result |= DVZ_KEY_MODIFIER_CONTROL; + if ((bits & static_cast(::aethera::Keyboard_Modifier::alt)) != 0) result |= DVZ_KEY_MODIFIER_ALT; + if ((bits & static_cast(::aethera::Keyboard_Modifier::meta)) != 0) result |= DVZ_KEY_MODIFIER_SUPER; + return result; +} +DvzPointerButton button(::aethera::Mouse_Button value) { + switch (value) { + case ::aethera::Mouse_Button::left: return DVZ_POINTER_BUTTON_LEFT; + case ::aethera::Mouse_Button::middle: return DVZ_POINTER_BUTTON_MIDDLE; + case ::aethera::Mouse_Button::right: return DVZ_POINTER_BUTTON_RIGHT; + case ::aethera::Mouse_Button::none: return DVZ_POINTER_BUTTON_NONE; + } + return DVZ_POINTER_BUTTON_NONE; +} +DvzKeyCode key_code(::aethera::Key key, std::uint32_t native_key) { + switch (key) { + case ::aethera::Key::escape: return DVZ_KEY_ESCAPE; + case ::aethera::Key::enter: return DVZ_KEY_ENTER; + case ::aethera::Key::space: return DVZ_KEY_SPACE; + case ::aethera::Key::delete_key: return DVZ_KEY_DELETE; + case ::aethera::Key::backspace: return DVZ_KEY_BACKSPACE; + case ::aethera::Key::left: return DVZ_KEY_LEFT; + case ::aethera::Key::right: return DVZ_KEY_RIGHT; + case ::aethera::Key::up: return DVZ_KEY_UP; + case ::aethera::Key::down: return DVZ_KEY_DOWN; + case ::aethera::Key::home: return DVZ_KEY_HOME; + case ::aethera::Key::unknown: break; + } + return native_key <= static_cast(DVZ_KEY_LAST) + ? static_cast(native_key) + : DVZ_KEY_UNKNOWN; +} +DvzShapeAspect aspect(Point_Aspect value) { + switch (value) { + case Point_Aspect::filled: return DVZ_SHAPE_ASPECT_FILLED; + case Point_Aspect::stroke: return DVZ_SHAPE_ASPECT_STROKE; + case Point_Aspect::outline: return DVZ_SHAPE_ASPECT_OUTLINE; + } + return DVZ_SHAPE_ASPECT_FILLED; +} +float axis_position(const plot::Axis_Descriptor& axis, double coordinate) { + const auto origin = axis.range.origin; + const auto target = axis.range.target; + if (!std::isfinite(origin) || !std::isfinite(target) || origin == target) throw std::invalid_argument("3D axis range must be finite and non-empty"); + double ratio{}; + if (axis.scale == plot::Axis_Scale::logarithmic) { + if (!(origin > 0.0) || !(target > 0.0) || !(coordinate > 0.0)) throw std::invalid_argument("logarithmic 3D axis coordinates must be positive"); + ratio = (std::log10(coordinate) - std::log10(origin)) / + (std::log10(target) - std::log10(origin)); + } + else { + ratio = (coordinate - origin) / (target - origin); + } + return static_cast(-1.0 + 2.0 * ratio); +} +std::string axis_title(const plot::Axis_Descriptor& axis) { + if (axis.label.empty()) return axis.unit; + if (axis.unit.empty()) return axis.label; + return axis.label + " (" + axis.unit + ")"; +} +DvzFont* configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) { + auto font_descriptor = dvz_font_desc(); + font_descriptor.family = "Roboto"; + font_descriptor.style = "Regular"; + auto* font = dvz_font(scene, &font_descriptor); + auto atlas_specification = + dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); + if (font == nullptr || + !dvz_font_atlas_ensure_string(font, &atlas_specification, text)) + throw std::runtime_error("failed to create Datoviz text atlas"); + const auto* atlas = dvz_font_atlas(font, &atlas_specification); + if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) throw std::runtime_error("failed to bind Datoviz text atlas"); + float line_width = 0.0F; + for (const auto* character = text; *character != '\0'; ++character) { + const auto* glyph = + dvz_text_atlas_glyph(atlas, static_cast(*character)); + if (glyph != nullptr) line_width += glyph->advance; + } + std::vector> positions; + std::vector> bounds; + std::vector> texture_coordinates; + std::vector> colors; + std::vector angles; + const auto atlas_info = dvz_text_atlas_info(atlas); + float cursor_x = 0.0F; + std::size_t glyph_index = 0; + for (const auto* character = text; *character != '\0'; ++character) { + const auto* glyph = + dvz_text_atlas_glyph(atlas, static_cast(*character)); + if (glyph == nullptr) continue; + const float x0 = cursor_x + glyph->xoff - 0.5F * line_width; + const float y0 = 0.5F * atlas_info.ascent + glyph->yoff; + const std::array glyph_bounds{ + x0, y0, x0 + glyph->width, y0 + glyph->height + }; + const std::array glyph_texture{ + glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3] + }; + const std::array glyph_color = + glyph_index % 2 == 0 + ? std::array{40, 235, 205, 255} + : std::array{255, 190, 80, 255}; + for (std::uint32_t vertex = 0; vertex < 6; ++vertex) { + positions.push_back({0.0F, 0.0F, 0.0F}); + bounds.push_back(glyph_bounds); + texture_coordinates.push_back(glyph_texture); + colors.push_back(glyph_color); + angles.push_back(0.0F); + } + cursor_x += glyph->advance; + ++glyph_index; + } + if (positions.empty()) throw std::runtime_error("Datoviz text atlas contains no visible glyphs"); + const auto count = static_cast(positions.size()); + const std::array updates{ + { + {"position", positions.data(), count}, {"bounds", bounds.data(), count}, + {"texcoords", texture_coordinates.data(), count}, + {"color", colors.data(), count}, {"angle", angles.data(), count} + } + }; + if (dvz_visual_set_data_many(visual, updates.data(), + static_cast(updates.size())) != DVZ_OK || + dvz_visual_set_depth_test(visual, false) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz text geometry"); + return font; +} +std::vector utf8_codepoints(std::string_view text) { + std::vector result; + result.reserve(text.size()); + for (std::size_t index = 0; index < text.size();) { + const auto lead = static_cast(text[index]); + std::uint32_t codepoint{}; + std::size_t length{}; + if (lead < 0x80U) { + codepoint = lead; + length = 1; + } + else if ((lead & 0xE0U) == 0xC0U) { + codepoint = lead & 0x1FU; + length = 2; + } + else if ((lead & 0xF0U) == 0xE0U) { + codepoint = lead & 0x0FU; + length = 3; + } + else if ((lead & 0xF8U) == 0xF0U) { + codepoint = lead & 0x07U; + length = 4; + } + else { + throw std::invalid_argument("3D text contains invalid UTF-8"); + } + if (index + length > text.size()) throw std::invalid_argument("3D text contains truncated UTF-8"); + for (std::size_t offset = 1; offset < length; ++offset) { + const auto continuation = + static_cast(text[index + offset]); + if ((continuation & 0xC0U) != 0x80U) throw std::invalid_argument("3D text contains invalid UTF-8"); + codepoint = (codepoint << 6U) | (continuation & 0x3FU); + } + if ((length == 2 && codepoint < 0x80U) || + (length == 3 && codepoint < 0x800U) || + (length == 4 && codepoint < 0x10000U) || + codepoint > 0x10FFFFU || + (codepoint >= 0xD800U && codepoint <= 0xDFFFU)) + throw std::invalid_argument("3D text contains non-canonical UTF-8"); + result.push_back(codepoint); + index += length; + } + return result; +} +void upload_text(DvzVisual* visual, DvzFont* font, + const Text_Prepared_Data& data) { + if (font == nullptr) throw std::logic_error("Datoviz text visual has no font"); + auto atlas_specification = + dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); + for (const auto& text : data.strings) + if (!dvz_font_atlas_ensure_string( + font, &atlas_specification, text.c_str())) + throw std::runtime_error("failed to grow Datoviz text atlas"); + const auto* atlas = dvz_font_atlas(font, &atlas_specification); + if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) throw std::runtime_error("failed to bind Datoviz text atlas"); + std::vector> positions; + std::vector> bounds; + std::vector> texture_coordinates; + std::vector> colors; + std::vector angles; + for (std::size_t item_index = 0; item_index < data.strings.size(); + ++item_index) { + const auto& text = data.strings[item_index]; + const auto codepoints = utf8_codepoints(text); + float line_width{}; + for (const auto codepoint : codepoints) { + const auto* glyph = dvz_text_atlas_glyph(atlas, codepoint); + if (glyph != nullptr) line_width += glyph->advance; + } + const float scale = data.sizes[item_index] / 64.0F; + float cursor_x{}; + for (const auto codepoint : codepoints) { + const auto* glyph = dvz_text_atlas_glyph(atlas, codepoint); + if (glyph == nullptr) continue; + const float x0 = (cursor_x + glyph->xoff - 0.5F * line_width) * scale; + const float y0 = glyph->yoff * scale; + const std::array glyph_bounds{ + x0, y0, x0 + glyph->width * scale, + y0 + glyph->height * scale + }; + const std::array glyph_texture{ + glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3] + }; + for (std::uint32_t vertex = 0; vertex < 6; ++vertex) { + positions.push_back(data.positions[item_index]); + bounds.push_back(glyph_bounds); + texture_coordinates.push_back(glyph_texture); + colors.push_back(data.colors[item_index]); + angles.push_back(0.0F); + } + cursor_x += glyph->advance; + } + } + const auto count = static_cast(positions.size()); + const std::array updates{ + { + {"position", positions.data(), count}, + {"bounds", bounds.data(), count}, + {"texcoords", texture_coordinates.data(), count}, + {"color", colors.data(), count}, + {"angle", angles.data(), count} + } + }; + if (dvz_visual_set_data_many( + visual, updates.data(), + static_cast(updates.size())) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz text payload"); +} +bool has_payload(const Prepared_Visual& visual) noexcept { + return visual.operations != nullptr && visual.data != nullptr; +} +std::size_t prepared_item_count(const Prepared_Visual& visual) noexcept { + return has_payload(visual) + ? visual.operations->item_count(not_null{visual.data.get()}) + : 0; +} +bool has_coordinate_labels(const Prepared_Visual& visual) noexcept { + return has_payload(visual) && + visual.operations->has_coordinate_labels != nullptr && + visual.operations->has_coordinate_labels( + not_null{visual.data.get()}); +} +bool same_visual_structure(const Prepared_Visual& applied, + const Prepared_Visual& incoming) noexcept { + if (applied.operations != incoming.operations || + applied.transform != incoming.transform || + applied.visible != incoming.visible || + applied.depth_test != incoming.depth_test || + !has_payload(applied) || !has_payload(incoming) || + prepared_item_count(applied) != prepared_item_count(incoming)) + return false; + if (incoming.operations->has_coordinate_labels != nullptr && + (has_coordinate_labels(applied) || has_coordinate_labels(incoming))) + return applied.revision == incoming.revision; + return true; +} +std::string artifact_command_excerpt(const std::string& json, + std::uint32_t command_index) { + constexpr std::string_view marker{"{ \"cmd\":"}; + const auto first_index = command_index > 12 ? command_index - 12 : 0; + std::size_t position{}; + std::size_t first_position{}; + for (std::uint32_t index = 0; index <= command_index; ++index) { + position = json.find(marker, position); + if (position == std::string::npos) return {}; + if (index == first_index) first_position = position; + if (index != command_index) position += marker.size(); + } + const auto end = json.find('\n', position); + return json.substr(first_position, + end == std::string::npos ? 2048 : end - first_position); +} +} // namespace +struct Datoviz_Render_Context final : + std::enable_shared_from_this { +public: + [[nodiscard]] static std::shared_ptr acquire( + std::uint32_t gpu_index, bool validation_enabled) { + return std::shared_ptr( + new Datoviz_Render_Context(gpu_index, validation_enabled)); + } + ~Datoviz_Render_Context() { + if (gpu_context_ != nullptr) dvz_gpu_ctx_destroy(gpu_context_); + } + [[nodiscard]] not_null gpu_context() const noexcept { + return not_null{gpu_context_}; + } + void enqueue_submission(std::function command) { + if (!command) throw std::invalid_argument("empty Datoviz queue command"); + if (!submissions_.enqueue(std::move(command))) throw std::bad_alloc{}; + submission_generation_.fetch_add(1, std::memory_order_release); + arm_submission_drain(); + } +private: + Datoviz_Render_Context(std::uint32_t gpu_index, bool validation_enabled) { + DvzGpuCtxConfig configuration = dvz_gpu_ctx_config(); + dvz_gpu_ctx_config_validation(&configuration, validation_enabled); + dvz_gpu_ctx_config_gpu(&configuration, gpu_index); + dvz_gpu_ctx_config_enable_canvas_extensions(&configuration, false); + gpu_context_ = dvz_gpu_ctx(&configuration); + if (gpu_context_ == nullptr) + throw std::runtime_error("failed to create Scene Datoviz GPU context"); + } + void arm_submission_drain() { + if (submission_drain_active_.exchange(true, std::memory_order_acq_rel)) return; + auto lifetime = shared_from_this(); + aethera::schedule_task("datoviz.queue.submit", [lifetime = std::move(lifetime)] { + lifetime->drain_submissions(); + }); + } + void drain_submissions() noexcept { + const auto observed = submission_generation_.load(std::memory_order_acquire); + std::function command; + while (submissions_.try_dequeue(command)) { + try { command(); } + catch (...) {} + command = {}; + } + submission_drain_active_.store(false, std::memory_order_release); + if (submission_generation_.load(std::memory_order_acquire) != observed) + arm_submission_drain(); + } + owner gpu_context_{}; /* 当前 Scene 独占 GPU Device 与分配器。 */ + moodycamel::ConcurrentQueue> submissions_{}; + std::atomic_uint64_t submission_generation_{}; + std::atomic_bool submission_drain_active_{}; +}; +void retain_quarantined_context( + std::shared_ptr& context) noexcept { + if (!context) return; + static moodycamel::ConcurrentQueue< + std::shared_ptr> quarantined; + if (quarantined.enqueue(context)) + context.reset(); + else + static_cast(new(std::nothrow) + std::shared_ptr(std::move(context))); +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Components.hpp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Components.hpp new file mode 100644 index 0000000..663ad99 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Components.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "Datoviz_Frame_State.hpp" +#include "Datoviz_Visual_State.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct DvzBuffer; +struct DvzController; +struct DvzDrp2Runtime; +struct DvzFigure; +struct DvzFramePlanEmitter; +struct DvzInputRouter; +struct DvzItemInteraction; +struct DvzPanel; +struct DvzPinnedReadout; +struct DvzPointerGestureHandler; +struct DvzScene; +struct DvzText; +struct DvzVisual; + +namespace aethera::render_3d::detail { + +struct Datoviz_Render_Context; + +struct Datoviz_Runtime_Slot { + owner runtime{}; /* 本目标槽独占的 DRP2 Runtime。 */ + owner emitter{}; /* 本目标槽独占的帧计划 emitter。 */ +}; + +struct Datoviz_Native_Scene { + std::shared_ptr render_context_{}; /* 本 Scene 独占 GPU 上下文的共享销毁门闩。 */ + std::uintptr_t command_pool_{}; /* 本 Scene 独占 Vulkan Command Pool。 */ + std::uintptr_t descriptor_pool_{}; /* 本 Scene 独占 Vulkan Descriptor Pool。 */ + std::array runtime_slots_{}; /* 三个固定目标槽的 Runtime。 */ + owner scene_{}; /* Datoviz Scene 所有权。 */ + DvzFigure* figure_{}; /* 可空、非拥有;由 scene_ 拥有。 */ + DvzPanel* panel_{}; /* 可空、非拥有;由 scene_ 拥有。 */ + DvzVisual* axes_visual_{}; /* 可空、非拥有;由 scene_ 拥有。 */ + DvzText* axes_text_{}; /* 可空、非拥有;由 scene_ 拥有。 */ + Extent figure_extent_{}; /* 当前原生 Figure 尺寸。 */ + std::uint64_t command_revision_{1}; /* 原生命令结构版本。 */ + std::optional applied_camera_{}; /* 已应用相机,仅用于结构复用判定。 */ + std::optional> applied_axes_{}; /* 已应用坐标轴。 */ + std::atomic_bool quarantined_{}; /* Unknown Failure 后是否禁止销毁在途资源。 */ +}; + +struct Datoviz_Visual_Set { + std::vector visuals_{}; /* 注册顺序稳定的具体 Visual 集合。 */ + std::vector> external_buffers_{}; /* Visual 三槽属性 Buffer 所有权。 */ +}; + +struct Datoviz_Interaction_State { + owner item_interaction_{}; /* item query 能力所有权。 */ + owner hover_readout_{}; /* 当前 hover readout 所有权。 */ + owner camera_controller_{}; /* 相机控制器所有权。 */ + owner input_router_{}; /* 输入路由所有权。 */ + owner gesture_handler_{}; /* 指针手势处理器所有权。 */ + std::uint64_t controller_revision_{1}; /* 已应用控制器输入版本。 */ + bool input_changed_{}; /* 当前录制周期是否消费了输入。 */ +}; + +struct Datoviz_Frame_Pipeline { + Datoviz_Frame_Pipeline(); + mutable std::mutex target_mutex_{}; /* 仅保护三槽在提交与回读边界的生命周期。 */ + std::unique_ptr targets_{}; /* 三槽唯一所有权。 */ + std::uint64_t target_generation_{}; /* 目标槽重建代数。 */ +}; + +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp new file mode 100644 index 0000000..82d1be7 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp @@ -0,0 +1,435 @@ +Scene_Datoviz_State::Scene_Datoviz_State() = default; +Scene_Datoviz_State::~Scene_Datoviz_State() { + try { + destroy(); + } + catch (...) { + abandon_resources(); + } +} +void Scene_Datoviz_State::initialize( + std::uint32_t gpu_index, bool validation_enabled, + const std::vector& visuals, + const Scene_3D_Parameters& initial_scene) { + try { + render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled); + const auto gpu_context = render_context_->gpu_context(); + DvzDevice* device = dvz_gpu_ctx_device(gpu_context.get()); + DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context.get(), DVZ_QUEUE_MAIN); + VkDevice vk_device = dvz_device_handle(device); + VkCommandPoolCreateInfo command_pool_info{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = dvz_queue_family(queue)}; + VkCommandPool command_pool{VK_NULL_HANDLE}; + if (vkCreateCommandPool(vk_device, &command_pool_info, nullptr, + &command_pool) != VK_SUCCESS) + throw std::runtime_error("failed to create Scene command pool"); + command_pool_ = reinterpret_cast(command_pool); + std::array pool_sizes{{ + {VK_DESCRIPTOR_TYPE_SAMPLER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, DVZ_MAX_DESCRIPTOR_SETS}, + {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, DVZ_MAX_DESCRIPTOR_SETS}}}; + VkDescriptorPoolCreateInfo descriptor_pool_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = DVZ_MAX_DESCRIPTOR_SETS * + static_cast(pool_sizes.size()), + .poolSizeCount = static_cast(pool_sizes.size()), + .pPoolSizes = pool_sizes.data()}; + VkDescriptorPool descriptor_pool{VK_NULL_HANDLE}; + if (vkCreateDescriptorPool(vk_device, &descriptor_pool_info, nullptr, + &descriptor_pool) != VK_SUCCESS) + throw std::runtime_error("failed to create Scene descriptor pool"); + descriptor_pool_ = reinterpret_cast(descriptor_pool); + DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config( + device, + dvz_gpu_ctx_alloc(gpu_context.get())); + dvz_drp2_runtime_vklite_pools( + &runtime_configuration, + command_pool_, descriptor_pool_); + for (auto& slot : runtime_slots_) { + slot.runtime = dvz_drp2_runtime_vklite(&runtime_configuration); + slot.emitter = dvz_frame_plan_emitter(); + if (slot.runtime == nullptr || slot.emitter == nullptr) throw std::runtime_error("failed to create a Datoviz frame-target runtime"); + } + create_scene(visuals, initial_scene); + } + catch (...) { + const auto failure = std::current_exception(); + try { + destroy(); + } + catch (...) {} + raise_context("creating Datoviz backend", failure); + } +} +void Scene_Datoviz_State::create_scene( + const std::vector& 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(); + if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK) throw std::runtime_error("failed to configure Datoviz scene capabilities"); + auto* figure = dvz_figure(scene_, initial_scene.viewport.width, + initial_scene.viewport.height, 0); + if (figure == nullptr) throw std::runtime_error("failed to create Datoviz figure"); + auto* panel = dvz_panel_full(figure); + if (panel == nullptr) throw std::runtime_error("failed to create Datoviz panel"); + figure_ = not_null{figure}; + panel_ = not_null{panel}; + bool wants_item_interaction{}; + visuals_.reserve(registrations.size()); + for (const auto& registration : registrations) { + if (registration.identity == 0 || + std::ranges::any_of(visuals_, [&](const Datoviz_Visual_Instance& value) { + return value.identity == registration.identity; + })) + throw std::invalid_argument("Datoviz backend received duplicate Visual identity"); + const auto native = registration.operations->create( + not_null{scene_}, not_null{panel}); + auto visual = native.visual; + if (dvz_visual_set_alpha_mode( + visual, DVZ_ALPHA_BLENDED) != DVZ_OK || + dvz_panel_add_visual(panel, visual, nullptr) != DVZ_OK) + throw std::runtime_error( + "failed to attach Datoviz Visual to panel"); + if (registration.operations->supports_item_interaction) { + if (dvz_visual_set_query_capabilities( + 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, registration.operations, visual, + native.field, native.font, native.coordinate_text, + native.field_extent, 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"); + } + auto* axes_visual = dvz_segment(scene_, 0); + auto* axes_text = dvz_text(panel, 0); + if (axes_visual == nullptr || axes_text == nullptr || + dvz_segment_set_caps(axes_visual, DVZ_SEGMENT_CAP_BUTT, + DVZ_SEGMENT_CAP_BUTT) != DVZ_OK || + dvz_visual_set_alpha_mode(axes_visual, DVZ_ALPHA_OPAQUE) != DVZ_OK || + dvz_panel_add_visual(panel, axes_visual, nullptr) != DVZ_OK) + throw std::runtime_error("failed to create Datoviz 3D axes visuals"); + axes_visual_ = not_null{axes_visual}; + axes_text_ = not_null{axes_text}; + DvzTextPlacement axes_placement = dvz_text_placement(); + axes_placement.mode = DVZ_TEXT_PLACEMENT_DATA; + axes_placement.anchor = DVZ_SCENE_ANCHOR_DATA; + axes_placement.depth_test = false; + DvzTextStyle axes_style = dvz_text_style(); + axes_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS; + axes_style.size_px = 12.0F; + if (dvz_text_set_placement(axes_text, &axes_placement) != DVZ_OK || + dvz_text_set_style(axes_text, &axes_style) != DVZ_OK) + throw std::runtime_error("failed to configure Datoviz 3D axes text"); + apply_axes(initial_scene); + input_router_ = dvz_input_router(); + gesture_handler_ = input_router_ != nullptr + ? dvz_pointer_gesture_handler(input_router_) + : nullptr; + if (input_router_ == nullptr || gesture_handler_ == nullptr || + dvz_panel_connect_input(panel, input_router_) != DVZ_OK) + throw std::runtime_error("failed to connect Datoviz point input"); + apply_camera(initial_scene.camera); + DvzInputResizeEvent resize{ + initial_scene.viewport.width, initial_scene.viewport.height, + initial_scene.viewport.width, initial_scene.viewport.height, 1.0F, 1.0F + }; + dvz_input_emit_resize(input_router_, &resize); +} +void Scene_Datoviz_State::apply_axes( + const Scene_3D_Parameters& scene) { + const std::array descriptors{scene.x_axis, scene.y_axis, scene.z_axis}; + if (applied_axes_ && *applied_axes_ == descriptors) return; + using Position = std::array; + std::vector starts; + std::vector ends; + std::vector colors; + std::vector widths; + const DvzColor axis_color{190, 207, 226, 255}; + const DvzColor grid_color{58, 70, 86, 255}; + const DvzColor tick_color{145, 164, 188, 255}; + const auto segment = [&](Position start, Position end, DvzColor color, + float width) { + starts.push_back(start); + ends.push_back(end); + colors.push_back(color); + widths.push_back(width); + }; + std::vector strings; + std::vector text_items; + const auto text = [&](std::string value, Position position, + std::array offset, + std::array anchor, float size, + DvzColor color) { + strings.push_back(std::move(value)); + DvzTextItem item{}; + item.struct_size = sizeof(DvzTextItem); + item.position[0] = position[0]; + item.position[1] = position[1]; + item.position[2] = position[2]; + item.offset[0] = offset[0]; + item.offset[1] = offset[1]; + item.anchor[0] = anchor[0]; + item.anchor[1] = anchor[1]; + item.size_px = size; + item.color = color; + text_items.push_back(item); + }; + const auto append_axis = [&](const plot::Axis_Descriptor& axis, + std::size_t dimension) { + if (!axis.visible) return; + const auto ticks = plot::axis_ticks(axis); + if (dimension == 0) segment({-1, -1, -1}, {1, -1, -1}, axis_color, 2.2F); + else if (dimension == 1) segment({-1, -1, -1}, {-1, 1, -1}, axis_color, 2.2F); + else segment({-1, -1, -1}, {-1, -1, 1}, axis_color, 2.2F); + for (const auto& tick : ticks) { + const auto position = axis_position(axis, tick.coordinate); + if (dimension == 0) { + segment({position, -1, -1}, {position, -1.055F, -1}, + tick_color, 1.4F); + if (axis.grid_visible) + segment({position, -1, -1}, {position, 1, -1}, + grid_color, 1.0F); + if (axis.labels_visible) + text(tick.label, {position, -1, -1}, {0, 12}, {.5F, 0}, + 11, tick_color); + } + else if (dimension == 1) { + segment({-1, position, -1}, {-1.055F, position, -1}, + tick_color, 1.4F); + if (axis.grid_visible) + segment({-1, position, -1}, {1, position, -1}, + grid_color, 1.0F); + if (axis.labels_visible) + text(tick.label, {-1, position, -1}, {-10, 0}, {1, .5F}, + 11, tick_color); + } + else { + segment({-1, -1, position}, {-1.055F, -1, position}, + tick_color, 1.4F); + if (axis.grid_visible) { + segment({-1, -1, position}, {1, -1, position}, + grid_color, 1.0F); + segment({-1, -1, position}, {-1, 1, position}, + grid_color, 1.0F); + } + if (axis.labels_visible) + text(tick.label, {-1, -1, position}, {-10, 0}, {1, .5F}, + 11, tick_color); + } + } + const auto title = axis_title(axis); + if (title.empty()) return; + if (dimension == 0) text(title, {0, -1, -1}, {0, 34}, {.5F, 0}, 14, axis_color); + else if (dimension == 1) text(title, {-1, 0, -1}, {-78, 0}, {1, .5F}, 14, axis_color); + else text(title, {-1, -1, 0}, {-78, 0}, {1, .5F}, 14, axis_color); + }; + append_axis(descriptors[0], 0); + append_axis(descriptors[1], 1); + append_axis(descriptors[2], 2); + const auto segment_count = static_cast(starts.size()); + const std::array updates{ + { + {"position_start", starts.data(), segment_count}, + {"position_end", ends.data(), segment_count}, + {"color", colors.data(), segment_count}, + {"stroke_width_px", widths.data(), segment_count} + } + }; + if (dvz_visual_set_data_many(axes_visual_, updates.data(), + static_cast(updates.size())) != DVZ_OK || + dvz_visual_set_visible(axes_visual_, !starts.empty()) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz 3D axes geometry"); + for (std::size_t index = 0; index < text_items.size(); ++index) text_items[index].string = strings[index].c_str(); + if (dvz_text_set_items( + axes_text_, text_items.empty() ? nullptr : text_items.data(), + static_cast(text_items.size())) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz 3D axes labels"); + applied_axes_ = descriptors; +} +void Scene_Datoviz_State::apply_camera( + const Camera_Descriptor& source) { + if (applied_camera_ && *applied_camera_ == source) return; + if (camera_controller_ != nullptr) { + dvz_controller_destroy(camera_controller_); + camera_controller_ = nullptr; + } + DvzCameraDesc camera = dvz_camera_desc(); + const auto assign = [](vec3 target, Spatial_Point value) { + target[0] = static_cast(value.x); + target[1] = static_cast(value.y); + target[2] = static_cast(value.z); + }; + assign(camera.view.eye, source.initial_view.eye); + assign(camera.view.target, source.initial_view.target); + assign(camera.view.up, source.initial_view.up); + camera.projection.type = source.projection == Camera_Projection::orthographic + ? DVZ_CAMERA_ORTHOGRAPHIC + : DVZ_CAMERA_PERSPECTIVE; + camera.projection.fov_y = static_cast( + source.vertical_field_of_view_degrees * std::numbers::pi / 180.0); + camera.projection.near_clip = static_cast(source.near_plane); + camera.projection.far_clip = static_cast(source.far_plane); + const auto dx = source.initial_view.eye.x - source.initial_view.target.x; + const auto dy = source.initial_view.eye.y - source.initial_view.target.y; + const auto dz = source.initial_view.eye.z - source.initial_view.target.z; + const auto distance = std::sqrt(dx * dx + dy * dy + dz * dz); + camera.projection.ortho_height = static_cast( + 2.0 * distance * + 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"); + DvzDimMask dimensions = DVZ_DIM_MASK_XYZ; + switch (source.controller) { + case Camera_Controller::turntable: { + DvzTurntableDesc descriptor = dvz_turntable_desc(); + descriptor.initial_view = camera.view; + descriptor.yaw_speed = static_cast(source.turntable_control.yaw_speed); + descriptor.pitch_speed = static_cast(source.turntable_control.pitch_speed); + descriptor.zoom_speed = static_cast(source.turntable_control.zoom_speed); + descriptor.pan_speed = static_cast(source.turntable_control.pan_speed); + descriptor.min_pitch = static_cast(source.turntable_control.minimum_pitch); + descriptor.max_pitch = static_cast(source.turntable_control.maximum_pitch); + descriptor.min_distance = static_cast(source.turntable_control.minimum_distance); + descriptor.max_distance = static_cast(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 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(source.arcball_control.constraint_axis.x), + static_cast(source.arcball_control.constraint_axis.y), + static_cast(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 Camera_Controller::fly: { + DvzFlyDesc descriptor = dvz_fly_desc(); + descriptor.initial_view = camera.view; + descriptor.mode = source.fly_control.mode == Camera_Fly_Mode::plane + ? DVZ_FLY_MODE_PLANE + : DVZ_FLY_MODE_FREE; + descriptor.speed = static_cast(source.fly_control.movement_speed); + descriptor.fast_multiplier = static_cast(source.fly_control.fast_multiplier); + descriptor.slow_multiplier = static_cast(source.fly_control.slow_multiplier); + descriptor.look_speed = static_cast(source.fly_control.look_speed); + descriptor.wheel_speed = static_cast(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 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; +} +bool Scene_Datoviz_State::matches_command_structure( + const Scene_3D_Parameters& scene, + const Prepared_Visual_Batch& prepared) const { + if (figure_extent_ != scene.viewport || + !applied_camera_ || *applied_camera_ != scene.camera || + !applied_axes_ || *applied_axes_ != + std::array{scene.x_axis, scene.y_axis, scene.z_axis} || + prepared.size() != visuals_.size()) + return false; + for (const auto& source : prepared) { + const auto target = std::ranges::find_if( + visuals_, [&](const Datoviz_Visual_Instance& value) { + return value.identity == source.identity; + }); + if (target == visuals_.end()) return false; + if (!target->applied || + !same_visual_structure(*target->applied, source.visual)) + return false; + } + return true; +} +bool Scene_Datoviz_State::target_runtime_resources_current( + const Prepared_Visual_Batch& prepared, + std::uint8_t target_index) const { + if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + for (const auto& source : prepared) { + const auto target = std::ranges::find_if( + visuals_, [&](const Datoviz_Visual_Instance& value) { + return value.identity == source.identity; + }); + if (target == visuals_.end()) + throw std::logic_error( + "Datoviz runtime update contains an unknown Visual identity"); + if (target->operations->external_attributes.empty() && + target->uploaded_data_revisions[target_index] != + source.visual.data_revision) + return false; + } + return true; +} +std::vector Scene_Datoviz_State::stale_runtime_visuals( + const Prepared_Visual_Batch& prepared, + std::uint8_t target_index) const { + if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + std::vector result; + result.reserve(prepared.size()); + for (const auto& source : prepared) { + const auto target = std::ranges::find_if( + visuals_, [&](const Datoviz_Visual_Instance& value) { + return value.identity == source.identity; + }); + if (target == visuals_.end()) + throw std::logic_error( + "Datoviz runtime update contains an unknown Visual identity"); + if (target->operations->external_attributes.empty() && + target->uploaded_data_revisions[target_index] != + source.visual.data_revision) + result.push_back(target->visual); + } + return result; +} +void Scene_Datoviz_State::mark_target_runtime_resources_uploaded( + std::uint8_t target_index) { + if (target_index >= runtime_slots_.size()) throw std::out_of_range("Datoviz runtime target index is invalid"); + for (auto& visual : visuals_) + if (visual.operations->external_attributes.empty() && visual.applied) + visual.uploaded_data_revisions[target_index] = + visual.applied->data_revision; +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp b/render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp new file mode 100644 index 0000000..e91ea22 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp @@ -0,0 +1,232 @@ +namespace { +not_null require_visual(DvzVisual* visual) { + if (visual == nullptr) + throw std::runtime_error("failed to create Datoviz Visual"); + return not_null{visual}; +} +Datoviz_Native_Visual create_point_visual( + not_null scene, not_null) { + return {require_visual(dvz_point(scene, 0))}; +} +Datoviz_Native_Visual create_splat_visual( + not_null scene, not_null) { + return {require_visual(dvz_splat(scene, 0))}; +} +Datoviz_Native_Visual create_pixel_visual( + not_null scene, not_null) { + return {require_visual(dvz_pixel(scene, 0))}; +} +Datoviz_Native_Visual create_marker_visual( + not_null scene, not_null panel) { + auto visual = require_visual(dvz_marker(scene, 0)); + auto* coordinate_text = dvz_text(panel, 0); + DvzTextPlacement placement = dvz_text_placement(); + placement.mode = DVZ_TEXT_PLACEMENT_DATA; + placement.anchor = DVZ_SCENE_ANCHOR_DATA; + placement.depth_test = false; + DvzTextStyle style = dvz_text_style(); + style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS; + style.size_px = 12.0F; + if (coordinate_text == nullptr || + dvz_text_set_placement(coordinate_text, &placement) != DVZ_OK || + dvz_text_set_style(coordinate_text, &style) != DVZ_OK) + throw std::runtime_error( + "failed to create Datoviz marker coordinate labels"); + return {visual, nullptr, nullptr, coordinate_text}; +} +Datoviz_Native_Visual create_sphere_visual( + not_null scene, not_null) { + return {require_visual(dvz_sphere(scene, 0))}; +} +Datoviz_Native_Visual create_segment_visual( + not_null scene, not_null) { + return {require_visual(dvz_segment(scene, 0))}; +} +Datoviz_Native_Visual create_vector_visual( + not_null scene, not_null) { + return {require_visual(dvz_vector(scene, 0))}; +} +Datoviz_Native_Visual create_primitive_visual( + not_null scene, not_null) { + return {require_visual(dvz_primitive( + scene, DVZ_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0))}; +} +Datoviz_Native_Visual create_mesh_visual( + not_null scene, not_null) { + return {require_visual(dvz_mesh(scene, 0))}; +} +Datoviz_Native_Visual create_path_visual( + not_null scene, not_null) { + return {require_visual(dvz_path(scene, 0))}; +} +Datoviz_Native_Visual create_image_visual( + not_null scene, not_null) { + auto visual = require_visual(dvz_image(scene, 0)); + constexpr std::uint32_t width = 32; + constexpr std::uint32_t height = 32; + std::vector> pixels(width * height); + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < width; ++x) { + const bool stroke = (x / 8 + y / 8) % 2 == 0; + pixels[y * width + x] = stroke + ? std::array{40, 235, 205, 255} + : std::array{18, 42, 72, 255}; + } + } + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_2D; + descriptor.format = DVZ_FIELD_FORMAT_RGBA8_UNORM; + descriptor.semantic = DVZ_FIELD_SEMANTIC_COLOR; + descriptor.color_role = DVZ_COLOR_ROLE_SRGB_COLOR; + descriptor.width = width; + descriptor.height = height; + descriptor.depth = 1; + auto* field = dvz_sampled_field(scene, &descriptor); + auto view = dvz_field_data_view(); + view.data = pixels.data(); + 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) + throw std::runtime_error("failed to create Datoviz 2D sampled field"); + return {visual, field, nullptr, nullptr, {width, height, 1}}; +} +Datoviz_Native_Visual create_labels_visual( + not_null scene, not_null) { + auto visual = require_visual(dvz_labels(scene, 0)); + constexpr std::uint32_t width = 8; + constexpr std::uint32_t height = 8; + std::array labels{}; + for (std::uint32_t y = 0; y < height; ++y) + for (std::uint32_t x = 0; x < width; ++x) + labels[y * width + x] = + static_cast((x / 4) + 2 * (y / 4)); + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_2D; + descriptor.format = DVZ_FIELD_FORMAT_R32_SINT; + descriptor.semantic = DVZ_FIELD_SEMANTIC_LABEL; + descriptor.color_role = DVZ_COLOR_ROLE_DATA; + descriptor.width = width; + descriptor.height = height; + descriptor.depth = 1; + auto* field = dvz_sampled_field(scene, &descriptor); + auto view = dvz_field_data_view(); + view.data = labels.data(); + view.bytes_per_row = width * sizeof(labels.front()); + view.rows_per_image = height; + auto scale_descriptor = dvz_scale_desc(); + scale_descriptor.kind = DVZ_SCALE_CATEGORICAL; + auto* scale = dvz_scale(scene, &scale_descriptor); + const std::array categories{{ + {.category_id = 0, .order = 0, .label = "north west", + .color = {235, 70, 70, 255}}, + {.category_id = 1, .order = 1, .label = "north east", + .color = {70, 220, 100, 255}}, + {.category_id = 2, .order = 2, .label = "south west", + .color = {70, 120, 245, 255}}, + {.category_id = 3, .order = 3, .label = "south east", + .color = {245, 210, 55, 255}}, + }}; + if (field == nullptr || scale == nullptr || + dvz_sampled_field_set_data(field, &view) != DVZ_OK || + dvz_visual_set_field(visual, "field", field) != DVZ_OK || + dvz_scale_set_categories( + scale, categories.data(), + static_cast(categories.size())) != DVZ_OK || + dvz_visual_set_scale(visual, "labels", scale) != DVZ_OK) + throw std::runtime_error("failed to create Datoviz label field"); + return {visual, field, nullptr, nullptr, {width, height, 1}}; +} +Datoviz_Native_Visual create_glyph_visual( + not_null scene, not_null) { + auto visual = require_visual(dvz_glyph(scene, 0)); + return {visual, nullptr, + configure_glyph_text(scene, visual, "GLYPH")}; +} +Datoviz_Native_Visual create_text_visual( + not_null scene, not_null) { + auto visual = require_visual(dvz_glyph(scene, 0)); + return {visual, nullptr, + configure_glyph_text(scene, visual, "TEXT")}; +} +Datoviz_Native_Visual create_volume_visual( + not_null scene, not_null) { + auto visual = require_visual(dvz_volume(scene, 0)); + constexpr std::uint32_t side = 16; + std::vector voxels(side * side * side); + for (std::uint32_t z = 0; z < side; ++z) { + for (std::uint32_t y = 0; y < side; ++y) { + for (std::uint32_t x = 0; x < side; ++x) { + const float dx = static_cast(x) - 7.5F; + const float dy = static_cast(y) - 7.5F; + const float dz = static_cast(z) - 7.5F; + const float distance = std::sqrt( + dx * dx + dy * dy + dz * dz); + voxels[(z * side + y) * side + x] = + distance < 6.5F ? 1.0F - distance / 6.5F : 0.0F; + } + } + } + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_3D; + descriptor.format = DVZ_FIELD_FORMAT_R32_FLOAT; + descriptor.semantic = DVZ_FIELD_SEMANTIC_SCALAR; + descriptor.color_role = DVZ_COLOR_ROLE_DATA; + descriptor.width = side; + descriptor.height = side; + descriptor.depth = side; + auto* field = dvz_sampled_field(scene, &descriptor); + auto view = dvz_field_data_view(); + view.data = voxels.data(); + view.bytes_per_row = side * sizeof(voxels.front()); + 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) + throw std::runtime_error("failed to create Datoviz volume field"); + return {visual, field, nullptr, nullptr, {side, side, side}}; +} +} // namespace + +namespace { +constexpr std::array point_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"diameter_px", sizeof(float)}}; +constexpr std::array splat_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"sigma", sizeof(std::array)}, + Datoviz_Attribute_Description{"angle", sizeof(float)}}; +constexpr std::array pixel_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"pixel_size_px", sizeof(float)}}; +constexpr std::array marker_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"diameter_px", sizeof(float)}, + Datoviz_Attribute_Description{"angle", sizeof(float)}, + Datoviz_Attribute_Description{"shape", sizeof(std::uint32_t)}}; +constexpr std::array sphere_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"radius", sizeof(float)}}; +constexpr std::array primitive_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"normal", sizeof(Prepared_Position)}}; +constexpr std::array mesh_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"normal", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"texcoords", sizeof(std::array)}}; +constexpr std::array path_attributes{ + Datoviz_Attribute_Description{"position", sizeof(Prepared_Position)}, + Datoviz_Attribute_Description{"color", sizeof(Prepared_Color)}, + Datoviz_Attribute_Description{"stroke_width_px", sizeof(float)}}; +} + diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp b/render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp new file mode 100644 index 0000000..77db094 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp @@ -0,0 +1,427 @@ +namespace { +using Attribute_Sink = Datoviz_Visual_Operations::Attribute_Sink; + +void visit_external_attributes( + const Point_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.diameters.data()}, data.diameters.size()); +} +void visit_external_attributes( + const Splat_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.sigmas.data()}, data.sigmas.size()); + sink(context, not_null{data.angles.data()}, data.angles.size()); +} +void visit_external_attributes( + const Pixel_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.sizes.data()}, data.sizes.size()); +} +void visit_external_attributes( + const Marker_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.diameters.data()}, data.diameters.size()); + sink(context, not_null{data.angles.data()}, data.angles.size()); + sink(context, not_null{data.shapes.data()}, data.shapes.size()); +} +void visit_external_attributes( + const Sphere_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.centers.data()}, data.centers.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.radii.data()}, data.radii.size()); +} +void visit_external_attributes( + const Primitive_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.normals.data()}, data.normals.size()); +} +void visit_external_attributes( + const Mesh_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.normals.data()}, data.normals.size()); + sink(context, not_null{data.texture_coordinates.data()}, + data.texture_coordinates.size()); +} +void visit_external_attributes( + const Path_Prepared_Data& data, not_null context, + Attribute_Sink sink) { + sink(context, not_null{data.positions.data()}, data.positions.size()); + sink(context, not_null{data.colors.data()}, data.colors.size()); + sink(context, not_null{data.widths.data()}, data.widths.size()); +} +} // namespace + +namespace { +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Point_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"diameter_px", data.diameters.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Splat_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"sigma", data.sigmas.data(), count}, + {"angle", data.angles.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 4); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Pixel_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"pixel_size_px", data.sizes.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Marker_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"diameter_px", data.diameters.data(), count}, + {"angle", data.angles.data(), count}, + {"shape", data.shapes.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 5); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Sphere_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.centers.data(), count}, + {"color", data.colors.data(), count}, + {"radius", data.radii.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Segment_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position_start", data.starts.data(), count}, + {"position_end", data.ends.data(), count}, + {"color", data.colors.data(), count}, + {"stroke_width_px", data.widths.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 4); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Vector_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.origins.data(), count}, + {"vector", data.directions.data(), count}, + {"color", data.colors.data(), count}, + {"stroke_width_px", data.widths.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 4); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Primitive_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"normal", data.normals.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Mesh_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"normal", data.normals.data(), count}, + {"texcoords", data.texture_coordinates.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 4); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Path_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"color", data.colors.data(), count}, + {"stroke_width_px", data.widths.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Image_Prepared_Data& data, + std::uint32_t count) { + if (target.field == nullptr) + throw std::logic_error("Datoviz image has no sampled field"); + auto view = dvz_field_data_view(); + view.data = data.field_pixels.data(); + view.bytes_per_row = static_cast(data.field_width) * + sizeof(data.field_pixels.front()); + view.rows_per_image = data.field_height; + const std::array extent{data.field_width, data.field_height, 1U}; + auto result = extent == target.field_extent + ? dvz_sampled_field_set_data(target.field, &view) + : dvz_sampled_field_resize( + target.field, extent[0], extent[1], extent[2], &view); + if (result != DVZ_OK) return result; + target.field_extent = extent; + const std::array updates{{ + {"position", data.positions.data(), count}, + {"extent", data.extents.data(), count}, + {"tex_rect", data.texture_rectangles.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Labels_Prepared_Data& data, + std::uint32_t count) { + if (target.field == nullptr) + throw std::logic_error("Datoviz labels Visual has no sampled field"); + auto view = dvz_field_data_view(); + view.data = data.field_labels.data(); + view.bytes_per_row = static_cast(data.field_width) * + sizeof(data.field_labels.front()); + view.rows_per_image = data.field_height; + const std::array extent{data.field_width, data.field_height, 1U}; + auto result = extent == target.field_extent + ? dvz_sampled_field_set_data(target.field, &view) + : dvz_sampled_field_resize( + target.field, extent[0], extent[1], extent[2], &view); + if (result != DVZ_OK) return result; + target.field_extent = extent; + const std::array updates{{ + {"position", data.positions.data(), count}, + {"extent", data.extents.data(), count}, + {"tex_rect", data.texture_rectangles.data(), count}}}; + return dvz_visual_set_data_many(target.visual, updates.data(), 3); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Glyph_Prepared_Data& data, + std::uint32_t count) { + const std::array updates{{ + {"position", data.positions.data(), count}, + {"bounds", data.bounds.data(), count}, + {"texcoords", data.texture_coordinates.data(), count}, + {"color", data.colors.data(), count}, + {"angle", data.angles.data(), count}}}; + return dvz_visual_set_data_many( + target.visual, updates.data(), + static_cast(updates.size())); +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Text_Prepared_Data& data, + std::uint32_t) { + if (target.font == nullptr) + throw std::logic_error("Datoviz text Visual has no font"); + upload_text(target.visual, target.font, data); + return DVZ_OK; +} +DvzResult upload_visual_payload( + Datoviz_Visual_Instance& target, const Volume_Prepared_Data& data, + std::uint32_t) { + if (data.field_width == 0 || data.field_height == 0 || + data.field_depth == 0) + throw std::invalid_argument( + "3D volume field dimensions must be non-zero"); + const auto expected = static_cast(data.field_width) * + data.field_height * data.field_depth; + if (expected != data.values.size()) + throw std::invalid_argument( + "3D volume voxel count does not match field dimensions"); + if (target.field == nullptr) + throw std::logic_error("Datoviz volume has no sampled field"); + auto view = dvz_field_data_view(); + view.data = data.values.data(); + view.bytes_per_row = static_cast(data.field_width) * + sizeof(data.values.front()); + view.rows_per_image = data.field_height; + const std::array extent{ + data.field_width, data.field_height, data.field_depth}; + const auto result = extent == target.field_extent + ? dvz_sampled_field_set_data(target.field, &view) + : dvz_sampled_field_resize( + target.field, extent[0], extent[1], extent[2], &view); + if (result == DVZ_OK) target.field_extent = extent; + return result; +} +} // namespace + +namespace { +std::size_t visual_item_count(const Point_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Splat_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Pixel_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Marker_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Sphere_Prepared_Data& data) noexcept { return data.centers.size(); } +std::size_t visual_item_count(const Segment_Prepared_Data& data) noexcept { return data.starts.size(); } +std::size_t visual_item_count(const Vector_Prepared_Data& data) noexcept { return data.origins.size(); } +std::size_t visual_item_count(const Primitive_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Mesh_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Path_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Image_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Labels_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Glyph_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Text_Prepared_Data& data) noexcept { return data.positions.size(); } +std::size_t visual_item_count(const Volume_Prepared_Data& data) noexcept { return data.values.size(); } + +template +std::size_t erased_item_count(not_null data) noexcept { + return visual_item_count(*static_cast(data.get())); +} +template +void erased_upload_visual( + Datoviz_Visual_Instance& target, not_null data, + std::uint32_t count) { + if (upload_visual_payload( + target, *static_cast(data.get()), count) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz Visual payload"); +} +template +void erased_visit_external_attributes( + not_null data, not_null context, + Datoviz_Visual_Operations::Attribute_Sink sink) { + visit_external_attributes( + *static_cast(data.get()), context, sink); +} +void apply_point_style( + not_null visual, not_null data) { + const auto& point = *static_cast(data.get()); + DvzPointStyleDesc style = dvz_point_style_desc(); + style.edge_color.r = point.style.edge_color.red; + style.edge_color.g = point.style.edge_color.green; + style.edge_color.b = point.style.edge_color.blue; + style.edge_color.a = point.style.edge_color.alpha; + style.stroke_width_px = point.style.stroke_width_px; + style.aspect = aspect(point.style.aspect); + if (dvz_point_set_style(visual, &style) != DVZ_OK) + throw std::runtime_error("failed to apply Datoviz point style"); +} +bool marker_has_coordinate_labels(not_null data) { + const auto& marker = + *static_cast(data.get()); + return std::ranges::any_of( + marker.coordinate_label_visibility, + [](std::uint8_t visible) { return visible != 0; }); +} +} // namespace + +const Datoviz_Visual_Operations& Point_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_point_visual, erased_item_count, + erased_upload_visual, true, true, + point_attributes, + erased_visit_external_attributes, + apply_point_style, nullptr}; + return value; +} +const Datoviz_Visual_Operations& Splat_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_splat_visual, erased_item_count, + erased_upload_visual, false, true, + splat_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Pixel_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_pixel_visual, erased_item_count, + erased_upload_visual, true, true, + pixel_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Marker_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_marker_visual, erased_item_count, + erased_upload_visual, true, false, + marker_attributes, + erased_visit_external_attributes, + nullptr, marker_has_coordinate_labels}; + return value; +} +const Datoviz_Visual_Operations& Sphere_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_sphere_visual, erased_item_count, + erased_upload_visual, false, true, + sphere_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Segment_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_segment_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Vector_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_vector_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Primitive_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_primitive_visual, erased_item_count, + erased_upload_visual, false, true, + primitive_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Mesh_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_mesh_visual, erased_item_count, + erased_upload_visual, false, true, + mesh_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Path_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_path_visual, erased_item_count, + erased_upload_visual, false, true, + path_attributes, + erased_visit_external_attributes}; + return value; +} +const Datoviz_Visual_Operations& Image_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_image_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Labels_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_labels_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Glyph_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_glyph_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Text_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_text_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} +const Datoviz_Visual_Operations& Volume_Spec::datoviz_operations() { + static const Datoviz_Visual_Operations value{ + create_volume_visual, erased_item_count, + erased_upload_visual, false, false}; + return value; +} + diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp b/render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp new file mode 100644 index 0000000..5a0ae17 --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp @@ -0,0 +1,284 @@ +std::uint64_t Scene_Datoviz_State::apply( + const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared, + std::uint8_t target_index, bool bind_target) { + if (figure_extent_ != scene.viewport) { + if (dvz_figure_resize(figure_, scene.viewport.width, + scene.viewport.height) != DVZ_OK) + throw std::runtime_error("failed to resize Datoviz point figure"); + DvzInputResizeEvent resize{ + scene.viewport.width, scene.viewport.height, + scene.viewport.width, scene.viewport.height, 1.0F, 1.0F + }; + dvz_input_emit_resize(input_router_, &resize); + figure_extent_ = scene.viewport; + } + apply_camera(scene.camera); + apply_axes(scene); + if (prepared.size() != visuals_.size()) throw std::logic_error("3D frame did not publish every registered Visual"); + std::uint64_t uploaded_bytes{}; + 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"); + uploaded_bytes += apply_visual( + target, found->visual, target_index, bind_target); + } + return uploaded_bytes; +} +void Scene_Datoviz_State::ensure_external_attributes( + Datoviz_Visual_Instance& target, const Prepared_Visual& visual) { + const auto item_count = prepared_item_count(visual); + if (item_count == 0) return; + if (item_count > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute item count exceeds uint32"); + const auto layout = target.operations->external_attributes; + if (layout.empty()) + throw std::logic_error( + "external attributes requested for a Datoviz-managed Visual"); + const auto expected_count = layout.size(); + if (target.attributes.size() == expected_count && + std::ranges::all_of(target.attributes, [&](const Datoviz_External_Attribute& value) { + return value.capacity >= item_count; + })) + return; + std::uint64_t capacity = 1; + while (capacity < item_count) capacity *= 2; + if (capacity > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute capacity exceeds uint32"); + std::vector attributes; + attributes.reserve(expected_count); + const auto create = [&](const char* name, std::uint32_t stride) { + const std::uint64_t byte_size = capacity * stride * Datoviz_Frame_Target_Set::count; + if (byte_size == 0 || byte_size > std::numeric_limits::max()) throw std::length_error("Datoviz external attribute buffer is too large"); + auto descriptor = dvz_scene_buffer_desc(); + descriptor.usage = DVZ_SCENE_BUFFER_USAGE_VERTEX; + descriptor.stride = stride; + descriptor.byte_size = byte_size; + auto* scene_buffer = dvz_scene_buffer(scene_, &descriptor); + if (scene_buffer == nullptr) throw std::runtime_error("failed to create Datoviz external scene buffer"); + auto* gpu_buffer = allocate_wrapper( + dvz_buffer_create_wrapper, + "failed to allocate Datoviz external GPU buffer wrapper"); + const auto gpu_context = render_context_->gpu_context(); + dvz_buffer(dvz_gpu_ctx_device(gpu_context.get()), + dvz_gpu_ctx_alloc(gpu_context.get()), + gpu_buffer); + dvz_buffer_size(gpu_buffer, static_cast(byte_size)); + dvz_buffer_usage(gpu_buffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + dvz_buffer_flags(gpu_buffer, DVZ_ALLOC_HOST_ACCESS_SEQUENTIAL_WRITE); + if (dvz_buffer_create(gpu_buffer) != DVZ_OK) { + dvz_buffer_free(gpu_buffer); + throw std::runtime_error("failed to create Datoviz external GPU buffer"); + } + external_buffers_.push_back(gpu_buffer); + attributes.push_back({ + name, stride, static_cast(capacity), + not_null{scene_buffer}, gpu_buffer, false + }); + }; + for (const auto& attribute : layout) + create(attribute.name, attribute.stride); + target.attributes = std::move(attributes); + target.uploaded_data_revisions = {}; +} +std::uint64_t Scene_Datoviz_State::upload_external_attributes( + Datoviz_Visual_Instance& target, const Prepared_Visual& visual, + std::uint8_t target_index) { + if (visual.operations != target.operations || + target.operations->visit_external_attributes == nullptr) + throw std::logic_error( + "3D prepared Visual operations do not match registration"); + const auto item_count = prepared_item_count(visual); + if (item_count == 0) return 0; + std::uint64_t uploaded_bytes{}; + struct Upload_Context { + std::vector::iterator attribute; + std::vector::iterator end; + std::size_t item_count{}; + std::uint8_t target_index{}; + not_null uploaded_bytes; + } context{ + target.attributes.begin(), target.attributes.end(), item_count, target_index, + not_null{&uploaded_bytes}}; + const auto upload = [](not_null raw_context, + not_null source, + std::size_t count) { + auto& context = *static_cast(raw_context.get()); + if (count != context.item_count) + throw std::logic_error( + "Datoviz external attribute fields have different item counts"); + if (context.attribute == context.end) + throw std::logic_error( + "Datoviz external attribute layout is incomplete"); + auto& attribute = *context.attribute++; + const auto byte_count = static_cast(count * attribute.stride); + const auto byte_offset = static_cast(context.target_index) * + attribute.capacity * attribute.stride; + dvz_buffer_upload( + attribute.gpu_buffer, byte_offset, byte_count, source.get()); + *context.uploaded_bytes += byte_count; + }; + target.operations->visit_external_attributes( + not_null{visual.data.get()}, not_null{static_cast(&context)}, + upload); + if (context.attribute != context.end) + throw std::logic_error( + "Datoviz external attribute layout has unused fields"); + return uploaded_bytes; +} +void Scene_Datoviz_State::bind_external_attributes( + Datoviz_Visual_Instance& target, std::uint8_t target_index, + std::uint32_t item_count) { + for (auto& attribute : target.attributes) { + const std::uint64_t byte_offset = static_cast(target_index) * + attribute.capacity * attribute.stride; + if (dvz_visual_set_attr_buffer(target.visual.get(), attribute.name.c_str(), + attribute.scene_buffer.get(), byte_offset, + item_count) != DVZ_OK) + throw std::runtime_error("failed to bind Datoviz external visual attribute"); + } +} +void Scene_Datoviz_State::register_external_attributes( + const DvzDrp2CommandStream* stream, std::uint8_t target_index) { + if (stream == nullptr) return; + if (target_index >= runtime_slots_.size()) throw std::logic_error("Datoviz external attribute target is invalid"); + const auto target_bit = static_cast(1U << target_index); + for (auto& visual : visuals_) { + for (auto& attribute : visual.attributes) { + if ((attribute.registered_targets & target_bit) != 0) continue; + char key[DVZ_SCENE_LABEL_SIZE]{}; + if (!dvz_scene_buffer_resource_key(attribute.scene_buffer.get(), key, + sizeof(key))) + throw std::runtime_error("failed to resolve Datoviz external buffer key"); + const auto id = dvz_drp2_stream_label_id(stream, key); + if (id == 0) throw std::runtime_error("Datoviz frame omitted an external buffer label"); + DvzSceneBufferDesc scene_descriptor{}; + if (!dvz_scene_buffer_info(attribute.scene_buffer.get(), + &scene_descriptor)) + throw std::runtime_error("failed to inspect Datoviz external buffer"); + auto descriptor = dvz_drp2_external_buffer_desc(); + descriptor.buffer = attribute.gpu_buffer; + descriptor.size = scene_descriptor.byte_size; + descriptor.usage = DVZ_DRP2_BUFFER_USAGE_VERTEX; + if (!dvz_drp2_runtime_register_external_buffer( + runtime_slots_[target_index].runtime, id, + &descriptor)) + throw std::runtime_error("failed to register Datoviz external GPU buffer"); + attribute.registered_targets |= target_bit; + } + } +} +std::uint64_t Scene_Datoviz_State::apply_visual( + Datoviz_Visual_Instance& target, const Prepared_Visual& point, + std::uint8_t target_index, bool bind_target) { + if (!has_payload(point)) throw std::logic_error("3D prepared visual has no immutable payload"); + if (point.operations != target.operations) + throw std::logic_error( + "3D prepared Visual operations do not match registration"); + if (target.operations->external_attributes.empty() && + point.revision == target.applied_revision) + return 0; + const auto item_count = prepared_item_count(point); + auto* visual = target.visual.get(); + const bool structure_changed = !target.applied || + !same_visual_structure(*target.applied, point); + const bool coordinate_text_changed = target.coordinate_text != nullptr && + (!target.applied || has_coordinate_labels(*target.applied) || + has_coordinate_labels(point)); + if (coordinate_text_changed) { + const auto& data = + *static_cast(point.data.get()); + std::vector strings; + std::vector items; + if (point.visible) { + strings.reserve(data.positions.size()); + items.reserve(data.positions.size()); + for (std::size_t index = 0; index < data.positions.size(); ++index) { + if (index >= data.coordinate_label_visibility.size() || + !data.coordinate_label_visibility[index]) + continue; + const auto& source = data.positions[index]; + const auto& matrix = point.transform.values; + const float x = matrix[0] * source[0] + matrix[1] * source[1] + matrix[2] * source[2] + matrix[3]; + const float y = matrix[4] * source[0] + matrix[5] * source[1] + matrix[6] * source[2] + matrix[7]; + const float z = matrix[8] * source[0] + matrix[9] * source[1] + matrix[10] * source[2] + matrix[11]; + std::ostringstream stream; + stream << std::fixed << std::setprecision(3) + << "X " << x << " Y " << y << " Z " << z; + strings.push_back(std::move(stream).str()); + DvzTextItem item{}; + item.struct_size = sizeof(DvzTextItem); + item.position[0] = x; + item.position[1] = y; + item.position[2] = z; + item.offset[0] = 10.0F; + item.offset[1] = -10.0F; + item.anchor[0] = 0.0F; + item.anchor[1] = 1.0F; + item.size_px = 12.0F; + item.color = {235, 244, 255, 255}; + items.push_back(item); + } + } + for (std::size_t index = 0; index < items.size(); ++index) items[index].string = strings[index].c_str(); + if (dvz_text_set_items(target.coordinate_text, + items.empty() ? nullptr : items.data(), + static_cast(items.size())) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz marker coordinate labels"); + } + if (structure_changed) { + mat4 transform{}; + for (std::size_t row = 0; row < 4; ++row) for (std::size_t column = 0; column < 4; ++column) transform[row][column] = point.transform.values[row * 4 + column]; + if (target.operations->apply_style != nullptr) + target.operations->apply_style( + not_null{visual}, not_null{point.data.get()}); + 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 && item_count != 0) != DVZ_OK) + throw std::runtime_error("failed to apply Datoviz visual state"); + } + if (item_count == 0) { + 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; + target.applied = point; + return 0; + } + const auto count = static_cast(item_count); + if (!target.operations->external_attributes.empty()) { + /* + * 普通顶点族在第一次录制前即绑定三槽外部属性。Segment/Vector 的 + * typed-stroke 元数据、Glyph/Text 的 atlas 几何以及 Image/Labels/Volume + * 的采样场资源继续使用 Datoviz 结构路径; + * 把 dense 数据先写入再改绑会违反 Datoviz 的单一属性所有权契约。 + */ + std::uint64_t uploaded_bytes{}; + ensure_external_attributes(target, point); + if (bind_target) bind_external_attributes(target, target_index, count); + if (target.uploaded_data_revisions[target_index] != + point.data_revision) { + uploaded_bytes = upload_external_attributes( + target, point, target_index); + target.uploaded_data_revisions[target_index] = + point.data_revision; + } + if (structure_changed && + dvz_visual_set_visible(visual, point.visible) != DVZ_OK) + throw std::runtime_error( + "failed to apply Datoviz external visual visibility"); + target.applied_revision = point.revision; + target.applied = point; + return uploaded_bytes; + } + if (target.applied && target.applied->data_revision == point.data_revision) { + target.applied_revision = point.revision; + target.applied = point; + return 0; + } + target.operations->upload_visual( + target, not_null{point.data.get()}, count); + if (dvz_visual_set_visible(visual, point.visible) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz visual payload"); + target.applied_revision = point.revision; + target.applied = point; + return 0; +} diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp b/render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp new file mode 100644 index 0000000..cdf6bfb --- /dev/null +++ b/render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "../../detail/Backend_Types.hpp" +#include "../../visual/detail/Datoviz_Visual_Operations.hpp" +#include +#include +#include +#include +#include +#include + +struct DvzBuffer; +struct DvzFont; +struct DvzSampledField; +struct DvzSceneBuffer; +struct DvzText; +struct DvzVisual; + +namespace aethera::render_3d::detail { + +struct Datoviz_External_Attribute { + std::string name{}; /* Datoviz Visual 属性名。 */ + std::uint32_t stride{}; /* 单项字节跨度。 */ + std::uint32_t capacity{}; /* 每个帧槽可容纳的项数。 */ + not_null scene_buffer; /* Scene 图借用的资源描述。 */ + owner gpu_buffer{}; /* 本 Scene 独占的映射 GPU Buffer。 */ + std::uint8_t registered_targets{}; /* 已绑定到哪些固定帧槽。 */ +}; + +struct Datoviz_Visual_Instance { + Visual_Identity identity{}; /* 与借用 Visual 对象一一对应。 */ + not_null operations; /* 该具体 Visual 的唯一后端描述。 */ + not_null visual; /* DvzScene 拥有;本实例必填借用。 */ + DvzSampledField* field{}; /* 可空、非拥有的采样场借用。 */ + DvzFont* font{}; /* 可空、非拥有的字体借用。 */ + DvzText* coordinate_text{}; /* 可空、非拥有的坐标文本借用。 */ + std::array field_extent{}; /* 当前采样场尺寸。 */ + std::uint64_t applied_revision{}; /* 已应用的 Visual 版本。 */ + std::array uploaded_data_revisions{}; /* 各槽上传版本。 */ + std::optional applied{}; /* 已应用结构,仅用于录制复用判定。 */ + std::vector attributes{}; /* 本 Visual 的独立外部属性。 */ +}; + +} diff --git a/render_3D/render_3D/visual/Glyph_Visual.hpp b/render_3D/render_3D/visual/Glyph_Visual.hpp new file mode 100644 index 0000000..bb3627c --- /dev/null +++ b/render_3D/render_3D/visual/Glyph_Visual.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Glyph { + Vec3 position{}; + Vec4 bounds{}; + Vec4 texture_coordinates{}; + Color color{Color::white()}; + Coordinate_3D angle{}; + bool operator==(const Glyph&) const = default; +}; + +namespace detail { +struct Glyph_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector angles{}; + std::vector> bounds{}; + std::vector> texture_coordinates{}; +}; + +struct Glyph_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Glyph_Visual = Basic_Visual; +} + +#include "Glyph_Visual.ipp" diff --git a/render_3D/render_3D/visual/Glyph_Visual.ipp b/render_3D/render_3D/visual/Glyph_Visual.ipp new file mode 100644 index 0000000..7b1c1cb --- /dev/null +++ b/render_3D/render_3D/visual/Glyph_Visual.ipp @@ -0,0 +1,26 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Glyph_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); +} + +inline void Glyph_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.angles.reserve(items.size()); + output.bounds.reserve(items.size()); + output.texture_coordinates.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.angles.push_back(item.angle); + output.bounds.push_back({item.bounds.x, item.bounds.y, item.bounds.z, item.bounds.w}); + output.texture_coordinates.push_back({item.texture_coordinates.x, + item.texture_coordinates.y, + item.texture_coordinates.z, + item.texture_coordinates.w}); + } +} +} diff --git a/render_3D/render_3D/visual/Image_Visual.hpp b/render_3D/render_3D/visual/Image_Visual.hpp new file mode 100644 index 0000000..7e073ec --- /dev/null +++ b/render_3D/render_3D/visual/Image_Visual.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Image_Field_Settings { + std::uint32_t field_width{}; + std::uint32_t field_height{}; + std::vector field_pixels{}; + bool operator==(const Image_Field_Settings&) const = default; +}; + +struct Image { + Vec3 position{}; + Vec2 extent{1.0F, 1.0F}; + Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; + bool operator==(const Image&) const = default; +}; + +namespace detail { +struct Image_Prepared_Data { + std::vector positions{}; + std::vector> extents{}; + std::vector> texture_rectangles{}; + std::vector field_pixels{}; + std::uint32_t field_width{}; + std::uint32_t field_height{}; +}; + +struct Image_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Image_Visual = Basic_Visual; +} + +#include "Image_Visual.ipp" diff --git a/render_3D/render_3D/visual/Image_Visual.ipp b/render_3D/render_3D/visual/Image_Visual.ipp new file mode 100644 index 0000000..82edcd2 --- /dev/null +++ b/render_3D/render_3D/visual/Image_Visual.ipp @@ -0,0 +1,20 @@ +#pragma once + +namespace aethera::render_3d::detail { +inline bool Image_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && + finite(item.texture_rectangle); +} + +inline void Image_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.positions.reserve(items.size()); + output.extents.reserve(items.size()); + output.texture_rectangles.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.extents.push_back({item.extent.x, item.extent.y}); + output.texture_rectangles.push_back( + {item.texture_rectangle.x, item.texture_rectangle.y, item.texture_rectangle.z, item.texture_rectangle.w}); + } +} +} diff --git a/render_3D/render_3D/visual/Labels_Visual.hpp b/render_3D/render_3D/visual/Labels_Visual.hpp new file mode 100644 index 0000000..c2ac661 --- /dev/null +++ b/render_3D/render_3D/visual/Labels_Visual.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Labels_Field_Settings { + std::uint32_t field_width{}; + std::uint32_t field_height{}; + std::vector field_labels{}; + bool operator==(const Labels_Field_Settings&) const = default; +}; + +struct Label { + Vec3 position{}; + Vec2 extent{32.0F, 16.0F}; + Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; + bool operator==(const Label&) const = default; +}; + +namespace detail { +struct Labels_Prepared_Data { + std::vector positions{}; + std::vector> extents{}; + std::vector> texture_rectangles{}; + std::vector field_labels{}; + std::uint32_t field_width{}; + std::uint32_t field_height{}; +}; + +struct Labels_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Labels_Visual = Basic_Visual; +} + +#include "Labels_Visual.ipp" diff --git a/render_3D/render_3D/visual/Labels_Visual.ipp b/render_3D/render_3D/visual/Labels_Visual.ipp new file mode 100644 index 0000000..d32f6ac --- /dev/null +++ b/render_3D/render_3D/visual/Labels_Visual.ipp @@ -0,0 +1,20 @@ +#pragma once + +namespace aethera::render_3d::detail { +inline bool Labels_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && + finite(item.texture_rectangle); +} + +inline void Labels_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.positions.reserve(items.size()); + output.extents.reserve(items.size()); + output.texture_rectangles.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.extents.push_back({item.extent.x, item.extent.y}); + output.texture_rectangles.push_back( + {item.texture_rectangle.x, item.texture_rectangle.y, item.texture_rectangle.z, item.texture_rectangle.w}); + } +} +} diff --git a/render_3D/render_3D/visual/Marker_Visual.hpp b/render_3D/render_3D/visual/Marker_Visual.hpp new file mode 100644 index 0000000..2ed06d8 --- /dev/null +++ b/render_3D/render_3D/visual/Marker_Visual.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +enum struct Marker_Shape : std::uint8_t { disc, square, triangle, diamond, cross }; + +struct Marker { + Vec3 position{}; + Color color{Color::white()}; + Pixel_Distance diameter_px{12.0F}; + Coordinate_3D angle{}; + Marker_Shape shape{Marker_Shape::disc}; + bool coordinate_label_visible{false}; + bool operator==(const Marker&) const = default; +}; + +namespace detail { +struct Marker_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector diameters{}; + std::vector angles{}; + std::vector shapes{}; + std::vector coordinate_label_visibility{}; +}; + +struct Marker_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Marker_Visual = Basic_Visual; +} + +#include "Marker_Visual.ipp" diff --git a/render_3D/render_3D/visual/Marker_Visual.ipp b/render_3D/render_3D/visual/Marker_Visual.ipp new file mode 100644 index 0000000..b8ee989 --- /dev/null +++ b/render_3D/render_3D/visual/Marker_Visual.ipp @@ -0,0 +1,25 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Marker_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); +} + +inline void Marker_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.diameters.reserve(items.size()); + output.angles.reserve(items.size()); + output.shapes.reserve(items.size()); + output.coordinate_label_visibility.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.diameters.push_back(item.diameter_px); + output.angles.push_back(item.angle); + output.shapes.push_back(static_cast(item.shape)); + output.coordinate_label_visibility.push_back(item.coordinate_label_visible); + } +} +} diff --git a/render_3D/render_3D/visual/Mesh_Visual.hpp b/render_3D/render_3D/visual/Mesh_Visual.hpp new file mode 100644 index 0000000..fe845c4 --- /dev/null +++ b/render_3D/render_3D/visual/Mesh_Visual.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Mesh_Vertex { + Vec3 position{}; + Color color{Color::white()}; + Vec3 normal{}; + Vec2 texture_coordinate{}; + bool operator==(const Mesh_Vertex&) const = default; +}; + +namespace detail { +struct Mesh_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector normals{}; + std::vector> texture_coordinates{}; +}; + +struct Mesh_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Mesh_Visual = Basic_Visual; +} + +#include "Mesh_Visual.ipp" diff --git a/render_3D/render_3D/visual/Mesh_Visual.ipp b/render_3D/render_3D/visual/Mesh_Visual.ipp new file mode 100644 index 0000000..1364c00 --- /dev/null +++ b/render_3D/render_3D/visual/Mesh_Visual.ipp @@ -0,0 +1,21 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Mesh_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); +} + +inline void Mesh_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.normals.reserve(items.size()); + output.texture_coordinates.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); + output.texture_coordinates.push_back({item.texture_coordinate.x, item.texture_coordinate.y}); + } +} +} diff --git a/render_3D/render_3D/visual/Path_Visual.hpp b/render_3D/render_3D/visual/Path_Visual.hpp new file mode 100644 index 0000000..76a4ab8 --- /dev/null +++ b/render_3D/render_3D/visual/Path_Visual.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Path_Vertex { + Vec3 position{}; + Color color{Color::white()}; + Pixel_Distance width_px{1.0F}; + bool operator==(const Path_Vertex&) const = default; +}; + +namespace detail { +struct Path_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector widths{}; +}; + +struct Path_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Path_Visual = Basic_Visual; +} + +#include "Path_Visual.ipp" diff --git a/render_3D/render_3D/visual/Path_Visual.ipp b/render_3D/render_3D/visual/Path_Visual.ipp new file mode 100644 index 0000000..f61b405 --- /dev/null +++ b/render_3D/render_3D/visual/Path_Visual.ipp @@ -0,0 +1,19 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Path_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; +} + +inline void Path_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.widths.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.widths.push_back(item.width_px); + } +} +} diff --git a/render_3D/render_3D/visual/Pixel_Visual.hpp b/render_3D/render_3D/visual/Pixel_Visual.hpp new file mode 100644 index 0000000..71f5ffa --- /dev/null +++ b/render_3D/render_3D/visual/Pixel_Visual.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Pixel { + Vec3 position{}; + Color color{Color::white()}; + Pixel_Distance size_px{1.0F}; + bool operator==(const Pixel&) const = default; +}; + +namespace detail { +struct Pixel_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector sizes{}; +}; + +struct Pixel_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Pixel_Visual = Basic_Visual; +} + +#include "Pixel_Visual.ipp" diff --git a/render_3D/render_3D/visual/Pixel_Visual.ipp b/render_3D/render_3D/visual/Pixel_Visual.ipp new file mode 100644 index 0000000..a36792d --- /dev/null +++ b/render_3D/render_3D/visual/Pixel_Visual.ipp @@ -0,0 +1,19 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Pixel_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; +} + +inline void Pixel_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.sizes.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.sizes.push_back(item.size_px); + } +} +} diff --git a/render_3D/render_3D/visual/Point_Visual.hpp b/render_3D/render_3D/visual/Point_Visual.hpp new file mode 100644 index 0000000..6acdd80 --- /dev/null +++ b/render_3D/render_3D/visual/Point_Visual.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +enum struct Point_Aspect : std::uint8_t { filled, stroke, outline }; + +struct Point_Style { + Color edge_color{Color::black()}; + Pixel_Distance stroke_width_px{}; + Point_Aspect aspect{Point_Aspect::filled}; + bool operator==(const Point_Style&) const = default; +}; + +struct Point { + Vec3 position{}; + Color color{Color::white()}; + Pixel_Distance diameter_px{8.0F}; + bool operator==(const Point&) const = default; +}; + +namespace detail { +struct Point_Settings : Visual_Settings { + Point_Style style{}; + bool operator==(const Point_Settings&) const = default; +}; + +struct Point_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector diameters{}; + Point_Style style{}; +}; + +struct Point_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Point_Visual = Basic_Visual; +} + +#include "Point_Visual.ipp" diff --git a/render_3D/render_3D/visual/Point_Visual.ipp b/render_3D/render_3D/visual/Point_Visual.ipp new file mode 100644 index 0000000..d8314b2 --- /dev/null +++ b/render_3D/render_3D/visual/Point_Visual.ipp @@ -0,0 +1,19 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Point_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; +} + +inline void Point_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.diameters.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.diameters.push_back(item.diameter_px); + } +} +} diff --git a/render_3D/render_3D/visual/Primitive_Visual.hpp b/render_3D/render_3D/visual/Primitive_Visual.hpp new file mode 100644 index 0000000..904813a --- /dev/null +++ b/render_3D/render_3D/visual/Primitive_Visual.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Primitive_Vertex { + Vec3 position{}; + Color color{Color::white()}; + Vec3 normal{}; + bool operator==(const Primitive_Vertex&) const = default; +}; + +namespace detail { +struct Primitive_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector normals{}; +}; + +struct Primitive_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Primitive_Visual = Basic_Visual; +} + +#include "Primitive_Visual.ipp" diff --git a/render_3D/render_3D/visual/Primitive_Visual.ipp b/render_3D/render_3D/visual/Primitive_Visual.ipp new file mode 100644 index 0000000..8ba2775 --- /dev/null +++ b/render_3D/render_3D/visual/Primitive_Visual.ipp @@ -0,0 +1,19 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Primitive_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.normal); +} + +inline void Primitive_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.normals.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); + } +} +} diff --git a/render_3D/render_3D/visual/Segment_Visual.hpp b/render_3D/render_3D/visual/Segment_Visual.hpp new file mode 100644 index 0000000..67c56dd --- /dev/null +++ b/render_3D/render_3D/visual/Segment_Visual.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Segment { + Vec3 start{}; + Vec3 end{}; + Color color{Color::white()}; + Pixel_Distance width_px{1.0F}; + bool operator==(const Segment&) const = default; +}; + +namespace detail { +struct Segment_Prepared_Data { + std::vector starts{}; + std::vector ends{}; + std::vector colors{}; + std::vector widths{}; +}; + +struct Segment_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Segment_Visual = Basic_Visual; +} + +#include "Segment_Visual.ipp" diff --git a/render_3D/render_3D/visual/Segment_Visual.ipp b/render_3D/render_3D/visual/Segment_Visual.ipp new file mode 100644 index 0000000..fe5d7df --- /dev/null +++ b/render_3D/render_3D/visual/Segment_Visual.ipp @@ -0,0 +1,22 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Segment_Spec::valid(const Item& item) noexcept { + return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; +} + +inline void Segment_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.starts.reserve(items.size()); + output.ends.reserve(items.size()); + output.colors.reserve(items.size()); + output.widths.reserve(items.size()); + for (const auto& item : items) { + output.starts.push_back({item.start.x, item.start.y, item.start.z}); + output.ends.push_back({item.end.x, item.end.y, item.end.z}); + output.colors.push_back(channels(item.color)); + output.widths.push_back(item.width_px); + } +} +} diff --git a/render_3D/render_3D/visual/Sphere_Visual.hpp b/render_3D/render_3D/visual/Sphere_Visual.hpp new file mode 100644 index 0000000..cf0cbc6 --- /dev/null +++ b/render_3D/render_3D/visual/Sphere_Visual.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Sphere { + Vec3 center{}; + Color color{Color::white()}; + Coordinate_3D radius{0.1F}; + bool operator==(const Sphere&) const = default; +}; + +namespace detail { +struct Sphere_Prepared_Data { + std::vector centers{}; + std::vector colors{}; + std::vector radii{}; +}; + +struct Sphere_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Sphere_Visual = Basic_Visual; +} + +#include "Sphere_Visual.ipp" diff --git a/render_3D/render_3D/visual/Sphere_Visual.ipp b/render_3D/render_3D/visual/Sphere_Visual.ipp new file mode 100644 index 0000000..c9ca12c --- /dev/null +++ b/render_3D/render_3D/visual/Sphere_Visual.ipp @@ -0,0 +1,20 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Sphere_Spec::valid(const Item& item) noexcept { + return finite(item.center) && finite(item.radius) && item.radius > 0.0F; +} + +inline void Sphere_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.centers.reserve(items.size()); + output.colors.reserve(items.size()); + output.radii.reserve(items.size()); + for (const auto& item : items) { + output.centers.push_back({item.center.x, item.center.y, item.center.z}); + output.colors.push_back(channels(item.color)); + output.radii.push_back(item.radius); + } +} +} diff --git a/render_3D/render_3D/visual/Splat_Visual.hpp b/render_3D/render_3D/visual/Splat_Visual.hpp new file mode 100644 index 0000000..c2adbc1 --- /dev/null +++ b/render_3D/render_3D/visual/Splat_Visual.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Splat { + Vec3 position{}; + Color color{Color::white()}; + Vec2 sigma{0.05F, 0.05F}; + Coordinate_3D angle{}; + bool operator==(const Splat&) const = default; +}; + +namespace detail { +struct Splat_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector> sigmas{}; + std::vector angles{}; +}; + +struct Splat_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Splat_Visual = Basic_Visual; +} + +#include "Splat_Visual.ipp" diff --git a/render_3D/render_3D/visual/Splat_Visual.ipp b/render_3D/render_3D/visual/Splat_Visual.ipp new file mode 100644 index 0000000..ea8c20e --- /dev/null +++ b/render_3D/render_3D/visual/Splat_Visual.ipp @@ -0,0 +1,21 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Splat_Spec::valid(const Item& item) noexcept { + return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); +} + +inline void Splat_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.sigmas.reserve(items.size()); + output.angles.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.sigmas.push_back({item.sigma.x, item.sigma.y}); + output.angles.push_back(item.angle); + } +} +} diff --git a/render_3D/render_3D/visual/Text_Visual.hpp b/render_3D/render_3D/visual/Text_Visual.hpp new file mode 100644 index 0000000..bf300f0 --- /dev/null +++ b/render_3D/render_3D/visual/Text_Visual.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Text_Label { + Vec3 position{}; + std::string text{}; + Color color{Color::white()}; + Pixel_Distance size_px{18.0F}; + bool operator==(const Text_Label&) const = default; +}; + +namespace detail { +struct Text_Prepared_Data { + std::vector positions{}; + std::vector colors{}; + std::vector sizes{}; + std::vector strings{}; +}; + +struct Text_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Text_Visual = Basic_Visual; +} + +#include "Text_Visual.ipp" diff --git a/render_3D/render_3D/visual/Text_Visual.ipp b/render_3D/render_3D/visual/Text_Visual.ipp new file mode 100644 index 0000000..9645235 --- /dev/null +++ b/render_3D/render_3D/visual/Text_Visual.ipp @@ -0,0 +1,21 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Text_Spec::valid(const Item& item) noexcept { + return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; +} + +inline void Text_Spec::prepare(const std::vector& items, Prepared_Data& output) { + reserve_position_color(output, items.size()); + output.sizes.reserve(items.size()); + output.strings.reserve(items.size()); + for (const auto& item : items) { + output.positions.push_back({item.position.x, item.position.y, item.position.z}); + output.colors.push_back(channels(item.color)); + output.sizes.push_back(item.size_px); + output.strings.push_back(item.text); + } +} +} diff --git a/render_3D/render_3D/visual/Vector_Visual.hpp b/render_3D/render_3D/visual/Vector_Visual.hpp new file mode 100644 index 0000000..12b609c --- /dev/null +++ b/render_3D/render_3D/visual/Vector_Visual.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Vector_Glyph { + Vec3 origin{}; + Vec3 direction{1.0F, 0.0F, 0.0F}; + Color color{Color::white()}; + Pixel_Distance width_px{1.0F}; + bool operator==(const Vector_Glyph&) const = default; +}; + +namespace detail { +struct Vector_Prepared_Data { + std::vector origins{}; + std::vector directions{}; + std::vector colors{}; + std::vector widths{}; +}; + +struct Vector_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Vector_Visual = Basic_Visual; +} + +#include "Vector_Visual.ipp" diff --git a/render_3D/render_3D/visual/Vector_Visual.ipp b/render_3D/render_3D/visual/Vector_Visual.ipp new file mode 100644 index 0000000..71ffb1f --- /dev/null +++ b/render_3D/render_3D/visual/Vector_Visual.ipp @@ -0,0 +1,22 @@ +#pragma once + +#include "detail/Visual_Prepare.hpp" + +namespace aethera::render_3d::detail { +inline bool Vector_Spec::valid(const Item& item) noexcept { + return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; +} + +inline void Vector_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.origins.reserve(items.size()); + output.directions.reserve(items.size()); + output.colors.reserve(items.size()); + output.widths.reserve(items.size()); + for (const auto& item : items) { + output.origins.push_back({item.origin.x, item.origin.y, item.origin.z}); + output.directions.push_back({item.direction.x, item.direction.y, item.direction.z}); + output.colors.push_back(channels(item.color)); + output.widths.push_back(item.width_px); + } +} +} diff --git a/render_3D/render_3D/visual/Visual_Spec.hpp b/render_3D/render_3D/visual/Visual_Spec.hpp new file mode 100644 index 0000000..bd01254 --- /dev/null +++ b/render_3D/render_3D/visual/Visual_Spec.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "Basic_Visual.hpp" + +namespace aethera::render_3d::detail { +struct Datoviz_Visual_Operations; + +template +struct Visual_Spec { + using Item = Item_Type; + using Settings = Settings_Type; + using Prepared_Data = Prepared_Type; +}; +} diff --git a/render_3D/render_3D/visual/Visuals.hpp b/render_3D/render_3D/visual/Visuals.hpp index 30a847a..605152c 100644 --- a/render_3D/render_3D/visual/Visuals.hpp +++ b/render_3D/render_3D/visual/Visuals.hpp @@ -1,285 +1,17 @@ #pragma once -#include "Basic_Visual.hpp" -namespace aethera::render_3d { -struct Point { - Vec3 position{}; /* Scene 坐标位置。 */ - Color color{Color::white()}; /* 点填充颜色。 */ - Pixel_Distance diameter_px{8.0F}; /* 点直径,单位为像素;必须为正数。 */ - bool operator==(const Point&) const = default; -}; -struct Splat { - Vec3 position{}; /* Scene 坐标位置。 */ - Color color{Color::white()}; /* Splat 颜色。 */ - Vec2 sigma{0.05F, 0.05F}; /* 两个主轴方向的标准差;必须为正数。 */ - Coordinate_3D angle{}; /* 主轴旋转角,单位为弧度。 */ - bool operator==(const Splat&) const = default; -}; -struct Pixel { - Vec3 position{}; /* Scene 坐标位置。 */ - Color color{Color::white()}; /* 像素颜色。 */ - Pixel_Distance size_px{1.0F}; /* 方形像素边长,单位为像素;必须为正数。 */ - bool operator==(const Pixel&) const = default; -}; -struct Marker { - Vec3 position{}; /* Scene 坐标位置。 */ - Color color{Color::white()}; /* 标记颜色。 */ - Pixel_Distance diameter_px{12.0F}; /* 标记直径,单位为像素;必须为正数。 */ - Coordinate_3D angle{}; /* 标记旋转角,单位为弧度。 */ - Marker_Shape shape{Marker_Shape::disc}; /* 标记图形。 */ - bool coordinate_label_visible{false}; /* 是否显示由此 Marker 实际位置派生的 XYZ 标签。 */ - bool operator==(const Marker&) const = default; -}; -struct Sphere { - Vec3 center{}; /* 球心 Scene 坐标。 */ - Color color{Color::white()}; /* 球体颜色。 */ - Coordinate_3D radius{0.1F}; /* Scene 坐标半径;必须为正数。 */ - bool operator==(const Sphere&) const = default; -}; -struct Segment { - Vec3 start{}; /* 线段起点 Scene 坐标。 */ - Vec3 end{}; /* 线段终点 Scene 坐标。 */ - Color color{Color::white()}; /* 线段颜色。 */ - Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */ - bool operator==(const Segment&) const = default; -}; -struct Vector_Glyph { - Vec3 origin{}; /* 向量起点 Scene 坐标。 */ - Vec3 direction{1.0F, 0.0F, 0.0F}; /* 向量方向和长度。 */ - Color color{Color::white()}; /* 向量颜色。 */ - Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */ - bool operator==(const Vector_Glyph&) const = default; -}; -struct Primitive_Vertex { - Vec3 position{}; /* 顶点 Scene 坐标。 */ - Color color{Color::white()}; /* 顶点颜色。 */ - Vec3 normal{}; /* 顶点法线。 */ - bool operator==(const Primitive_Vertex&) const = default; -}; -struct Mesh_Vertex { - Vec3 position{}; /* 顶点 Scene 坐标。 */ - Color color{Color::white()}; /* 顶点颜色。 */ - Vec3 normal{}; /* 顶点法线。 */ - Vec2 texture_coordinate{}; /* 纹理坐标。 */ - bool operator==(const Mesh_Vertex&) const = default; -}; -struct Path_Vertex { - Vec3 position{}; /* 路径顶点 Scene 坐标。 */ - Color color{Color::white()}; /* 路径颜色。 */ - Pixel_Distance width_px{1.0F}; /* 路径宽度,单位为像素;必须为正数。 */ - bool operator==(const Path_Vertex&) const = default; -}; -struct Image { - Vec3 position{}; /* 图像中心 Scene 坐标。 */ - Vec2 extent{1.0F, 1.0F}; /* Scene 坐标尺寸;两个分量必须为正数。 */ - Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 纹理采样矩形。 */ - bool operator==(const Image&) const = default; -}; -struct Label { - Vec3 position{}; /* 标签中心 Scene 坐标。 */ - Vec2 extent{32.0F, 16.0F}; /* 标签尺寸,单位为像素;两个分量必须为正数。 */ - Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 分类字段采样矩形。 */ - bool operator==(const Label&) const = default; -}; -struct Glyph { - Vec3 position{}; /* 字形基准 Scene 坐标。 */ - Vec4 bounds{}; /* 字形边界。 */ - Vec4 texture_coordinates{}; /* 字形纹理坐标。 */ - Color color{Color::white()}; /* 字形颜色。 */ - Coordinate_3D angle{}; /* 字形旋转角,单位为弧度。 */ - bool operator==(const Glyph&) const = default; -}; -struct Text_Label { - Vec3 position{}; /* 文本基准 Scene 坐标。 */ - std::string text{}; /* UTF-8 文本;不能为空。 */ - Color color{Color::white()}; /* 文本颜色。 */ - Pixel_Distance size_px{18.0F}; /* 字号,单位为像素;必须为正数。 */ - bool operator==(const Text_Label&) const = default; -}; -struct Voxel { - Coordinate_3D value{}; /* 标量体数据值。 */ - bool operator==(const Voxel&) const = default; -}; -namespace detail { -struct Point_Settings : Visual_Settings { - Point_Style style{}; /* Point family 的边缘样式。 */ - bool operator==(const Point_Settings&) const = default; -}; -struct Point_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector diameters{}; - Point_Style style{}; -}; -struct Splat_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector> sigmas{}; - std::vector angles{}; -}; -struct Pixel_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector sizes{}; -}; -struct Marker_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector diameters{}; - std::vector angles{}; - std::vector shapes{}; - std::vector coordinate_label_visibility{}; -}; -struct Sphere_Prepared_Data { - std::vector centers{}; - std::vector colors{}; - std::vector radii{}; -}; -struct Segment_Prepared_Data { - std::vector starts{}; - std::vector ends{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Vector_Prepared_Data { - std::vector origins{}; - std::vector directions{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Primitive_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector normals{}; -}; -struct Mesh_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector normals{}; - std::vector> texture_coordinates{}; -}; -struct Path_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Image_Prepared_Data { - std::vector positions{}; - std::vector> extents{}; - std::vector> texture_rectangles{}; - std::vector field_pixels{}; - std::uint32_t field_width{}; - std::uint32_t field_height{}; -}; -struct Labels_Prepared_Data { - std::vector positions{}; - std::vector> extents{}; - std::vector> texture_rectangles{}; - std::vector field_labels{}; - std::uint32_t field_width{}; - std::uint32_t field_height{}; -}; -struct Glyph_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector angles{}; - std::vector> bounds{}; - std::vector> texture_coordinates{}; -}; -struct Text_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector sizes{}; - std::vector strings{}; -}; -struct Volume_Prepared_Data { - std::vector values{}; - std::uint32_t field_width{}; - std::uint32_t field_height{}; - std::uint32_t field_depth{}; -}; -template -struct Visual_Spec { - using Item = Item_Type; - using Settings = Settings_Type; - using Prepared_Data = Prepared_Type; - static constexpr Visual_Family family = Family; -}; -struct Point_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Splat_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Pixel_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Marker_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Sphere_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Segment_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Vector_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Primitive_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Mesh_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Path_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Image_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Labels_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Glyph_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Text_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -struct Volume_Spec : Visual_Spec { - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} -using Point_Visual = Basic_Visual; -using Splat_Visual = Basic_Visual; -using Pixel_Visual = Basic_Visual; -using Marker_Visual = Basic_Visual; -using Sphere_Visual = Basic_Visual; -using Segment_Visual = Basic_Visual; -using Vector_Visual = Basic_Visual; -using Primitive_Visual = Basic_Visual; -using Mesh_Visual = Basic_Visual; -using Path_Visual = Basic_Visual; -using Image_Visual = Basic_Visual; -using Labels_Visual = Basic_Visual; -using Glyph_Visual = Basic_Visual; -using Text_Visual = Basic_Visual; -using Volume_Visual = Basic_Visual; -} -#include "Visuals.ipp" + +#include "Glyph_Visual.hpp" +#include "Image_Visual.hpp" +#include "Labels_Visual.hpp" +#include "Marker_Visual.hpp" +#include "Mesh_Visual.hpp" +#include "Path_Visual.hpp" +#include "Pixel_Visual.hpp" +#include "Point_Visual.hpp" +#include "Primitive_Visual.hpp" +#include "Segment_Visual.hpp" +#include "Sphere_Visual.hpp" +#include "Splat_Visual.hpp" +#include "Text_Visual.hpp" +#include "Vector_Visual.hpp" +#include "Volume_Visual.hpp" diff --git a/render_3D/render_3D/visual/Visuals.ipp b/render_3D/render_3D/visual/Visuals.ipp deleted file mode 100644 index 944ddd7..0000000 --- a/render_3D/render_3D/visual/Visuals.ipp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -namespace aethera::render_3d::detail { -inline Prepared_Color channels(Color value) { - return {value.red, value.green, value.blue, value.alpha}; -} -template -void reserve_position_color(Data& output, std::size_t count) { - output.positions.reserve(count); - output.colors.reserve(count); -} -inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; } -inline void Point_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); } } -inline bool Splat_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); } -inline void Splat_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sigmas.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sigmas.push_back({item.sigma.x, item.sigma.y}); output.angles.push_back(item.angle); } } -inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; } -inline void Pixel_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } } -inline bool Marker_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); } -inline void Marker_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); output.angles.reserve(items.size()); output.shapes.reserve(items.size()); output.coordinate_label_visibility.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); output.angles.push_back(item.angle); output.shapes.push_back(static_cast(item.shape)); output.coordinate_label_visibility.push_back(item.coordinate_label_visible); } } -inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; } -inline void Sphere_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.centers.reserve(items.size()); output.colors.reserve(items.size()); output.radii.reserve(items.size()); for (const auto& item : items) { output.centers.push_back({item.center.x, item.center.y, item.center.z}); output.colors.push_back(channels(item.color)); output.radii.push_back(item.radius); } } -inline bool Segment_Spec::valid(const Item& item) noexcept { return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; } -inline void Segment_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.starts.reserve(items.size()); output.ends.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.starts.push_back({item.start.x, item.start.y, item.start.z}); output.ends.push_back({item.end.x, item.end.y, item.end.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } } -inline bool Vector_Spec::valid(const Item& item) noexcept { return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; } -inline void Vector_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.origins.reserve(items.size()); output.directions.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.origins.push_back({item.origin.x, item.origin.y, item.origin.z}); output.directions.push_back({item.direction.x, item.direction.y, item.direction.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } } -inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); } -inline void Primitive_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } } -inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); } -inline void Mesh_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); output.texture_coordinates.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); output.texture_coordinates.push_back({item.texture_coordinate.x, item.texture_coordinate.y}); } } -inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; } -inline void Path_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } } -inline bool Image_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); } -inline void Image_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.texture_rectangles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.texture_rectangles.push_back({item.texture_rectangle.x, item.texture_rectangle.y, item.texture_rectangle.z, item.texture_rectangle.w}); } } -inline bool Labels_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); } -inline void Labels_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.texture_rectangles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.texture_rectangles.push_back({item.texture_rectangle.x, item.texture_rectangle.y, item.texture_rectangle.z, item.texture_rectangle.w}); } } -inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); } -inline void Glyph_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.angles.reserve(items.size()); output.bounds.reserve(items.size()); output.texture_coordinates.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.angles.push_back(item.angle); output.bounds.push_back({item.bounds.x, item.bounds.y, item.bounds.z, item.bounds.w}); output.texture_coordinates.push_back({item.texture_coordinates.x, item.texture_coordinates.y, item.texture_coordinates.z, item.texture_coordinates.w}); } } -inline bool Text_Spec::valid(const Item& item) noexcept { return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; } -inline void Text_Spec::prepare(const std::vector& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); output.strings.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); output.strings.push_back(item.text); } } -inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); } -inline void Volume_Spec::prepare(const std::vector& items, Prepared_Data& output) { output.values.reserve(items.size()); for (const auto& item : items) output.values.push_back(item.value); } -} diff --git a/render_3D/render_3D/visual/Volume_Visual.hpp b/render_3D/render_3D/visual/Volume_Visual.hpp new file mode 100644 index 0000000..24b4207 --- /dev/null +++ b/render_3D/render_3D/visual/Volume_Visual.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "Visual_Spec.hpp" + +namespace aethera::render_3d { +struct Volume_Field_Settings { + std::uint32_t field_width{}; + std::uint32_t field_height{}; + std::uint32_t field_depth{}; + bool operator==(const Volume_Field_Settings&) const = default; +}; + +struct Voxel { + Coordinate_3D value{}; + bool operator==(const Voxel&) const = default; +}; + +namespace detail { +struct Volume_Prepared_Data { + std::vector values{}; + std::uint32_t field_width{}; + std::uint32_t field_height{}; + std::uint32_t field_depth{}; +}; + +struct Volume_Spec : Visual_Spec { + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; +} + +using Volume_Visual = Basic_Visual; +} + +#include "Volume_Visual.ipp" diff --git a/render_3D/render_3D/visual/Volume_Visual.ipp b/render_3D/render_3D/visual/Volume_Visual.ipp new file mode 100644 index 0000000..4b6b562 --- /dev/null +++ b/render_3D/render_3D/visual/Volume_Visual.ipp @@ -0,0 +1,14 @@ +#pragma once + +namespace aethera::render_3d::detail { +inline bool Volume_Spec::valid(const Item& item) noexcept { + return finite(item.value); +} + +inline void Volume_Spec::prepare(const std::vector& items, Prepared_Data& output) { + output.values.reserve(items.size()); + for (const auto& item : items) { + output.values.push_back(item.value); + } +} +} diff --git a/render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.hpp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.hpp new file mode 100644 index 0000000..a72554a --- /dev/null +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct DvzFont; +struct DvzPanel; +struct DvzSampledField; +struct DvzScene; +struct DvzText; +struct DvzVisual; + +namespace aethera::render_3d::detail { + +struct Datoviz_Visual_Instance; + +struct Datoviz_Attribute_Description { + const char* name; /* Datoviz Visual 属性名。 */ + std::uint32_t stride{}; /* 单项字节跨度。 */ +}; + +struct Datoviz_Native_Visual { + not_null visual; /* DvzScene 拥有;Visual 实例必填借用。 */ + DvzSampledField* field{}; /* 可空、非拥有采样场借用。 */ + DvzFont* font{}; /* 可空、非拥有字体借用。 */ + DvzText* coordinate_text{}; /* 可空、非拥有坐标文本借用。 */ + std::array field_extent{}; /* 初始采样场尺寸。 */ +}; + +struct Datoviz_Visual_Operations { + using Create = Datoviz_Native_Visual (*)( + not_null, not_null); + using Item_Count = std::size_t (*)(not_null); + using Attribute_Sink = void (*)( + not_null, not_null, std::size_t); + using Visit_External_Attributes = void (*)( + not_null, not_null, Attribute_Sink); + using Upload_Visual = void (*)( + Datoviz_Visual_Instance&, not_null, std::uint32_t); + using Apply_Style = void (*)( + not_null, not_null); + using Has_Coordinate_Labels = bool (*)(not_null); + Create create; /* 创建并配置该具体 Datoviz Visual。 */ + Item_Count item_count; /* 读取具体 Prepared_Data 的项数。 */ + Upload_Visual upload_visual; /* 上传 Datoviz 管理的具体 payload。 */ + bool supports_item_interaction{}; /* 是否参与 item query。 */ + bool overlaps_gpu{}; /* 提交后是否可立即准备下一帧。 */ + std::span external_attributes{}; /* 三槽映射属性布局;空表示 Datoviz 管理数据。 */ + Visit_External_Attributes visit_external_attributes{}; /* 非空时按布局顺序枚举具体字段。 */ + Apply_Style apply_style{}; /* 可空;仅有独立样式的 Visual 提供。 */ + Has_Coordinate_Labels has_coordinate_labels{}; /* 可空;仅 Marker 提供。 */ +}; + +} diff --git a/render_3D/render_3D/visual/detail/Visual_Prepare.hpp b/render_3D/render_3D/visual/detail/Visual_Prepare.hpp new file mode 100644 index 0000000..a2307cc --- /dev/null +++ b/render_3D/render_3D/visual/detail/Visual_Prepare.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "../Prepared_Visual.hpp" + +namespace aethera::render_3d::detail { +[[nodiscard]] Prepared_Color channels(Color value); + +template +void reserve_position_color(Data& output, std::size_t count); +} + +#include "Visual_Prepare.ipp" diff --git a/render_3D/render_3D/visual/detail/Visual_Prepare.ipp b/render_3D/render_3D/visual/detail/Visual_Prepare.ipp new file mode 100644 index 0000000..25c62fc --- /dev/null +++ b/render_3D/render_3D/visual/detail/Visual_Prepare.ipp @@ -0,0 +1,13 @@ +#pragma once + +namespace aethera::render_3d::detail { +inline Prepared_Color channels(Color value) { + return {value.red, value.green, value.blue, value.alpha}; +} + +template +void reserve_position_color(Data& output, std::size_t count) { + output.positions.reserve(count); + output.colors.reserve(count); +} +}