接入3D secene taskflow

This commit is contained in:
2026-08-14 23:38:18 +08:00
parent 9a0601073b
commit 12e46b4c21
31 changed files with 1210 additions and 6116 deletions
@@ -12,7 +12,8 @@ using Render_Plan_Version = std::uint64_t;
enum class Render_Node_Kind : std::uint8_t {
prepare,
paint,
composite
composite,
render
};
struct Render_Node {
@@ -176,6 +176,8 @@ void Renderable_Base::Impl::build_paint_graph(
void Renderable_Base::Impl::prepare(const Prepare_Render_Context&) {}
void Renderable_Base::Impl::capture_frame_data(Frame_Render_Snapshot&) const {}
void Renderable_Base::Impl::add_prepare_action(Prepare_Action action) {
if (!action)
throw std::invalid_argument("renderable prepare action is empty");
@@ -254,6 +256,18 @@ bool Renderable_Base::is_visible() const noexcept {
return d_func().visible.load(std::memory_order_acquire);
}
std::string Renderable_Base::object_name() const {
const auto& implementation = d_func();
std::lock_guard lock(implementation.metadata_mutex);
return implementation.object_name;
}
void Renderable_Base::set_object_name(std::string name) {
auto& implementation = d_func();
std::lock_guard lock(implementation.metadata_mutex);
implementation.object_name = std::move(name);
}
void Renderable_Base::set_visible(bool visible) {
d_func().set_visible(visible);
}
@@ -2,6 +2,7 @@
#include <memory>
#include <memory_resource>
#include <string>
#include "renderive/renderable/Renderable_Configuration.hpp"
#include "renderive/renderable/Renderable_Id.hpp"
@@ -17,6 +18,8 @@ public:
[[nodiscard]] Renderable_Id renderable_id() const noexcept;
[[nodiscard]] bool is_visible() const noexcept;
[[nodiscard]] std::string object_name() const;
void set_object_name(std::string name);
void set_visible(bool visible);
void discard_stale_frame_on_latest_data_update(bool enabled) noexcept;
@@ -59,6 +59,7 @@ public:
void reset_render_graph() noexcept;
void add_prepare_action(Prepare_Action action);
void execute_prepare(const Prepare_Render_Context& context);
virtual void capture_frame_data(Frame_Render_Snapshot& snapshot) const;
protected:
[[nodiscard]] Renderable_Base& owner() noexcept;
@@ -80,6 +81,8 @@ public:
const Render_Node_Id composite_node_id;
std::atomic<Renderable_Configuration> configuration;
std::atomic<bool> visible{true};
mutable std::mutex metadata_mutex;
std::string object_name;
std::mutex render_graph_mutex;
std::shared_ptr<const Renderable_Graph> render_graph;
std::unordered_map<std::string, Render_Node_Id> node_identities;
@@ -85,6 +85,10 @@ protected:
std::uint64_t acquire_scene_state() override {
return this->Scene_State_Strategy::state_revision();
}
void capture_scene_state(Frame_Render_Snapshot& snapshot) const override {
snapshot.set_scene_state(
this->Scene_State_Strategy::render_state_value());
}
void observe_scene(const Scene_Base::Observation& observation) noexcept override {
scene_observer_.observe(observation);
}
@@ -14,7 +14,7 @@ template <class Frame_Control, class... Args>
concept Scene3D_Frame_Control_Constructible = std::constructible_from<Frame_Control, Args...> || std::constructible_from<Frame_Control, std::pmr::memory_resource&, Args...>;
template <class Strategy = Low_Latency_Strategy<Scene3D_Frame_Data>, class State = Scene3D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>>
requires Frame_Control_Strategy_For<Strategy, Scene3D_Frame_Data> && State_Value<State>
class Scene3D_Context final : public Scene_3D_Base,
class Scene3D_Context : public Scene_3D_Base,
public Triple_State_Storage<
State, Atomic_Spin_Mutex, State_Observer> {
public:
@@ -59,6 +59,10 @@ protected:
std::uint64_t acquire_scene_state() override {
return this->Scene_State_Strategy::acquire_render_state();
}
void capture_scene_state(Frame_Render_Snapshot& snapshot) const override {
snapshot.set_scene_state(
this->Scene_State_Strategy::render_state_value());
}
void observe_scene(const Scene_Base::Observation& observation) noexcept override {
scene_observer_.observe(observation);
}
@@ -50,6 +50,7 @@ struct Frame_Snapshot {
};
class Scene_Base;
class Frame_Render_Snapshot;
class Abstract_Frame {
public:
@@ -74,6 +75,7 @@ private:
std::shared_ptr<const Frame_Snapshot> completed_snapshot_;
bool capture_{};
bool rendering_{};
std::shared_ptr<Frame_Render_Snapshot> published_snapshot_;
};
[[nodiscard]] std::uint64_t render_clock_now_ns() noexcept;
@@ -1,7 +1,12 @@
#pragma once
#include <any>
#include <cstdint>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
#include <vector>
#include "renderive/capture/Capture_Types.hpp"
#include "renderive/renderable/Renderable_Configuration.hpp"
@@ -44,9 +49,83 @@ public:
std::vector<Renderable_Frame_State> renderables;
std::vector<Renderable_Id> display_order;
[[nodiscard]] std::uint64_t scene_state_revision() const noexcept {
return scene_state_revision_;
}
template <class State>
[[nodiscard]] const State& scene_state() const {
const auto* value = std::any_cast<State>(&scene_state_);
if (!value)
throw std::logic_error("frame scene state type mismatch");
return *value;
}
template <class State>
void set_scene_state(State state) {
scene_state_ = std::move(state);
}
template <class Data>
void capture(Renderable_Id owner, std::shared_ptr<const Data> data) {
if (!data)
throw std::invalid_argument("captured render data is null");
const auto [entry, inserted] = captured_.try_emplace(
owner, Typed_Data{typeid(Data), std::move(data)});
if (!inserted)
throw std::logic_error(
"renderable captured frame data more than once");
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> captured(
Renderable_Id owner) const {
const auto entry = captured_.find(owner);
if (entry == captured_.end())
return {};
if (entry->second.type != std::type_index(typeid(Data)))
throw std::logic_error("captured render data type mismatch");
return std::static_pointer_cast<const Data>(entry->second.value);
}
template <class Data>
void publish_prepared(Renderable_Id owner,
std::shared_ptr<const Data> data) const {
if (!data)
throw std::invalid_argument("prepared render data is null");
std::lock_guard lock(prepared_mutex_);
const auto [entry, inserted] = prepared_.try_emplace(
owner, Typed_Data{typeid(Data), std::move(data)});
if (!inserted)
throw std::logic_error(
"renderable published prepared data more than once");
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> prepared(
Renderable_Id owner) const {
std::lock_guard lock(prepared_mutex_);
const auto entry = prepared_.find(owner);
if (entry == prepared_.end())
return {};
if (entry->second.type != std::type_index(typeid(Data)))
throw std::logic_error("prepared render data type mismatch");
return std::static_pointer_cast<const Data>(entry->second.value);
}
private:
friend class Scene_Base;
struct Typed_Data {
std::type_index type{typeid(void)};
std::shared_ptr<const void> value;
};
std::uint64_t scene_state_revision_{};
std::uint64_t next_refresh_interval_ns_{};
Capture_Frame_Ticket capture_ticket_;
std::any scene_state_;
std::unordered_map<Renderable_Id, Typed_Data> captured_;
mutable std::mutex prepared_mutex_;
mutable std::unordered_map<Renderable_Id, Typed_Data> prepared_;
};
@@ -10,6 +10,16 @@ struct Prepare_Render_Context {
const Frame_Render_Snapshot& frame;
const Renderable_Frame_State& renderable;
Node_Execution_Metrics* metrics{};
template <class Data>
void publish(std::shared_ptr<const Data> data) const {
frame.publish_prepared(renderable.renderable_id, std::move(data));
}
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> captured() const {
return frame.captured<Data>(renderable.renderable_id);
}
};
struct Paint_Render_Context {
@@ -25,3 +35,14 @@ struct Composite_Render_Context {
const Color_Cache* color_cache{};
Node_Execution_Metrics* metrics{};
};
struct Scene_Render_Context {
const Frame_Render_Snapshot& frame;
Node_Execution_Metrics* metrics{};
template <class Data>
[[nodiscard]] std::shared_ptr<const Data> prepared(
Renderable_Id owner) const {
return frame.prepared<Data>(owner);
}
};
+56 -4
View File
@@ -179,7 +179,8 @@ Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource)
color_caches_(&memory_domain_->resource()),
task_(memory_domain_->resource()),
renderable_edit_queue_(&memory_domain_->resource()),
composite_begin_node_id_(allocate_renderive_node_id()) {
composite_begin_node_id_(allocate_renderive_node_id()),
scene_render_node_id_(allocate_renderive_node_id()) {
worker_ = std::thread([this] { render_loop(); });
}
Scene_Base::~Scene_Base() {
@@ -366,14 +367,15 @@ void Scene_Base::submit_render(Abstract_Frame* frame) {
auto& strategy = frame_control_strategy();
strategy.swap();
auto snapshot = snapshot_live_model();
auto snapshot = frame ? std::exchange(frame->published_snapshot_, {})
: nullptr;
if (!snapshot)
snapshot = capture_live_frame();
snapshot->next_refresh_interval_ns_ =
strategy.frame_control_state().next_refresh_interval_ns;
snapshot->scene_state_revision_ = acquire_scene_state();
if (render_sequence_ == std::numeric_limits<std::uint64_t>::max())
throw std::overflow_error("render sequence exhausted");
snapshot->render_sequence = ++render_sequence_;
snapshot->viewport = frame_viewport();
Render_Task task(memory_resource());
task.frame = frame
@@ -527,6 +529,16 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
}
void Scene_Base::publish_frame_state() {
auto task_lock = lock_render_idle();
publish_frame_state_locked();
}
void Scene_Base::publish_frame_state(Abstract_Frame& frame) {
auto task_lock = lock_render_idle();
publish_frame_state_locked();
frame.published_snapshot_ = capture_live_frame();
}
void Scene_Base::publish_frame_state_locked() {
if (auto* state = dynamic_cast<State_Strategy_Base*>(this))
state->publish();
std::vector<Renderable> renderables;
@@ -640,6 +652,7 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::snapshot_live_model() {
if (const auto cache = color_caches_.find(id);
cache != color_caches_.end())
state.paint_buffer_ = cache->second;
renderable_data.capture_frame_data(*snapshot);
snapshot->renderables.push_back(std::move(state));
}
@@ -675,6 +688,14 @@ std::shared_ptr<Frame_Render_Snapshot> Scene_Base::snapshot_live_model() {
return snapshot;
}
std::shared_ptr<Frame_Render_Snapshot> Scene_Base::capture_live_frame() {
auto snapshot = snapshot_live_model();
snapshot->scene_state_revision_ = acquire_scene_state();
capture_scene_state(*snapshot);
snapshot->viewport = frame_viewport();
return snapshot;
}
std::shared_ptr<const Render_Plan> Scene_Base::render_plan_snapshot() const {
return render_plan_history_.current();
}
@@ -772,6 +793,8 @@ std::uint64_t Scene_Base::acquire_scene_state() {
return 0;
}
void Scene_Base::capture_scene_state(Frame_Render_Snapshot&) const {}
void Scene_Base::observe_scene(const Observation&) noexcept {}
std::uint64_t Scene_Base::observer_now_ns() const noexcept {
@@ -1043,6 +1066,31 @@ std::shared_ptr<const Render_Plan> Scene_Base::compile_render_plan(
}
}
if (auto* renderer = dynamic_cast<Scene_Renderer*>(this)) {
std::vector<Render_Node_Id> terminals;
terminals.reserve(graph.nodes.size());
for (const auto& node : graph.nodes) {
const bool has_successor = std::any_of(
graph.edges.begin(), graph.edges.end(),
[&node](const Render_Edge& edge) {
return edge.from == node.node_id;
});
if (!has_successor)
terminals.push_back(node.node_id);
}
graph.nodes.push_back({scene_render_node_id_, 0, "Render Scene",
Render_Node_Kind::render, 0});
functions.emplace(
scene_render_node_id_,
Execution_Binding{{}, std::nullopt,
Scene_Render_Node_Function{
[renderer](const Scene_Render_Context& context) {
renderer->render_scene(context);
}}});
for (const Render_Node_Id terminal : terminals)
append_edge(terminal, scene_render_node_id_);
}
auto plan = render_plan_history_.publish(std::move(graph));
task.execution_bindings.clear();
task.execution_bindings.resize(plan->graph.nodes.size());
@@ -1129,6 +1177,10 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
metrics});
break;
}
case Render_Node_Kind::render:
std::get<Scene_Render_Node_Function>(binding.function)(
Scene_Render_Context{snapshot, metrics});
break;
}
};
if (!execution) {
+16 -1
View File
@@ -39,6 +39,12 @@ public:
virtual void composite(const Composite_Render_Context& context) = 0;
};
class Scene_Renderer {
public:
virtual ~Scene_Renderer() = default;
virtual void render_scene(const Scene_Render_Context& context) = 0;
};
class Scene_Base {
private:
struct Render_Completion;
@@ -128,6 +134,7 @@ public:
void render(Abstract_Frame& frame);
void wait_for_render();
void publish_frame_state();
void publish_frame_state(Abstract_Frame& frame);
void notify_model_dirty() noexcept;
Attach_Builder attach_builder();
@@ -164,6 +171,7 @@ protected:
virtual std::shared_ptr<Color_Cache> make_renderable_color_cache();
virtual Frame_Viewport frame_viewport() const;
virtual std::uint64_t acquire_scene_state();
virtual void capture_scene_state(Frame_Render_Snapshot& snapshot) const;
virtual void observe_scene(const Observation& observation) noexcept;
virtual std::uint64_t observer_now_ns() const noexcept;
@@ -209,9 +217,12 @@ private:
using Composite_Render_Node_Function =
std::function<void(const Composite_Render_Context&)>;
using Scene_Render_Node_Function =
std::function<void(const Scene_Render_Context&)>;
using Execution_Function =
std::variant<Prepare_Render_Node_Function, Paint_Render_Node_Function,
Composite_Render_Node_Function>;
Composite_Render_Node_Function,
Scene_Render_Node_Function>;
struct Execution_Binding {
Renderable owner;
@@ -251,6 +262,9 @@ private:
void enqueue_renderable_edit_locked(Renderable_Edit edit);
void request_render_graph_rebuild(Renderable_Base& renderable);
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot> snapshot_live_model();
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot>
capture_live_frame();
void publish_frame_state_locked();
std::shared_ptr<const Render_Plan> compile_render_plan(
const Frame_Render_Snapshot& snapshot, Render_Task& task);
void render_loop();
@@ -293,6 +307,7 @@ private:
bool stop_{};
const Render_Node_Id composite_begin_node_id_;
const Render_Node_Id scene_render_node_id_;
Render_Plan_History render_plan_history_;
Capture_Controller capture_controller_;
Capture_Repository capture_repository_;
@@ -125,13 +125,15 @@ struct Triple_State_Storage : State_Strategy_Base {
return publish_count;
}
private:
protected:
friend class Render_State_View;
[[nodiscard]] const State& render_state_value() const noexcept {
return *render_state;
}
private:
Observer observer;
State states[4];
State* render_state;
@@ -1,6 +1,8 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <vector>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/renderable/Renderable_Test_Harness.hpp"
#include "renderive/scene/Scene.hpp"
@@ -23,6 +25,95 @@ TEST(scene3d_context_test, uses_scene_3d_base_contract) {
EXPECT_EQ(renderable->render_count.load(), 1);
EXPECT_TRUE(renderable->scene_is_3d);
}
struct Prepared_Mesh_Count {
std::size_t vertex_count{};
};
struct Scene3D_Prepared_Data_Renderable : Renderable_Test_Harness {
void prepare_for_test(const Prepare_Render_Context& context) override {
context.publish<Prepared_Mesh_Count>(
std::make_shared<const Prepared_Mesh_Count>(
Prepared_Mesh_Count{42}));
}
};
struct Scene3D_Render_Node_Test_Scene final
: Scene3D_Context<>, Scene_Renderer {
void render_scene(const Scene_Render_Context& context) override {
prepared = context.prepared<Prepared_Mesh_Count>(owner);
}
Renderable_Id owner{};
std::shared_ptr<const Prepared_Mesh_Count> prepared;
};
TEST(scene3d_context_test,
transfers_frame_local_prepared_data_to_single_scene_render_node) {
Scene3D_Render_Node_Test_Scene scene;
auto renderable = renderive_Owner<Scene3D_Prepared_Data_Renderable>::make();
scene.owner = renderable->renderable_id();
attach_initial(scene, renderable);
scene.render();
scene.wait_for_render();
ASSERT_TRUE(scene.prepared);
EXPECT_EQ(scene.prepared->vertex_count, 42U);
const auto plan = scene.render_plan_snapshot();
ASSERT_TRUE(plan);
const auto render_node = std::find_if(
plan->graph.nodes.begin(), plan->graph.nodes.end(),
[](const Render_Node& node) {
return node.kind == Render_Node_Kind::render;
});
ASSERT_NE(render_node, plan->graph.nodes.end());
EXPECT_TRUE(std::any_of(
plan->graph.edges.begin(), plan->graph.edges.end(),
[&](const Render_Edge& edge) {
return edge.to == render_node->node_id;
}));
}
struct Scene3D_Historical_State {
int value{};
};
struct Scene3D_Playback_Snapshot_Test_Scene final
: Scene3D_Context<Flow_Refresh_Strategy<Scene3D_Frame_Data>,
Scene3D_Historical_State>,
Scene_Renderer {
void enqueue(int value) {
Scene_State_Strategy::set<&Scene3D_Historical_State::value>(value);
auto frame = frame_control.acquire_painter();
ASSERT_TRUE(frame);
publish_frame_state(*frame);
}
void render_next() {
auto frame = frame_control.acquire_renderer();
ASSERT_TRUE(frame);
render(*frame);
wait_for_render();
}
void render_scene(const Scene_Render_Context& context) override {
rendered.push_back(
context.frame.scene_state<Scene3D_Historical_State>().value);
}
std::vector<int> rendered;
};
TEST(scene3d_context_test,
playback_uses_the_scene_snapshot_captured_at_frame_publication) {
Scene3D_Playback_Snapshot_Test_Scene scene;
scene.enqueue(11);
scene.enqueue(23);
scene.render_next();
scene.render_next();
EXPECT_EQ(scene.rendered, (std::vector<int>{11, 23}));
}
struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Test_Harness {
explicit Scene3D_Dependency_Cache_Test_Renderable()
: Renderable_Test_Harness({.cache_enabled = true}) {}
@@ -9,4 +9,4 @@ TEST(scene_concept_test, accepts_2d_and_3d_contexts) {
SUCCEED();
}
static_assert(!std::is_final_v<Scene2D_Context<>>);
static_assert(std::is_final_v<Scene3D_Context<>>);
static_assert(!std::is_final_v<Scene3D_Context<>>);