This commit is contained in:
2026-08-17 22:55:51 +08:00
parent 93064c5ab9
commit 5ff0f6369d
14 changed files with 24 additions and 85 deletions
@@ -141,26 +141,16 @@ void Renderable_Base::Impl::set_configuration(
if (previous == value) return;
configuration.store(value, std::memory_order_release);
invalidate_prepare();
notify_scene_model_dirty();
}
void Renderable_Base::Impl::set_visible(bool value) {
if (visible.exchange(value, std::memory_order_acq_rel) == value) return;
invalidate_prepare();
notify_scene_model_dirty();
}
void Renderable_Base::Impl::discard_stale_frame_on_latest_data_update(
bool enabled) noexcept {
real_time_data_state->discard_stale_frame_on_latest_data_update.store(
enabled, std::memory_order_release);
}
void Renderable_Base::Impl::notify_scene_model_dirty() {
const auto& state = real_time_data_state;
if (!state->attached.load(std::memory_order_acquire) ||
!state->scene_lifetime)
return;
auto lease = state->scene_lifetime->acquire();
if (lease) lease.scene().notify_model_dirty();
}
void Renderable_Base::Impl::build_prepare_graph(
Renderable_Graph_Builder& builder) {
builder.emplace("prepare", "Prepare",
@@ -251,11 +241,9 @@ void Renderable_Base::discard_stale_frame_on_latest_data_update(
}
void Renderable_Base::Impl::changed() {
invalidate_prepare();
notify_scene_model_dirty();
}
void Renderable_Base::Impl::paint_changed() {
invalidate_paint();
notify_scene_model_dirty();
}
void Renderable_Base::Impl::render_graph_changed() {
rebuild_render_graph();
@@ -39,7 +39,6 @@ public:
[[nodiscard]] static Render_State_View render_state_view() noexcept;
[[nodiscard]] Real_Time_Data_Binding bind_real_time_data(
Real_Time_Data_Base& data);
void notify_scene_model_dirty();
void bind_scene(std::shared_ptr<Scene_Lifetime> lifetime);
[[nodiscard]] std::shared_ptr<const Renderable_Graph> render_graph_snapshot();
void mark_prepared(std::uint64_t revision) noexcept;
+1 -22
View File
@@ -224,8 +224,7 @@ struct Scene_Base::Impl::Compiled_Render_Plan {
};
class Scene_Base::Impl::Renderable_Edit_Transaction {
public:
explicit Renderable_Edit_Transaction(Impl& scene) : scene_(scene), dependency_(scene.dependency_resolver_.resolve()),
model_dirty_(scene.model_dirty_.load(std::memory_order_acquire)) {
explicit Renderable_Edit_Transaction(Impl& scene) : scene_(scene), dependency_(scene.dependency_resolver_.resolve()) {
renderables_.reserve(scene.renderables_.size());
color_caches_.reserve(scene.color_caches_.size());
for (const auto& [id, renderable] : scene.renderables_) {
@@ -262,7 +261,6 @@ public:
data.real_time_data_state->attached.store(state.attached, std::memory_order_release);
data.prepare_revision.store(state.prepare_revision, std::memory_order_release);
}
scene_.model_dirty_.store(model_dirty_, std::memory_order_release);
}
private:
struct Renderable_State {
@@ -277,7 +275,6 @@ private:
std::optional<renderive::scene::dependency::Resolution<Renderable_Id>> display_;
std::vector<std::pair<Renderable_Id, std::shared_ptr<Color_Cache>>> color_caches_;
std::vector<Renderable_State> renderable_states_;
bool model_dirty_{};
};
Scene_Base::Impl::Impl(std::pmr::memory_resource& upstream_memory_resource) : memory_domain_(std::allocate_shared<Scene_Memory_Domain>(
std::pmr::polymorphic_allocator<Scene_Memory_Domain>(
@@ -415,7 +412,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::attach(Renderable renderable) {
}
renderable_data.real_time_data_state->attached.store(true, std::memory_order_release);
renderable_data.invalidate_prepare();
scene_data.model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_Base::Renderable_Editor::detach(
@@ -432,7 +428,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::detach(
renderable_data.real_time_data_state->attached.store(false, std::memory_order_release);
scene_data.color_caches_.erase(id);
scene_data.renderables_.erase(id);
scene_data.model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_Base::Renderable_Editor::set_dependency_parent(
@@ -453,7 +448,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::set_dependency_parent(
if (!result) return fail(relationship_error(result.error, false));
if (!result.changed) return Scene_Edit_Error::none;
child->d_func().invalidate_prepare();
scene_data.model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_Base::Renderable_Editor::add_dependency_parent(
@@ -474,7 +468,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::add_dependency_parent(
if (!result) return fail(relationship_error(result.error, false));
if (!result.changed) return Scene_Edit_Error::none;
child->d_func().invalidate_prepare();
scene_data.model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_Base::Renderable_Editor::clear_dependency_parent(
@@ -491,7 +484,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::clear_dependency_parent(
if (!result) return fail(relationship_error(result.error, false));
if (!result.changed) return Scene_Edit_Error::none;
child->d_func().invalidate_prepare();
scene_data.model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
void Scene_Base::Renderable_Editor::replace_frame_control_strategy(
@@ -602,7 +594,6 @@ Scene_Edit_Error Scene_2D_Base::Impl::set_display_parent_locked(
const auto result = display_resolver_.replace_parents(
child->renderable_id(), {parent->renderable_id()});
if (!result) return relationship_error(result.error, true);
if (result.changed) model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_2D_Base::Impl::add_display_parent_locked(
@@ -618,7 +609,6 @@ Scene_Edit_Error Scene_2D_Base::Impl::add_display_parent_locked(
const auto result = display_resolver_.add_parent(
child->renderable_id(), parent->renderable_id());
if (!result) return relationship_error(result.error, true);
if (result.changed) model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_Edit_Error Scene_2D_Base::Impl::clear_display_parent_locked(
@@ -629,7 +619,6 @@ Scene_Edit_Error Scene_2D_Base::Impl::clear_display_parent_locked(
if (!is_renderable_attached_locked(child)) return Scene_Edit_Error::renderable_not_attached;
const auto result = display_resolver_.clear_parents(child->renderable_id());
if (!result) return relationship_error(result.error, true);
if (result.changed) model_dirty_.store(true, std::memory_order_release);
return Scene_Edit_Error::none;
}
Scene_3D_Base::Scene_3D_Base() : Scene_3D_Base(*std::pmr::get_default_resource()) {}
@@ -922,7 +911,6 @@ void Scene_Base::Impl::request_render_graph_rebuild(
if (renderable_scene_error(renderable) != Scene_Edit_Error::none) ::renderive::error::unexpected<std::logic_error>("attached renderable scene invariant violated");
if (active_renderable_edit_scene_ == owner_) {
renderable_data.reset_render_graph();
model_dirty_.store(true, std::memory_order_release);
return;
}
Renderable owner;
@@ -936,13 +924,11 @@ void Scene_Base::Impl::request_render_graph_rebuild(
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
if (!runtime_started_) {
owner->d_func().reset_render_graph();
model_dirty_.store(true, std::memory_order_release);
return;
}
}
static_cast<void>(enqueue_renderable_edit([owner = std::move(owner)] {
owner->d_func().reset_render_graph();
owner->d_func().notify_scene_model_dirty();
return Scene_Edit_Error::none;
}));
}
@@ -1016,16 +1002,12 @@ void Scene_Base::Impl::publish_frame_state_locked() {
}
publish_observation();
}
void Scene_Base::notify_model_dirty() noexcept {
d_func().model_dirty_.store(true, std::memory_order_release);
}
void Scene_Base::Impl::invalidate_renderables() {
std::lock_guard lock(model_mutex_);
for (const auto& [id, renderable] : renderables_) {
static_cast<void>(id);
renderable->d_func().invalidate_prepare();
}
model_dirty_.store(true, std::memory_order_release);
}
std::size_t Scene_Base::renderable_count() const {
const auto& implementation = d_func();
@@ -1294,9 +1276,6 @@ std::unique_lock<std::recursive_mutex> Scene_Base::Impl::lock_render_idle() {
bool Scene_Base::Impl::is_render_execution_context() const noexcept {
return active_execution_scene_ == owner_;
}
bool Scene_Base::Impl::consume_model_dirty() noexcept {
return model_dirty_.exchange(false, std::memory_order_acq_rel);
}
void Scene_Base::Impl::shutdown() {
std::shared_ptr<Compiled_Render_Plan> compiled_plan;
{
@@ -158,7 +158,6 @@ public:
Scene_Render_Result wait_for_render();
void publish_frame_state();
void publish_frame_state(Abstract_Frame& frame);
void notify_model_dirty() noexcept;
[[nodiscard]] Edit_Operation edit_renderables(Renderable_Edit edit);
[[nodiscard]] std::size_t renderable_count() const;
[[nodiscard]] Topology_Snapshot topology_snapshot() const;
@@ -119,7 +119,6 @@ public:
[[nodiscard]] Scene_Edit_Error cleanup_detached_topology_locked();
[[nodiscard]] std::unique_lock<std::recursive_mutex> lock_render_idle();
[[nodiscard]] bool is_render_execution_context() const noexcept;
[[nodiscard]] bool consume_model_dirty() noexcept;
void invalidate_renderables();
void shutdown();
[[nodiscard]] Scene_Base::Edit_Operation enqueue_renderable_edit(
@@ -131,7 +130,6 @@ public:
std::shared_ptr<Scene_Lifetime> scene_lifetime_;
std::shared_ptr<Scene_Memory_Domain> memory_domain_;
std::unique_ptr<Execution_Context> execution_context_;
std::atomic_bool model_dirty_{true};
std::pmr::unordered_map<Renderable_Id, Renderable> renderables_;
renderive::scene::dependency::Dependency_Resolver<Renderable_Id>
dependency_resolver_;
+1 -1
View File
@@ -8,7 +8,7 @@ Explicit_Plot::Explicit_Plot()
: Abs_Plot(new Abs_Plot_Private(false)) {}
Render_Ticket Explicit_Plot::replot() {
const auto result = d->scene->render_frame(true);
const auto result = d->scene->render_frame();
return {result && *result == Plot_Render_Status::rendered};
}
@@ -72,7 +72,6 @@ inline std::shared_ptr<Performance_Overlay> attach_performance_overlay(Render_Sc
inline std::shared_ptr<Performance_Overlay> attach_performance_overlay(Render_Scene_2D& scene, const Performance_Overlay_Options& options) {
if (auto existing = scene.performance_overlay()) {
existing->set_options(options);
scene.request_redraw();
return existing;
}
auto overlay = std::make_shared<Performance_Overlay>(options);
@@ -83,13 +82,11 @@ inline void set_performance_plot_name(Render_Scene_2D& scene, std::string name)
auto overlay = scene.performance_overlay();
if (!overlay) overlay = attach_performance_overlay(scene);
overlay->set_plot_name(std::move(name));
scene.request_redraw();
}
inline void set_performance_overlay_enabled(Render_Scene_2D& scene, bool enabled) {
auto overlay = scene.performance_overlay();
if (!overlay && !enabled) return;
if (!overlay) overlay = attach_performance_overlay(scene);
overlay->set_enabled(enabled);
scene.request_redraw();
}
} // namespace renderive
+5 -15
View File
@@ -80,7 +80,6 @@ struct detail::Render_Scene_2D::Impl
});
const auto published = scene_state.published_state();
if (pending.viewport != published.viewport) invalidate_renderables();
else if (pending != published) scene().notify_model_dirty();
}
void swap_scene_state() override {
scene_state.publish();
@@ -154,17 +153,15 @@ void detail::Render_Scene_2D::dispatch_event(const Event& event) {
}
}
}
auto detail::Render_Scene_2D::render_frame(bool force) -> Expected<Plot_Render_Status, Plot_Render_Frame_Error> {
if (!view_active() && !force) return Plot_Render_Status::view_inactive;
auto detail::Render_Scene_2D::render_frame() -> Expected<Plot_Render_Status, Plot_Render_Frame_Error> {
if (!view_active()) return Plot_Render_Status::view_inactive;
const Size viewport = get<&State::viewport>();
if (viewport.empty()) return Plot_Render_Status::empty_viewport;
if (!d_func<Impl>().consume_model_dirty() && !force) return Plot_Render_Status::model_unchanged;
const auto started = std::chrono::steady_clock::now();
try {
if (const auto result = d_func<Impl>().frame_control->request(*this); !result) return unexpected(result.error());
}
catch (...) {
notify_model_dirty();
::renderive::error::unexpected(
"rolling back plot frame preparation",
std::current_exception());
@@ -205,7 +202,6 @@ void detail::Render_Scene_2D::with_frame(
}
void detail::Render_Scene_2D::activate_view() {
d_func<Impl>().active.store(true, std::memory_order_release);
notify_model_dirty();
}
void detail::Render_Scene_2D::deactivate_view() noexcept {
d_func<Impl>().active.store(false, std::memory_order_release);
@@ -213,9 +209,6 @@ void detail::Render_Scene_2D::deactivate_view() noexcept {
bool detail::Render_Scene_2D::view_active() const noexcept {
return d_func<Impl>().active.load(std::memory_order_acquire);
}
void detail::Render_Scene_2D::request_redraw() noexcept {
notify_model_dirty();
}
void detail::Render_Scene_2D::set_presentation_sink(
std::weak_ptr<Presentation_Sink> sink) {
auto& impl = d_func<Impl>();
@@ -229,11 +222,8 @@ std::shared_ptr<Performance_Overlay> detail::Render_Scene_2D::performance_overla
}
void detail::Render_Scene_2D::set_performance_overlay(
std::shared_ptr<Performance_Overlay> overlay) {
{
auto& impl = d_func<Impl>();
std::lock_guard lock(impl.control_mutex);
impl.performance = std::move(overlay);
}
notify_model_dirty();
auto& impl = d_func<Impl>();
std::lock_guard lock(impl.control_mutex);
impl.performance = std::move(overlay);
}
} // namespace renderive
+1 -3
View File
@@ -17,7 +17,6 @@ enum class Plot_Render_Status : std::uint8_t {
rendered,
view_inactive,
empty_viewport,
model_unchanged
};
enum class Plot_Render_Frame_Error : std::uint8_t {
painter_unavailable,
@@ -108,12 +107,11 @@ public:
struct Observer : Base::template next_Observer<Observer>, Observer_Fields {};
[[nodiscard]] ::renderive_Owner<::renderive::Renderable> root_renderable() const;
void dispatch_event(const Event& event);
[[nodiscard]] auto render_frame(bool force = false) -> Expected<Plot_Render_Status, Plot_Render_Frame_Error>;
[[nodiscard]] auto render_frame() -> Expected<Plot_Render_Status, Plot_Render_Frame_Error>;
void with_frame(const std::function<void(Image_View)>& consumer);
void activate_view();
void deactivate_view() noexcept;
[[nodiscard]] bool view_active() const noexcept;
void request_redraw() noexcept;
void set_presentation_sink(std::weak_ptr<Presentation_Sink> sink);
[[nodiscard]] std::shared_ptr<Performance_Overlay> performance_overlay() const;
void set_performance_overlay(
@@ -21,7 +21,7 @@ TEST(RenderScene2DFramePipeline, BuilderOwnsConstructionAndState) {
EXPECT_EQ(scene->root_renderable()->object_name(), "root");
}
TEST(RenderScene2DFramePipeline, ActiveDirtyAndForceGatesAreOrthogonal) {
TEST(RenderScene2DFramePipeline, SceneValidityDoesNotGateStrategyProgress) {
Render_Scene_2D::State state;
state.viewport = {80, 45};
auto scene = make_scene(state);
@@ -30,8 +30,7 @@ TEST(RenderScene2DFramePipeline, ActiveDirtyAndForceGatesAreOrthogonal) {
EXPECT_EQ(scene->render_frame().value(), Plot_Render_Status::view_inactive);
scene->activate_view();
EXPECT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered);
EXPECT_EQ(scene->render_frame().value(), Plot_Render_Status::model_unchanged);
EXPECT_EQ(scene->render_frame(true).value(), Plot_Render_Status::rendered);
EXPECT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered);
EXPECT_EQ(scene->wait_for_render(), Scene_Render_Result::none);
}
@@ -40,7 +39,7 @@ TEST(RenderScene2DFramePipeline, ObserverReadsPublishedDoubleBufferedState) {
state.viewport = {32, 24};
auto scene = make_scene(state);
scene->activate_view();
ASSERT_EQ(scene->render_frame(true).value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->wait_for_render(), Scene_Render_Result::none);
const auto observation = scene->observer();
EXPECT_EQ(observation.state, state);
@@ -11,7 +11,7 @@ TEST(RenderScene2DIntegration, BuilderStatePublishesAtFrameBoundary) {
auto scene = Render_Scene_2D::Builder{initial}.build(
std::make_shared<Manual_Render_Scene_2D_Strategy>());
scene->activate_view();
ASSERT_EQ(scene->render_frame(true).value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->wait_for_render(), Scene_Render_Result::none);
EXPECT_EQ(scene->observer().state, initial);
}
@@ -22,7 +22,7 @@ TEST(RenderScene2DIntegration, FrameImageMatchesConfiguredViewport) {
auto scene = Render_Scene_2D::Builder{state}.build(
std::make_shared<Low_Latency_Render_Scene_2D_Strategy>());
scene->activate_view();
ASSERT_EQ(scene->render_frame(true).value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered);
ASSERT_EQ(scene->wait_for_render(), Scene_Render_Result::none);
scene->with_frame([](Image_View image) {
EXPECT_EQ(image.width, 48);
+9 -17
View File
@@ -132,7 +132,7 @@ struct detail::Render_Scene_3D::Impl
backend_state(
std::make_shared<Point_Backend_State>(gpu_index)) {}
Scene_Control_Error dispatch_pointer(
Render_Scene_3D& scene, ::renderive::Event_Type type, float x,
::renderive::Event_Type type, float x,
float y, ::renderive::Mouse_Button button,
::renderive::Keyboard_Modifier modifiers) {
const Extent viewport =
@@ -147,12 +147,10 @@ struct detail::Render_Scene_3D::Impl
return Scene_Control_Error::none;
});
if (!invocation) return Scene_Control_Error::backend_unavailable;
const auto error = *invocation.value;
if (error == Scene_Control_Error::none) scene.notify_model_dirty();
return error;
return *invocation.value;
}
Scene_Control_Error dispatch_wheel(
Render_Scene_3D& scene, float x, float y, float delta_x,
float x, float y, float delta_x,
float delta_y, ::renderive::Keyboard_Modifier modifiers) {
const Extent viewport =
scene_state.template get<&State::viewport>();
@@ -166,12 +164,9 @@ struct detail::Render_Scene_3D::Impl
return Scene_Control_Error::none;
});
if (!invocation) return Scene_Control_Error::backend_unavailable;
const auto error = *invocation.value;
if (error == Scene_Control_Error::none) scene.notify_model_dirty();
return error;
return *invocation.value;
}
Scene_Control_Error dispatch_key(Render_Scene_3D& scene,
const ::renderive::Key_Event& event) {
Scene_Control_Error dispatch_key(const ::renderive::Key_Event& event) {
const auto backend = backend_state;
if (!backend->backend_available()) return Scene_Control_Error::backend_unavailable;
const auto invocation = backend->render_domain->invoke(
@@ -181,9 +176,7 @@ struct detail::Render_Scene_3D::Impl
return Scene_Control_Error::none;
});
if (!invocation) return Scene_Control_Error::backend_unavailable;
const auto error = *invocation.value;
if (error == Scene_Control_Error::none) scene.notify_model_dirty();
return error;
return *invocation.value;
}
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame() const {
const auto backend = backend_state;
@@ -394,7 +387,6 @@ struct detail::Render_Scene_3D::Impl
});
const auto published = scene_state.published_state();
if (pending.viewport != published.viewport) invalidate_renderables();
else if (pending != published) scene().notify_model_dirty();
}
void swap_scene_state() override {
scene_state.publish();
@@ -501,7 +493,7 @@ Scene_Control_Error detail::Render_Scene_3D::dispatch(
!std::isfinite(angle_delta_y))
return Scene_Control_Error::invalid_event;
return d_func<Impl>().dispatch_wheel(
*this, x, y,
x, y,
datoviz_wheel_step(pixel_delta_x, angle_delta_x),
datoviz_wheel_step(pixel_delta_y, angle_delta_y),
pointer->keyboard_modifiers());
@@ -516,10 +508,10 @@ Scene_Control_Error detail::Render_Scene_3D::dispatch(
event.type != ::renderive::Event_Type::Pointer_Release)
return Scene_Control_Error::invalid_event;
return d_func<Impl>().dispatch_pointer(
*this, event.type, x, y, pointer->pointer_button(),
event.type, x, y, pointer->pointer_button(),
pointer->keyboard_modifiers());
}
if (const auto* key = dynamic_cast<const ::renderive::Key_Event*>(&event)) return d_func<Impl>().dispatch_key(*this, *key);
if (const auto* key = dynamic_cast<const ::renderive::Key_Event*>(&event)) return d_func<Impl>().dispatch_key(*key);
return Scene_Control_Error::none;
}
Frame_Request_Result detail::Render_Scene_3D::request_frame() {
+1 -1
View File
@@ -22,7 +22,7 @@ struct Web_Plot_Session::Impl {
return std::visit([this](const auto& value) -> std::optional<Web_Response> {
using T = std::decay_t<decltype(value)>;
if constexpr (std::same_as<T, Frame_Request>) {
const auto result = scene->render_frame(true);
const auto result = scene->render_frame();
if (!result || scene->wait_for_render() != Scene_Render_Result::none)
return std::nullopt;
std::string pixels;
+1 -1
View File
@@ -137,7 +137,7 @@ public:
bool request_frame() override {
update_samples();
const auto result = scene_->render_frame(true);
const auto result = scene_->render_frame();
if (!result || *result != Plot_Render_Status::rendered)
return false;
if (scene_->wait_for_render() != Scene_Render_Result::none)