diff --git a/kernel/src/kernel/scene.ipp b/kernel/src/kernel/scene.ipp index 0ae6bdb..b1bb9e2 100644 --- a/kernel/src/kernel/scene.ipp +++ b/kernel/src/kernel/scene.ipp @@ -24,6 +24,11 @@ struct Scene::Private : Prev_Private { State_Access pending_states, const Prop* current_prop, State_Access current_states); + /* 写入事件统计字段表示开始新的统计窗口;累加器与发布 State 在同一入口归零。 */ + template + void before_state_set(Object* object, Member Owner::* member, + State_Access pending_states); /* Private CRTP 入口:执行当前总 Taskflow;派生 Private 只有在替换完整 Scene 处理语义时才应覆盖。 */ template void process(Object* object, Callback&& callback) requires std::invocable; @@ -36,6 +41,15 @@ struct Scene::Private : Prev_Private { requires std::invocable; /* 派生 Scene 的 Private 还可覆盖 Def::Private 的四个 State 生命周期 hook,并通过 State_Access::get() 访问状态层。 */ }; + +template +void Scene::Private::before_state_set( + Object* object, Member Owner::* member, + State_Access pending_states) { + if constexpr (std::same_as) + if (member == &State::event_statistics) event_statistics.reset(); +} struct Scene::Private::Runtime { std::unique_ptr taskflow; /* 当前已构建的总业务 DAG;为空表示尚未构建。 */ }; diff --git a/mcp/core/runtime/Plot.cpp b/mcp/core/runtime/Plot.cpp index e20d289..d936cc9 100644 --- a/mcp/core/runtime/Plot.cpp +++ b/mcp/core/runtime/Plot.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -1828,7 +1828,8 @@ nlohmann::json Plot::diagnostics() const { {"gpu_completion_count", pipeline.gpu_completion_count}, {"last_completed_sequence", pipeline.last_completed_sequence} }; - const auto gpu = gpu_completion_state(); + const auto& gpu = render_3d::detail::Gpu_Completion_Service::instance(). + read_state(); const auto milliseconds = [](std::uint64_t nanoseconds) { return static_cast(nanoseconds) / 1'000'000.0; }; @@ -1917,7 +1918,10 @@ nlohmann::json Plot::post_publish_taskflow_trace() const { } void Plot::reset_diagnostics() { - std::visit([](auto& scene) { scene->reset_diagnostics(); }, d->scene); + std::visit([](auto& scene) { + scene->template update_state<&Scene::State::event_statistics>( + Event_Statistics_State{}); + }, d->scene); d->statistics_generation.fetch_add(1, std::memory_order_acq_rel); d->with_frame_policy([](auto& policy) { policy.reset_statistics(); }); d->arm_tick_consumer(weak_from_this()); diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index a5cbb8b..3d53ac6 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -14,8 +14,5 @@ void Render_Scene_2D::activate_view() { void Render_Scene_2D::deactivate_view() { double_buffer::detail::Internal_Access::get(this).set_view_active(this, false); } -void Render_Scene_2D::reset_diagnostics() { - double_buffer::detail::Internal_Access::get(this).reset_diagnostics(this); -} } diff --git a/render_2D/render_2D/scene/Render_Scene_2D.hpp b/render_2D/render_2D/scene/Render_Scene_2D.hpp index 7adcb07..32a240e 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.hpp @@ -50,7 +50,6 @@ struct Render_Scene_2D : Def frame, Frame_Callback callback); template void set_view_active(Object* object, bool active); - template void reset_diagnostics(Object* object); /* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */ template void bind_private_crtp(Object* object); @@ -469,15 +468,6 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, } } template -void Render_Scene_2D::Private::reset_diagnostics(Object* object) { - double_buffer::detail::Internal_Access::publish_state(object, - [this](State_Access states) { - event_statistics.reset(); - auto& state = states.template get(); - state.event_statistics = {}; - }); -} -template void Render_Scene_2D::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/Gpu_Completion_State.cpp b/render_3D/render_3D/Gpu_Completion_State.cpp deleted file mode 100644 index 6091029..0000000 --- a/render_3D/render_3D/Gpu_Completion_State.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Gpu_Completion_State.hpp" -#include "detail/Gpu_Completion_Service.hpp" - -namespace aethera::render_3d { -Gpu_Completion_State gpu_completion_state() { - return detail::Gpu_Completion_Service::instance().state(); -} -} diff --git a/render_3D/render_3D/Gpu_Completion_State.hpp b/render_3D/render_3D/Gpu_Completion_State.hpp deleted file mode 100644 index d12cb98..0000000 --- a/render_3D/render_3D/Gpu_Completion_State.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include - -namespace aethera::render_3d { -/* Gpu_Completion_Service::State 的业务字段;权威实例由 Def State 双缓冲持有。 */ -struct Gpu_Completion_State { - std::size_t capacity{}; - std::size_t in_flight{}; - std::size_t peak_in_flight{}; - std::size_t watched{}; - std::size_t peak_watched{}; - std::size_t active_fences{}; - std::size_t pending_fences{}; - std::uint64_t reservation_count{}; - std::uint64_t completion_count{}; - std::uint64_t cancellation_count{}; - std::uint64_t fence_probe_count{}; - std::uint64_t fence_wait_count{}; - std::uint64_t fence_wait_timeout_count{}; - std::uint64_t fence_wait_total_ns{}; - std::uint64_t fence_wait_max_ns{}; - std::uint64_t callback_total_ns{}; - std::uint64_t callback_max_ns{}; - std::uint64_t callback_failure_count{}; - std::uint64_t backpressure_count{}; - std::uint64_t fault_count{}; - std::uint64_t abandoned_count{}; - bool operator==(const Gpu_Completion_State&) const = default; -}; - -[[nodiscard]] Gpu_Completion_State gpu_completion_state(); -} diff --git a/render_3D/render_3D/detail/Backend_Types.cpp b/render_3D/render_3D/detail/Backend_Types.cpp new file mode 100644 index 0000000..9e27297 --- /dev/null +++ b/render_3D/render_3D/detail/Backend_Types.cpp @@ -0,0 +1,16 @@ +#include "Backend_Types.hpp" +#include + +namespace aethera::render_3d::detail { +bool prepared_visual_batch_complete( + const Prepared_Visual_Batch& visuals) noexcept { + return !visuals.empty() && + std::ranges::all_of( + visuals, [](const Prepared_Visual_Instance& value) { + return value.visual.has_payload(); + }); +} + +bool Scene_3D_Parameters::operator==( + const Scene_3D_Parameters&) const = default; +} diff --git a/render_3D/render_3D/detail/Backend_Types.hpp b/render_3D/render_3D/detail/Backend_Types.hpp index b2cc38a..d74f463 100644 --- a/render_3D/render_3D/detail/Backend_Types.hpp +++ b/render_3D/render_3D/detail/Backend_Types.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include namespace aethera::render_3d::detail { using Visual_Identity = std::uintptr_t; @@ -17,30 +16,21 @@ struct Visual_Registration { }; struct Prepared_Visual_Instance { Visual_Identity identity{}; /* 与 Builder 注册身份一一对应。 */ - Prepared_Visual visual{}; /* Prepare 发布的不可变字段快照。 */ + Prepared_Visual visual{}; /* Prepare 交给本帧后端阶段的不可变提交数据。 */ }; using Prepared_Visual_Batch = std::vector; using Shared_Prepared_Visual_Batch = std::shared_ptr; -[[nodiscard]] inline bool prepared_visual_has_payload( - const Prepared_Visual& visual) noexcept { - return visual.operations != nullptr && visual.data != nullptr; -} -[[nodiscard]] inline bool prepared_visual_batch_complete( - const Prepared_Visual_Batch& visuals) noexcept { - return !visuals.empty() && - std::ranges::all_of(visuals, [](const Prepared_Visual_Instance& value) { - return prepared_visual_has_payload(value.visual); - }); -} +[[nodiscard]] bool prepared_visual_batch_complete( + const Prepared_Visual_Batch& visuals) noexcept; struct Scene_3D_Parameters { Extent viewport{560, 320}; /* 离屏渲染目标尺寸,单位为像素。 */ Linear_Color clear_color{}; /* 每帧开始时写入的线性背景色。 */ - Camera_Descriptor camera{}; /* Camera 组件发布的本帧配置快照。 */ + Camera_Descriptor camera{}; /* Camera 组件为本帧后端阶段提供的配置。 */ plot::Axis_Descriptor x_axis{}; /* 三维数据 X 轴的业务范围与标签策略。 */ plot::Axis_Descriptor y_axis{}; /* 三维数据 Y 轴的业务范围与标签策略。 */ plot::Axis_Descriptor z_axis{}; /* 三维数据 Z 轴的业务范围与标签策略。 */ - bool operator==(const Scene_3D_Parameters&) const = default; + bool operator==(const Scene_3D_Parameters&) const; }; } diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp index 7f491ad..0ced1a6 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp @@ -95,10 +95,6 @@ void Gpu_Completion_Service::cancel( static_cast(*d).cancel(pending); } -Gpu_Completion_State Gpu_Completion_Service::state() const { - return static_cast(*d).state(); -} - Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::Private::prepare( Completion completion, Exception_Handler on_exception, bool observe) { @@ -117,11 +113,11 @@ Gpu_Completion_Service::Private::prepare( pending->service = object; Admission_Result admission{Admission_Result::none}; double_buffer::detail::Internal_Access::update_state< - &State::in_flight, &State::peak_in_flight, - &State::reservation_count, &State::backpressure_count>( + &Gpu_Completion_State::State::in_flight, &Gpu_Completion_State::State::peak_in_flight, + &Gpu_Completion_State::State::reservation_count, &Gpu_Completion_State::State::backpressure_count>( object, [&](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); if (state.in_flight >= state.capacity) { ++state.backpressure_count; admission = Admission_Result::capacity_exhausted; @@ -137,9 +133,9 @@ Gpu_Completion_Service::Private::prepare( double_buffer::detail::Internal_Access::submit_stream(object, pending); } catch (...) { - double_buffer::detail::Internal_Access::update_state<&State::in_flight>(object, + double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::in_flight>(object, [](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); if (state.in_flight != 0) --state.in_flight; }); throw; @@ -162,9 +158,9 @@ void Gpu_Completion_Service::Private::watch( pending->watched_at = std::chrono::steady_clock::now(); pending->status = Gpu_Completion_Pending_Fence::Status::watched; } - double_buffer::detail::Internal_Access::update_state<&State::watched, &State::peak_watched>(object, + double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::watched, &Gpu_Completion_State::State::peak_watched>(object, [](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); ++state.watched; update_peak(state.peak_watched, state.watched); }); @@ -182,35 +178,6 @@ void Gpu_Completion_Service::Private::cancel( request_poll(std::chrono::nanoseconds{1}); } -Gpu_Completion_State Gpu_Completion_Service::Private::state() const { - Gpu_Completion_State result{}; - double_buffer::detail::Internal_Access::access_state(object, - [&](const State& state) { - result.capacity = state.capacity; - result.in_flight = state.in_flight; - result.peak_in_flight = state.peak_in_flight; - result.watched = state.watched; - result.peak_watched = state.peak_watched; - result.active_fences = state.active_fences; - result.pending_fences = state.pending_fences; - result.reservation_count = state.reservation_count; - result.completion_count = state.completion_count; - result.cancellation_count = state.cancellation_count; - result.fence_probe_count = state.fence_probe_count; - result.fence_wait_count = state.fence_wait_count; - result.fence_wait_timeout_count = state.fence_wait_timeout_count; - result.fence_wait_total_ns = state.fence_wait_total_ns; - result.fence_wait_max_ns = state.fence_wait_max_ns; - result.callback_total_ns = state.callback_total_ns; - result.callback_max_ns = state.callback_max_ns; - result.callback_failure_count = state.callback_failure_count; - result.backpressure_count = state.backpressure_count; - result.fault_count = state.fault_count; - result.abandoned_count = state.abandoned_count; - }); - return result; -} - void Gpu_Completion_Service::Private::request_poll( std::chrono::nanoseconds delay) noexcept { if (stopping.load(std::memory_order_acquire)) return; @@ -246,10 +213,10 @@ void Gpu_Completion_Service::Private::poll() noexcept { pending->status = Gpu_Completion_Pending_Fence::Status::canceled; } double_buffer::detail::Internal_Access::update_state< - &State::watched, &State::fault_count, &State::abandoned_count>( + &Gpu_Completion_State::State::watched, &Gpu_Completion_State::State::fault_count, &Gpu_Completion_State::State::abandoned_count>( object, [error](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); if (state.watched != 0) --state.watched; if (error != Completion_Error::none) { ++state.fault_count; @@ -264,12 +231,12 @@ void Gpu_Completion_Service::Private::poll() noexcept { catch (...) { callback_failure = std::current_exception(); } const auto callback_ns = elapsed_nanoseconds(callback_started); double_buffer::detail::Internal_Access::update_state< - &State::callback_total_ns, &State::callback_max_ns, - &State::callback_failure_count, &State::completion_count, - &State::in_flight>( + &Gpu_Completion_State::State::callback_total_ns, &Gpu_Completion_State::State::callback_max_ns, + &Gpu_Completion_State::State::callback_failure_count, &Gpu_Completion_State::State::completion_count, + &Gpu_Completion_State::State::in_flight>( object, [callback_ns, &callback_failure](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); state.callback_total_ns += callback_ns; update_peak(state.callback_max_ns, callback_ns); if (callback_failure) ++state.callback_failure_count; @@ -301,10 +268,10 @@ void Gpu_Completion_Service::Private::poll() noexcept { if (status == Gpu_Completion_Pending_Fence::Status::canceled) { iterator = active.erase(iterator); double_buffer::detail::Internal_Access::update_state< - &State::cancellation_count, &State::in_flight>( + &Gpu_Completion_State::State::cancellation_count, &Gpu_Completion_State::State::in_flight>( object, [](State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); ++state.cancellation_count; if (state.in_flight != 0) --state.in_flight; }); @@ -321,9 +288,9 @@ void Gpu_Completion_Service::Private::poll() noexcept { finish(pending, VK_TIMEOUT, Completion_Error::fence_abandoned); continue; } - double_buffer::detail::Internal_Access::update_state<&State::fence_probe_count>(object, + double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::fence_probe_count>(object, [](State_Access states) { - ++states.template get().fence_probe_count; + ++states.template get().fence_probe_count; }); const VkResult result = vkGetFenceStatus( reinterpret_cast(device), @@ -344,15 +311,15 @@ void Gpu_Completion_Service::Private::poll() noexcept { if (last_state_publication == std::chrono::steady_clock::time_point{} || now - last_state_publication >= state_publication_interval) { double_buffer::detail::Internal_Access::update_state< - &State::active_fences, &State::pending_fences>( + &Gpu_Completion_State::State::active_fences, &Gpu_Completion_State::State::pending_fences>( object, [active_count = active.size(), reserved_count]( State_Access states) { - auto& state = states.template get(); + auto& state = states.template get(); state.active_fences = active_count; state.pending_fences = reserved_count; }); - double_buffer::detail::Internal_Access::publish_state(object); + double_buffer::detail::Internal_Access::publish_state(object); last_state_publication = now; } diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.hpp b/render_3D/render_3D/detail/Gpu_Completion_Service.hpp index 6fc0361..f036f88 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.hpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.hpp @@ -1,16 +1,15 @@ #pragma once -#include "../Gpu_Completion_State.hpp" #include +#include #include #include #include #include namespace aethera::render_3d::detail { struct GPU_Task_Tag {}; -struct Gpu_Completion_Pending_Fence; -struct Gpu_Completion_Service : Def>> { + +/* GPU completion 统计是独立状态领域;Service 只负责行为与任务流。 */ +struct Gpu_Completion_State : Def { struct Prop : Prev_Prop {}; struct State : Prev_State { std::size_t capacity{}; /* 可同时监视的最大 fence 数量。 */ @@ -18,24 +17,33 @@ struct Gpu_Completion_Service : Def>> { + struct Prop : Prev_Prop {}; + struct State : Prev_State {}; struct Private; enum struct Admission_Result : std::uint8_t { none, @@ -83,7 +91,6 @@ struct Gpu_Completion_Service : Def& pending, std::uintptr_t device, std::uintptr_t fence); diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.ipp b/render_3D/render_3D/detail/Gpu_Completion_Service.ipp index 777da69..276d2f2 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.ipp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.ipp @@ -35,9 +35,9 @@ struct Gpu_Completion_Service::Private : Prev_Private { void bind_private_crtp(Attached_Object* attached) { Prev_Private::bind_private_crtp(attached); object = not_null{static_cast(attached)}; - double_buffer::detail::Internal_Access::update_state<&State::capacity>(object, + double_buffer::detail::Internal_Access::update_state<&Gpu_Completion_State::State::capacity>(object, static_cast(default_capacity)); - double_buffer::detail::Internal_Access::publish_state(object); + double_buffer::detail::Internal_Access::publish_state(object); active.reserve(static_cast(default_capacity)); poll_timer = Frame_Scheduler::instance().make_timer( [this](Frame_Scheduler::Tick) { poll(); }); @@ -48,7 +48,6 @@ struct Gpu_Completion_Service::Private : Prev_Private { void watch(const std::shared_ptr& pending, std::uintptr_t device, std::uintptr_t fence); void cancel(const std::shared_ptr& pending); - [[nodiscard]] Gpu_Completion_State state() const; void request_poll(std::chrono::nanoseconds delay = probe_interval) noexcept; void poll() noexcept; static constexpr std::ptrdiff_t default_capacity = 1024; diff --git a/render_3D/render_3D/scene/Render_Scene_3D.cpp b/render_3D/render_3D/scene/Render_Scene_3D.cpp index 04be731..48d62b3 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.cpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.cpp @@ -7,13 +7,4 @@ Render_Scene_3D::Render_Result Render_Scene_3D::render( 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); -} -void Render_Scene_3D::deactivate_view() { - double_buffer::detail::Internal_Access::get(this).set_view_active(this, false); -} -void Render_Scene_3D::reset_diagnostics() { - double_buffer::detail::Internal_Access::get(this).reset_diagnostics(this); -} } diff --git a/render_3D/render_3D/scene/Render_Scene_3D.hpp b/render_3D/render_3D/scene/Render_Scene_3D.hpp index d74447b..f15dd60 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.hpp +++ b/render_3D/render_3D/scene/Render_Scene_3D.hpp @@ -2,8 +2,6 @@ #include "../axis/Axes_3D.hpp" #include "../base/Frame_3D.hpp" #include "../camera/Camera_3D.hpp" -#include "../detail/Backend_Types.hpp" -#include "../visual/Visuals.hpp" #include #include #include @@ -13,6 +11,7 @@ #include #include namespace aethera::render_3d { +namespace detail { struct Datoviz_Visual_Operations; } /* 执行公共 Renderable 图,并把结果无等待地提交给 Datoviz 渲染域。 */ struct Render_Scene_3D : Def { struct Prop : Prev_Prop { @@ -59,8 +58,7 @@ struct Render_Scene_3D : Def { }; using Render_Result_Type = Render_Result; using Render_Frame_Type = Frame_3D; - [[nodiscard]] Render_Result render( - not_null frame, Frame_Callbacks callbacks); + [[nodiscard]] Render_Result render(not_null frame, Frame_Callbacks callbacks); /* * 无等待写入调用方拥有的 Frame;Scene 始终只借用地址。最多三个借用可在 * CPU Prepare、GPU 和读回阶段重叠,完成全部写入并清除借用后调用唯一完成回调。 @@ -71,9 +69,6 @@ struct Render_Scene_3D : Def { * completion Taskflow 完成前调用方必须保持本帧像素所有权;节点若启动更晚结束的异步工作, * 必须捕获 share_pixels() 等拥有像素所有权的对象,禁止只捕获会被下一帧复用的裸 Frame_3D*。 */ - void activate_view(); - void deactivate_view(); - void reset_diagnostics(); }; } #include "Render_Scene_3D.ipp" diff --git a/render_3D/render_3D/scene/Render_Scene_3D.ipp b/render_3D/render_3D/scene/Render_Scene_3D.ipp index 24fd4f6..e797e35 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.ipp +++ b/render_3D/render_3D/scene/Render_Scene_3D.ipp @@ -1,5 +1,182 @@ #pragma once -#include "Scene_Datoviz_State.hpp" +#include "../base/Datoviz_Frame_Observation.hpp" +#include "../detail/Backend_Types.hpp" +#include "../visual/detail/Datoviz_Visual_State.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct DvzController; +struct DvzDrp2CommandStream; +struct DvzDrp2Runtime; +struct DvzFigure; +struct DvzFramePlanEmitter; +struct DvzInputRouter; +struct DvzItemInteraction; +struct DvzPanel; +struct DvzPinnedReadout; +struct DvzPointerGestureHandler; +struct DvzScene; +struct DvzSceneFrameArtifact; + +namespace aethera { namespace render_3d { namespace detail { + +struct Datoviz_Render_Context; +struct Datoviz_Frame_Target; +struct Datoviz_Frame_Target_Set; + +/* Scene 帧槽从录制到 GPU 完成期间携带的唯一状态。 */ +struct Datoviz_Pending_Frame { + std::uintptr_t device{}; /* Datoviz Device 的非拥有句柄值。 */ + std::uintptr_t fence{}; /* 本帧完成 Fence 的非拥有句柄值。 */ + Extent extent{}; /* 本帧离屏目标尺寸。 */ + std::uint8_t target_index{}; /* Scene 三槽中的目标下标。 */ + std::uint64_t target_generation{}; /* 防止槽重建后的陈旧引用命中。 */ + Datoviz_Frame_Observation observation{}; /* 本帧后端执行事实。 */ +}; + +/* 同一个 Scene 帧完成后的交付状态,不拆成独立模块。 */ +struct Datoviz_Completed_Frame { + Extent extent{}; + std::vector pixels{}; + Datoviz_Frame_Observation observation{}; +}; + +/* + * 3D 后端核心资源所有权: + * 1. render_context_ 独占 GPU 上下文入口,并串行投递 Vulkan 工作。 + * 2. 本对象拥有命令池、描述符池、三个 DRP2 Runtime、Datoviz Scene 和外部属性 Buffer。 + * 3. Datoviz Scene 继续拥有 Figure、Panel、Visual、字体和采样场;对应裸指针只在 Scene 生命周期内借用。 + * 4. targets_ 固定拥有三个离屏目标槽;每槽独占颜色图像、命令缓冲、Fence、Submit 和读回 Buffer。 + * 5. prepare() 只取得空闲槽并录制,submit() 异步提交,collect() 在 GPU 完成后读回并归还该槽。 + */ +struct Scene_Datoviz_State { + Scene_Datoviz_State(); + ~Scene_Datoviz_State(); + Scene_Datoviz_State(const Scene_Datoviz_State&) = delete; + Scene_Datoviz_State& operator=(const Scene_Datoviz_State&) = delete; + + /* 一次性建立 GPU、Vulkan 池、三槽 Runtime 和 Datoviz Scene 资源。 */ + void initialize(std::uint32_t gpu_index, bool validation_enabled, + const std::vector& visuals, + const Scene_3D_Parameters& initial_scene); + void create_scene(const std::vector& visuals, + const Scene_3D_Parameters& initial_scene); + void apply_camera(const Camera_Descriptor& camera); + void apply_axes(const Scene_3D_Parameters& scene); + [[nodiscard]] bool matches_command_structure( + const Scene_3D_Parameters& scene, + const Prepared_Visual_Batch& visuals) const; + /* 把本帧 Scene 参数和各 Visual 发布版本应用到 Datoviz 权威对象。 */ + [[nodiscard]] std::uint64_t apply( + const Scene_3D_Parameters& scene, + const Prepared_Visual_Batch& visuals, + std::uint8_t target_index, bool bind_target); + [[nodiscard]] std::uint64_t apply_visual( + Datoviz_Visual_Instance& target, const Prepared_Visual& visual, + std::uint8_t target_index, bool bind_target); + void ensure_external_attributes(Datoviz_Visual_Instance& target, + const Prepared_Visual& visual); + [[nodiscard]] std::uint64_t upload_external_attributes( + Datoviz_Visual_Instance& target, const Prepared_Visual& visual, + std::uint8_t target_index); + 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, + std::uint8_t target_index); + [[nodiscard]] bool target_runtime_resources_current( + const Prepared_Visual_Batch& visuals, + std::uint8_t target_index) const; + [[nodiscard]] std::vector stale_runtime_visuals( + const Prepared_Visual_Batch& visuals, + std::uint8_t target_index) const; + void mark_target_runtime_resources_uploaded(std::uint8_t target_index); + /* 帧流水线:尝试复用已录制命令,失败时取得空槽并重新录制。 */ + [[nodiscard]] std::optional 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( + 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]] Datoviz_Frame_Target& target(const Datoviz_Pending_Frame& pending); + /* 提交不等待 GPU;完成通知由 Gpu_Completion_Service 驱动 collect()。 */ + void submit(Datoviz_Pending_Frame& pending, + std::function completion); + [[nodiscard]] Datoviz_Completed_Frame collect(Datoviz_Pending_Frame pending); + void discard(Datoviz_Pending_Frame pending); + void quarantine(Datoviz_Pending_Frame pending) noexcept; + /* 输入只修改 Datoviz Controller;revision 使下一帧重新录制必要命令。 */ + void mark_controller_input_applied() noexcept; + void dispatch_pointer(Event_Type type, float x, float y, + Mouse_Button button, + Keyboard_Modifier modifiers, + Extent viewport, + std::uint64_t occurred_at_ns); + void dispatch_wheel(float x, float y, float delta_x, float delta_y, + Keyboard_Modifier modifiers, + Extent viewport, + std::uint64_t occurred_at_ns); + void dispatch_key(const Key_Event& event); + /* 正常路径按所有权逆序销毁;故障隔离路径只转移资源,不在线程上等待。 */ + void abandon_resources() noexcept; + void destroy(); + +private: + struct Runtime_Slot { + owner runtime{}; /* 本目标槽独占的 DRP2 Runtime。 */ + owner emitter{}; /* 本目标槽独占的帧计划 emitter。 */ + }; + + 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 后是否禁止销毁在途资源。 */ + + std::vector visuals_{}; /* 注册顺序稳定的具体 Visual 集合。 */ + std::vector> external_buffers_{}; /* Visual 三槽属性 Buffer 唯一所有权。 */ + + 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_{}; /* 当前录制周期是否消费了输入。 */ + + mutable std::mutex target_mutex_{}; /* 仅保护三槽在提交与回读边界的生命周期。 */ + std::unique_ptr targets_{}; /* 三槽唯一所有权。 */ + std::uint64_t target_generation_{}; /* 目标槽重建代数。 */ +}; + +}}} // namespace aethera::render_3d::detail + #include "../detail/Gpu_Completion_Service.hpp" #include #include @@ -185,10 +362,6 @@ struct Render_Scene_3D::Private : Prev_Private, [[nodiscard]] Render_Result render( Object* object, not_null frame, Frame_Callbacks callbacks); - template - void set_view_active(Object* object, bool active); - template - void reset_diagnostics(Object* object); }; template Render_Scene_3D::Builder::Builder() : Base() {} @@ -742,6 +915,13 @@ void Render_Scene_3D::Private::initialize_backend( } template void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { + /* + * 每帧主链只有三个业务阶段: + * scene.begin 推进 Camera/Axes、读取本帧参数并取得事件; + * scene.renderables 执行各 Visual 的 Prepare/发布; + * scene.backend.submit 把不可变提交数据交给 Datoviz 三槽流水线。 + * GPU 完成和像素读回不挂在这条 Taskflow 上,由 Gpu_Completion_Service 异步回到 completion_graph。 + */ if (frame_taskflow) return; if (!runtime->taskflow) runtime->taskflow = std::make_unique("scene.render"); frame_taskflow = std::make_unique("render_3d.frame"); @@ -755,6 +935,8 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { frame->mark(Frame_Trace_Marker::event_dispatch_started); active_context->events = take_events(object); frame->mark(Frame_Trace_Marker::event_dispatch_finished); + camera_component->advance_object(); + axes_component->advance_object(); auto context = std::static_pointer_cast< detail::Scene_Paint_Context>(paint_context); context->parameters = parameters(object); @@ -769,8 +951,6 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) { if (context->visuals.use_count() != 1) context->visuals = std::make_shared(*context->visuals); - camera_component->advance_object(); - axes_component->advance_object(); frame->mark(Frame_Trace_Marker::prepare_started); frame->mark(Frame_Trace_Marker::prepare_finished); frame->mark(Frame_Trace_Marker::paint_started); @@ -902,19 +1082,6 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render( return Render_Result::submitted; } template -void Render_Scene_3D::Private::reset_diagnostics(Object* object) { - double_buffer::detail::Internal_Access::publish_state(object, - [this](State_Access states) { - event_statistics.reset(); - auto& state = states.template get(); - state.event_statistics = {}; - }); -} -template -void Render_Scene_3D::Private::set_view_active(Object* object, bool active) { - double_buffer::detail::Internal_Access::set<&Prop::view_active>(object, active); -} -template void Render_Scene_3D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); } 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 4cbb4f4..fcbf13a 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp +++ b/render_3D/render_3D/scene/Render_Scene_3D_Datoviz.cpp @@ -1,8 +1 @@ -#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 deleted file mode 100644 index d91e1d0..0000000 --- a/render_3D/render_3D/scene/Scene_Datoviz_State.hpp +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include "../base/Datoviz_Frame_Observation.hpp" -#include "../detail/Backend_Types.hpp" -#include "detail/Datoviz_Frame_State.hpp" -#include "detail/Datoviz_Visual_State.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct DvzBuffer; -struct DvzController; -struct DvzDrp2CommandStream; -struct DvzDrp2Runtime; -struct DvzFigure; -struct DvzFont; -struct DvzFramePlanEmitter; -struct DvzInputRouter; -struct DvzItemInteraction; -struct DvzPanel; -struct DvzPinnedReadout; -struct DvzPointerGestureHandler; -struct DvzSampledField; -struct DvzScene; -struct DvzSceneBuffer; -struct DvzSceneFrameArtifact; -struct DvzText; -struct DvzVisual; - -namespace aethera { namespace render_3d { namespace detail { - -struct Datoviz_Render_Context; - -struct Scene_Datoviz_State { - Scene_Datoviz_State(); - ~Scene_Datoviz_State(); - Scene_Datoviz_State(const Scene_Datoviz_State&) = delete; - Scene_Datoviz_State& operator=(const Scene_Datoviz_State&) = delete; - - void initialize(std::uint32_t gpu_index, bool validation_enabled, - const std::vector& visuals, - const Scene_3D_Parameters& initial_scene); - void create_scene(const std::vector& visuals, - const Scene_3D_Parameters& initial_scene); - void apply_camera(const Camera_Descriptor& camera); - void apply_axes(const Scene_3D_Parameters& scene); - [[nodiscard]] bool matches_command_structure( - const Scene_3D_Parameters& scene, - const Prepared_Visual_Batch& visuals) const; - [[nodiscard]] std::uint64_t apply( - const Scene_3D_Parameters& scene, - const Prepared_Visual_Batch& visuals, - std::uint8_t target_index, bool bind_target); - [[nodiscard]] std::uint64_t apply_visual( - Datoviz_Visual_Instance& target, const Prepared_Visual& visual, - std::uint8_t target_index, bool bind_target); - void ensure_external_attributes(Datoviz_Visual_Instance& target, - const Prepared_Visual& visual); - [[nodiscard]] std::uint64_t upload_external_attributes( - Datoviz_Visual_Instance& target, const Prepared_Visual& visual, - std::uint8_t target_index); - 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, - std::uint8_t target_index); - [[nodiscard]] bool target_runtime_resources_current( - const Prepared_Visual_Batch& visuals, - std::uint8_t target_index) const; - [[nodiscard]] std::vector stale_runtime_visuals( - const Prepared_Visual_Batch& visuals, - std::uint8_t target_index) const; - void mark_target_runtime_resources_uploaded(std::uint8_t target_index); - [[nodiscard]] std::optional 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( - 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]] Datoviz_Frame_Target& target(const Datoviz_Pending_Frame& pending); - void submit(Datoviz_Pending_Frame& pending, - std::function completion); - [[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, - Keyboard_Modifier modifiers, - Extent viewport, - std::uint64_t occurred_at_ns); - void dispatch_wheel(float x, float y, float delta_x, float delta_y, - Keyboard_Modifier modifiers, - Extent viewport, - std::uint64_t occurred_at_ns); - void dispatch_key(const Key_Event& event); - void abandon_resources() noexcept; - void destroy(); - -private: - struct Runtime_Slot { - owner runtime{}; /* 本目标槽独占的 DRP2 Runtime。 */ - owner emitter{}; /* 本目标槽独占的帧计划 emitter。 */ - }; - - 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 后是否禁止销毁在途资源。 */ - - std::vector visuals_{}; /* 注册顺序稳定的具体 Visual 集合。 */ - std::vector> external_buffers_{}; /* Visual 三槽属性 Buffer 唯一所有权。 */ - - 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_{}; /* 当前录制周期是否消费了输入。 */ - - mutable std::mutex target_mutex_{}; /* 仅保护三槽在提交与回读边界的生命周期。 */ - std::unique_ptr targets_{}; /* 三槽唯一所有权。 */ - std::uint64_t target_generation_{}; /* 目标槽重建代数。 */ -}; - -}}} // 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 index e705f88..8231ccb 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp @@ -1,3 +1,12 @@ +#pragma once + +#include "Datoviz_Scene_Resources.ipp" +#include "../../visual/detail/Datoviz_Visual_Set.ipp" +#include "Datoviz_Interaction.ipp" + +namespace aethera::render_3d::detail { + +/* Datoviz Scene 根据当前权威资源生成 DRP2 命令流描述,不提交 GPU。 */ DvzSceneFrameArtifact* Scene_Datoviz_State::emit( const Scene_3D_Parameters& scene, std::uint8_t target_index) { if (target_index >= runtime_slots_.size() || @@ -95,10 +104,10 @@ std::optional Scene_Datoviz_State::reuse_recorded_target( throw std::logic_error( "recorded Datoviz Visual operations do not match registration"); if (visual->operations->external_attributes.empty()) continue; - if (!has_payload(source.visual)) + if (!source.visual.has_payload()) throw std::logic_error( "recorded Datoviz Visual has no immutable payload"); - if (prepared_item_count(source.visual) != 0 && + if (source.visual.item_count() != 0 && visual->uploaded_data_revisions[target_index] != source.visual.data_revision) { observation.uploaded_bytes += upload_external_attributes( @@ -107,7 +116,7 @@ std::optional Scene_Datoviz_State::reuse_recorded_target( source.visual.data_revision; } visual->applied_revision = source.visual.revision; - visual->applied = source.visual; + visual->applied_data_revision = source.visual.data_revision; } candidate->reuse( command_revision_, controller_revision_, observe, readback); @@ -121,6 +130,7 @@ std::optional Scene_Datoviz_State::reuse_recorded_target( } return std::nullopt; } +/* 取得一个空闲目标槽,应用资源、生成命令并完成录制;没有空槽时立即返回。 */ 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) { @@ -366,6 +376,7 @@ std::optional Scene_Datoviz_State::prepare( std::move(observation) }; } +/* 把已录制槽投递到 GPU 队列;这里只登记完成回调,不执行同步等待。 */ void Scene_Datoviz_State::submit( Datoviz_Pending_Frame& pending, std::function completion) { @@ -386,6 +397,7 @@ void Scene_Datoviz_State::submit( catch (...) { completion(std::current_exception()); } }); } +/* Fence 已由完成服务确认后读取映射内存,发布像素并把槽恢复为空闲。 */ Datoviz_Completed_Frame Scene_Datoviz_State::collect( Datoviz_Pending_Frame pending) { /* Fence 已完成,读回缓冲与查询池只属于本目标槽;不同 Scene 的映射内存 @@ -424,6 +436,7 @@ void Scene_Datoviz_State::abandon_resources() noexcept { static_cast(targets_.release()); retain_quarantined_context(render_context_); } +/* 必须在无在途帧时调用;按 Runtime -> Scene -> Vulkan 池 -> GPU 上下文逆序释放。 */ void Scene_Datoviz_State::destroy() { if (quarantined_.load(std::memory_order_acquire)) { abandon_resources(); diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp b/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp deleted file mode 100644 index 407b475..0000000 --- a/render_3D/render_3D/scene/detail/Datoviz_Frame_State.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#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::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 index eb9977b..ac62bd7 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Frame_Target.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Target.ipp @@ -1,3 +1,13 @@ +#pragma once + +#include "Datoviz_Scene_Common.ipp" + +namespace aethera::render_3d::detail { + +/* + * 单个离屏帧槽拥有从 GPU 颜色附件到 CPU RGBA 读回的完整资源链。 + * 状态只在 free -> recording -> submitted -> free 间推进;Fence 由外部完成服务观察,槽内绝不等待。 + */ struct Datoviz_Frame_Target final { public: struct Collection { @@ -499,3 +509,5 @@ struct Datoviz_Frame_Target_Set { static constexpr std::size_t count = 3; std::array, count> values{}; /* 固定三槽,槽地址在后端生命周期内稳定。 */ }; + +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp b/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp index 0d8fb8d..7be18bc 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Interaction.ipp @@ -1,3 +1,9 @@ +#pragma once + +#include "Datoviz_Scene_Common.ipp" + +namespace aethera::render_3d::detail { + void Scene_Datoviz_State::mark_controller_input_applied() noexcept { input_changed_ = true; ++controller_revision_; @@ -75,3 +81,5 @@ void Scene_Datoviz_State::dispatch_key( modifiers(event.modifiers), nullptr); mark_controller_input_applied(); } + +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp index 92c3f54..09776b2 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp @@ -1,5 +1,7 @@ -#include "Scene_Datoviz_State.hpp" -#include "../detail/Exception.hpp" +#pragma once + +#include "../Render_Scene_3D.hpp" +#include "../../detail/Exception.hpp" #include #include #include @@ -82,14 +84,6 @@ DvzKeyCode key_code(::aethera::Key key, std::uint32_t native_key) { ? 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; @@ -110,209 +104,6 @@ std::string axis_title(const plot::Axis_Descriptor& axis) { 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\":"}; @@ -395,3 +186,4 @@ void retain_quarantined_context( static_cast(new(std::nothrow) std::shared_ptr(std::move(context))); } +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp index 37a539a..21b96b6 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Resources.ipp @@ -1,3 +1,9 @@ +#pragma once + +#include "Datoviz_Frame_Target.ipp" + +namespace aethera::render_3d::detail { + Scene_Datoviz_State::Scene_Datoviz_State() : targets_(std::make_unique()) {} Scene_Datoviz_State::~Scene_Datoviz_State() { @@ -380,8 +386,7 @@ bool Scene_Datoviz_State::matches_command_structure( return value.identity == source.identity; }); if (target == visuals_.end()) return false; - if (!target->applied || - !same_visual_structure(*target->applied, source.visual)) + if (target->applied_revision != source.visual.revision) return false; } return true; @@ -430,7 +435,9 @@ 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) + if (visual.operations->external_attributes.empty()) visual.uploaded_data_revisions[target_index] = - visual.applied->data_revision; + visual.applied_data_revision; } + +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/visual/Basic_Visual.ipp b/render_3D/render_3D/visual/Basic_Visual.ipp index 0b78a8a..f47ad19 100644 --- a/render_3D/render_3D/visual/Basic_Visual.ipp +++ b/render_3D/render_3D/visual/Basic_Visual.ipp @@ -47,8 +47,7 @@ inline void Basic_Visual::Private::bind_paint_target( template void Basic_Visual::Private::prepare_data(Object* object) { - using Definition = typename Object::Attached_Object; - using Visual = typename Definition::Visual_Base; + using Visual = typename Object::Attached_Object; using Visual_State = typename Visual::State; using Specification = typename Visual::Specification; using Frame_Tag = typename Visual::Frame_Tag; @@ -148,7 +147,7 @@ template void Basic_Visual::Private::after_prop_set( Object* object, Member Owner::*, Prop_Access) { - using Visual = typename Object::Attached_Object::Visual_Base; + using Visual = typename Object::Attached_Object; if constexpr (std::same_as) data_changed = true; if constexpr (std::same_as || @@ -161,7 +160,7 @@ template void Basic_Visual::Private::before_advance( Object* object, Prop_Type*, State_Access, const Prop_Type*, State_Access) { - using Frame_Tag = typename Object::Attached_Object::Visual_Base::Frame_Tag; + using Frame_Tag = typename Object::Attached_Object::Frame_Tag; if (double_buffer::detail::Internal_Access::template pending_buffer( object) != double_buffer::detail::Internal_Access::template current_buffer( diff --git a/render_3D/render_3D/visual/Glyph_Visual.hpp b/render_3D/render_3D/visual/Glyph_Visual.hpp index 4341568..957eaea 100644 --- a/render_3D/render_3D/visual/Glyph_Visual.hpp +++ b/render_3D/render_3D/visual/Glyph_Visual.hpp @@ -17,29 +17,13 @@ struct Glyph_Frame { std::vector items{}; /* 本次提交的完整 Glyph 集合。 */ bool operator==(const Glyph_Frame&) const; }; -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 { - using Item = Glyph; - using Prepared_Data = Glyph_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Glyph_Spec; } struct Glyph_Visual : Def> { - using Visual_Base = Glyph_Visual; using Frame_Tag = Glyph_Frame_Tag; using Frame = Glyph_Frame; using Specification = detail::Glyph_Spec; - using Item = typename Specification::Item; + using Item = Glyph; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Glyph_Visual.ipp b/render_3D/render_3D/visual/Glyph_Visual.ipp index 22536bc..bca33cf 100644 --- a/render_3D/render_3D/visual/Glyph_Visual.ipp +++ b/render_3D/render_3D/visual/Glyph_Visual.ipp @@ -3,6 +3,22 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Glyph_Prepared_Data { + std::vector positions{}; /* Datoviz 顶点位置。 */ + std::vector colors{}; /* Datoviz 顶点颜色。 */ + std::vector angles{}; /* 每个 Glyph 的旋转角。 */ + std::vector> bounds{}; /* 每个 Glyph 的局部边界。 */ + std::vector> texture_coordinates{}; /* 每个 Glyph 的纹理坐标。 */ +}; +struct Glyph_Spec { + using Item = Glyph; + using Prepared_Data = Glyph_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); } diff --git a/render_3D/render_3D/visual/Image_Visual.hpp b/render_3D/render_3D/visual/Image_Visual.hpp index 32a5de4..0a26e60 100644 --- a/render_3D/render_3D/visual/Image_Visual.hpp +++ b/render_3D/render_3D/visual/Image_Visual.hpp @@ -18,30 +18,13 @@ struct Image_Frame { std::vector field_pixels{}; /* 本帧采样场的 RGBA 像素。 */ bool operator==(const Image_Frame&) const; }; -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 { - using Item = Image; - using Prepared_Data = Image_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Image_Spec; } struct Image_Visual : Def> { - using Visual_Base = Image_Visual; using Frame_Tag = Image_Frame_Tag; using Frame = Image_Frame; using Specification = detail::Image_Spec; - using Item = typename Specification::Item; + using Item = Image; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Image_Visual.ipp b/render_3D/render_3D/visual/Image_Visual.ipp index 6b95d0e..08a5b08 100644 --- a/render_3D/render_3D/visual/Image_Visual.ipp +++ b/render_3D/render_3D/visual/Image_Visual.ipp @@ -1,6 +1,23 @@ #pragma once namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Image_Prepared_Data { + std::vector positions{}; /* 图像实例位置。 */ + std::vector> extents{}; /* 图像实例尺寸。 */ + std::vector> texture_rectangles{}; /* 图像纹理采样矩形。 */ + std::vector field_pixels{}; /* 本帧 RGBA 采样场。 */ + std::uint32_t field_width{}; /* 采样场宽度,单位为像素。 */ + std::uint32_t field_height{}; /* 采样场高度,单位为像素。 */ +}; +struct Image_Spec { + using Item = Image; + using Prepared_Data = Image_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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); diff --git a/render_3D/render_3D/visual/Labels_Visual.hpp b/render_3D/render_3D/visual/Labels_Visual.hpp index aad306d..07b2ce3 100644 --- a/render_3D/render_3D/visual/Labels_Visual.hpp +++ b/render_3D/render_3D/visual/Labels_Visual.hpp @@ -18,30 +18,13 @@ struct Labels_Frame { std::vector field_labels{}; /* 本帧标签场数据。 */ bool operator==(const Labels_Frame&) const; }; -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 { - using Item = Label; - using Prepared_Data = Labels_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Labels_Spec; } struct Labels_Visual : Def> { - using Visual_Base = Labels_Visual; using Frame_Tag = Labels_Frame_Tag; using Frame = Labels_Frame; using Specification = detail::Labels_Spec; - using Item = typename Specification::Item; + using Item = Label; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Labels_Visual.ipp b/render_3D/render_3D/visual/Labels_Visual.ipp index 0750b2d..2fb6864 100644 --- a/render_3D/render_3D/visual/Labels_Visual.ipp +++ b/render_3D/render_3D/visual/Labels_Visual.ipp @@ -1,6 +1,23 @@ #pragma once namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +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 { + using Item = Label; + using Prepared_Data = Labels_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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); diff --git a/render_3D/render_3D/visual/Marker_Visual.hpp b/render_3D/render_3D/visual/Marker_Visual.hpp index 2226663..fcaba6f 100644 --- a/render_3D/render_3D/visual/Marker_Visual.hpp +++ b/render_3D/render_3D/visual/Marker_Visual.hpp @@ -19,30 +19,13 @@ struct Marker_Frame { std::vector items{}; /* 本次提交的完整 Marker 集合。 */ bool operator==(const Marker_Frame&) const; }; -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 { - using Item = Marker; - using Prepared_Data = Marker_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Marker_Spec; } struct Marker_Visual : Def> { - using Visual_Base = Marker_Visual; using Frame_Tag = Marker_Frame_Tag; using Frame = Marker_Frame; using Specification = detail::Marker_Spec; - using Item = typename Specification::Item; + using Item = Marker; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Marker_Visual.ipp b/render_3D/render_3D/visual/Marker_Visual.ipp index 6409be1..c962994 100644 --- a/render_3D/render_3D/visual/Marker_Visual.ipp +++ b/render_3D/render_3D/visual/Marker_Visual.ipp @@ -3,6 +3,23 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Marker_Prepared_Data { + std::vector positions{}; /* Marker 实例位置。 */ + std::vector colors{}; /* Marker 实例颜色。 */ + std::vector diameters{}; /* Marker 直径,单位为像素。 */ + std::vector angles{}; /* Marker 旋转角。 */ + std::vector shapes{}; /* Datoviz Marker 形状编号。 */ + std::vector coordinate_label_visibility{}; /* 各 Marker 的坐标标签可见位。 */ +}; +struct Marker_Spec { + using Item = Marker; + using Prepared_Data = Marker_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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); } diff --git a/render_3D/render_3D/visual/Mesh_Visual.hpp b/render_3D/render_3D/visual/Mesh_Visual.hpp index eed5f48..ca3cc6c 100644 --- a/render_3D/render_3D/visual/Mesh_Visual.hpp +++ b/render_3D/render_3D/visual/Mesh_Visual.hpp @@ -16,28 +16,13 @@ struct Mesh_Frame { std::vector items{}; /* 本次提交的完整 Mesh 顶点集合。 */ bool operator==(const Mesh_Frame&) const; }; -namespace detail { -struct Mesh_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector normals{}; - std::vector> texture_coordinates{}; -}; -struct Mesh_Spec { - using Item = Mesh_Vertex; - using Prepared_Data = Mesh_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Mesh_Spec; } struct Mesh_Visual : Def> { - using Visual_Base = Mesh_Visual; using Frame_Tag = Mesh_Frame_Tag; using Frame = Mesh_Frame; using Specification = detail::Mesh_Spec; - using Item = typename Specification::Item; + using Item = Mesh_Vertex; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Mesh_Visual.ipp b/render_3D/render_3D/visual/Mesh_Visual.ipp index 3f423dd..17a53e8 100644 --- a/render_3D/render_3D/visual/Mesh_Visual.ipp +++ b/render_3D/render_3D/visual/Mesh_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Mesh_Prepared_Data { + std::vector positions{}; /* Mesh 顶点位置。 */ + std::vector colors{}; /* Mesh 顶点颜色。 */ + std::vector normals{}; /* Mesh 顶点法线。 */ + std::vector> texture_coordinates{}; /* Mesh 顶点纹理坐标。 */ +}; +struct Mesh_Spec { + using Item = Mesh_Vertex; + using Prepared_Data = Mesh_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); } diff --git a/render_3D/render_3D/visual/Path_Visual.hpp b/render_3D/render_3D/visual/Path_Visual.hpp index 53a840e..87f9f6d 100644 --- a/render_3D/render_3D/visual/Path_Visual.hpp +++ b/render_3D/render_3D/visual/Path_Visual.hpp @@ -15,27 +15,13 @@ struct Path_Frame { std::vector items{}; /* 本次提交的完整路径顶点集合。 */ bool operator==(const Path_Frame&) const; }; -namespace detail { -struct Path_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Path_Spec { - using Item = Path_Vertex; - using Prepared_Data = Path_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Path_Spec; } struct Path_Visual : Def> { - using Visual_Base = Path_Visual; using Frame_Tag = Path_Frame_Tag; using Frame = Path_Frame; using Specification = detail::Path_Spec; - using Item = typename Specification::Item; + using Item = Path_Vertex; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Path_Visual.ipp b/render_3D/render_3D/visual/Path_Visual.ipp index c529dde..81ecb42 100644 --- a/render_3D/render_3D/visual/Path_Visual.ipp +++ b/render_3D/render_3D/visual/Path_Visual.ipp @@ -3,6 +3,20 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Path_Prepared_Data { + std::vector positions{}; /* Path 顶点位置。 */ + std::vector colors{}; /* Path 顶点颜色。 */ + std::vector widths{}; /* Path 顶点线宽,单位为像素。 */ +}; +struct Path_Spec { + using Item = Path_Vertex; + using Prepared_Data = Path_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; } diff --git a/render_3D/render_3D/visual/Pixel_Visual.hpp b/render_3D/render_3D/visual/Pixel_Visual.hpp index c22f50f..097e569 100644 --- a/render_3D/render_3D/visual/Pixel_Visual.hpp +++ b/render_3D/render_3D/visual/Pixel_Visual.hpp @@ -1,8 +1,6 @@ #pragma once - #include "Basic_Visual.hpp" #include - namespace aethera::render_3d { struct Pixel { Vec3 position{}; @@ -16,29 +14,19 @@ struct Pixel_Frame { bool operator==(const Pixel_Frame&) const; }; namespace detail { -struct Pixel_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector sizes{}; -}; -struct Pixel_Spec { - using Item = Pixel; - using Prepared_Data = Pixel_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; +struct Pixel_Spec; } struct Pixel_Visual : Def> { - using Visual_Base = Pixel_Visual; using Frame_Tag = Pixel_Frame_Tag; using Frame = Pixel_Frame; using Specification = detail::Pixel_Spec; - using Item = typename Specification::Item; - struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; + using Item = Pixel; + struct Prop : Prev_Prop { + bool operator==(const Prop&) const; + }; struct State : Prev_State { - std::size_t item_count{}; /* 最近一次发布的输入项数。 */ + std::size_t item_count{}; /* 最近一次发布的输入项数。 */ std::size_t prepared_item_count{}; /* 最近一次 Prepare 的后端项数。 */ std::uint64_t prepared_revision{}; /* 最近一次 Prepare 的提交版本。 */ bool operator==(const State&) const; diff --git a/render_3D/render_3D/visual/Pixel_Visual.ipp b/render_3D/render_3D/visual/Pixel_Visual.ipp index 9a2564c..34bbac0 100644 --- a/render_3D/render_3D/visual/Pixel_Visual.ipp +++ b/render_3D/render_3D/visual/Pixel_Visual.ipp @@ -3,6 +3,20 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Pixel_Prepared_Data { + std::vector positions{}; /* Pixel 实例位置。 */ + std::vector colors{}; /* Pixel 实例颜色。 */ + std::vector sizes{}; /* Pixel 实例尺寸,单位为像素。 */ +}; +struct Pixel_Spec { + using Item = Pixel; + using Prepared_Data = Pixel_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; } diff --git a/render_3D/render_3D/visual/Point_Visual.hpp b/render_3D/render_3D/visual/Point_Visual.hpp index b1f7d67..80a2875 100644 --- a/render_3D/render_3D/visual/Point_Visual.hpp +++ b/render_3D/render_3D/visual/Point_Visual.hpp @@ -22,28 +22,13 @@ struct Point_Frame { std::vector items{}; /* 本次提交的完整 Point 集合。 */ bool operator==(const Point_Frame&) const; }; -namespace detail { -struct Point_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector diameters{}; - Point_Style style{}; -}; -struct Point_Spec { - using Item = Point; - using Prepared_Data = Point_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Point_Spec; } struct Point_Visual : Def> { - using Visual_Base = Point_Visual; using Frame_Tag = Point_Frame_Tag; using Frame = Point_Frame; using Specification = detail::Point_Spec; - using Item = typename Specification::Item; + using Item = Point; struct Prop : Prev_Prop { Point_Style style{}; /* 当前 Point Visual 的统一描边样式。 */ bool operator==(const Prop&) const; diff --git a/render_3D/render_3D/visual/Point_Visual.ipp b/render_3D/render_3D/visual/Point_Visual.ipp index bbd6af4..884a866 100644 --- a/render_3D/render_3D/visual/Point_Visual.ipp +++ b/render_3D/render_3D/visual/Point_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Point_Prepared_Data { + std::vector positions{}; /* Point 实例位置。 */ + std::vector colors{}; /* Point 实例颜色。 */ + std::vector diameters{}; /* Point 直径,单位为像素。 */ + Point_Style style{}; /* 本批 Point 的统一样式。 */ +}; +struct Point_Spec { + using Item = Point; + using Prepared_Data = Point_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; } diff --git a/render_3D/render_3D/visual/Prepared_Visual.hpp b/render_3D/render_3D/visual/Prepared_Visual.hpp index a27dc11..d86f65c 100644 --- a/render_3D/render_3D/visual/Prepared_Visual.hpp +++ b/render_3D/render_3D/visual/Prepared_Visual.hpp @@ -1,12 +1,11 @@ #pragma once #include "../base/Types.hpp" +#include "detail/Datoviz_Visual_Operations.hpp" #include namespace aethera::render_3d::detail { -struct Datoviz_Visual_Operations; - using Prepared_Position = std::array; using Prepared_Color = std::array; @@ -19,6 +18,15 @@ struct Prepared_Visual { std::uint64_t data_revision{}; const Datoviz_Visual_Operations* operations{}; /* 可空借用;尚未发布时为空。 */ std::shared_ptr data{}; /* 具体 Prepared_Data 的只读所有权。 */ + + [[nodiscard]] bool has_payload() const noexcept { + return operations != nullptr && data != nullptr; + } + [[nodiscard]] std::size_t item_count() const noexcept { + return has_payload() + ? operations->item_count(not_null{data.get()}) + : 0; + } }; } diff --git a/render_3D/render_3D/visual/Primitive_Visual.hpp b/render_3D/render_3D/visual/Primitive_Visual.hpp index 706b7c7..57a677a 100644 --- a/render_3D/render_3D/visual/Primitive_Visual.hpp +++ b/render_3D/render_3D/visual/Primitive_Visual.hpp @@ -15,27 +15,13 @@ struct Primitive_Frame { std::vector items{}; /* 本次提交的完整 Primitive 顶点集合。 */ bool operator==(const Primitive_Frame&) const; }; -namespace detail { -struct Primitive_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector normals{}; -}; -struct Primitive_Spec { - using Item = Primitive_Vertex; - using Prepared_Data = Primitive_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Primitive_Spec; } struct Primitive_Visual : Def> { - using Visual_Base = Primitive_Visual; using Frame_Tag = Primitive_Frame_Tag; using Frame = Primitive_Frame; using Specification = detail::Primitive_Spec; - using Item = typename Specification::Item; + using Item = Primitive_Vertex; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Primitive_Visual.ipp b/render_3D/render_3D/visual/Primitive_Visual.ipp index 871c064..540e61e 100644 --- a/render_3D/render_3D/visual/Primitive_Visual.ipp +++ b/render_3D/render_3D/visual/Primitive_Visual.ipp @@ -3,6 +3,20 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Primitive_Prepared_Data { + std::vector positions{}; /* Primitive 顶点位置。 */ + std::vector colors{}; /* Primitive 顶点颜色。 */ + std::vector normals{}; /* Primitive 顶点法线。 */ +}; +struct Primitive_Spec { + using Item = Primitive_Vertex; + using Prepared_Data = Primitive_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); } diff --git a/render_3D/render_3D/visual/Segment_Visual.hpp b/render_3D/render_3D/visual/Segment_Visual.hpp index 9696f2d..bb5e95a 100644 --- a/render_3D/render_3D/visual/Segment_Visual.hpp +++ b/render_3D/render_3D/visual/Segment_Visual.hpp @@ -16,28 +16,13 @@ struct Segment_Frame { std::vector items{}; /* 本次提交的完整 Segment 集合。 */ bool operator==(const Segment_Frame&) const; }; -namespace detail { -struct Segment_Prepared_Data { - std::vector starts{}; - std::vector ends{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Segment_Spec { - using Item = Segment; - using Prepared_Data = Segment_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Segment_Spec; } struct Segment_Visual : Def> { - using Visual_Base = Segment_Visual; using Frame_Tag = Segment_Frame_Tag; using Frame = Segment_Frame; using Specification = detail::Segment_Spec; - using Item = typename Specification::Item; + using Item = Segment; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Segment_Visual.ipp b/render_3D/render_3D/visual/Segment_Visual.ipp index 6cce3f2..c3bf63b 100644 --- a/render_3D/render_3D/visual/Segment_Visual.ipp +++ b/render_3D/render_3D/visual/Segment_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Segment_Prepared_Data { + std::vector starts{}; /* 线段起点。 */ + std::vector ends{}; /* 线段终点。 */ + std::vector colors{}; /* 线段颜色。 */ + std::vector widths{}; /* 线段宽度,单位为像素。 */ +}; +struct Segment_Spec { + using Item = Segment; + using Prepared_Data = Segment_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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; } diff --git a/render_3D/render_3D/visual/Sphere_Visual.hpp b/render_3D/render_3D/visual/Sphere_Visual.hpp index d72e2b1..c29707b 100644 --- a/render_3D/render_3D/visual/Sphere_Visual.hpp +++ b/render_3D/render_3D/visual/Sphere_Visual.hpp @@ -15,27 +15,13 @@ struct Sphere_Frame { std::vector items{}; /* 本次提交的完整 Sphere 集合。 */ bool operator==(const Sphere_Frame&) const; }; -namespace detail { -struct Sphere_Prepared_Data { - std::vector centers{}; - std::vector colors{}; - std::vector radii{}; -}; -struct Sphere_Spec { - using Item = Sphere; - using Prepared_Data = Sphere_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Sphere_Spec; } struct Sphere_Visual : Def> { - using Visual_Base = Sphere_Visual; using Frame_Tag = Sphere_Frame_Tag; using Frame = Sphere_Frame; using Specification = detail::Sphere_Spec; - using Item = typename Specification::Item; + using Item = Sphere; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Sphere_Visual.ipp b/render_3D/render_3D/visual/Sphere_Visual.ipp index cc6211d..aa4c80c 100644 --- a/render_3D/render_3D/visual/Sphere_Visual.ipp +++ b/render_3D/render_3D/visual/Sphere_Visual.ipp @@ -3,6 +3,20 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Sphere_Prepared_Data { + std::vector centers{}; /* 球体中心。 */ + std::vector colors{}; /* 球体颜色。 */ + std::vector radii{}; /* 球体半径。 */ +}; +struct Sphere_Spec { + using Item = Sphere; + using Prepared_Data = Sphere_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; } diff --git a/render_3D/render_3D/visual/Splat_Visual.hpp b/render_3D/render_3D/visual/Splat_Visual.hpp index ff45483..1cb78d1 100644 --- a/render_3D/render_3D/visual/Splat_Visual.hpp +++ b/render_3D/render_3D/visual/Splat_Visual.hpp @@ -16,28 +16,13 @@ struct Splat_Frame { std::vector items{}; /* 本次提交的完整 Splat 集合。 */ bool operator==(const Splat_Frame&) const; }; -namespace detail { -struct Splat_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector> sigmas{}; - std::vector angles{}; -}; -struct Splat_Spec { - using Item = Splat; - using Prepared_Data = Splat_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Splat_Spec; } struct Splat_Visual : Def> { - using Visual_Base = Splat_Visual; using Frame_Tag = Splat_Frame_Tag; using Frame = Splat_Frame; using Specification = detail::Splat_Spec; - using Item = typename Specification::Item; + using Item = Splat; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Splat_Visual.ipp b/render_3D/render_3D/visual/Splat_Visual.ipp index ddf24ec..0aa2abf 100644 --- a/render_3D/render_3D/visual/Splat_Visual.ipp +++ b/render_3D/render_3D/visual/Splat_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Splat_Prepared_Data { + std::vector positions{}; /* Splat 中心位置。 */ + std::vector colors{}; /* Splat 颜色。 */ + std::vector> sigmas{}; /* Splat 两轴标准差。 */ + std::vector angles{}; /* Splat 旋转角。 */ +}; +struct Splat_Spec { + using Item = Splat; + using Prepared_Data = Splat_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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); } diff --git a/render_3D/render_3D/visual/Text_Visual.hpp b/render_3D/render_3D/visual/Text_Visual.hpp index 3385f0e..afa6ca3 100644 --- a/render_3D/render_3D/visual/Text_Visual.hpp +++ b/render_3D/render_3D/visual/Text_Visual.hpp @@ -17,28 +17,13 @@ struct Text_Frame { std::vector items{}; /* 本次提交的完整 Text 集合。 */ bool operator==(const Text_Frame&) const; }; -namespace detail { -struct Text_Prepared_Data { - std::vector positions{}; - std::vector colors{}; - std::vector sizes{}; - std::vector strings{}; -}; -struct Text_Spec { - using Item = Text_Label; - using Prepared_Data = Text_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Text_Spec; } struct Text_Visual : Def> { - using Visual_Base = Text_Visual; using Frame_Tag = Text_Frame_Tag; using Frame = Text_Frame; using Specification = detail::Text_Spec; - using Item = typename Specification::Item; + using Item = Text_Label; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Text_Visual.ipp b/render_3D/render_3D/visual/Text_Visual.ipp index ca998ea..04b1ca9 100644 --- a/render_3D/render_3D/visual/Text_Visual.ipp +++ b/render_3D/render_3D/visual/Text_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Text_Prepared_Data { + std::vector positions{}; /* 文本锚点位置。 */ + std::vector colors{}; /* 文本颜色。 */ + std::vector sizes{}; /* 字号,单位为像素。 */ + std::vector strings{}; /* UTF-8 文本内容。 */ +}; +struct Text_Spec { + using Item = Text_Label; + using Prepared_Data = Text_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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; } diff --git a/render_3D/render_3D/visual/Vector_Visual.hpp b/render_3D/render_3D/visual/Vector_Visual.hpp index 1aa4872..278c72d 100644 --- a/render_3D/render_3D/visual/Vector_Visual.hpp +++ b/render_3D/render_3D/visual/Vector_Visual.hpp @@ -16,28 +16,13 @@ struct Vector_Frame { std::vector items{}; /* 本次提交的完整 Vector 集合。 */ bool operator==(const Vector_Frame&) const; }; -namespace detail { -struct Vector_Prepared_Data { - std::vector origins{}; - std::vector directions{}; - std::vector colors{}; - std::vector widths{}; -}; -struct Vector_Spec { - using Item = Vector_Glyph; - using Prepared_Data = Vector_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Vector_Spec; } struct Vector_Visual : Def> { - using Visual_Base = Vector_Visual; using Frame_Tag = Vector_Frame_Tag; using Frame = Vector_Frame; using Specification = detail::Vector_Spec; - using Item = typename Specification::Item; + using Item = Vector_Glyph; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Vector_Visual.ipp b/render_3D/render_3D/visual/Vector_Visual.ipp index c96d666..9455818 100644 --- a/render_3D/render_3D/visual/Vector_Visual.ipp +++ b/render_3D/render_3D/visual/Vector_Visual.ipp @@ -3,6 +3,21 @@ #include "detail/Visual_Prepare.hpp" namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +struct Vector_Prepared_Data { + std::vector origins{}; /* 向量起点。 */ + std::vector directions{}; /* 向量方向和长度。 */ + std::vector colors{}; /* 向量颜色。 */ + std::vector widths{}; /* 向量线宽,单位为像素。 */ +}; +struct Vector_Spec { + using Item = Vector_Glyph; + using Prepared_Data = Vector_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + 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; } diff --git a/render_3D/render_3D/visual/Volume_Visual.hpp b/render_3D/render_3D/visual/Volume_Visual.hpp index 7c5c350..3f76689 100644 --- a/render_3D/render_3D/visual/Volume_Visual.hpp +++ b/render_3D/render_3D/visual/Volume_Visual.hpp @@ -16,28 +16,13 @@ struct Volume_Frame { std::uint32_t field_depth{}; /* 本帧体素场深度。 */ bool operator==(const Volume_Frame&) const; }; -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 { - using Item = Voxel; - using Prepared_Data = Volume_Prepared_Data; - [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); - [[nodiscard]] static bool valid(const Item&) noexcept; - static void prepare(const std::vector&, Prepared_Data&); -}; -} +namespace detail { struct Volume_Spec; } struct Volume_Visual : Def> { - using Visual_Base = Volume_Visual; using Frame_Tag = Volume_Frame_Tag; using Frame = Volume_Frame; using Specification = detail::Volume_Spec; - using Item = typename Specification::Item; + using Item = Voxel; struct Prop : Prev_Prop { bool operator==(const Prop&) const; }; struct State : Prev_State { std::size_t item_count{}; /* 最近一次发布的输入项数。 */ diff --git a/render_3D/render_3D/visual/Volume_Visual.ipp b/render_3D/render_3D/visual/Volume_Visual.ipp index 59b38ce..1b9f772 100644 --- a/render_3D/render_3D/visual/Volume_Visual.ipp +++ b/render_3D/render_3D/visual/Volume_Visual.ipp @@ -1,6 +1,21 @@ #pragma once namespace aethera::render_3d::detail { +/* 后端转换结构与 Datoviz 分派契约仅属于实现,不进入 Visual 公共头文件。 */ +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 { + using Item = Voxel; + using Prepared_Data = Volume_Prepared_Data; + [[nodiscard]] static const Datoviz_Visual_Operations& datoviz_operations(); + [[nodiscard]] static bool valid(const Item&) noexcept; + static void prepare(const std::vector&, Prepared_Data&); +}; + inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); } diff --git a/render_3D/render_3D/visual/detail/Datoviz_Visual_Common.ipp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Common.ipp new file mode 100644 index 0000000..44e68ac --- /dev/null +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Common.ipp @@ -0,0 +1,198 @@ +#pragma once + +#include "Datoviz_Visual_State.hpp" +#include +#include +#include +#include +#include +#include + +namespace aethera::render_3d::detail { +namespace { +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; +} +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"); +} +} // namespace +} // namespace aethera::render_3d::detail + diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Factories.ipp similarity index 98% rename from render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp rename to render_3D/render_3D/visual/detail/Datoviz_Visual_Factories.ipp index e91ea22..3deed08 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Visual_Factories.ipp +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Factories.ipp @@ -1,3 +1,8 @@ +#pragma once + +#include "Datoviz_Visual_Common.ipp" + +namespace aethera::render_3d::detail { namespace { not_null require_visual(DvzVisual* visual) { if (visual == nullptr) @@ -230,3 +235,4 @@ constexpr std::array path_attributes{ Datoviz_Attribute_Description{"stroke_width_px", sizeof(float)}}; } +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.ipp similarity index 99% rename from render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp rename to render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.ipp index 77db094..a9d0dad 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Visual_Operations.ipp +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Operations.ipp @@ -1,3 +1,8 @@ +#pragma once + +#include "Datoviz_Visual_Factories.ipp" /* 具体 Visual 的 Datoviz 实现统一归属 visual 模块。 */ + +namespace aethera::render_3d::detail { namespace { using Attribute_Sink = Datoviz_Visual_Operations::Attribute_Sink; @@ -425,3 +430,4 @@ const Datoviz_Visual_Operations& Volume_Spec::datoviz_operations() { return value; } +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp b/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp similarity index 95% rename from render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp rename to render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp index 1989541..098cd98 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Visual_Set.ipp +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_Set.ipp @@ -1,3 +1,10 @@ +#pragma once + +#include "Datoviz_Visual_Operations.ipp" +#include "../../scene/detail/Datoviz_Frame_Target.ipp" + +namespace aethera::render_3d::detail { + 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) { @@ -28,7 +35,7 @@ std::uint64_t Scene_Datoviz_State::apply( } void Scene_Datoviz_State::ensure_external_attributes( Datoviz_Visual_Instance& target, const Prepared_Visual& visual) { - const auto item_count = prepared_item_count(visual); + const auto item_count = visual.item_count(); 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; @@ -87,7 +94,7 @@ std::uint64_t Scene_Datoviz_State::upload_external_attributes( 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); + const auto item_count = visual.item_count(); if (item_count == 0) return 0; std::uint64_t uploaded_bytes{}; struct Upload_Context { @@ -170,20 +177,19 @@ void Scene_Datoviz_State::register_external_attributes( 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.has_payload()) 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); + const auto item_count = point.item_count(); auto* visual = target.visual.get(); - const bool structure_changed = !target.applied || - !same_visual_structure(*target.applied, point); + const bool structure_changed = + target.applied_revision != point.revision; const bool coordinate_text_changed = target.coordinate_text != nullptr && - (!target.applied || has_coordinate_labels(*target.applied) || - has_coordinate_labels(point)); + structure_changed; if (coordinate_text_changed) { const auto& data = *static_cast(point.data.get()); @@ -263,8 +269,7 @@ std::uint64_t Scene_Datoviz_State::apply_visual( throw std::runtime_error( "failed to apply Datoviz external visual visibility"); } - else if (!target.applied || - target.applied->data_revision != point.data_revision) { + else if (target.applied_data_revision != point.data_revision) { target.operations->upload_visual( target, not_null{point.data.get()}, static_cast(item_count)); @@ -272,6 +277,8 @@ std::uint64_t Scene_Datoviz_State::apply_visual( throw std::runtime_error("failed to upload Datoviz visual payload"); } target.applied_revision = point.revision; - target.applied = point; + target.applied_data_revision = point.data_revision; return uploaded_bytes; } + +} // namespace aethera::render_3d::detail diff --git a/render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp b/render_3D/render_3D/visual/detail/Datoviz_Visual_State.hpp similarity index 86% rename from render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp rename to render_3D/render_3D/visual/detail/Datoviz_Visual_State.hpp index d90f263..3fa6847 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Visual_State.hpp +++ b/render_3D/render_3D/visual/detail/Datoviz_Visual_State.hpp @@ -1,11 +1,10 @@ #pragma once #include "../../detail/Backend_Types.hpp" -#include "../../visual/detail/Datoviz_Visual_Operations.hpp" +#include "Datoviz_Visual_Operations.hpp" #include #include #include -#include #include #include @@ -35,9 +34,9 @@ struct Datoviz_Visual_Instance { DvzFont* font{}; /* 可空、非拥有的字体借用。 */ DvzText* coordinate_text{}; /* 可空、非拥有的坐标文本借用。 */ std::array field_extent{}; /* 当前采样场尺寸。 */ - std::uint64_t applied_revision{}; /* 已应用的 Visual 版本。 */ + std::uint64_t applied_revision{}; /* 已进入 Datoviz 的完整 Visual 版本。 */ + std::uint64_t applied_data_revision{}; /* 已进入 Datoviz Visual 的业务数据版本。 */ std::array uploaded_data_revisions{}; /* 各槽上传版本。 */ - std::optional applied{}; /* 已应用结构,仅用于录制复用判定。 */ std::vector attributes{}; /* 本 Visual 的独立外部属性。 */ };