This commit is contained in:
2026-08-24 10:45:26 +08:00
parent 4f21169ade
commit cb9822074b
31 changed files with 363 additions and 536 deletions
@@ -2172,58 +2172,62 @@ void Datoviz_Visual_Backend::destroy() {
queue_lock = std::unique_lock(context->queue_mutex(), std::defer_lock);
std::lock(api_lock, queue_lock);
}
std::lock_guard target_lock(target_mutex_);
for (auto& slot : runtime_slots_) {
if (slot.runtime != nullptr) {
dvz_drp2_runtime_destroy(slot.runtime);
slot.runtime = nullptr;
{
std::lock_guard target_lock(target_mutex_);
for (auto& slot : runtime_slots_) {
if (slot.runtime != nullptr) {
dvz_drp2_runtime_destroy(slot.runtime);
slot.runtime = nullptr;
}
if (slot.emitter != nullptr) {
dvz_frame_plan_emitter_destroy(slot.emitter);
slot.emitter = nullptr;
}
}
if (slot.emitter != nullptr) {
dvz_frame_plan_emitter_destroy(slot.emitter);
slot.emitter = nullptr;
for (auto& target : targets_->values) target.reset();
for (auto* buffer : external_buffers_) {
if (buffer == nullptr) continue;
dvz_buffer_destroy(buffer);
dvz_buffer_free(buffer);
}
external_buffers_.clear();
if (camera_controller_ != nullptr) {
dvz_controller_destroy(camera_controller_);
camera_controller_ = nullptr;
}
if (item_interaction_ != nullptr) {
dvz_item_interaction_destroy(item_interaction_);
item_interaction_ = nullptr;
}
if (hover_readout_ != nullptr) {
dvz_pinned_readout_destroy(hover_readout_);
hover_readout_ = nullptr;
}
if (panel_ != nullptr && input_router_ != nullptr)
(void)dvz_panel_connect_input(panel_, nullptr);
if (gesture_handler_ != nullptr) {
dvz_pointer_gesture_handler_destroy(gesture_handler_);
gesture_handler_ = nullptr;
}
if (input_router_ != nullptr) {
dvz_input_router_destroy(input_router_);
input_router_ = nullptr;
}
visuals_.clear();
axes_visual_ = nullptr;
axes_text_ = nullptr;
applied_camera_.reset();
applied_axes_.reset();
panel_ = nullptr;
figure_ = nullptr;
if (scene_ != nullptr) {
dvz_scene_destroy(scene_);
scene_ = nullptr;
}
}
for (auto& target : targets_->values) target.reset();
for (auto* buffer : external_buffers_) {
if (buffer == nullptr) continue;
dvz_buffer_destroy(buffer);
dvz_buffer_free(buffer);
}
external_buffers_.clear();
if (camera_controller_ != nullptr) {
dvz_controller_destroy(camera_controller_);
camera_controller_ = nullptr;
}
if (item_interaction_ != nullptr) {
dvz_item_interaction_destroy(item_interaction_);
item_interaction_ = nullptr;
}
if (hover_readout_ != nullptr) {
dvz_pinned_readout_destroy(hover_readout_);
hover_readout_ = nullptr;
}
if (panel_ != nullptr && input_router_ != nullptr) (void)dvz_panel_connect_input(panel_, nullptr);
if (gesture_handler_ != nullptr) {
dvz_pointer_gesture_handler_destroy(gesture_handler_);
gesture_handler_ = nullptr;
}
if (input_router_ != nullptr) {
dvz_input_router_destroy(input_router_);
input_router_ = nullptr;
}
visuals_.clear();
axes_visual_ = nullptr;
axes_text_ = nullptr;
applied_camera_.reset();
applied_axes_.reset();
panel_ = nullptr;
figure_ = nullptr;
if (scene_ != nullptr) {
dvz_scene_destroy(scene_);
scene_ = nullptr;
}
render_context_.reset();
if (api_lock.owns_lock()) api_lock.unlock();
if (queue_lock.owns_lock()) queue_lock.unlock();
context.reset();
}
} // namespace aethera::render_3d::detail
+1 -3
View File
@@ -5,9 +5,7 @@
#include <vector>
namespace aethera::render_3d {
template <typename Spec>
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable,
Tagged_Buffer<detail::Prepared_Visual_Tag,
detail::Prepared_Visual_For<typename Spec::Prepared_Data>>> {
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable> {
using Specification = Spec;
using Item = typename Spec::Item;
using Prepared_Data = typename Spec::Prepared_Data;
+6 -15
View File
@@ -10,13 +10,14 @@ struct Basic_Visual<Spec>::Private : Basic_Visual::Prev_Private {
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
};
Paint_Target paint_target{}; /* Builder 绑定的所属 Scene 异步 Paint 目标。 */
Prepared_Visual prepared{}; /* 最近一次 Prepare 生成的唯一提交产物。 */
std::uint64_t next_revision{1}; /* 下一份 Prepared_Visual 的单调版本。 */
/* Scene Builder 调用:绑定只入队的异步 Paint 目标,目标生命周期必须覆盖 Visual。 */
void bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue);
/* CRTP 数据能力:从当前 Prop 构建后端字段,不访问其他缓冲角色。 */
template <Attached Object>
void prepare_data(Object* object);
/* CRTP 子图能力:每帧提交一次后端渲染;比较双槽 revision 后直接使用最新 Prepared 数据。 */
/* CRTP 子图能力:提交本 Visual 最近一次 Prepare 生成的唯一产物。 */
template <Attached Object>
[[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& prop);
/* CRTP 覆盖:绑定 Scene 后每帧执行 Paintdirty 只表示 Prepared 数据是否更新,不控制帧生成。 */
@@ -25,9 +26,6 @@ struct Basic_Visual<Spec>::Private : Basic_Visual::Prev_Private {
/* CRTP Prop hook:本层字段改变时标记 Prepare。 */
template <typename Object, typename Owner, typename Member, typename Prop_Type>
void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
/* CRTP 推进 hook:从 Prop 计算公开计数,不复制图元集合。 */
template <typename Object, typename Prop_Type, typename State_Type>
void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
[[nodiscard]] static bool valid_items(const std::vector<Item>& items);
};
template <typename Spec>
@@ -53,15 +51,16 @@ void Basic_Visual<Spec>::Private::prepare_data(Object* object) {
auto data = std::make_shared<Prepared_Data>();
Spec::prepare(prop.items, *data);
if constexpr (requires { data->style = prop.style; }) data->style = prop.style;
auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>();
auto& output = prepared;
output = {};
output.transform = prop.transform;
output.visible = prop.visible;
output.depth_test = prop.depth_test;
output.revision = next_revision++;
output.data = std::move(data);
object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) {
object->template update_state<&State::item_count, &State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) {
auto& state = states.template get<Visual_Tag>();
state.item_count = prop.items.size();
state.prepared_item_count = prop.items.size();
state.prepared_revision = output.revision;
});
@@ -72,9 +71,7 @@ tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, cons
tf::Taskflow graph;
graph.emplace([this, object] {
if (auto context = paint_target.context.lock(); context && paint_target.enqueue) {
const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>();
const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>();
paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current);
paint_target.enqueue(context.get(), object, prepared);
}
}).name("render_3d.paint.publish");
return graph;
@@ -89,10 +86,4 @@ template <typename Object, typename Owner, typename Member, typename Prop_Type>
void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) {
if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>();
}
template <typename Spec>
template <typename Object, typename Prop_Type, typename State_Type>
void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) {
using Visual_Tag = typename Basic_Visual::Base_Tag;
pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size();
}
}
@@ -2,11 +2,10 @@
#include "../base/Types.hpp"
#include <memory>
namespace aethera::render_3d::detail {
struct Prepared_Visual_Tag {};
using Prepared_Position = std::array<float, 3>;
using Prepared_Color = std::array<std::uint8_t, 4>;
/* 每个 Visual 的双缓冲只保存自己 Spec::Prepared_Data 的强类型结果。 */
/* 每个 Visual 的 Private 只保存自己 Spec::Prepared_Data 的强类型结果。 */
template <typename Data>
struct Prepared_Visual_For {
Matrix4 transform{};
+9 -3
View File
@@ -15,7 +15,6 @@ void verify_visual(Item valid, Item) {
object->template set<&Visual::Prop::items>(std::vector<Item>{valid});
object->advance();
EXPECT_EQ(object->template read_prop<typename Visual::Base_Tag>().items.size(), 1U);
EXPECT_EQ(object->template read_state<typename Visual::Base_Tag>().item_count, 1U);
}
}
TEST(Render_3D_Visual, Migrated_Families_Build_And_Validate) {
@@ -41,7 +40,10 @@ TEST(Render_3D_Scene, Builder_Instantiates_Async_Paint_Contract) {
auto visual_result = typename Visual_Object::Builder{}.build();
ASSERT_TRUE(visual_result.has_value());
auto visual = std::move(visual_result).value();
typename Scene_Object::Builder scene_builder(visual.get());
auto camera = *Impl<Camera_3D>::Builder{}.build();
auto axes = *Impl<Axes_3D>::Builder{}.build();
typename Scene_Object::Builder scene_builder;
scene_builder.add_renderable(visual.get()).add_camera(camera.get()).add_axes(axes.get());
static_assert(std::same_as<decltype(scene_builder.build()), std::expected<std::unique_ptr<Scene_Object>, Dependency_Graph_Error>>);
}
TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
@@ -50,10 +52,14 @@ TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
auto visual_result = typename Visual_Object::Builder{}.build();
ASSERT_TRUE(visual_result.has_value());
auto visual = std::move(visual_result).value();
auto camera = *Impl<Camera_3D>::Builder{}.build();
auto axes = *Impl<Axes_3D>::Builder{}.build();
visual->set<&Point_Visual::Prop::items>(std::vector<Point>{Point{.position = {0.0F, 0.0F, 0.0F}, .color = Color::red_color(), .diameter_px = 8.0F}});
visual->advance();
try {
auto scene_result = typename Scene_Object::Builder(visual.get()).build();
typename Scene_Object::Builder scene_builder;
scene_builder.add_renderable(visual.get()).add_camera(camera.get()).add_axes(axes.get());
auto scene_result = scene_builder.build();
ASSERT_TRUE(scene_result.has_value());
auto scene = std::move(scene_result).value();
std::atomic_bool frame_ready{};