接入3D secene taskflow
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 family:point、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/arcball;Web 保留事件的真实派生类型,不经过 `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 runner:558/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` 双缓冲策略 | 不可变共享快照 |
|
||||
| viewport、clear 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 CTest:Kernel、render2D、render3D、Datoviz、Qt、Web 全套通过。
|
||||
- Point 状态/并发快照、真实 Vulkan 渲染、resize、事件和 Playback 历史顺序均有测试覆盖。
|
||||
- `webapp_gallery` Vitest、TypeScript 和 Vite production build 通过。
|
||||
|
||||
+302
-128
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user