接入3D secene taskflow

This commit is contained in:
2026-08-14 23:38:18 +08:00
parent 9a0601073b
commit 12e46b4c21
31 changed files with 1210 additions and 6116 deletions
@@ -12,7 +12,8 @@ using Render_Plan_Version = std::uint64_t;
enum class Render_Node_Kind : std::uint8_t {
prepare,
paint,
composite
composite,
render
};
struct Render_Node {
@@ -176,6 +176,8 @@ void Renderable_Base::Impl::build_paint_graph(
void Renderable_Base::Impl::prepare(const Prepare_Render_Context&) {}
void Renderable_Base::Impl::capture_frame_data(Frame_Render_Snapshot&) const {}
void Renderable_Base::Impl::add_prepare_action(Prepare_Action action) {
if (!action)
throw std::invalid_argument("renderable prepare action is empty");
@@ -254,6 +256,18 @@ bool Renderable_Base::is_visible() const noexcept {
return d_func().visible.load(std::memory_order_acquire);
}
std::string Renderable_Base::object_name() const {
const auto& implementation = d_func();
std::lock_guard lock(implementation.metadata_mutex);
return implementation.object_name;
}
void Renderable_Base::set_object_name(std::string name) {
auto& implementation = d_func();
std::lock_guard lock(implementation.metadata_mutex);
implementation.object_name = std::move(name);
}
void Renderable_Base::set_visible(bool visible) {
d_func().set_visible(visible);
}
@@ -2,6 +2,7 @@
#include <memory>
#include <memory_resource>
#include <string>
#include "renderive/renderable/Renderable_Configuration.hpp"
#include "renderive/renderable/Renderable_Id.hpp"
@@ -17,6 +18,8 @@ public:
[[nodiscard]] Renderable_Id renderable_id() const noexcept;
[[nodiscard]] bool is_visible() const noexcept;
[[nodiscard]] std::string object_name() const;
void set_object_name(std::string name);
void set_visible(bool visible);
void discard_stale_frame_on_latest_data_update(bool enabled) noexcept;
@@ -59,6 +59,7 @@ public:
void reset_render_graph() noexcept;
void add_prepare_action(Prepare_Action action);
void execute_prepare(const Prepare_Render_Context& context);
virtual void capture_frame_data(Frame_Render_Snapshot& snapshot) const;
protected:
[[nodiscard]] Renderable_Base& owner() noexcept;
@@ -80,6 +81,8 @@ public:
const Render_Node_Id composite_node_id;
std::atomic<Renderable_Configuration> configuration;
std::atomic<bool> visible{true};
mutable std::mutex metadata_mutex;
std::string object_name;
std::mutex render_graph_mutex;
std::shared_ptr<const Renderable_Graph> render_graph;
std::unordered_map<std::string, Render_Node_Id> node_identities;
@@ -85,6 +85,10 @@ protected:
std::uint64_t acquire_scene_state() override {
return this->Scene_State_Strategy::state_revision();
}
void capture_scene_state(Frame_Render_Snapshot& snapshot) const override {
snapshot.set_scene_state(
this->Scene_State_Strategy::render_state_value());
}
void observe_scene(const Scene_Base::Observation& observation) noexcept override {
scene_observer_.observe(observation);
}
@@ -14,7 +14,7 @@ template <class Frame_Control, class... Args>
concept Scene3D_Frame_Control_Constructible = std::constructible_from<Frame_Control, Args...> || std::constructible_from<Frame_Control, std::pmr::memory_resource&, Args...>;
template <class Strategy = Low_Latency_Strategy<Scene3D_Frame_Data>, class State = Scene3D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>>
requires Frame_Control_Strategy_For<Strategy, Scene3D_Frame_Data> && State_Value<State>
class Scene3D_Context final : public Scene_3D_Base,
class Scene3D_Context : public Scene_3D_Base,
public Triple_State_Storage<
State, Atomic_Spin_Mutex, State_Observer> {
public:
@@ -59,6 +59,10 @@ protected:
std::uint64_t acquire_scene_state() override {
return this->Scene_State_Strategy::acquire_render_state();
}
void capture_scene_state(Frame_Render_Snapshot& snapshot) const override {
snapshot.set_scene_state(
this->Scene_State_Strategy::render_state_value());
}
void observe_scene(const Scene_Base::Observation& observation) noexcept override {
scene_observer_.observe(observation);
}
@@ -50,6 +50,7 @@ struct Frame_Snapshot {
};
class Scene_Base;
class Frame_Render_Snapshot;
class Abstract_Frame {
public:
@@ -74,6 +75,7 @@ private:
std::shared_ptr<const Frame_Snapshot> completed_snapshot_;
bool capture_{};
bool rendering_{};
std::shared_ptr<Frame_Render_Snapshot> published_snapshot_;
};
[[nodiscard]] std::uint64_t render_clock_now_ns() noexcept;
@@ -1,7 +1,12 @@
#pragma once
#include <any>
#include <cstdint>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
#include <vector>
#include "renderive/capture/Capture_Types.hpp"
#include "renderive/renderable/Renderable_Configuration.hpp"
@@ -44,9 +49,83 @@ public:
std::vector<Renderable_Frame_State> renderables;
std::vector<Renderable_Id> display_order;
[[nodiscard]] std::uint64_t scene_state_revision() const noexcept {
return scene_state_revision_;
}
template <class State>
[[nodiscard]] const State& scene_state() const {
const auto* value = std::any_cast<State>(&scene_state_);
if (!value)
throw std::logic_error("frame scene state type mismatch");
return *value;
}
template <class State>
void set_scene_state(State state) {
scene_state_ = std::move(state);
}
template <class Data>
void capture(Renderable_Id owner, std::shared_ptr<const Data> data) {
if (!data)
throw std::invalid_argument("captured render data is null");
const auto [entry, inserted] = captured_.try_emplace(
owner, Typed_Data{typeid(Data), std::move(data)});
if (!inserted)
throw std::logic_error(
"renderable captured frame data more than once");
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> captured(
Renderable_Id owner) const {
const auto entry = captured_.find(owner);
if (entry == captured_.end())
return {};
if (entry->second.type != std::type_index(typeid(Data)))
throw std::logic_error("captured render data type mismatch");
return std::static_pointer_cast<const Data>(entry->second.value);
}
template <class Data>
void publish_prepared(Renderable_Id owner,
std::shared_ptr<const Data> data) const {
if (!data)
throw std::invalid_argument("prepared render data is null");
std::lock_guard lock(prepared_mutex_);
const auto [entry, inserted] = prepared_.try_emplace(
owner, Typed_Data{typeid(Data), std::move(data)});
if (!inserted)
throw std::logic_error(
"renderable published prepared data more than once");
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> prepared(
Renderable_Id owner) const {
std::lock_guard lock(prepared_mutex_);
const auto entry = prepared_.find(owner);
if (entry == prepared_.end())
return {};
if (entry->second.type != std::type_index(typeid(Data)))
throw std::logic_error("prepared render data type mismatch");
return std::static_pointer_cast<const Data>(entry->second.value);
}
private:
friend class Scene_Base;
struct Typed_Data {
std::type_index type{typeid(void)};
std::shared_ptr<const void> value;
};
std::uint64_t scene_state_revision_{};
std::uint64_t next_refresh_interval_ns_{};
Capture_Frame_Ticket capture_ticket_;
std::any scene_state_;
std::unordered_map<Renderable_Id, Typed_Data> captured_;
mutable std::mutex prepared_mutex_;
mutable std::unordered_map<Renderable_Id, Typed_Data> prepared_;
};
@@ -10,6 +10,16 @@ struct Prepare_Render_Context {
const Frame_Render_Snapshot& frame;
const Renderable_Frame_State& renderable;
Node_Execution_Metrics* metrics{};
template <class Data>
void publish(std::shared_ptr<const Data> data) const {
frame.publish_prepared(renderable.renderable_id, std::move(data));
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> captured() const {
return frame.captured<Data>(renderable.renderable_id);
}
};
struct Paint_Render_Context {
@@ -25,3 +35,14 @@ struct Composite_Render_Context {
const Color_Cache* color_cache{};
Node_Execution_Metrics* metrics{};
};
struct Scene_Render_Context {
const Frame_Render_Snapshot& frame;
Node_Execution_Metrics* metrics{};
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> prepared(
Renderable_Id owner) const {
return frame.prepared<Data>(owner);
}
};
+56 -4
View File
@@ -179,7 +179,8 @@ Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource)
color_caches_(&memory_domain_->resource()),
task_(memory_domain_->resource()),
renderable_edit_queue_(&memory_domain_->resource()),
composite_begin_node_id_(allocate_renderive_node_id()) {
composite_begin_node_id_(allocate_renderive_node_id()),
scene_render_node_id_(allocate_renderive_node_id()) {
worker_ = std::thread([this] { render_loop(); });
}
Scene_Base::~Scene_Base() {
@@ -366,14 +367,15 @@ void Scene_Base::submit_render(Abstract_Frame* frame) {
auto& strategy = frame_control_strategy();
strategy.swap();
auto snapshot = snapshot_live_model();
auto snapshot = frame ? std::exchange(frame->published_snapshot_, {})
: nullptr;
if (!snapshot)
snapshot = capture_live_frame();
snapshot->next_refresh_interval_ns_ =
strategy.frame_control_state().next_refresh_interval_ns;
snapshot->scene_state_revision_ = acquire_scene_state();
if (render_sequence_ == std::numeric_limits<std::uint64_t>::max())
throw std::overflow_error("render sequence exhausted");
snapshot->render_sequence = ++render_sequence_;
snapshot->viewport = frame_viewport();
Render_Task task(memory_resource());
task.frame = frame
@@ -527,6 +529,16 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
}
void Scene_Base::publish_frame_state() {
auto task_lock = lock_render_idle();
publish_frame_state_locked();
}
void Scene_Base::publish_frame_state(Abstract_Frame& frame) {
auto task_lock = lock_render_idle();
publish_frame_state_locked();
frame.published_snapshot_ = capture_live_frame();
}
void Scene_Base::publish_frame_state_locked() {
if (auto* state = dynamic_cast<State_Strategy_Base*>(this))
state->publish();
std::vector<Renderable> renderables;
@@ -640,6 +652,7 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::snapshot_live_model() {
if (const auto cache = color_caches_.find(id);
cache != color_caches_.end())
state.paint_buffer_ = cache->second;
renderable_data.capture_frame_data(*snapshot);
snapshot->renderables.push_back(std::move(state));
}
@@ -675,6 +688,14 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::snapshot_live_model() {
return snapshot;
}
std::shared_ptr<Frame_Render_Snapshot> Scene_Base::capture_live_frame() {
auto snapshot = snapshot_live_model();
snapshot->scene_state_revision_ = acquire_scene_state();
capture_scene_state(*snapshot);
snapshot->viewport = frame_viewport();
return snapshot;
}
std::shared_ptr<const Render_Plan> Scene_Base::render_plan_snapshot() const {
return render_plan_history_.current();
}
@@ -772,6 +793,8 @@ std::uint64_t Scene_Base::acquire_scene_state() {
return 0;
}
void Scene_Base::capture_scene_state(Frame_Render_Snapshot&) const {}
void Scene_Base::observe_scene(const Observation&) noexcept {}
std::uint64_t Scene_Base::observer_now_ns() const noexcept {
@@ -1043,6 +1066,31 @@ std::shared_ptr<const Render_Plan> Scene_Base::compile_render_plan(
}
}
if (auto* renderer = dynamic_cast<Scene_Renderer*>(this)) {
std::vector<Render_Node_Id> terminals;
terminals.reserve(graph.nodes.size());
for (const auto& node : graph.nodes) {
const bool has_successor = std::any_of(
graph.edges.begin(), graph.edges.end(),
[&node](const Render_Edge& edge) {
return edge.from == node.node_id;
});
if (!has_successor)
terminals.push_back(node.node_id);
}
graph.nodes.push_back({scene_render_node_id_, 0, "Render Scene",
Render_Node_Kind::render, 0});
functions.emplace(
scene_render_node_id_,
Execution_Binding{{}, std::nullopt,
Scene_Render_Node_Function{
[renderer](const Scene_Render_Context& context) {
renderer->render_scene(context);
}}});
for (const Render_Node_Id terminal : terminals)
append_edge(terminal, scene_render_node_id_);
}
auto plan = render_plan_history_.publish(std::move(graph));
task.execution_bindings.clear();
task.execution_bindings.resize(plan->graph.nodes.size());
@@ -1129,6 +1177,10 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
metrics});
break;
}
case Render_Node_Kind::render:
std::get<Scene_Render_Node_Function>(binding.function)(
Scene_Render_Context{snapshot, metrics});
break;
}
};
if (!execution) {
+16 -1
View File
@@ -39,6 +39,12 @@ public:
virtual void composite(const Composite_Render_Context& context) = 0;
};
class Scene_Renderer {
public:
virtual ~Scene_Renderer() = default;
virtual void render_scene(const Scene_Render_Context& context) = 0;
};
class Scene_Base {
private:
struct Render_Completion;
@@ -128,6 +134,7 @@ public:
void render(Abstract_Frame& frame);
void wait_for_render();
void publish_frame_state();
void publish_frame_state(Abstract_Frame& frame);
void notify_model_dirty() noexcept;
Attach_Builder attach_builder();
@@ -164,6 +171,7 @@ protected:
virtual std::shared_ptr<Color_Cache> make_renderable_color_cache();
virtual Frame_Viewport frame_viewport() const;
virtual std::uint64_t acquire_scene_state();
virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const;
virtual void observe_scene(const Observation& observation) noexcept;
virtual std::uint64_t observer_now_ns() const noexcept;
@@ -209,9 +217,12 @@ private:
using Composite_Render_Node_Function =
std::function<void(const Composite_Render_Context&)>;
using Scene_Render_Node_Function =
std::function<void(const Scene_Render_Context&)>;
using Execution_Function =
std::variant<Prepare_Render_Node_Function, Paint_Render_Node_Function,
Composite_Render_Node_Function>;
Composite_Render_Node_Function,
Scene_Render_Node_Function>;
struct Execution_Binding {
Renderable owner;
@@ -251,6 +262,9 @@ private:
void enqueue_renderable_edit_locked(Renderable_Edit edit);
void request_render_graph_rebuild(Renderable_Base& renderable);
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot> snapshot_live_model();
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot>
capture_live_frame();
void publish_frame_state_locked();
std::shared_ptr<const Render_Plan> compile_render_plan(
const Frame_Render_Snapshot& snapshot, Render_Task& task);
void render_loop();
@@ -293,6 +307,7 @@ private:
bool stop_{};
const Render_Node_Id composite_begin_node_id_;
const Render_Node_Id scene_render_node_id_;
Render_Plan_History render_plan_history_;
Capture_Controller capture_controller_;
Capture_Repository capture_repository_;
@@ -125,13 +125,15 @@ struct Triple_State_Storage : State_Strategy_Base {
return publish_count;
}
private:
protected:
friend class Render_State_View;
[[nodiscard]] const State& render_state_value() const noexcept {
return *render_state;
}
private:
Observer observer;
State states[4];
State* render_state;
@@ -1,6 +1,8 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <vector>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/renderable/Renderable_Test_Harness.hpp"
#include "renderive/scene/Scene.hpp"
@@ -23,6 +25,95 @@ TEST(scene3d_context_test, uses_scene_3d_base_contract) {
EXPECT_EQ(renderable->render_count.load(), 1);
EXPECT_TRUE(renderable->scene_is_3d);
}
struct Prepared_Mesh_Count {
std::size_t vertex_count{};
};
struct Scene3D_Prepared_Data_Renderable : Renderable_Test_Harness {
void prepare_for_test(const Prepare_Render_Context& context) override {
context.publish<Prepared_Mesh_Count>(
std::make_shared<const Prepared_Mesh_Count>(
Prepared_Mesh_Count{42}));
}
};
struct Scene3D_Render_Node_Test_Scene final
: Scene3D_Context<>, Scene_Renderer {
void render_scene(const Scene_Render_Context& context) override {
prepared = context.prepared<Prepared_Mesh_Count>(owner);
}
Renderable_Id owner{};
std::shared_ptr<const Prepared_Mesh_Count> prepared;
};
TEST(scene3d_context_test,
transfers_frame_local_prepared_data_to_single_scene_render_node) {
Scene3D_Render_Node_Test_Scene scene;
auto renderable = renderive_Owner<Scene3D_Prepared_Data_Renderable>::make();
scene.owner = renderable->renderable_id();
attach_initial(scene, renderable);
scene.render();
scene.wait_for_render();
ASSERT_TRUE(scene.prepared);
EXPECT_EQ(scene.prepared->vertex_count, 42U);
const auto plan = scene.render_plan_snapshot();
ASSERT_TRUE(plan);
const auto render_node = std::find_if(
plan->graph.nodes.begin(), plan->graph.nodes.end(),
[](const Render_Node& node) {
return node.kind == Render_Node_Kind::render;
});
ASSERT_NE(render_node, plan->graph.nodes.end());
EXPECT_TRUE(std::any_of(
plan->graph.edges.begin(), plan->graph.edges.end(),
[&](const Render_Edge& edge) {
return edge.to == render_node->node_id;
}));
}
struct Scene3D_Historical_State {
int value{};
};
struct Scene3D_Playback_Snapshot_Test_Scene final
: Scene3D_Context<Flow_Refresh_Strategy<Scene3D_Frame_Data>,
Scene3D_Historical_State>,
Scene_Renderer {
void enqueue(int value) {
Scene_State_Strategy::set<&Scene3D_Historical_State::value>(value);
auto frame = frame_control.acquire_painter();
ASSERT_TRUE(frame);
publish_frame_state(*frame);
}
void render_next() {
auto frame = frame_control.acquire_renderer();
ASSERT_TRUE(frame);
render(*frame);
wait_for_render();
}
void render_scene(const Scene_Render_Context& context) override {
rendered.push_back(
context.frame.scene_state<Scene3D_Historical_State>().value);
}
std::vector<int> rendered;
};
TEST(scene3d_context_test,
playback_uses_the_scene_snapshot_captured_at_frame_publication) {
Scene3D_Playback_Snapshot_Test_Scene scene;
scene.enqueue(11);
scene.enqueue(23);
scene.render_next();
scene.render_next();
EXPECT_EQ(scene.rendered, (std::vector<int>{11, 23}));
}
struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Test_Harness {
explicit Scene3D_Dependency_Cache_Test_Renderable()
: Renderable_Test_Harness({.cache_enabled = true}) {}
@@ -9,4 +9,4 @@ TEST(scene_concept_test, accepts_2d_and_3d_contexts) {
SUCCEED();
}
static_assert(!std::is_final_v<Scene2D_Context<>>);
static_assert(std::is_final_v<Scene3D_Context<>>);
static_assert(!std::is_final_v<Scene3D_Context<>>);
File diff suppressed because it is too large Load Diff
@@ -23,18 +23,6 @@ void Renderable::set_cache_mode(Renderable_Cache_Mode mode) {
{.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel});
}
std::string Renderable::object_name() const {
const auto& d = d_func<Impl>();
std::lock_guard lock(d.metadata_mutex);
return d.object_name;
}
void Renderable::set_object_name(std::string name) {
auto& d = d_func<Impl>();
std::lock_guard lock(d.metadata_mutex);
d.object_name = std::move(name);
}
Renderable_Observation Renderable::observation() const noexcept {
const auto& d = d_func<Impl>();
std::lock_guard lock(d.observation_mutex);
@@ -37,8 +37,6 @@ struct LIB_DECL Renderable
~Renderable() override;
[[nodiscard]] Renderable_Cache_Mode get_cache_mode() const noexcept;
void set_cache_mode(Renderable_Cache_Mode mode);
[[nodiscard]] std::string object_name() const;
void set_object_name(std::string name);
[[nodiscard]] Renderable_Observation observation() const noexcept;
void dispatch_event(const Event& event);
protected:
@@ -92,8 +92,6 @@ private:
void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns,
std::uint64_t cache_update_count,
std::uint64_t publish_count) noexcept;
mutable std::mutex metadata_mutex;
std::string object_name;
mutable std::mutex observation_mutex;
Renderable_Observation observation;
friend class Renderable;
+1 -1
View File
@@ -262,7 +262,7 @@ struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Co
auto paint_frame = this->frame_control.acquire_painter();
if (!paint_frame)
return false;
this->publish_frame_state();
this->publish_frame_state(*paint_frame);
return true;
}
[[nodiscard]] bool refresh_manual_frame() {
+45 -63
View File
@@ -1,78 +1,60 @@
# Datoviz 迁移与封装审计
## 目标边界
## 线程与数据边界
Renderive 负责逻辑状态收集、批量数据收集、Kernel 帧策略调度、输入事件汇聚和像素帧发布。Datoviz 的 scene/visual API 只允许在 `Render_Domain` 所属线程创建、修改、渲染和销毁,对外接口不暴露 `Dvz*`、Vulkan 对象、缓冲区角色或工作线程状态
Renderive 负责状态发布、实时数据交换、Taskflow CPU 数据准备、事件入口、帧调度和像素帧发布。Datoviz 的 scene/visual mutation、FramePlan emission、DRP2 execution、GPU submission 与 readback 全部归单一 `Render_Domain` 所有
当前垂直切片的数据流
当前数据流:
```text
Point_State --Kernel Double_State_Strategy--+
+--帧边界--> Point_Frame_Strategy
Point payload --Kernel Multi_Double_Buffer_Strategy-----------+
Kernel Event --Input_Collector---------------+ v
Render_Domain 单线程
|
Datoviz Point Visual
|
Vulkan 外部目标 + RGBA8 回读
|
immutable Pixel_Frame
|
WebSocket RVP1 像素帧
Scene / Renderable state ----+
Point bulk data -------------+-- frame publication snapshot
|
+-- Taskflow: per-renderable CPU prepare
| |
Kernel Event -----------------+ +-- immutable Prepared_Point
|
v
Render_Domain (single thread)
|
Datoviz apply / emit / DRP2 / GPU
|
immutable Pixel_Frame
```
## 已迁移的 Datoviz 生产模块
Taskflow worker 不调用 `dvz_*`。Datoviz FramePlan 继续负责 GPU upload/render/copy/readback 依赖,Renderive RenderPlan 只负责应用级 CPU prepare 依赖和末端单一 render node。
`render_3D/datoviz/CMakeLists.txt` 已直接编译以下模块,现有 Point 后端不再需要复制 Datoviz 实现:
## 帧快照
- 基础层:`common``fileio``geom``math``thread`
- 输入与控制器:`input``controller`
- GPU`vk``vklite``drp2`
- 场景层:`scene` 以及 `scene/visuals` 下全部生产源、GLSL/WGSL 注册表和 SPIR-V 构建产物
- Visual familypoint、pixel、marker、segment、path、image、mesh、volume、primitive、sphere、glyph、text、labels、splat、vector
- `Scene_Base::publish_frame_state(Abstract_Frame&)` 在 frame-control 的发布边界交换 Scene、Renderable 与实时数据
- `Frame_Render_Snapshot` 保存对应帧的 Scene 状态、Renderable 元数据和类型安全的大数据共享快照
- Manual、Low Latency、Playback 保存同一类快照;Playback 出队不会读取后来版本的数据
- Taskflow prepare 只从帧快照读取输入并发布不可变 prepared output
- Scene render node 等待所有末端 prepare 依赖,然后在 `Render_Domain` 串行消费 prepared output
`registry``stroke` 是 visual 内部支撑目录,不是独立业务组件,不应再增加一层公共包装。
## Point 封装
明确不迁移 `app``gui`、原生 window/canvas、stream、video、Qt bridge 和 wasm。无窗口像素服务由 Renderive 自己的帧策略、事件入口和 Web 传输承担,这些上游模块没有消费者,迁入会形成第二套调度和事件来源
- `Point_Visual` 的小状态由 Renderable 状态策略管理
- 点集的唯一权威来源是 `Multi_Double_Buffer_Strategy`;帧只持有已发布版本的不可变共享快照。
- positions、colors、sizes、sigma、angles、radii、normals 等派生数组只存在于 `Prepared_Point`,不保存为 Visual 成员。
- `Point_Scene` 复用 Kernel `Scene3D_Context`、frame-control、RenderPlan、Taskflow、capture 和 observer。
- Pointer、wheel、key 直接保留 Kernel Event 语义并投递到 `Render_Domain`,没有第二套输入命令或收集队列。
- 后端只应用 prepared data,并负责 Datoviz/Vulkan 资源和不可变 `Pixel_Frame` 输出。
## 已完成的 Point 封装
## 唯一状态源检查
- `Point_Visual`:直接组合状态与实时数据策略;`Point_State` 只保存样式、变换、可见性和深度测试。
- `Point_Visual`:状态使用 Kernel `Double_State_Strategy`,不可变点集使用单 Entry 的 `Multi_Double_Buffer_Strategy` 在帧边界发布。
- `Point_Scene`:只使用满足 Kernel frame-control concept 的 3D `Point_Frame_Strategy`;统一发布 `Frame_Status` 查询。
- `Datoviz_Point_Backend`:所有 Datoviz/Vulkan 资源均限制在单一 `Render_Domain`;批量属性使用 `dvz_visual_set_data_many()` 原子提交。
- 输入:Kernel pointer/wheel/key 事件映射到 Datoviz router/arcballWeb 保留事件的真实派生类型,不经过 `Event` 切片。
- 输出:外部 RGBA8 target 渲染、同步回读并发布不可变 `Pixel_Frame`
- Demo/Web`Point_Demo`、图库案例 `point_3d`、RVP1 RGBA 编码、三种帧模式动作和前端目录展示。
## 后续 visual 包装批次
Datoviz 生产源码和上游测试已经迁入;以下是尚未实现的 Renderive 业务包装,不应在没有业务消费者时一次性生成空类:
1. 点/线批量族:Pixel、Marker、Segment、Path、Vector、Sphere、Splat。复用 Point 的状态/实时数据/单线程后端结构,但每个 family 保留自己的业务状态和 Datoviz 属性映射。
2. 几何族:Primitive、Mesh。分别建模顶点/索引/实例数据;索引和实例不可复制成 visual 成员的第二份权威数据。
3. 图像与文字族:Image、Glyph、Labels、Text。纹理/字形数据使用 real-time data 或具有明确所有权的资源对象,不能把 Datoviz field 指针暴露到公共接口。
4. 体数据:Volume。体素 payload、传输函数和采样状态分离;大体数据必须走实时数据接口,不能进入双缓冲小状态对象。
每新增一个 family 都必须同时完成:状态边界测试、并发批量数据测试、Datoviz 属性映射测试、真实 GPU 像素测试、resize 测试、三种帧策略测试、事件测试,以及 Web 展示测试。Point 的实现是这些测试的基准,不是供公共 API 继承的万能基类。
## 测试覆盖
- Datoviz scene runner558/558,通过;其中已包含 Point 的 typed upload、属性校验、item range、resize、external buffer、large count、GLSL/WGSL emit 和 GPU 执行测试。
- Renderive Point 状态/Kernel 策略测试:4/4,通过。
- Renderive Point Vulkan/事件集成测试:2/2,通过。
- Web Point 专项测试:8/8,通过,覆盖 RGBA 行步长、目录动作、resize、自动低延迟、Web 事件透传、Manual 快照、Playback 顺序和增删恢复。
- `webapp_gallery`TypeScript 与 Vite production build 通过。
## 状态来源检查
| 状态 | 唯一权威来源 | 读取方式 |
| 数据 | 权威来源 | 帧内读取 |
| --- | --- | --- |
| Point 样式/变换/可见性 | `Point_Visual` 的 Kernel 双状态策略 | 帧边界发布快照 |
| Point 大批量数据 | `Point_Visual` 的 Kernel 双缓冲 | 不可变已发布点集 |
| viewport/clear color | `Scene_State_Buffer` | 帧边界发布快照 |
| 输入事件 | `Input_Collector` 队列 | 每帧 drain |
| 帧生命周期/计数 | Kernel frame strategy | `Frame_Status` 即时查询 |
| Datoviz/Vulkan 资源 | `Datoviz_Point_Backend` | 仅 Render_Domain 内访问 |
| 对外像素 | `Point_Scene::latest_frame()` | 不可变共享快照 |
| Point 样式变换可见性 | `Point_Visual` 状态策略 | 发布快照 |
| Point 大批量数据 | `Point_Visual` 双缓冲策略 | 不可变共享快照 |
| viewportclear color、visual family | `Scene3D_Context` 状态策略 | 发布快照 |
| 输入 | Kernel Event | `Render_Domain` 直接处理 |
| CPU 派生数组 | 当前帧 Taskflow prepare | `Prepared_Point` |
| Datoviz/Vulkan 资源 | `Datoviz_Visual_Backend` | 仅 `Render_Domain` |
| 输出像素 | 最近完成的 render node | `Point_Scene::latest_frame()` |
## 验证
- Renderive CTestKernel、render2D、render3D、Datoviz、Qt、Web 全套通过。
- Point 状态/并发快照、真实 Vulkan 渲染、resize、事件和 Playback 历史顺序均有测试覆盖。
- `webapp_gallery` Vitest、TypeScript 和 Vite production build 通过。
+302 -128
View File
@@ -4,9 +4,13 @@
#include "detail/Point_Core.h"
#include "detail/Render_Domain.h"
#include <atomic>
#include <renderive/frame_control/Frame_Control.hpp>
#include <renderive/scene/Scene.hpp>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <utility>
@@ -28,73 +32,280 @@ void validate(Clear_Color color) {
throw std::invalid_argument("Point_Scene clear color must be in [0, 1]");
}
detail::Input_Command_Type pointer_type(::renderive::Event_Type type) {
switch (type) {
case ::renderive::Event_Type::Pointer_Move:
return detail::Input_Command_Type::Pointer_Move;
case ::renderive::Event_Type::Pointer_Press:
return detail::Input_Command_Type::Pointer_Press;
case ::renderive::Event_Type::Pointer_Release:
return detail::Input_Command_Type::Pointer_Release;
default:
throw std::invalid_argument("Point_Scene expected a pointer event");
}
float datoviz_wheel_step(float pixel_delta, float angle_delta) noexcept {
return angle_delta != 0.0F ? angle_delta / 120.0F
: pixel_delta / 100.0F;
}
float datoviz_wheel_step(float pixel_delta, float angle_delta) {
if (angle_delta != 0.0F)
return angle_delta / 120.0F;
return pixel_delta / 100.0F;
struct Scene_Model {
virtual ~Scene_Model() = default;
virtual void resize(Extent extent) = 0;
virtual void set_clear_color(Clear_Color color) = 0;
virtual void dispatch_pointer(
::renderive::Event_Type type, float x, float y,
::renderive::Mouse_Button button,
::renderive::Keyboard_Modifier modifiers) = 0;
virtual void dispatch_wheel(
float x, float y, float delta_x, float delta_y,
::renderive::Keyboard_Modifier modifiers) = 0;
virtual void dispatch_key(const ::renderive::Key_Event& event) = 0;
[[nodiscard]] virtual bool prepare_frame() = 0;
[[nodiscard]] virtual bool refresh_manual_frame() = 0;
[[nodiscard]] virtual bool discard_pending_frame() = 0;
[[nodiscard]] virtual bool render_prepared_frame() = 0;
[[nodiscard]] virtual bool request_frame() = 0;
[[nodiscard]] virtual std::shared_ptr<const Pixel_Frame> latest_frame()
const = 0;
[[nodiscard]] virtual Frame_Status frame_status() const = 0;
[[nodiscard]] virtual Scene_Base& scene() noexcept = 0;
[[nodiscard]] virtual const Scene_Base& scene() const noexcept = 0;
};
using Manual_Frame = ::Manual_Refresh_Strategy<::Scene3D_Frame_Data>;
using Low_Latency_Frame = ::Low_Latency_Strategy<::Scene3D_Frame_Data>;
using Playback_Frame = ::Flow_Refresh_Strategy<::Scene3D_Frame_Data>;
template <class Strategy, Frame_Mode Mode>
struct Basic_Point_Scene final
: ::Scene3D_Context<Strategy, detail::Scene_State>,
::Scene_Renderer,
Scene_Model {
using Kernel_Scene = ::Scene3D_Context<Strategy, detail::Scene_State>;
using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy;
Basic_Point_Scene(const Scene_Options& options,
std::shared_ptr<Point_Visual> visual)
requires (!std::same_as<Strategy, Low_Latency_Frame>)
: Kernel_Scene() {
initialize(options, std::move(visual));
}
Basic_Point_Scene(const Scene_Options& options,
std::shared_ptr<Point_Visual> visual)
requires std::same_as<Strategy, Low_Latency_Frame>
: Kernel_Scene(Observer_State<>{}, typename Strategy::Configuration{
.frequency_hz = options.maximum_frames_per_second,
.replace_pending_frame = true}) {
initialize(options, std::move(visual));
}
~Basic_Point_Scene() override {
this->shutdown();
render_domain.invoke([this] { backend.reset(); });
}
void initialize(const Scene_Options& options,
std::shared_ptr<Point_Visual> point_visual) {
if (!point_visual)
throw std::invalid_argument("Point_Scene requires a Point_Visual");
visual = std::move(point_visual);
point_id = visual->renderable_id();
this->Scene_State_Strategy::template set<
&detail::Scene_State::viewport>(options.viewport);
this->Scene_State_Strategy::template set<
&detail::Scene_State::clear_color>(options.clear_color);
this->Scene_State_Strategy::template set<
&detail::Scene_State::visual_family>(options.visual_family);
{
auto builder = this->attach_builder();
builder.attach(renderive_Owner<Point_Visual>(visual));
}
const detail::Scene_State initial{
options.viewport, options.clear_color, options.visual_family};
render_domain.invoke([this, &options, &initial] {
backend = std::make_unique<detail::Datoviz_Visual_Backend>(
options.gpu_index, options.validation_enabled, initial);
});
}
void resize(Extent extent) override {
this->Scene_State_Strategy::template set<
&detail::Scene_State::viewport>(extent);
this->invalidate_renderables();
}
void set_clear_color(Clear_Color color) override {
this->Scene_State_Strategy::template set<
&detail::Scene_State::clear_color>(color);
this->notify_model_dirty();
}
void dispatch_pointer(
::renderive::Event_Type type, float x, float y,
::renderive::Mouse_Button button,
::renderive::Keyboard_Modifier modifiers) override {
const Extent viewport = this->Scene_State_Strategy::template get<
&detail::Scene_State::viewport>();
render_domain.invoke([this, type, x, y, button, modifiers, viewport] {
backend->dispatch_pointer(type, x, y, button, modifiers, viewport);
});
this->notify_model_dirty();
}
void dispatch_wheel(
float x, float y, float delta_x, float delta_y,
::renderive::Keyboard_Modifier modifiers) override {
const Extent viewport = this->Scene_State_Strategy::template get<
&detail::Scene_State::viewport>();
render_domain.invoke(
[this, x, y, delta_x, delta_y, modifiers, viewport] {
backend->dispatch_wheel(x, y, delta_x, delta_y, modifiers,
viewport);
});
this->notify_model_dirty();
}
void dispatch_key(const ::renderive::Key_Event& event) override {
render_domain.invoke([this, event] { backend->dispatch_key(event); });
this->notify_model_dirty();
}
[[nodiscard]] bool prepare_frame() override {
auto painter = this->frame_control.acquire_painter();
if (!painter)
return false;
this->publish_frame_state(*painter);
return true;
}
[[nodiscard]] bool refresh_manual_frame() override {
if constexpr (Mode == Frame_Mode::Manual)
return this->frame_control.refresh();
return false;
}
[[nodiscard]] bool discard_pending_frame() override {
if constexpr (Mode == Frame_Mode::Playback)
return false;
else
return this->frame_control.discard_pending_frame();
}
[[nodiscard]] bool render_prepared_frame() override {
auto renderer = this->frame_control.acquire_renderer();
if (!renderer)
return false;
this->render(*renderer);
this->wait_for_render();
return true;
}
[[nodiscard]] bool request_frame() override {
if (!prepare_frame())
return false;
if constexpr (Mode == Frame_Mode::Manual) {
if (!refresh_manual_frame())
return false;
}
return render_prepared_frame();
}
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame()
const override {
std::lock_guard lock(frame_mutex);
return latest;
}
[[nodiscard]] Frame_Status frame_status() const override {
Frame_Status result;
result.mode = Mode;
if constexpr (Mode == Frame_Mode::Manual) {
const auto state = this->frame_control.state();
result.produced_frame_count = state.prepared_frame_count;
result.consumed_frame_count = state.render_count;
result.dropped_frame_count = state.replaced_prepared_frame_count +
state.discarded_prepared_frame_count;
result.failed_operation_count = state.failed_refresh_count;
result.pending_frame_count = state.pending_frame ? 1U : 0U;
result.latest_sequence = state.render_frame_sequence;
} else if constexpr (Mode == Frame_Mode::Low_Latency) {
const auto state = this->frame_control.state();
const auto counters = this->frame_control.counter_statistics();
result.frequency_hz = state.frequency_hz;
result.produced_frame_count = counters.published_frame_count;
result.consumed_frame_count = counters.render_pass_count;
result.dropped_frame_count = counters.abandoned_frame_count +
counters.manually_discarded_frame_count;
result.failed_operation_count = counters.swap_failure_count;
result.pending_frame_count =
this->frame_control.pending_frame_count();
result.latest_sequence = state.frame_sequence;
result.next_refresh_interval_ns = state.next_refresh_interval_ns;
} else {
const auto state = this->frame_control.state();
result.produced_frame_count = state.enqueued_frame_count;
result.consumed_frame_count = state.rendered_frame_count;
result.failed_operation_count = state.empty_acquire_count;
result.pending_frame_count = state.pending_frame_count;
}
std::lock_guard lock(frame_mutex);
if (latest)
result.latest_sequence =
std::max(result.latest_sequence, latest->sequence);
return result;
}
[[nodiscard]] Scene_Base& scene() noexcept override { return *this; }
[[nodiscard]] const Scene_Base& scene() const noexcept override {
return *this;
}
void render_scene(const Scene_Render_Context& context) override {
const auto prepared = context.prepared<detail::Prepared_Point>(point_id);
if (!prepared)
throw std::logic_error("Point_Visual did not publish prepared data");
const auto& scene_state =
context.frame.scene_state<detail::Scene_State>();
auto frame = render_domain.invoke([this, &scene_state, &context,
&prepared] {
return backend->render(scene_state,
context.frame.scene_state_revision(),
*prepared, context.frame.render_sequence);
});
if (context.metrics && frame)
context.metrics->set(
Node_Metric_Kind::pixel_count,
static_cast<std::uint64_t>(frame->extent.width) *
frame->extent.height);
std::lock_guard lock(frame_mutex);
latest = std::move(frame);
}
std::shared_ptr<Point_Visual> visual;
Renderable_Id point_id{};
detail::Render_Domain render_domain;
std::unique_ptr<detail::Datoviz_Visual_Backend> backend;
mutable std::mutex frame_mutex;
std::shared_ptr<const Pixel_Frame> latest;
};
std::unique_ptr<Scene_Model> make_scene_model(
const Scene_Options& options, std::shared_ptr<Point_Visual> visual) {
switch (options.frame_mode) {
case Frame_Mode::Manual:
return std::make_unique<
Basic_Point_Scene<Manual_Frame, Frame_Mode::Manual>>(
options, std::move(visual));
case Frame_Mode::Low_Latency:
return std::make_unique<
Basic_Point_Scene<Low_Latency_Frame, Frame_Mode::Low_Latency>>(
options, std::move(visual));
case Frame_Mode::Playback:
return std::make_unique<
Basic_Point_Scene<Playback_Frame, Frame_Mode::Playback>>(
options, std::move(visual));
}
throw std::invalid_argument("unknown Point_Scene frame mode");
}
} // namespace
struct Point_Scene::Impl {
Impl(const Scene_Options& options, std::shared_ptr<Point_Visual> point_visual)
: visual(std::move(point_visual)),
states(detail::Scene_State{options.viewport, options.clear_color}),
scheduler(options.frame_mode, options.maximum_frames_per_second) {
if (!visual)
throw std::invalid_argument("Point_Scene requires a Point_Visual");
render_domain.invoke([&] {
backend = std::make_unique<detail::Datoviz_Visual_Backend>(
options.gpu_index, options.validation_enabled,
detail::Scene_State{options.viewport, options.clear_color},
options.visual_family);
});
}
explicit Impl(const Scene_Options& options,
std::shared_ptr<Point_Visual> visual)
: model(make_scene_model(options, std::move(visual))) {}
~Impl() {
accepting.store(false, std::memory_order_release);
render_domain.invoke([&] { backend.reset(); });
}
[[nodiscard]] bool render() {
if (!accepting.load(std::memory_order_acquire))
return false;
return render_domain.invoke([&] {
auto lease = scheduler.acquire_renderer();
if (!lease)
return false;
auto rendered = backend->render(
static_cast<const detail::Point_Frame_Data&>(*lease));
if (!rendered)
return false;
std::lock_guard lock(frame_mutex);
latest = std::move(rendered);
return true;
});
}
std::shared_ptr<Point_Visual> visual;
detail::Scene_State_Buffer states;
detail::Input_Collector input;
detail::Point_Frame_Strategy scheduler;
detail::Render_Domain render_domain;
std::unique_ptr<detail::Datoviz_Visual_Backend> backend;
mutable std::mutex frame_mutex;
std::shared_ptr<const Pixel_Frame> latest;
std::atomic<bool> accepting{true};
std::unique_ptr<Scene_Model> model;
};
Point_Scene::Point_Scene(Scene_Options options,
@@ -103,7 +314,8 @@ Point_Scene::Point_Scene(Scene_Options options,
validate(options.clear_color);
if (!std::isfinite(options.maximum_frames_per_second) ||
options.maximum_frames_per_second <= 0.0)
throw std::invalid_argument("Point_Scene frame frequency must be positive");
throw std::invalid_argument(
"Point_Scene frame frequency must be positive");
impl_ = std::make_unique<Impl>(options, std::move(visual));
}
@@ -111,36 +323,29 @@ Point_Scene::~Point_Scene() = default;
void Point_Scene::resize(Extent extent) {
validate(extent);
impl_->states.update([extent](detail::Scene_State& state) {
state.viewport = extent;
});
impl_->model->resize(extent);
}
void Point_Scene::set_clear_color(Clear_Color color) {
validate(color);
impl_->states.update([color](detail::Scene_State& state) {
state.clear_color = color;
});
impl_->model->set_clear_color(color);
}
void Point_Scene::dispatch(const ::renderive::Event&) {
// Non-positional Show/Hide/Leave events carry no Datoviz controller payload.
// Show, hide and leave carry no Datoviz controller payload.
}
void Point_Scene::dispatch_pointer(
::renderive::Event_Type type, float x, float y,
::renderive::Mouse_Button button, ::renderive::Mouse_Button_Mask buttons,
::renderive::Mouse_Button button, ::renderive::Mouse_Button_Mask,
::renderive::Keyboard_Modifier modifiers) {
if (!std::isfinite(x) || !std::isfinite(y))
return;
detail::Input_Command command;
command.type = pointer_type(type);
command.x = x;
command.y = y;
command.button = button;
command.buttons = buttons;
command.modifiers = modifiers;
impl_->input.push(command);
if (type != ::renderive::Event_Type::Pointer_Move &&
type != ::renderive::Event_Type::Pointer_Press &&
type != ::renderive::Event_Type::Pointer_Release)
throw std::invalid_argument("Point_Scene expected a pointer event");
impl_->model->dispatch_pointer(type, x, y, button, modifiers);
}
void Point_Scene::dispatch_wheel(
@@ -151,72 +356,41 @@ void Point_Scene::dispatch_wheel(
!std::isfinite(pixel_delta_x) || !std::isfinite(pixel_delta_y) ||
!std::isfinite(angle_delta_x) || !std::isfinite(angle_delta_y))
return;
detail::Input_Command command;
command.type = detail::Input_Command_Type::Wheel;
command.x = x;
command.y = y;
command.delta_x = datoviz_wheel_step(pixel_delta_x, angle_delta_x);
command.delta_y = datoviz_wheel_step(pixel_delta_y, angle_delta_y);
command.modifiers = modifiers;
impl_->input.push(command);
impl_->model->dispatch_wheel(
x, y, datoviz_wheel_step(pixel_delta_x, angle_delta_x),
datoviz_wheel_step(pixel_delta_y, angle_delta_y), modifiers);
}
void Point_Scene::dispatch(const ::renderive::Key_Event& event) {
detail::Input_Command command;
command.type = event.type == ::renderive::Event_Type::Key_Release
? detail::Input_Command_Type::Key_Release
: event.auto_repeat
? detail::Input_Command_Type::Key_Repeat
: detail::Input_Command_Type::Key_Press;
command.key = event.key;
command.native_key = event.native_key;
command.modifiers = event.modifiers;
impl_->input.push(command);
}
bool Point_Scene::prepare_frame() {
if (!impl_->accepting.load(std::memory_order_acquire))
return false;
auto lease = impl_->scheduler.acquire_painter();
if (!lease)
return false;
auto scene = impl_->states.publish_state();
lease->scene = std::move(scene.state);
lease->scene_revision = scene.revision;
lease->point = detail::publish_point(*impl_->visual);
lease->input = impl_->input.drain();
return true;
impl_->model->dispatch_key(event);
}
bool Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
bool Point_Scene::refresh_manual_frame() {
return impl_->accepting.load(std::memory_order_acquire) &&
impl_->scheduler.refresh();
return impl_->model->refresh_manual_frame();
}
bool Point_Scene::discard_pending_frame() {
return impl_->accepting.load(std::memory_order_acquire) &&
impl_->scheduler.discard_pending();
return impl_->model->discard_pending_frame();
}
bool Point_Scene::render_prepared_frame() { return impl_->render(); }
bool Point_Scene::request_frame() {
return prepare_frame() && impl_->scheduler.activate_for_request() &&
render_prepared_frame();
bool Point_Scene::render_prepared_frame() {
return impl_->model->render_prepared_frame();
}
bool Point_Scene::request_frame() { return impl_->model->request_frame(); }
std::shared_ptr<const Pixel_Frame> Point_Scene::latest_frame() const {
std::lock_guard lock(impl_->frame_mutex);
return impl_->latest;
return impl_->model->latest_frame();
}
Frame_Status Point_Scene::frame_status() const {
auto result = impl_->scheduler.status();
std::lock_guard lock(impl_->frame_mutex);
if (impl_->latest)
result.latest_sequence = std::max(result.latest_sequence,
impl_->latest->sequence);
return result;
return impl_->model->frame_status();
}
Scene_Base& Point_Scene::render_scene() noexcept {
return impl_->model->scene();
}
const Scene_Base& Point_Scene::render_scene() const noexcept {
return impl_->model->scene();
}
} // namespace renderive::render_3d
+4
View File
@@ -9,6 +9,8 @@
#include <memory>
#include <vector>
class Scene_Base;
namespace renderive::render_3d {
struct Extent {
@@ -121,6 +123,8 @@ public:
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame() const;
[[nodiscard]] Frame_Status frame_status() const;
[[nodiscard]] Scene_Base& render_scene() noexcept;
[[nodiscard]] const Scene_Base& render_scene() const noexcept;
private:
void dispatch_pointer(::renderive::Event_Type type, float x, float y,
+142 -16
View File
@@ -4,8 +4,13 @@
#include "renderable/Renderable_p.h"
#include <renderive/real_time_data/Double_Buffer_Strategy.hpp>
#include <renderive/real_time_data/Frame_Strategy_Observer.hpp>
#include <renderive/scene/base/Abstract_Frame.hpp>
#include <datoviz/scene/enums.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <mutex>
#include <stdexcept>
#include <utility>
@@ -28,8 +33,33 @@ struct Point_Payload_Tag {};
using Point_Payload = std::shared_ptr<const std::vector<Point>>;
using Point_Buffer_Layout = ::Double_Buffer_Layout<
::Buffered_Data<Point_Payload_Tag, Point_Payload>>;
struct Point_Buffer_Observer {
static constexpr bool enabled = true;
template <class Target>
decltype(auto) bind(Target& target) {
return observer_.bind(target);
}
template <class Observation>
void observe(const Observation& observation) noexcept {
if (observation.event != Observation::Event::cache_updated)
return;
observer_.observe({
Real_Time_Data_Observation_Event::updated,
{this, Real_Time_Data_Retention::latest,
observation.cache_update_count, observation.time_ns,
observation.cache_update_count, 1}});
}
private:
Frame_Strategy_Real_Time_Data_Observer observer_;
};
using Point_Buffer_Strategy =
::Multi_Double_Buffer_Strategy<Point_Buffer_Layout, std::mutex>;
::Multi_Double_Buffer_Strategy<
Point_Buffer_Layout, std::mutex, Observer_State<Point_Buffer_Observer>>;
struct Point_Data_Observation {
std::uint64_t revision{};
@@ -54,6 +84,8 @@ struct Point_Visual::Impl : next_Impl<Impl>, Point_Buffer_Strategy {
struct Observer : next_Observer {
static void handle(Impl& impl,
const Renderable_Event_View& observation) noexcept {
if (observation.event == Renderable_Observer_Event::Published)
impl.Point_Buffer_Strategy::publish();
if (const auto* data =
observation.payload_if<Point_Data_Observation>()) {
impl.observe_data(data->revision, data->point_count);
@@ -65,6 +97,8 @@ struct Point_Visual::Impl : next_Impl<Impl>, Point_Buffer_Strategy {
update_points(std::move(points));
}
std::shared_ptr<void> real_time_data_binding;
void update_points(std::vector<Point> points) {
validate_points(points);
const std::size_t count = points.size();
@@ -92,11 +126,118 @@ struct Point_Visual::Impl : next_Impl<Impl>, Point_Buffer_Strategy {
return cached ? cached->size() : 0U;
}
[[nodiscard]] std::uint64_t dependent_state_revision() const noexcept {
return revision();
}
void capture_frame_data(Frame_Render_Snapshot& snapshot) const override {
const auto& control = static_cast<const Point_Visual&>(owner());
auto input = std::make_shared<Captured_Point>();
input->state = control.render_state<State>();
input->points = points();
input->state_revision = control.state_revision<State>();
input->data_revision = buffer_revision<Point_Payload_Tag>();
snapshot.capture<Captured_Point>(renderable_id, std::move(input));
}
void prepare(const Prepare_Render_Context& context) override {
const auto& scene = context.frame.scene_state<Scene_State>();
const auto input = context.captured<Captured_Point>();
if (!input)
throw std::logic_error("Point_Visual frame input was not captured");
auto output = std::make_shared<Prepared_Point>();
output->state = input->state;
output->family = scene.visual_family;
output->state_revision = input->state_revision;
output->data_revision = input->data_revision;
const auto& source = input->points;
const std::size_t count = source ? source->size() : 0U;
if (count > std::numeric_limits<std::uint32_t>::max())
throw std::length_error("Datoviz point payload is too large");
output->positions.reserve(count);
output->colors.reserve(count);
output->sizes.reserve(count);
if (source) {
for (const auto& point : *source) {
output->positions.push_back(
{point.position.x, point.position.y, point.position.z});
output->colors.push_back(
{point.color.red, point.color.green, point.color.blue,
point.color.alpha});
output->sizes.push_back(point.diameter_px);
}
}
switch (output->family) {
case Visual_Family::Splat:
output->sigma.resize(count);
output->angles.assign(count, 0.0F);
for (std::size_t index = 0; index < count; ++index)
output->sigma[index] = {output->sizes[index] * 0.35F,
output->sizes[index] * 0.18F};
break;
case Visual_Family::Marker:
output->angles.assign(count, 0.0F);
output->shapes.assign(count, DVZ_MARKER_SHAPE_DIAMOND);
break;
case Visual_Family::Sphere:
for (float& size : output->sizes)
size *= 0.0025F;
break;
case Visual_Family::Segment:
output->secondary_positions = output->positions;
for (auto& end : output->secondary_positions)
end[1] += 0.28F;
output->sizes.assign(count, 4.0F);
break;
case Visual_Family::Vector:
output->secondary_positions.assign(
count, {0.18F, 0.28F, 0.0F});
output->sizes.assign(count, 3.0F);
break;
case Visual_Family::Primitive:
case Visual_Family::Mesh:
if (count >= 3) {
output->positions[0] = {-0.62F, -0.42F, 0.0F};
output->positions[1] = {0.62F, -0.42F, 0.0F};
output->positions[2] = {0.0F, 0.58F, 0.0F};
}
output->normals.assign(count, {0.0F, 0.0F, 1.0F});
break;
case Visual_Family::Path:
output->sizes.assign(count, 4.0F);
break;
case Visual_Family::Image:
output->extents.assign(count, {0.38F, 0.38F});
break;
case Visual_Family::Labels:
output->positions.assign(1, {0.0F, 0.0F, 0.0F});
output->extents.assign(1, {1.44F, 1.44F});
break;
case Visual_Family::Point:
case Visual_Family::Pixel:
case Visual_Family::Glyph:
case Visual_Family::Text:
case Visual_Family::Volume:
break;
}
if (context.metrics) {
context.metrics->set(Node_Metric_Kind::input_count, count);
context.metrics->set(Node_Metric_Kind::primitive_count,
output->positions.size());
}
context.publish<Prepared_Point>(std::move(output));
}
};
Point_Visual::Point_Visual(const State& state, std::vector<Point> points)
: Renderable(With_Attached_Impl<Impl>{}, state, std::move(points)) {
State_Validator{}(state);
d_func<Impl>().real_time_data_binding =
d_func<Impl>().Point_Buffer_Strategy::bind(*this);
}
Point_Visual::~Point_Visual() = default;
@@ -120,19 +261,4 @@ std::uint64_t Point_Visual::data_revision() const {
return d_func<Impl>().cache_revision<Point_Payload_Tag>();
}
Published_Point Point_Visual::publish_frame() {
publish_state<State>();
d_func<Impl>().Point_Buffer_Strategy::publish();
return {
render_state<State>(),
d_func<Impl>().points(),
state_revision<State>(),
d_func<Impl>().Point_Buffer_Strategy::revision(),
};
}
Published_Point publish_point(Point_Visual& visual) {
return visual.publish_frame();
}
} // namespace renderive::render_3d::detail
-7
View File
@@ -57,8 +57,6 @@ struct Point_Style {
};
namespace detail {
struct Published_Point;
struct Point_Visual : Renderable<Point_Visual> {
struct State : next_State<State> {
Point_Style style;
@@ -104,12 +102,7 @@ protected:
private:
struct Impl;
[[nodiscard]] Published_Point publish_frame();
friend Published_Point publish_point(Point_Visual& visual);
};
[[nodiscard]] Published_Point publish_point(Point_Visual& visual);
} // namespace detail
using Point_Visual = ::renderive::renderable::attach<detail::Point_Visual>;
@@ -401,8 +401,8 @@ private:
Datoviz_Visual_Backend::Datoviz_Visual_Backend(
std::uint32_t gpu_index, bool validation_enabled,
const Scene_State& initial_scene, Visual_Family family)
: domain_thread_(std::this_thread::get_id()), family_(family) {
const Scene_State& initial_scene)
: domain_thread_(std::this_thread::get_id()) {
try {
DvzGpuCtxConfig configuration = dvz_gpu_ctx_config();
dvz_gpu_ctx_config_validation(&configuration, validation_enabled);
@@ -431,6 +431,7 @@ void Datoviz_Visual_Backend::require_domain() const {
}
void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
const Visual_Family family = initial_scene.visual_family;
scene_ = dvz_scene();
if (scene_ == nullptr)
throw std::runtime_error("failed to create Datoviz scene");
@@ -441,7 +442,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
initial_scene.viewport.height, 0);
panel_ = figure_ != nullptr ? dvz_panel_full(figure_) : nullptr;
if (panel_ != nullptr) {
switch (family_) {
switch (family) {
case Visual_Family::Point:
visual_ = dvz_point(scene_, 0);
break;
@@ -492,20 +493,20 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
dvz_visual_set_alpha_mode(visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK)
throw std::runtime_error("failed to configure Datoviz visual alpha mode");
if (visual_ != nullptr && family_ == Visual_Family::Glyph)
if (visual_ != nullptr && family == Visual_Family::Glyph)
configure_glyph_text(scene_, visual_, "GLYPH");
if (visual_ != nullptr && family_ == Visual_Family::Text)
if (visual_ != nullptr && family == Visual_Family::Text)
configure_glyph_text(scene_, visual_, "TEXT");
if (visual_ != nullptr &&
(family_ == Visual_Family::Image || family_ == Visual_Family::Glyph ||
family_ == Visual_Family::Text)) {
(family == Visual_Family::Image || family == Visual_Family::Glyph ||
family == Visual_Family::Text)) {
constexpr std::uint32_t width = 32;
constexpr std::uint32_t height = 32;
std::vector<std::array<std::uint8_t, 4>> pixels(width * height);
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const bool stroke = family_ == Visual_Family::Text
const bool stroke = family == Visual_Family::Text
? (y < 6 || (x >= 13 && x <= 18))
: ((x / 8 + y / 8) % 2 == 0);
pixels[y * width + x] = stroke
@@ -531,7 +532,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
throw std::runtime_error("failed to create Datoviz 2D sampled field");
}
if (visual_ != nullptr && family_ == Visual_Family::Labels) {
if (visual_ != nullptr && family == Visual_Family::Labels) {
constexpr std::uint32_t width = 8;
constexpr std::uint32_t height = 8;
std::array<std::int32_t, width * height> labels{};
@@ -569,7 +570,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
throw std::runtime_error("failed to create Datoviz label field");
}
if (visual_ != nullptr && family_ == Visual_Family::Volume) {
if (visual_ != nullptr && family == Visual_Family::Volume) {
constexpr std::uint32_t side = 16;
std::vector<std::uint8_t> voxels(side * side * side);
for (std::uint32_t z = 0; z < side; ++z) {
@@ -637,28 +638,28 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
dvz_input_emit_resize(input_router_, &resize);
}
void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) {
void Datoviz_Visual_Backend::apply(
const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point) {
require_domain();
const auto& points = *frame.point.data;
if (frame.scene_revision != applied_scene_revision_) {
if (dvz_figure_resize(figure_, frame.scene.viewport.width,
frame.scene.viewport.height) != DVZ_OK)
if (scene_revision != applied_scene_revision_) {
if (dvz_figure_resize(figure_, scene.viewport.width,
scene.viewport.height) != DVZ_OK)
throw std::runtime_error("failed to resize Datoviz point figure");
DvzInputResizeEvent resize{
frame.scene.viewport.width, frame.scene.viewport.height,
frame.scene.viewport.width, frame.scene.viewport.height, 1.0F, 1.0F};
scene.viewport.width, scene.viewport.height,
scene.viewport.width, scene.viewport.height, 1.0F, 1.0F};
dvz_input_emit_resize(input_router_, &resize);
applied_scene_revision_ = frame.scene_revision;
applied_scene_revision_ = scene_revision;
}
if (frame.point.state_revision != applied_state_revision_) {
const auto& state = frame.point.state;
if (point.state_revision != applied_state_revision_) {
const auto& state = point.state;
mat4 transform{};
for (std::size_t row = 0; row < 4; ++row) {
for (std::size_t row = 0; row < 4; ++row)
for (std::size_t column = 0; column < 4; ++column)
transform[row][column] = state.transform.values[row * 4 + column];
}
if (family_ == Visual_Family::Point) {
if (point.family == Visual_Family::Point) {
DvzPointStyleDesc style = dvz_point_style_desc();
style.edge_color.r = state.style.edge_color.red;
style.edge_color.g = state.style.edge_color.green;
@@ -671,211 +672,176 @@ void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) {
}
if (dvz_visual_set_transform(visual_, transform) != DVZ_OK ||
dvz_visual_set_depth_test(visual_, state.depth_test) != DVZ_OK ||
dvz_visual_set_visible(visual_, state.visible && !points.empty()) != DVZ_OK)
dvz_visual_set_visible(
visual_, state.visible && !point.positions.empty()) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz visual state");
applied_state_revision_ = frame.point.state_revision;
applied_state_revision_ = point.state_revision;
}
if (frame.point.data_revision != applied_data_revision_) {
if (points.empty()) {
if (dvz_visual_set_visible(visual_, false) != DVZ_OK)
throw std::runtime_error("failed to hide empty Datoviz point visual");
} else {
std::vector<std::array<float, 3>> positions;
std::vector<std::array<std::uint8_t, 4>> colors;
std::vector<float> sizes;
positions.reserve(points.size());
colors.reserve(points.size());
sizes.reserve(points.size());
for (const auto& point : points) {
positions.push_back(
{point.position.x, point.position.y, point.position.z});
colors.push_back({point.color.red, point.color.green,
point.color.blue, point.color.alpha});
sizes.push_back(point.diameter_px);
}
if (positions.size() > std::numeric_limits<std::uint32_t>::max())
throw std::length_error("Datoviz point payload is too large");
const auto count = static_cast<std::uint32_t>(positions.size());
DvzResult upload_result = DVZ_OK;
switch (family_) {
case Visual_Family::Point: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", positions.data(), count},
{"color", colors.data(), count},
{"diameter_px", sizes.data(), count},
}};
upload_result = dvz_visual_set_data_many(
visual_, updates.data(), static_cast<std::uint32_t>(updates.size()));
break;
}
case Visual_Family::Splat: {
std::vector<std::array<float, 2>> sigma(count);
std::vector<float> angles(count, 0.0F);
for (std::size_t index = 0; index < count; ++index)
sigma[index] = {sizes[index] * 0.35F, sizes[index] * 0.18F};
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"sigma", sigma.data(), count}, {"angle", angles.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Pixel: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"pixel_size_px", sizes.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Marker: {
std::vector<float> angles(count, 0.0F);
std::vector<std::uint32_t> shapes(count, DVZ_MARKER_SHAPE_DIAMOND);
const std::array<DvzVisualDataUpdate, 5> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"diameter_px", sizes.data(), count}, {"angle", angles.data(), count},
{"shape", shapes.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 5);
break;
}
case Visual_Family::Sphere: {
std::vector<float> radii(count);
for (std::size_t index = 0; index < count; ++index)
radii[index] = sizes[index] * 0.0025F;
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"radius", radii.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Segment: {
auto ends = positions;
std::vector<float> widths(count, 4.0F);
for (auto& end : ends)
end[1] += 0.28F;
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position_start", positions.data(), count},
{"position_end", ends.data(), count}, {"color", colors.data(), count},
{"stroke_width_px", widths.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Vector: {
std::vector<std::array<float, 3>> vectors(count, {0.18F, 0.28F, 0.0F});
std::vector<float> widths(count, 3.0F);
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position", positions.data(), count}, {"vector", vectors.data(), count},
{"color", colors.data(), count}, {"stroke_width_px", widths.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Primitive:
case Visual_Family::Mesh: {
if (positions.size() >= 3) {
positions[0] = {-0.62F, -0.42F, 0.0F};
positions[1] = {0.62F, -0.42F, 0.0F};
positions[2] = {0.0F, 0.58F, 0.0F};
}
std::vector<std::array<float, 3>> normals(count, {0.0F, 0.0F, 1.0F});
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"normal", normals.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Path: {
std::vector<float> widths(count, 4.0F);
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", positions.data(), count}, {"color", colors.data(), count},
{"stroke_width_px", widths.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Image: {
std::vector<std::array<float, 2>> extents(count, {0.38F, 0.38F});
const std::array<DvzVisualDataUpdate, 2> updates{{
{"position", positions.data(), count}, {"extent", extents.data(), count}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 2);
break;
}
case Visual_Family::Labels: {
const std::array<std::array<float, 3>, 1> anchors{{
{0.0F, 0.0F, 0.0F}}};
const std::array<std::array<float, 2>, 1> extents{{
{1.44F, 1.44F}}};
const std::array<DvzVisualDataUpdate, 2> updates{{
{"position", anchors.data(), 1}, {"extent", extents.data(), 1}}};
upload_result = dvz_visual_set_data_many(visual_, updates.data(), 2);
break;
}
case Visual_Family::Glyph:
case Visual_Family::Text:
upload_result = DVZ_OK;
break;
case Visual_Family::Volume:
upload_result = DVZ_OK;
break;
}
if (upload_result != DVZ_OK ||
dvz_visual_set_visible(visual_, frame.point.state.visible) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz visual payload");
}
applied_data_revision_ = frame.point.data_revision;
if (point.data_revision == applied_data_revision_)
return;
if (point.positions.empty()) {
if (dvz_visual_set_visible(visual_, false) != DVZ_OK)
throw std::runtime_error("failed to hide empty Datoviz point visual");
applied_data_revision_ = point.data_revision;
return;
}
const auto count = static_cast<std::uint32_t>(point.positions.size());
DvzResult result = DVZ_OK;
switch (point.family) {
case Visual_Family::Point: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"diameter_px", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Splat: {
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"sigma", point.sigma.data(), count},
{"angle", point.angles.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Pixel: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"pixel_size_px", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Marker: {
const std::array<DvzVisualDataUpdate, 5> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"diameter_px", point.sizes.data(), count},
{"angle", point.angles.data(), count},
{"shape", point.shapes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 5);
break;
}
case Visual_Family::Sphere: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"radius", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Segment: {
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position_start", point.positions.data(), count},
{"position_end", point.secondary_positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Vector: {
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position", point.positions.data(), count},
{"vector", point.secondary_positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::Primitive:
case Visual_Family::Mesh: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"normal", point.normals.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Path: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.sizes.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::Image:
case Visual_Family::Labels: {
const std::array<DvzVisualDataUpdate, 2> updates{{
{"position", point.positions.data(), count},
{"extent", point.extents.data(), count}}};
result = dvz_visual_set_data_many(visual_, updates.data(), 2);
break;
}
case Visual_Family::Glyph:
case Visual_Family::Text:
case Visual_Family::Volume:
break;
}
if (result != DVZ_OK ||
dvz_visual_set_visible(visual_, point.state.visible) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz visual payload");
applied_data_revision_ = point.data_revision;
}
void Datoviz_Visual_Backend::dispatch(const Input_Command& command, Extent viewport) {
void Datoviz_Visual_Backend::dispatch_pointer(
::renderive::Event_Type event, float x, float y,
::renderive::Mouse_Button mouse_button,
::renderive::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
const float width = static_cast<float>(viewport.width);
const float height = static_cast<float>(viewport.height);
switch (command.type) {
case Input_Command_Type::Pointer_Move:
case Input_Command_Type::Pointer_Press:
case Input_Command_Type::Pointer_Release: {
const DvzPointerEventType type = command.type == Input_Command_Type::Pointer_Move
? DVZ_POINTER_EVENT_MOVE
: command.type == Input_Command_Type::Pointer_Press
? DVZ_POINTER_EVENT_PRESS
: DVZ_POINTER_EVENT_RELEASE;
dvz_pointer_emit_position(input_router_, type, command.x, command.y,
width, height, button(command.button),
modifiers(command.modifiers), 1.0F,
dvz_input_timestamp_ns(), nullptr);
break;
}
case Input_Command_Type::Wheel:
dvz_pointer_emit_wheel(input_router_, command.x, command.y, width, height,
command.delta_x, command.delta_y,
modifiers(command.modifiers), 1.0F,
dvz_input_timestamp_ns(), nullptr);
break;
case Input_Command_Type::Key_Press:
case Input_Command_Type::Key_Repeat:
case Input_Command_Type::Key_Release: {
const DvzKeyboardEventType type = command.type == Input_Command_Type::Key_Press
? DVZ_KEYBOARD_EVENT_PRESS
: command.type == Input_Command_Type::Key_Repeat
? DVZ_KEYBOARD_EVENT_REPEAT
: DVZ_KEYBOARD_EVENT_RELEASE;
dvz_keyboard_emit(input_router_, type,
key_code(command.key, command.native_key),
modifiers(command.modifiers), nullptr);
break;
}
}
const DvzPointerEventType type =
event == ::renderive::Event_Type::Pointer_Move
? DVZ_POINTER_EVENT_MOVE
: event == ::renderive::Event_Type::Pointer_Press
? DVZ_POINTER_EVENT_PRESS
: DVZ_POINTER_EVENT_RELEASE;
dvz_pointer_emit_position(input_router_, type, x, y, width, height,
button(mouse_button),
modifiers(keyboard_modifiers), 1.0F,
dvz_input_timestamp_ns(), nullptr);
}
DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(const Point_Frame_Data& frame) {
void Datoviz_Visual_Backend::dispatch_wheel(
float x, float y, float delta_x, float delta_y,
::renderive::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
dvz_pointer_emit_wheel(
input_router_, x, y, static_cast<float>(viewport.width),
static_cast<float>(viewport.height), delta_x, delta_y,
modifiers(keyboard_modifiers), 1.0F, dvz_input_timestamp_ns(), nullptr);
}
void Datoviz_Visual_Backend::dispatch_key(
const ::renderive::Key_Event& event) {
require_domain();
const DvzKeyboardEventType type =
event.type == ::renderive::Event_Type::Key_Release
? DVZ_KEYBOARD_EVENT_RELEASE
: event.auto_repeat ? DVZ_KEYBOARD_EVENT_REPEAT
: DVZ_KEYBOARD_EVENT_PRESS;
dvz_keyboard_emit(input_router_, type,
key_code(event.key, event.native_key),
modifiers(event.modifiers), nullptr);
}
DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(
const Scene_State& scene) {
DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config();
configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL;
configuration.external_color_target = true;
configuration.color_target_id = color_target_id;
configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM;
configuration.target_width = frame.scene.viewport.width;
configuration.target_height = frame.scene.viewport.height;
configuration.clear_color[0] = frame.scene.clear_color.red;
configuration.clear_color[1] = frame.scene.clear_color.green;
configuration.clear_color[2] = frame.scene.clear_color.blue;
configuration.clear_color[3] = frame.scene.clear_color.alpha;
configuration.target_width = scene.viewport.width;
configuration.target_height = scene.viewport.height;
configuration.clear_color[0] = scene.clear_color.red;
configuration.clear_color[1] = scene.clear_color.green;
configuration.clear_color[2] = scene.clear_color.blue;
configuration.clear_color[3] = scene.clear_color.alpha;
const auto capabilities = dvz_capability_snapshot();
DvzDiagnosticReport report{};
dvz_diagnostic_report_init(&report);
@@ -894,17 +860,16 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(const Point_Frame_Data& fram
}
std::shared_ptr<const Pixel_Frame> Datoviz_Visual_Backend::render(
const Point_Frame_Data& frame) {
const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point, std::uint64_t frame_sequence) {
require_domain();
if (frame.scene.viewport.empty())
if (scene.viewport.empty())
return {};
apply(frame);
for (const auto& command : frame.input)
dispatch(command, frame.scene.viewport);
apply(scene, scene_revision, point);
if (target_ == nullptr || target_extent_ != frame.scene.viewport) {
if (target_ == nullptr || target_extent_ != scene.viewport) {
target_.reset();
target_extent_ = frame.scene.viewport;
target_extent_ = scene.viewport;
target_ = std::make_unique<Frame_Target>(gpu_context_, target_extent_,
++target_generation_);
}
@@ -912,7 +877,7 @@ std::shared_ptr<const Pixel_Frame> Datoviz_Visual_Backend::render(
target_->begin();
DvzSceneFrameArtifact* artifact{};
try {
artifact = emit(frame);
artifact = emit(scene);
} catch (...) {
target_->abort();
throw;
@@ -939,8 +904,8 @@ std::shared_ptr<const Pixel_Frame> Datoviz_Visual_Backend::render(
}
auto output = std::make_shared<Pixel_Frame>();
output->extent = frame.scene.viewport;
output->sequence = frame.frame_sequence;
output->extent = scene.viewport;
output->sequence = frame_sequence;
output->rgba8 = target_->finish();
return output;
}
@@ -20,23 +20,33 @@ namespace renderive::render_3d::detail {
class Datoviz_Visual_Backend final {
public:
Datoviz_Visual_Backend(std::uint32_t gpu_index, bool validation_enabled,
const Scene_State& initial_scene,
Visual_Family family = Visual_Family::Point);
const Scene_State& initial_scene);
~Datoviz_Visual_Backend();
Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete;
Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete;
[[nodiscard]] std::shared_ptr<const Pixel_Frame> render(const Point_Frame_Data& frame);
[[nodiscard]] std::shared_ptr<const Pixel_Frame> render(
const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point, std::uint64_t frame_sequence);
void dispatch_pointer(::renderive::Event_Type type, float x, float y,
::renderive::Mouse_Button button,
::renderive::Keyboard_Modifier modifiers,
Extent viewport);
void dispatch_wheel(float x, float y, float delta_x, float delta_y,
::renderive::Keyboard_Modifier modifiers,
Extent viewport);
void dispatch_key(const ::renderive::Key_Event& event);
private:
class Frame_Target;
void require_domain() const;
void create_scene(const Scene_State& initial_scene);
void apply(const Point_Frame_Data& frame);
void dispatch(const Input_Command& command, Extent viewport);
[[nodiscard]] DvzSceneFrameArtifact* emit(const Point_Frame_Data& frame);
void apply(const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point);
[[nodiscard]] DvzSceneFrameArtifact* emit(const Scene_State& scene);
void destroy() noexcept;
std::thread::id domain_thread_;
@@ -54,7 +64,6 @@ private:
std::uint64_t applied_scene_revision_{};
std::uint64_t applied_state_revision_{};
std::uint64_t applied_data_revision_{};
Visual_Family family_{Visual_Family::Point};
};
} // namespace renderive::render_3d::detail
+28 -266
View File
@@ -2,283 +2,45 @@
#include "render_3D/Point_Scene.h"
#include <renderive/frame_control/concept/Frame_Control_Strategy.hpp>
#include <renderive/state/Double_State_Storage.hpp>
#include <array>
#include <cstdint>
#include <concepts>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <utility>
#include <vector>
namespace renderive::render_3d::detail {
struct Published_Point {
struct Scene_State {
Extent viewport{560, 320};
Clear_Color clear_color;
Visual_Family visual_family{Visual_Family::Point};
bool operator==(const Scene_State&) const = default;
};
// Immutable input captured at the frame publication boundary. Playback frames
// retain this shared payload instead of consulting a later buffer role.
struct Captured_Point {
Point_State state;
std::shared_ptr<const std::vector<Point>> data;
std::shared_ptr<const std::vector<Point>> points;
std::uint64_t state_revision{};
std::uint64_t data_revision{};
};
struct Scene_State {
Extent viewport{560, 320};
Clear_Color clear_color;
bool operator==(const Scene_State&) const = default;
// Immutable, frame-local CPU output. Taskflow workers create this data; only
// the Datoviz render-domain task consumes it.
struct Prepared_Point {
Point_State state;
Visual_Family family{Visual_Family::Point};
std::uint64_t state_revision{};
std::uint64_t data_revision{};
std::vector<std::array<float, 3>> positions;
std::vector<std::array<std::uint8_t, 4>> colors;
std::vector<float> sizes;
std::vector<std::array<float, 2>> sigma;
std::vector<float> angles;
std::vector<std::uint32_t> shapes;
std::vector<std::array<float, 3>> secondary_positions;
std::vector<std::array<float, 3>> normals;
std::vector<std::array<float, 2>> extents;
};
class Scene_State_Buffer final
: public ::Double_State_Storage<Scene_State, std::mutex> {
using Base = ::Double_State_Storage<Scene_State, std::mutex>;
public:
struct Published {
Scene_State state;
std::uint64_t revision{};
};
explicit Scene_State_Buffer(const Scene_State& initial) : Base(initial) {}
[[nodiscard]] Published publish_state() {
std::lock_guard lock(publication_mutex_);
Base::publish();
return {Base::render_state_value(), Base::state_revision()};
}
private:
std::mutex publication_mutex_;
};
enum class Input_Command_Type : std::uint8_t {
Pointer_Move,
Pointer_Press,
Pointer_Release,
Wheel,
Key_Press,
Key_Repeat,
Key_Release,
};
struct Input_Command {
Input_Command_Type type{};
float x{};
float y{};
float delta_x{};
float delta_y{};
::renderive::Mouse_Button button{::renderive::Mouse_Button::None};
::renderive::Mouse_Button_Mask buttons{};
::renderive::Keyboard_Modifier modifiers{::renderive::Keyboard_Modifier::None};
::renderive::Key key{::renderive::Key::Unknown};
std::uint32_t native_key{};
};
class Input_Collector final {
public:
void push(Input_Command command) {
std::lock_guard lock(mutex_);
commands_.push_back(command);
}
[[nodiscard]] std::vector<Input_Command> drain() {
std::lock_guard lock(mutex_);
std::vector<Input_Command> result;
result.reserve(commands_.size());
while (!commands_.empty()) {
result.push_back(commands_.front());
commands_.pop_front();
}
return result;
}
private:
std::mutex mutex_;
std::deque<Input_Command> commands_;
};
struct Point_Frame_Data {
Scene_State scene;
Published_Point point;
std::vector<Input_Command> input;
std::uint64_t scene_revision{};
std::uint64_t frame_sequence{};
};
class Point_Frame_Strategy final : public ::Frame_Control_Strategy_Base {
public:
struct Frame final : Point_Frame_Data {};
class Painter_Lease final {
public:
explicit Painter_Lease(Point_Frame_Strategy& strategy)
: strategy_(&strategy), frame_(strategy.make_frame()) {}
Painter_Lease(const Painter_Lease&) = delete;
Painter_Lease& operator=(const Painter_Lease&) = delete;
Painter_Lease(Painter_Lease&& other) noexcept
: strategy_(std::exchange(other.strategy_, nullptr)),
frame_(std::move(other.frame_)) {}
~Painter_Lease() {
if (strategy_ && frame_)
strategy_->submit(std::move(frame_));
}
explicit operator bool() const noexcept { return frame_ != nullptr; }
Frame* get() noexcept { return frame_.get(); }
const Frame* get() const noexcept { return frame_.get(); }
Frame* operator->() noexcept { return get(); }
const Frame* operator->() const noexcept { return get(); }
Frame& operator*() noexcept { return *frame_; }
const Frame& operator*() const noexcept { return *frame_; }
private:
Point_Frame_Strategy* strategy_{};
std::unique_ptr<Frame> frame_;
};
class Render_Lease final {
public:
explicit Render_Lease(Point_Frame_Strategy& strategy)
: strategy_(&strategy), frame_(strategy.take_ready()) {}
Render_Lease(const Render_Lease&) = delete;
Render_Lease& operator=(const Render_Lease&) = delete;
Render_Lease(Render_Lease&& other) noexcept
: strategy_(std::exchange(other.strategy_, nullptr)),
frame_(std::move(other.frame_)) {}
~Render_Lease() {
if (strategy_ && frame_)
strategy_->complete(frame_->frame_sequence);
}
explicit operator bool() const noexcept { return frame_ != nullptr; }
Frame* get() noexcept { return frame_.get(); }
const Frame* get() const noexcept { return frame_.get(); }
Frame* operator->() noexcept { return get(); }
const Frame* operator->() const noexcept { return get(); }
Frame& operator*() noexcept { return *frame_; }
const Frame& operator*() const noexcept { return *frame_; }
private:
Point_Frame_Strategy* strategy_{};
std::unique_ptr<Frame> frame_;
};
Point_Frame_Strategy(Frame_Mode mode, double maximum_frames_per_second)
: Frame_Control_Strategy_Base(
mode == Frame_Mode::Low_Latency
? maximum_frames_per_second
: invalid_frequency_hz(),
mode == Frame_Mode::Low_Latency
? frequency_interval_ns(maximum_frames_per_second)
: 0),
mode_(mode) {}
[[nodiscard]] Painter_Lease acquire_painter() { return Painter_Lease(*this); }
[[nodiscard]] Render_Lease acquire_renderer() { return Render_Lease(*this); }
void swap() override { swap_frame_control_state(); }
[[nodiscard]] double frequency_hz() const noexcept override {
return render_frame_control_state().frequency_hz;
}
[[nodiscard]] std::uint64_t next_refresh_interval_ns() const noexcept override {
return render_frame_control_state().next_refresh_interval_ns;
}
[[nodiscard]] Frame_Control_Strategy_Base::State frame_control_state() const override {
return render_frame_control_state();
}
void on_real_time_data_update(const Real_Time_Data_Observation&) noexcept override {}
[[nodiscard]] bool refresh() {
std::lock_guard lock(mutex_);
if (mode_ != Frame_Mode::Manual || !manual_pending_) {
++status_.failed_operation_count;
return false;
}
status_.dropped_frame_count += ready_.size();
ready_.clear();
ready_.push_back(std::move(manual_pending_));
return true;
}
[[nodiscard]] bool activate_for_request() {
return mode_ != Frame_Mode::Manual || refresh();
}
[[nodiscard]] bool discard_pending() {
std::lock_guard lock(mutex_);
if (mode_ == Frame_Mode::Manual && manual_pending_) {
manual_pending_.reset();
++status_.dropped_frame_count;
return true;
}
if (mode_ == Frame_Mode::Low_Latency && !ready_.empty()) {
status_.dropped_frame_count += ready_.size();
ready_.clear();
return true;
}
return false;
}
[[nodiscard]] Frame_Status status() const {
std::lock_guard lock(mutex_);
auto result = status_;
const auto control = render_frame_control_state();
result.mode = mode_;
result.frequency_hz = control.frequency_hz;
result.next_refresh_interval_ns = control.next_refresh_interval_ns;
result.pending_frame_count = ready_.size() +
(manual_pending_ ? 1U : 0U);
return result;
}
private:
[[nodiscard]] std::unique_ptr<Frame> make_frame() {
auto frame = std::make_unique<Frame>();
std::lock_guard lock(mutex_);
frame->frame_sequence = ++next_sequence_;
return frame;
}
void submit(std::unique_ptr<Frame> frame) {
std::lock_guard lock(mutex_);
++status_.produced_frame_count;
if (mode_ == Frame_Mode::Manual) {
if (manual_pending_)
++status_.dropped_frame_count;
manual_pending_ = std::move(frame);
return;
}
if (mode_ == Frame_Mode::Low_Latency) {
status_.dropped_frame_count += ready_.size();
ready_.clear();
}
ready_.push_back(std::move(frame));
}
[[nodiscard]] std::unique_ptr<Frame> take_ready() {
std::lock_guard lock(mutex_);
if (ready_.empty()) {
++status_.failed_operation_count;
return {};
}
auto frame = std::move(ready_.front());
ready_.pop_front();
return frame;
}
void complete(std::uint64_t sequence) {
std::lock_guard lock(mutex_);
++status_.consumed_frame_count;
status_.latest_sequence = sequence;
}
Frame_Mode mode_;
mutable std::mutex mutex_;
std::uint64_t next_sequence_{};
std::unique_ptr<Frame> manual_pending_;
std::deque<std::unique_ptr<Frame>> ready_;
Frame_Status status_;
};
static_assert(::Frame_Control_Strategy<Point_Frame_Strategy>);
} // namespace renderive::render_3d::detail
+88 -84
View File
@@ -1,10 +1,14 @@
#include "render_3D/Point_Visual.h"
#include "render_3D/detail/Point_Core.h"
#include <renderive/frame_control/Frame_Control.hpp>
#include <renderive/scene/Scene.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <memory>
#include <thread>
#include <vector>
@@ -12,42 +16,73 @@
namespace renderive::render_3d {
namespace {
using Test_Frame_Control = Low_Latency_Strategy<Scene3D_Frame_Data>;
struct Test_Scene final
: Scene3D_Context<Test_Frame_Control, detail::Scene_State>,
Scene_Renderer {
explicit Test_Scene(const std::shared_ptr<Point_Visual>& visual) {
Scene_State_Strategy::set<&detail::Scene_State::viewport>(
Extent{320, 180});
point_id = visual->renderable_id();
auto builder = attach_builder();
builder.attach(renderive_Owner<Point_Visual>(visual));
}
~Test_Scene() override { shutdown(); }
void render_frame() {
{
auto painter = frame_control.acquire_painter();
ASSERT_TRUE(painter);
publish_frame_state();
}
auto renderer = frame_control.acquire_renderer();
ASSERT_TRUE(renderer);
render(*renderer);
wait_for_render();
}
void render_scene(const Scene_Render_Context& context) override {
prepared = context.prepared<detail::Prepared_Point>(point_id);
ASSERT_TRUE(prepared);
EXPECT_EQ(context.frame.scene_state<detail::Scene_State>().viewport,
(Extent{320, 180}));
}
Renderable_Id point_id{};
std::shared_ptr<const detail::Prepared_Point> prepared;
};
static_assert(std::derived_from<Point_Visual, Renderable>);
static_assert(std::derived_from<Point_Visual, ::Renderable_Base>);
TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) {
TEST(PointState, PublishesStateAndBulkDataIntoFrameLocalPreparedOutput) {
auto visual = Point_Visual::Builder{}.build(
std::vector<Point>{{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}});
ASSERT_TRUE(visual);
const auto initial_observation = visual->observation();
EXPECT_EQ(initial_observation.event,
Renderable_Observer_Event::Data_Updated);
EXPECT_EQ(initial_observation.data_revision, 1U);
EXPECT_EQ(initial_observation.item_count, 1U);
Point_State configured;
configured.visible = false;
configured.style.stroke_width_px = 3.0F;
visual->set<&Point_State::visible>(configured.visible);
visual->set<&Point_State::style>(configured.style);
EXPECT_EQ(visual->observation().event,
Renderable_Observer_Event::Cache_Updated);
const auto published = detail::publish_point(*visual);
EXPECT_EQ(visual->observation().event,
Renderable_Observer_Event::Published);
EXPECT_EQ(published.state, configured);
ASSERT_EQ(published.data->size(), 1U);
EXPECT_EQ(published.data->front().position, (Vec3{1.0F, 2.0F, 3.0F}));
EXPECT_EQ(published.state_revision, 1U);
EXPECT_EQ(published.data_revision, 1U);
Test_Scene scene(visual);
scene.render_frame();
ASSERT_TRUE(scene.prepared);
EXPECT_EQ(scene.prepared->state, configured);
ASSERT_EQ(scene.prepared->positions.size(), 1U);
EXPECT_EQ(scene.prepared->positions.front(),
(std::array<float, 3>{1.0F, 2.0F, 3.0F}));
EXPECT_EQ(scene.prepared->data_revision, 1U);
}
TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) {
TEST(PointState, PublishesImmutableBulkPayloadsAcrossConcurrentUpdates) {
auto visual = Point_Visual::Builder{}.build();
ASSERT_TRUE(visual);
constexpr int update_count = 500;
constexpr std::uint64_t initial_data_revision = 1;
Test_Scene scene(visual);
constexpr int update_count = 200;
std::atomic<bool> done{};
std::thread writer([&] {
@@ -63,21 +98,22 @@ TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) {
});
do {
const auto published = detail::publish_point(*visual);
if (!published.data->empty()) {
const float expected = published.data->front().position.x;
for (const auto& point : *published.data)
EXPECT_EQ(point.position.x, expected);
scene.render_frame();
const auto prepared = scene.prepared;
ASSERT_TRUE(prepared);
if (!prepared->positions.empty()) {
const float expected = prepared->positions.front()[0];
for (const auto& point : prepared->positions)
EXPECT_EQ(point[0], expected);
}
} while (!done.load(std::memory_order_acquire));
writer.join();
const auto published = detail::publish_point(*visual);
EXPECT_GT(published.data_revision, 0U);
EXPECT_LE(published.data_revision,
initial_data_revision + update_count);
ASSERT_FALSE(published.data->empty());
EXPECT_EQ(published.data->front().position.x, static_cast<float>(update_count));
scene.render_frame();
ASSERT_TRUE(scene.prepared);
ASSERT_FALSE(scene.prepared->positions.empty());
EXPECT_EQ(scene.prepared->positions.front()[0],
static_cast<float>(update_count));
}
TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) {
@@ -87,66 +123,34 @@ TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) {
invalid.style.stroke_width_px = -1.0F;
EXPECT_THROW(visual->set<&Point_State::style>(invalid.style),
std::invalid_argument);
EXPECT_THROW(visual->update_points(std::vector<Point>{{{}, {}, 0.0F}}),
std::invalid_argument);
}
TEST(PointFrameControl, UsesDedicatedThreeDimensionalFrameStrategy) {
TEST(PointRenderPlan, OrdersParallelPreparationBeforeSingleSceneRenderNode) {
auto visual = Point_Visual::Builder{}.build(
std::vector<Point>{{{}, {}, 8.0F}});
ASSERT_TRUE(visual);
detail::Scene_State_Buffer scene({{320, 180}, {}});
detail::Input_Collector input;
const auto prepare = [&](detail::Point_Frame_Strategy& strategy) {
auto lease = strategy.acquire_painter();
if (!lease)
return false;
auto published_scene = scene.publish_state();
lease->scene = std::move(published_scene.state);
lease->scene_revision = published_scene.revision;
lease->point = detail::publish_point(*visual);
lease->input = input.drain();
return true;
};
Test_Scene scene(visual);
scene.render_frame();
detail::Point_Frame_Strategy manual(Frame_Mode::Manual, 60.0);
EXPECT_TRUE(prepare(manual));
auto manual_status = manual.status();
EXPECT_EQ(manual_status.mode, Frame_Mode::Manual);
EXPECT_EQ(manual_status.produced_frame_count, 1U);
EXPECT_EQ(manual_status.pending_frame_count, 1U);
EXPECT_FALSE(static_cast<bool>(manual.acquire_renderer()));
EXPECT_TRUE(manual.refresh());
{
auto rendered = manual.acquire_renderer();
ASSERT_TRUE(rendered);
EXPECT_EQ(rendered->point.data->size(), 1U);
}
manual_status = manual.status();
EXPECT_EQ(manual_status.consumed_frame_count, 1U);
EXPECT_EQ(manual_status.pending_frame_count, 0U);
detail::Point_Frame_Strategy playback(Frame_Mode::Playback, 60.0);
EXPECT_TRUE(prepare(playback));
visual->update_points(std::vector<Point>(2, Point{{}, {}, 8.0F}));
EXPECT_TRUE(prepare(playback));
auto playback_status = playback.status();
EXPECT_EQ(playback_status.produced_frame_count, 2U);
EXPECT_EQ(playback_status.pending_frame_count, 2U);
{
auto first = playback.acquire_renderer();
ASSERT_TRUE(first);
EXPECT_EQ(first->point.data->size(), 1U);
}
{
auto second = playback.acquire_renderer();
ASSERT_TRUE(second);
EXPECT_EQ(second->point.data->size(), 2U);
}
playback_status = playback.status();
EXPECT_EQ(playback_status.consumed_frame_count, 2U);
EXPECT_EQ(playback_status.pending_frame_count, 0U);
const auto plan = scene.render_plan_snapshot();
ASSERT_TRUE(plan);
const auto prepare = std::find_if(
plan->graph.nodes.begin(), plan->graph.nodes.end(),
[&](const Render_Node& node) {
return node.owner_id == visual->renderable_id() &&
node.kind == Render_Node_Kind::prepare;
});
const auto render = std::find_if(
plan->graph.nodes.begin(), plan->graph.nodes.end(),
[](const Render_Node& node) {
return node.kind == Render_Node_Kind::render;
});
ASSERT_NE(prepare, plan->graph.nodes.end());
ASSERT_NE(render, plan->graph.nodes.end());
EXPECT_NE(std::find(plan->graph.edges.begin(), plan->graph.edges.end(),
Render_Edge{prepare->node_id, render->node_id}),
plan->graph.edges.end());
}
} // namespace
Binary file not shown.
+3 -4
View File
@@ -1,6 +1,5 @@
#pragma once
#include "render_2D/renderable/Renderable.h"
#include <renderive/scene/base/Scene_Base.hpp>
#include <nlohmann/json.hpp>
@@ -29,6 +28,8 @@ inline const char* node_kind(Render_Node_Kind kind) noexcept {
return "paint";
case Render_Node_Kind::composite:
return "composite";
case Render_Node_Kind::render:
return "render";
}
return "prepare";
}
@@ -65,9 +66,7 @@ inline std::unordered_map<std::uint64_t, std::string> owner_names(
const auto topology = scene.topology_snapshot();
result.reserve(topology.renderables.size());
for (const auto& base : topology.renderables) {
std::string name;
if (const auto* renderable = dynamic_cast<const renderive::Renderable*>(base.get()))
name = renderable->object_name();
std::string name = base->object_name();
if (name.empty())
name = "Renderable " + std::to_string(base->renderable_id());
result.emplace(base->renderable_id(), std::move(name));
+94 -3
View File
@@ -1,8 +1,13 @@
#include "Gallery_Scene3D.h"
#include "../Gallery_Capture_Json.h"
#include "../Gallery_Enum.h"
#include "../common/Pixel_Frame.h"
#include <render_3D/Point_Scene.h>
#include "adminive/adminive.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
@@ -16,6 +21,44 @@
#include <utility>
#include <vector>
namespace adminive {
template <class Json>
struct Value_Adapter<
renderive::render_3d::Renderable_Observer_Event, Json> {
using value_type = std::string;
static std::string read(
renderive::render_3d::Renderable_Observer_Event value) {
return renderive::web::gallery_enum_id(value);
}
static void write(
renderive::render_3d::Renderable_Observer_Event& target,
std::string value) {
const auto parsed = renderive::web::gallery_enum_cast<
renderive::render_3d::Renderable_Observer_Event>(value);
if (!parsed)
throw std::invalid_argument("unknown 3D observer event");
target = *parsed;
}
};
template <>
struct Type_Descriptor<renderive::render_3d::Renderable_Observation> {
static auto get() {
using T = renderive::render_3d::Renderable_Observation;
return object<T>(
"renderable_3d_observer",
ADMINIVE_FIELD_LABEL(T, event, "事件"),
ADMINIVE_FIELD_LABEL(T, event_time_ns, "事件时间"),
ADMINIVE_FIELD_LABEL(T, cache_update_count, "缓存更新"),
ADMINIVE_FIELD_LABEL(T, publish_count, "发布"),
ADMINIVE_FIELD_LABEL(T, data_revision, "数据版本"),
ADMINIVE_FIELD_LABEL(T, item_count, "项目数"));
}
};
} // namespace adminive
namespace renderive::web {
namespace {
using namespace renderive::render_3d;
@@ -119,6 +162,14 @@ Visual_Family visual_family(std::string_view case_id) {
return Visual_Family::Point;
}
std::optional<double> action_number(
const Gallery_Action_Request& request) {
if (!request.argument ||
!std::holds_alternative<double>(*request.argument))
return std::nullopt;
return std::get<double>(*request.argument);
}
class Gallery_Scene3D final : public Gallery_Scene_Interface {
public:
Gallery_Scene3D(std::uint64_t session_id, std::string case_id,
@@ -133,6 +184,7 @@ public:
visual_) {
if (!visual_)
throw std::runtime_error("failed to create Point_Visual");
visual_->set_object_name("3D Visual");
render_initial_frame();
}
@@ -141,10 +193,21 @@ public:
}
[[nodiscard]] nlohmann::json controls() const override {
const auto observation = visual_->observation();
return {{"resources", nlohmann::json::array()},
{"observers", nlohmann::json::array()},
{"render_plan", nullptr},
{"performance_capture", nullptr}};
{"observers",
nlohmann::json::array({
{{"descriptor", adminive::to_descriptor_json<
nlohmann::json,
Renderable_Observation>()},
{"data", adminive::to_frontend_json<nlohmann::json>(
observation)},
{"target", "point-visual"},
{"title", "3D Visual"}}})},
{"render_plan",
gallery_render_plan_json(scene_.render_scene())},
{"performance_capture",
gallery_performance_capture_json(scene_.render_scene())}};
}
[[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept override {
@@ -267,6 +330,32 @@ public:
[[nodiscard]] std::string action(const Gallery_Action_Request& request,
bool& recognized) override {
recognized = true;
if (request.id == "capture_next_frame") {
auto& scene = scene_.render_scene();
if (scene.capture_state().enabled())
return "A performance capture session is already active";
const auto session_id = scene.capture_next_frame();
last_action_result_ =
"capture session=" + std::to_string(session_id);
return "Next 3D frame capture requested";
}
if (request.id == "capture_frames") {
const auto argument = action_number(request);
if (!argument) {
recognized = false;
return {};
}
const std::size_t count = static_cast<std::size_t>(
std::clamp(std::llround(*argument), 1LL, 120LL));
auto& scene = scene_.render_scene();
if (scene.capture_state().enabled())
return "A performance capture session is already active";
const auto session_id = scene.capture_frames(count);
last_action_result_ = "capture session=" +
std::to_string(session_id) +
" frames=" + std::to_string(count);
return "Consecutive 3D frame capture requested";
}
if (request.id == "mode_prepare" || request.id == "mode_enqueue") {
if (frame_mode_ == Gallery_Frame_Mode::Playback) {
blue_offset_ = std::min(0.25F, blue_offset_ + 0.0125F);
@@ -366,6 +455,8 @@ public:
{"websocket_buffered_bytes",
client_performance_.websocket_buffered_bytes}}},
{"session_id", session_id_},
{"performance_capture",
gallery_performance_capture_json(scene_.render_scene())},
{"last_action", last_action_result_}}
.dump();
}