diff --git a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp index 576e716..c48509f 100644 --- a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp +++ b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp @@ -12,6 +12,7 @@ #include #include "concept/Real_Time_Data.hpp" #include "renderive/renderable/concept/Renderable.hpp" +#include "renderive/renderable/base/Renderable_Base_p.hpp" #include "renderive/scene/base/Scene_Base.hpp" #include "renderive/scene/base/Render_Contexts.hpp" template @@ -51,6 +52,8 @@ public: explicit Attach_Real_Time_Data(With_Real_Time_Data attachment, Args&&... args) : Renderable_Type(std::forward(args)...), data_(std::move(attachment.data)) { bind_data(std::index_sequence_for{}); + this->d_func().add_prepare_action( + [this] { static_cast(discard_real_time_data()); }); } template requires ((std::same_as || ...)) @@ -66,7 +69,7 @@ public: if (real_time_data_discard_mode.load(std::memory_order_acquire) != Real_Time_Data_Discard_Mode::retain_frame_interval) { return 0; } - const std::uint64_t interval_ns = Frame_Control_Strategy_Base::frequency_interval_ns(this->scene().frame_control_strategy().frequency_hz()); + const std::uint64_t interval_ns = Frame_Control_Strategy_Base::frequency_interval_ns(this->d_func().scene().frame_control_strategy().frequency_hz()); if (interval_ns == 0) { return 0; } @@ -91,12 +94,6 @@ public: }, data_); return state; } -protected: - void prepare(const Prepare_Render_Context& context) override { - discard_real_time_data(); - Renderable_Type::prepare(context); - } -public: std::atomic real_time_data_discard_mode{Real_Time_Data_Discard_Mode::retain_all}; private: template @@ -105,7 +102,8 @@ private: } template void bind_data(Data_Type& data) { - render_bindings_[Index].emplace(this->bind_real_time_data(data)); + render_bindings_[Index].emplace( + this->d_func().bind_real_time_data(data)); if constexpr (requires { data.bind(*this); }) { diff --git a/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.cpp b/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.cpp new file mode 100644 index 0000000..5207d02 --- /dev/null +++ b/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.cpp @@ -0,0 +1,157 @@ +#include "Frame_Strategy_Observer.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "renderive/renderable/base/Renderable_Base_p.hpp" +#include "renderive/scene/base/Scene_Base.hpp" + +struct Frame_Strategy_Real_Time_Data_Observer::State { + using Target = Renderable_Base::Impl::Real_Time_Data_State; + + struct Entry { + explicit Entry(std::shared_ptr target) + : target(std::move(target)) {} + + std::shared_ptr target; + std::size_t binding_count{1}; + std::atomic active{true}; + }; + + using Entry_List = std::vector>; + + class Binding; + + std::mutex mutex; + std::shared_ptr entries{ + std::make_shared()}; +}; + +class Frame_Strategy_Real_Time_Data_Observer::State::Binding { +public: + Binding(std::shared_ptr state, std::shared_ptr entry) + : state_(std::move(state)), entry_(std::move(entry)) {} + + ~Binding() { + std::lock_guard lock(state_->mutex); + if (entry_->binding_count > 1) { + --entry_->binding_count; + return; + } + entry_->binding_count = 0; + entry_->active.store(false, std::memory_order_release); + try { + auto entries = std::make_shared(); + entries->reserve(state_->entries->size()); + for (const auto& value : *state_->entries) { + if (value->active.load(std::memory_order_acquire)) + entries->push_back(value); + } + state_->entries = std::move(entries); + } catch (...) {} + } + +private: + std::shared_ptr state_; + std::shared_ptr entry_; +}; + +Frame_Strategy_Real_Time_Data_Observer:: + Frame_Strategy_Real_Time_Data_Observer() + : state_(std::make_shared()) {} + +Frame_Strategy_Real_Time_Data_Observer:: + Frame_Strategy_Real_Time_Data_Observer(Renderable_Base& renderable) + : Frame_Strategy_Real_Time_Data_Observer() { + direct_binding_ = bind(renderable); +} + +std::shared_ptr Frame_Strategy_Real_Time_Data_Observer::bind( + Renderable_Base& renderable) { + const auto target = renderable.d_func().real_time_data_state; + std::shared_ptr entry; + { + std::lock_guard lock(state_->mutex); + for (const auto& value : *state_->entries) { + if (value->active.load(std::memory_order_acquire) && + value->target == target) { + ++value->binding_count; + entry = value; + break; + } + } + if (!entry) { + entry = std::make_shared(target); + auto entries = std::make_shared(); + entries->reserve(state_->entries->size() + 1); + for (const auto& value : *state_->entries) { + if (value->active.load(std::memory_order_acquire)) + entries->push_back(value); + } + entries->push_back(entry); + state_->entries = std::move(entries); + } + } + return std::make_shared(state_, std::move(entry)); +} + +void Frame_Strategy_Real_Time_Data_Observer::observe( + const Real_Time_Data_Observation& observation) noexcept { + std::shared_ptr entries; + { + std::lock_guard lock(state_->mutex); + entries = state_->entries; + } + const auto is_attached = [](const auto& entry) { + return entry->active.load(std::memory_order_acquire) && + entry->target->renderable_alive.load( + std::memory_order_acquire) && + entry->target->attached.load(std::memory_order_acquire); + }; + for (std::size_t index = 0; index < entries->size(); ++index) { + const auto& entry = (*entries)[index]; + if (!is_attached(entry)) + continue; + const auto& lifetime = entry->target->scene_lifetime; + bool first_for_scene = true; + for (std::size_t previous = 0; previous < index; ++previous) { + const auto& previous_entry = (*entries)[previous]; + if (is_attached(previous_entry) && + previous_entry->target->scene_lifetime.get() == + lifetime.get()) { + first_for_scene = false; + break; + } + } + if (!first_for_scene) + continue; + bool discard_stale_frame{}; + if (observation.state.retention == + Real_Time_Data_Retention::latest) { + for (const auto& value : *entries) { + if (is_attached(value) && + value->target->scene_lifetime.get() == lifetime.get() && + value->target + ->discard_stale_frame_on_latest_data_update.load( + std::memory_order_acquire)) { + discard_stale_frame = true; + break; + } + } + } + auto lease = lifetime->acquire(); + if (!lease) + continue; + auto& strategy = lease.scene().frame_control_strategy(); + strategy.on_real_time_data_update(observation); + const double frequency_hz = strategy.frequency_hz(); + if (discard_stale_frame && std::isfinite(frequency_hz) && + frequency_hz > 0.0) + strategy.discard_stale_latest_data_frame(); + } +} diff --git a/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.hpp b/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.hpp index 306fd6f..8bbf40a 100644 --- a/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.hpp +++ b/Kernel/src/renderive/real_time_data/Frame_Strategy_Observer.hpp @@ -1,130 +1,24 @@ #pragma once -#include -#include -#include -#include + #include -#include -#include + #include "Observation.hpp" -#include "renderive/renderable/base/Renderable_Base.hpp" -#include "renderive/scene/base/Scene_Base.hpp" + +class Renderable_Base; + class Frame_Strategy_Real_Time_Data_Observer { public: static constexpr bool enabled = true; - Frame_Strategy_Real_Time_Data_Observer() : state_(std::make_shared()) {} - explicit Frame_Strategy_Real_Time_Data_Observer(Renderable_Base& renderable) : Frame_Strategy_Real_Time_Data_Observer() { - direct_binding_ = bind(renderable); - } - std::shared_ptr bind(Renderable_Base& renderable) { - const auto target = renderable.real_time_data_state_; - std::shared_ptr entry; - { - std::lock_guard lock(state_->mutex); - for (const auto& value : *state_->entries) { - if (value->active.load(std::memory_order_acquire) && value->target == target) { - ++value->binding_count; - entry = value; - break; - } - } - if (!entry) { - entry = std::make_shared(target); - auto entries = std::make_shared(); - entries->reserve(state_->entries->size() + 1); - for (const auto& value : *state_->entries) { - if (value->active.load(std::memory_order_acquire)) { - entries->push_back(value); - } - } - entries->push_back(entry); - state_->entries = std::move(entries); - } - } - return std::make_shared(state_, std::move(entry)); - } - void observe(const Real_Time_Data_Observation& observation) noexcept { - std::shared_ptr entries; - { - std::lock_guard lock(state_->mutex); - entries = state_->entries; - } - for (std::size_t index = 0; index < entries->size(); ++index) { - const auto& entry = (*entries)[index]; - if (!entry->active.load(std::memory_order_acquire) || !entry->target->renderable_alive.load(std::memory_order_acquire) || !entry->target->attached.load(std::memory_order_acquire)) { - continue; - } - const auto& lifetime = entry->target->scene_lifetime; - bool first_for_scene = true; - for (std::size_t previous = 0; previous < index; ++previous) { - const auto& previous_entry = (*entries)[previous]; - if (previous_entry->active.load(std::memory_order_acquire) && previous_entry->target->renderable_alive.load(std::memory_order_acquire) && previous_entry->target->attached.load(std::memory_order_acquire) && previous_entry->target->scene_lifetime.get() == lifetime.get()) { - first_for_scene = false; - break; - } - } - if (!first_for_scene) { - continue; - } - bool discard_stale_frame{}; - if (observation.state.retention == Real_Time_Data_Retention::latest) { - for (const auto& value : *entries) { - if (value->active.load(std::memory_order_acquire) && value->target->renderable_alive.load(std::memory_order_acquire) && value->target->attached.load(std::memory_order_acquire) && value->target->scene_lifetime.get() == lifetime.get() && value->target->discard_stale_frame_on_latest_data_update.load(std::memory_order_acquire)) { - discard_stale_frame = true; - break; - } - } - } - auto lease = lifetime->acquire(); - if (!lease) { - continue; - } - auto& strategy = lease.scene().frame_control_strategy(); - strategy.on_real_time_data_update(observation); - const double frequency_hz = strategy.frequency_hz(); - if (discard_stale_frame && std::isfinite(frequency_hz) && frequency_hz > 0.0) { - strategy.discard_stale_latest_data_frame(); - } - } - } + + Frame_Strategy_Real_Time_Data_Observer(); + explicit Frame_Strategy_Real_Time_Data_Observer( + Renderable_Base& renderable); + + std::shared_ptr bind(Renderable_Base& renderable); + void observe(const Real_Time_Data_Observation& observation) noexcept; + private: - struct Entry { - explicit Entry(std::shared_ptr target) : target(std::move(target)) {} - std::shared_ptr target; - std::size_t binding_count{1}; - std::atomic active{true}; - }; - using Entry_List = std::vector>; - struct State { - std::mutex mutex; - std::shared_ptr entries{std::make_shared()}; - }; - class Binding { - public: - Binding(std::shared_ptr state, std::shared_ptr entry) : state_(std::move(state)), entry_(std::move(entry)) {} - ~Binding() { - std::lock_guard lock(state_->mutex); - if (entry_->binding_count > 1) { - --entry_->binding_count; - return; - } - entry_->binding_count = 0; - entry_->active.store(false, std::memory_order_release); - try { - auto entries = std::make_shared(); - entries->reserve(state_->entries->size()); - for (const auto& value : *state_->entries) { - if (value->active.load(std::memory_order_acquire)) { - entries->push_back(value); - } - } - state_->entries = std::move(entries); - } catch (...) {} - } - private: - std::shared_ptr state_; - std::shared_ptr entry_; - }; + struct State; std::shared_ptr state_; std::shared_ptr direct_binding_; }; diff --git a/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp b/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp index 8b930f5..854e7c0 100644 --- a/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp +++ b/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp @@ -1,5 +1,5 @@ #include "Real_Time_Data_Base.hpp" -#include "renderive/renderable/base/Renderable_Base.hpp" +#include "renderive/renderable/base/Renderable_Base_p.hpp" #include #include @@ -12,7 +12,7 @@ Real_Time_Data_Binding::Real_Time_Data_Binding(Real_Time_Data_Binding&& other) n Real_Time_Data_Binding::~Real_Time_Data_Binding() { if (!renderable_) return; - renderable_->unregister_real_time_data(*data_); + renderable_->d_func().unregister_real_time_data(*data_); data_->unbind_renderable(*renderable_); } Real_Time_Data_Binding Real_Time_Data_Base::bind_renderable(Renderable_Base& renderable) { @@ -23,7 +23,7 @@ Real_Time_Data_Binding Real_Time_Data_Base::bind_renderable(Renderable_Base& ren bound_renderable_ = &renderable; } try { - renderable.register_real_time_data(*this); + renderable.d_func().register_real_time_data(*this); } catch (...) { std::lock_guard lock(binding_mutex_); bound_renderable_ = nullptr; diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index daae21d..1d308de 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -1,97 +1,118 @@ -#include "Renderable_Base.hpp" +#include "Renderable_Base_p.hpp" + #include #include #include +#include + #include "renderive/real_time_data/base/Real_Time_Data_Base.hpp" #include "renderive/renderable/Renderive_Id_Allocator.hpp" #include "renderive/scene/base/Scene_Base.hpp" + namespace { + void advance_revision(std::atomic& revision) noexcept { std::uint64_t value = revision.load(std::memory_order_relaxed); - while (value != std::numeric_limits::max() && !revision.compare_exchange_weak(value, value + 1, std::memory_order_release, std::memory_order_relaxed)) {} + while (value != std::numeric_limits::max() && + !revision.compare_exchange_weak(value, value + 1, + std::memory_order_release, + std::memory_order_relaxed)) {} } + +} // namespace + +Renderable_Base::Impl::Impl() + : renderable_id(allocate_renderive_renderable_id()), + composite_node_id(allocate_renderive_node_id()) {} + +Renderable_Base::Impl::~Impl() { + real_time_data_state->attached.store(false, std::memory_order_release); + real_time_data_state->renderable_alive.store(false, + std::memory_order_release); } -Renderable_Base::Renderable_Base(Renderable_Configuration configuration, std::pmr::memory_resource& memory_resource) - : memory_resource_(&memory_resource), real_time_data_state_(std::make_shared()), discard_stale_frame_on_latest_data_update(real_time_data_state_->discard_stale_frame_on_latest_data_update), renderable_id_(allocate_renderive_renderable_id()), composite_node_id_(allocate_renderive_node_id()), configuration_(configuration) {} -Renderable_Base::~Renderable_Base() { - real_time_data_state_->attached.store(false, std::memory_order_release); - real_time_data_state_->renderable_alive.store(false, std::memory_order_release); + +void Renderable_Base::Impl::initialize( + Renderable_Base& value, + Renderable_Configuration initial_configuration, + std::pmr::memory_resource& resource) noexcept { + owner_ = &value; + memory_resource_pointer = &resource; + configuration.store(initial_configuration, std::memory_order_relaxed); } -void Renderable_Base::invalidate_prepare() noexcept { - advance_revision(prepare_revision_); + +Renderable_Base& Renderable_Base::Impl::owner() noexcept { + return *owner_; } -void Renderable_Base::invalidate_paint() noexcept { - advance_revision(paint_revision_); + +const Renderable_Base& Renderable_Base::Impl::owner() const noexcept { + return *owner_; } -std::uint64_t Renderable_Base::prepare_revision() const noexcept { - return prepare_revision_.load(std::memory_order_acquire); + +void Renderable_Base::Impl::invalidate_prepare() noexcept { + advance_revision(prepare_revision); } -std::uint64_t Renderable_Base::prepared_revision() const noexcept { - return prepared_revision_.load(std::memory_order_acquire); + +void Renderable_Base::Impl::invalidate_paint() noexcept { + advance_revision(paint_revision); } -std::uint64_t Renderable_Base::paint_revision() const noexcept { - return paint_revision_.load(std::memory_order_acquire); -} -std::uint64_t Renderable_Base::painted_revision() const noexcept { - return painted_revision_.load(std::memory_order_acquire); -} -bool Renderable_Base::prepare_cache_valid() const noexcept { - const std::uint64_t revision = prepare_revision(); - return revision != std::numeric_limits::max() && configuration().cache_enabled && prepared_revision() == revision; -} -bool Renderable_Base::paint_cache_valid() const noexcept { - const std::uint64_t revision = paint_revision(); - return revision != std::numeric_limits::max() && configuration().cache_enabled && prepare_cache_valid() && painted_revision() == revision && painted_prepare_revision_.load(std::memory_order_acquire) == prepared_revision(); -} -void Renderable_Base::bind_scene(Scene_Base& scene) { - if (!real_time_data_state_->scene_lifetime) - real_time_data_state_->scene_lifetime = scene.scene_lifetime_; - else if (real_time_data_state_->scene_lifetime.get() != scene.scene_lifetime_.get()) + +void Renderable_Base::Impl::bind_scene( + std::shared_ptr lifetime) { + auto& current = real_time_data_state->scene_lifetime; + if (!current) + current = std::move(lifetime); + else if (current != lifetime) throw std::invalid_argument("renderable belongs to another scene"); } -void Renderable_Base::rebuild_render_graph() { + +void Renderable_Base::Impl::rebuild_render_graph() { if (!attached()) { reset_render_graph(); return; } - scene().request_render_graph_rebuild(*this); + scene().request_render_graph_rebuild(owner()); } -void Renderable_Base::reset_render_graph() noexcept { - { - std::lock_guard lock(render_graph_mutex_); - render_graph_.reset(); - } + +void Renderable_Base::Impl::reset_render_graph() noexcept { + render_graph.reset(); invalidate_prepare(); } -std::shared_ptr Renderable_Base::render_graph() { - std::lock_guard lock(render_graph_mutex_); - if (!render_graph_) { + +std::shared_ptr +Renderable_Base::Impl::render_graph_snapshot() { + if (!render_graph) { std::unordered_map next_identities; - Renderable_Graph_Builder builder(renderable_id_, [this, &next_identities](std::string_view key) { - const std::string logical_key(key); - if (next_identities.contains(logical_key)) - throw std::invalid_argument("duplicate render node logical key"); - const auto current = node_identities_.find(logical_key); - const Render_Node_Id node_id = current == node_identities_.end() ? allocate_renderive_node_id() : current->second; - next_identities.emplace(logical_key, node_id); - return node_id; - }); + Renderable_Graph_Builder builder( + renderable_id, + [this, &next_identities](std::string_view key) { + const std::string logical_key(key); + if (next_identities.contains(logical_key)) + throw std::invalid_argument( + "duplicate render node logical key"); + const auto current = node_identities.find(logical_key); + const Render_Node_Id node_id = + current == node_identities.end() + ? allocate_renderive_node_id() + : current->second; + next_identities.emplace(logical_key, node_id); + return node_id; + }); build_prepare_graph(builder); build_paint_graph(builder); - auto graph = std::make_shared(std::move(builder).finish()); - node_identities_ = std::move(next_identities); - render_graph_ = std::move(graph); + render_graph = std::make_shared( + std::move(builder).finish()); + node_identities = std::move(next_identities); } - return render_graph_; + return render_graph; } -Renderable_Id Renderable_Base::renderable_id() const noexcept { - return renderable_id_; + +std::pmr::memory_resource& +Renderable_Base::Impl::memory_resource() const noexcept { + return *memory_resource_pointer; } -std::pmr::memory_resource& Renderable_Base::memory_resource() const noexcept { - return *memory_resource_; -} -Scene_Base& Renderable_Base::scene() const { - const auto lifetime = real_time_data_state_->scene_lifetime; + +Scene_Base& Renderable_Base::Impl::scene() const { + const auto lifetime = real_time_data_state->scene_lifetime; if (!lifetime) throw std::logic_error("renderable is not bound to a scene"); auto lease = lifetime->acquire(); @@ -99,84 +120,141 @@ Scene_Base& Renderable_Base::scene() const { throw std::logic_error("renderable scene is no longer alive"); return lease.scene(); } -bool Renderable_Base::has_scene() const noexcept { - return static_cast(real_time_data_state_->scene_lifetime); + +bool Renderable_Base::Impl::attached() const noexcept { + return real_time_data_state->attached.load(std::memory_order_acquire); } -bool Renderable_Base::attached() const noexcept { - return real_time_data_state_->attached.load(std::memory_order_acquire); + +void Renderable_Base::Impl::set_configuration( + Renderable_Configuration value) { + const Renderable_Configuration previous = + configuration.load(std::memory_order_acquire); + if (previous == value) + return; + configuration.store(value, std::memory_order_release); + invalidate_prepare(); + notify_scene_model_dirty(); } -Renderable_Configuration Renderable_Base::configuration() const noexcept { - std::lock_guard lock(configuration_mutex_); - return configuration_; -} -void Renderable_Base::set_configuration(Renderable_Configuration configuration) { - bool changed{}; - { - std::lock_guard lock(configuration_mutex_); - changed = configuration_ != configuration; - if (changed) - configuration_ = configuration; - } - if (!changed) + +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(); } -bool Renderable_Base::is_visible() const noexcept { - std::lock_guard lock(configuration_mutex_); - return visible_; + +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::set_visible(bool visible) { - { - std::lock_guard lock(configuration_mutex_); - if (visible_ == visible) - return; - visible_ = visible; - invalidate_prepare(); - } - notify_scene_model_dirty(); -} -void Renderable_Base::notify_scene_model_dirty() noexcept { - if (!attached()) + +void Renderable_Base::Impl::notify_scene_model_dirty() noexcept { + const auto& state = real_time_data_state; + if (!state->attached.load(std::memory_order_acquire) || + !state->scene_lifetime) return; - const auto lifetime = real_time_data_state_->scene_lifetime; - if (!lifetime) - return; - auto lease = lifetime->acquire(); + auto lease = state->scene_lifetime->acquire(); if (lease) lease.scene().notify_model_dirty(); } -void Renderable_Base::build_prepare_graph(Renderable_Graph_Builder& builder) { - builder.emplace("prepare", "Prepare", [this](const Prepare_Render_Context& context) { - prepare(context); - }); + +void Renderable_Base::Impl::build_prepare_graph( + Renderable_Graph_Builder& builder) { + builder.emplace("prepare", "Prepare", + [this](const Prepare_Render_Context& context) { + execute_prepare(context); + }); } -void Renderable_Base::build_paint_graph(Renderable_Graph_Builder&) {} -void Renderable_Base::prepare(const Prepare_Render_Context&) {} -Render_State_View Renderable_Base::render_state_view() noexcept { + +void Renderable_Base::Impl::build_paint_graph( + Renderable_Graph_Builder&) {} + +void Renderable_Base::Impl::prepare(const Prepare_Render_Context&) {} + +void Renderable_Base::Impl::add_prepare_action(Prepare_Action action) { + if (!action) + throw std::invalid_argument("renderable prepare action is empty"); + prepare_actions.push_back(std::move(action)); +} + +void Renderable_Base::Impl::execute_prepare( + const Prepare_Render_Context& context) { + for (const auto& action : prepare_actions) + action(); + prepare(context); +} + +Render_State_View Renderable_Base::Impl::render_state_view() noexcept { return {}; } -Real_Time_Data_Binding Renderable_Base::bind_real_time_data(Real_Time_Data_Base& data) { - return data.bind_renderable(*this); + +Real_Time_Data_Binding Renderable_Base::Impl::bind_real_time_data( + Real_Time_Data_Base& data) { + return data.bind_renderable(owner()); } -void Renderable_Base::mark_prepared(std::uint64_t revision) noexcept { - prepared_revision_.store(revision, std::memory_order_release); + +void Renderable_Base::Impl::mark_prepared(std::uint64_t revision) noexcept { + prepared_revision.store(revision, std::memory_order_release); } -void Renderable_Base::mark_painted(std::uint64_t paint_revision, std::uint64_t prepare_revision) noexcept { - painted_prepare_revision_.store(prepare_revision, std::memory_order_release); - painted_revision_.store(paint_revision, std::memory_order_release); + +void Renderable_Base::Impl::mark_painted( + std::uint64_t current_paint_revision, + std::uint64_t current_prepare_revision) noexcept { + painted_prepare_revision.store(current_prepare_revision, + std::memory_order_release); + painted_revision.store(current_paint_revision, std::memory_order_release); } -void Renderable_Base::register_real_time_data(Real_Time_Data_Base& data) { - std::lock_guard lock(real_time_data_mutex_); - if (std::find(real_time_data_.begin(), real_time_data_.end(), &data) == real_time_data_.end()) - real_time_data_.push_back(&data); + +void Renderable_Base::Impl::register_real_time_data( + Real_Time_Data_Base& data) { + std::lock_guard lock(real_time_data_mutex); + if (std::find(real_time_data.begin(), real_time_data.end(), &data) == + real_time_data.end()) + real_time_data.push_back(&data); } -void Renderable_Base::unregister_real_time_data(Real_Time_Data_Base& data) noexcept { - std::lock_guard lock(real_time_data_mutex_); - std::erase(real_time_data_, &data); + +void Renderable_Base::Impl::unregister_real_time_data( + Real_Time_Data_Base& data) noexcept { + std::lock_guard lock(real_time_data_mutex); + std::erase(real_time_data, &data); } -void Renderable_Base::publish_real_time_data() { - std::lock_guard lock(real_time_data_mutex_); - for (Real_Time_Data_Base* data : real_time_data_) + +void Renderable_Base::Impl::publish_real_time_data() { + std::lock_guard lock(real_time_data_mutex); + for (Real_Time_Data_Base* data : real_time_data) data->publish_render_state(); } + +Renderable_Base::Renderable_Base(Renderable_Configuration configuration, + std::pmr::memory_resource& memory_resource) + : Renderable_Base(std::make_unique(), configuration, + memory_resource) {} + +Renderable_Base::Renderable_Base(std::unique_ptr implementation, + Renderable_Configuration configuration, + std::pmr::memory_resource& memory_resource) + : d_ptr(std::move(implementation)) { + if (!d_ptr) + throw std::invalid_argument("renderable implementation is null"); + d_ptr->initialize(*this, configuration, memory_resource); +} + +Renderable_Base::~Renderable_Base() = default; + +Renderable_Id Renderable_Base::renderable_id() const noexcept { + return d_func().renderable_id; +} + +bool Renderable_Base::is_visible() const noexcept { + return d_func().visible.load(std::memory_order_acquire); +} + +void Renderable_Base::set_visible(bool visible) { + d_func().set_visible(visible); +} + +void Renderable_Base::discard_stale_frame_on_latest_data_update( + bool enabled) noexcept { + d_func().discard_stale_frame_on_latest_data_update(enabled); +} diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp index 5109f20..39a432c 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp @@ -1,87 +1,53 @@ #pragma once -#include -#include + #include #include -#include -#include -#include -#include -#include + #include "renderive/renderable/Renderable_Configuration.hpp" -#include "renderive/renderable/Renderable_Graph_Builder.hpp" #include "renderive/renderable/Renderable_Id.hpp" -#include "renderive/scene/base/Scene_Lifetime.hpp" -#include "renderive/state/Render_State_View.hpp" + class Frame_Strategy_Real_Time_Data_Observer; class Real_Time_Data_Base; class Real_Time_Data_Binding; class Scene_Base; + class Renderable_Base { -private: - struct Real_Time_Data_State { - std::shared_ptr scene_lifetime; - std::atomic renderable_alive{true}; - std::atomic attached{}; - std::atomic discard_stale_frame_on_latest_data_update{}; - }; - std::pmr::memory_resource* memory_resource_; - std::shared_ptr real_time_data_state_; public: - explicit Renderable_Base(Renderable_Configuration configuration = {}, std::pmr::memory_resource& memory_resource = *std::pmr::get_default_resource()); virtual ~Renderable_Base(); - void invalidate_prepare() noexcept; - void invalidate_paint() noexcept; - [[nodiscard]] std::uint64_t prepare_revision() const noexcept; - [[nodiscard]] std::uint64_t prepared_revision() const noexcept; - [[nodiscard]] std::uint64_t paint_revision() const noexcept; - [[nodiscard]] std::uint64_t painted_revision() const noexcept; - [[nodiscard]] bool prepare_cache_valid() const noexcept; - [[nodiscard]] bool paint_cache_valid() const noexcept; + [[nodiscard]] Renderable_Id renderable_id() const noexcept; - std::pmr::memory_resource& memory_resource() const noexcept; - Scene_Base& scene() const; - [[nodiscard]] bool has_scene() const noexcept; - [[nodiscard]] bool attached() const noexcept; - Renderable_Configuration configuration() const noexcept; - void set_configuration(Renderable_Configuration configuration); [[nodiscard]] bool is_visible() const noexcept; void set_visible(bool visible); - std::atomic& discard_stale_frame_on_latest_data_update; + void discard_stale_frame_on_latest_data_update(bool enabled) noexcept; + protected: - virtual void build_prepare_graph(Renderable_Graph_Builder& builder); - virtual void build_paint_graph(Renderable_Graph_Builder& builder); - virtual void prepare(const Prepare_Render_Context& context); - void rebuild_render_graph(); - [[nodiscard]] std::shared_ptr render_graph(); - [[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() noexcept; + class Impl; + + explicit Renderable_Base( + Renderable_Configuration configuration = {}, + std::pmr::memory_resource& memory_resource = + *std::pmr::get_default_resource()); + explicit Renderable_Base( + std::unique_ptr implementation, + Renderable_Configuration configuration = {}, + std::pmr::memory_resource& memory_resource = + *std::pmr::get_default_resource()); + + template + Implementation& d_func() noexcept { + return static_cast(*d_ptr); + } + + template + const Implementation& d_func() const noexcept { + return static_cast(*d_ptr); + } + private: friend class Frame_Strategy_Real_Time_Data_Observer; friend class Real_Time_Data_Base; friend class Real_Time_Data_Binding; friend class Scene_Base; - void bind_scene(Scene_Base& scene); - void mark_prepared(std::uint64_t revision) noexcept; - void mark_painted(std::uint64_t paint_revision, std::uint64_t prepare_revision) noexcept; - void register_real_time_data(Real_Time_Data_Base& data); - void unregister_real_time_data(Real_Time_Data_Base& data) noexcept; - void publish_real_time_data(); - void reset_render_graph() noexcept; - const Renderable_Id renderable_id_; - const Render_Node_Id composite_node_id_; - mutable std::mutex configuration_mutex_; - Renderable_Configuration configuration_; - bool visible_{true}; - std::mutex render_graph_mutex_; - std::shared_ptr render_graph_; - std::unordered_map node_identities_; - std::atomic prepare_revision_{1}; - std::atomic prepared_revision_{}; - std::atomic paint_revision_{1}; - std::atomic painted_revision_{}; - std::atomic painted_prepare_revision_{}; - std::mutex real_time_data_mutex_; - std::vector real_time_data_; + + std::unique_ptr d_ptr; }; diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp new file mode 100644 index 0000000..c01f432 --- /dev/null +++ b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "Renderable_Base.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "renderive/renderable/Renderable_Graph_Builder.hpp" +#include "renderive/scene/base/Scene_Lifetime.hpp" +#include "renderive/state/Render_State_View.hpp" + +class Renderable_Base::Impl { +public: + struct Real_Time_Data_State { + std::shared_ptr scene_lifetime; + std::atomic renderable_alive{true}; + std::atomic attached{}; + std::atomic discard_stale_frame_on_latest_data_update{}; + }; + + using Prepare_Action = std::function; + + Impl(); + virtual ~Impl(); + + void initialize(Renderable_Base& owner, + Renderable_Configuration initial_configuration, + std::pmr::memory_resource& resource) noexcept; + void invalidate_prepare() noexcept; + void invalidate_paint() noexcept; + void set_configuration(Renderable_Configuration value); + void set_visible(bool value); + void discard_stale_frame_on_latest_data_update(bool enabled) noexcept; + [[nodiscard]] std::pmr::memory_resource& memory_resource() const noexcept; + [[nodiscard]] Scene_Base& scene() const; + [[nodiscard]] bool attached() const noexcept; + void rebuild_render_graph(); + [[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() noexcept; + void bind_scene(std::shared_ptr lifetime); + [[nodiscard]] std::shared_ptr render_graph_snapshot(); + void mark_prepared(std::uint64_t revision) noexcept; + void mark_painted(std::uint64_t paint_revision, + std::uint64_t prepare_revision) noexcept; + void register_real_time_data(Real_Time_Data_Base& data); + void unregister_real_time_data(Real_Time_Data_Base& data) noexcept; + void publish_real_time_data(); + void reset_render_graph() noexcept; + void add_prepare_action(Prepare_Action action); + void execute_prepare(const Prepare_Render_Context& context); + +protected: + [[nodiscard]] Renderable_Base& owner() noexcept; + [[nodiscard]] const Renderable_Base& owner() const noexcept; + virtual void build_prepare_graph(Renderable_Graph_Builder& builder); + virtual void build_paint_graph(Renderable_Graph_Builder& builder); + virtual void prepare(const Prepare_Render_Context& context); + +private: + Renderable_Base* owner_{}; + +public: + + std::pmr::memory_resource* memory_resource_pointer{ + std::pmr::get_default_resource()}; + std::shared_ptr real_time_data_state{ + std::make_shared()}; + const Renderable_Id renderable_id; + const Render_Node_Id composite_node_id; + std::atomic configuration; + std::atomic visible{true}; + std::shared_ptr render_graph; + std::unordered_map node_identities; + std::atomic prepare_revision{1}; + std::atomic prepared_revision{}; + std::atomic paint_revision{1}; + std::atomic painted_revision{}; + std::atomic painted_prepare_revision{}; + std::mutex real_time_data_mutex; + std::vector real_time_data; + std::vector prepare_actions; +}; diff --git a/Kernel/src/renderive/renderable/concept/Renderable.hpp b/Kernel/src/renderive/renderable/concept/Renderable.hpp index 9dd3d95..d24d58d 100644 --- a/Kernel/src/renderive/renderable/concept/Renderable.hpp +++ b/Kernel/src/renderive/renderable/concept/Renderable.hpp @@ -2,9 +2,4 @@ #include #include "renderive/renderable/base/Renderable_Base.hpp" template -concept Renderable = std::derived_from && requires(That& renderable, const That& const_renderable) { - { const_renderable.scene() } -> std::same_as; - { const_renderable.configuration() } -> std::same_as; - { renderable.invalidate_prepare() } -> std::same_as; - { renderable.invalidate_paint() } -> std::same_as; -}; +concept Renderable = std::derived_from; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index fce7b90..debe032 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -17,6 +17,7 @@ #include "renderive/renderable/Render_Frame_Completion.hpp" #include "renderive/renderable/Renderive_Id_Allocator.hpp" +#include "renderive/renderable/base/Renderable_Base_p.hpp" #include "renderive/renderable/color/Color_Cache.hpp" #include "renderive/state/base/State_Strategy_Base.hpp" @@ -360,8 +361,9 @@ void Scene_Base::enqueue_renderable_edit_locked(Renderable_Edit edit) { void Scene_Base::apply_attach_renderable_locked(Renderable renderable) { if (!renderable) throw std::invalid_argument("renderable is null"); - renderable->bind_scene(*this); - const Renderable_Id id = renderable->renderable_id(); + renderable->d_func().bind_scene(scene_lifetime_); + auto& renderable_data = renderable->d_func(); + const Renderable_Id id = renderable_data.renderable_id; if (const auto existing = renderables_.find(id); existing != renderables_.end()) { if (existing->second != renderable) throw std::logic_error("renderable id is already attached"); @@ -381,8 +383,9 @@ void Scene_Base::apply_attach_renderable_locked(Renderable renderable) { renderables_.erase(id); throw; } - renderable->real_time_data_state_->attached.store(true, std::memory_order_release); - renderable->invalidate_prepare(); + renderable_data.real_time_data_state->attached.store( + true, std::memory_order_release); + renderable_data.invalidate_prepare(); notify_model_dirty(); } void Scene_Base::apply_detach_renderable_locked(const Renderable& renderable) { @@ -391,8 +394,10 @@ void Scene_Base::apply_detach_renderable_locked(const Renderable& renderable) { validate_renderable_scene(*renderable); if (!is_renderable_attached_locked(renderable)) return; - const Renderable_Id id = renderable->renderable_id(); - renderable->real_time_data_state_->attached.store(false, std::memory_order_release); + auto& renderable_data = renderable->d_func(); + const Renderable_Id id = renderable_data.renderable_id; + renderable_data.real_time_data_state->attached.store( + false, std::memory_order_release); color_caches_.erase(id); renderables_.erase(id); notify_model_dirty(); @@ -402,7 +407,8 @@ void Scene_Base::apply_set_display_parent_locked(const Renderable& child, const throw std::invalid_argument("display relationship endpoint is null"); validate_renderable_scene(*child); validate_renderable_scene(*parent); - if (display_topology_.replace_parents(child->renderable_id(), {parent->renderable_id()})) + if (display_topology_.replace_parents(child->d_func().renderable_id, + {parent->d_func().renderable_id})) notify_model_dirty(); } void Scene_Base::apply_add_display_parent_locked(const Renderable& child, const Renderable& parent) { @@ -410,14 +416,15 @@ void Scene_Base::apply_add_display_parent_locked(const Renderable& child, const throw std::invalid_argument("display relationship endpoint is null"); validate_renderable_scene(*child); validate_renderable_scene(*parent); - if (display_topology_.add_parent(child->renderable_id(), parent->renderable_id())) + if (display_topology_.add_parent(child->d_func().renderable_id, + parent->d_func().renderable_id)) notify_model_dirty(); } void Scene_Base::apply_clear_display_parent_locked(const Renderable& child) { if (!child) throw std::invalid_argument("display child is null"); validate_renderable_scene(*child); - if (display_topology_.clear_parents(child->renderable_id())) + if (display_topology_.clear_parents(child->d_func().renderable_id)) notify_model_dirty(); } void Scene_Base::apply_set_dependency_parent_locked(const Renderable& child, const Renderable& parent) { @@ -425,9 +432,11 @@ void Scene_Base::apply_set_dependency_parent_locked(const Renderable& child, con throw std::invalid_argument("dependency relationship endpoint is null"); validate_renderable_scene(*child); validate_renderable_scene(*parent); - if (!dependency_topology_.replace_parents(child->renderable_id(), {parent->renderable_id()})) + if (!dependency_topology_.replace_parents( + child->d_func().renderable_id, + {parent->d_func().renderable_id})) return; - child->invalidate_prepare(); + child->d_func().invalidate_prepare(); notify_model_dirty(); } void Scene_Base::apply_add_dependency_parent_locked(const Renderable& child, const Renderable& parent) { @@ -435,18 +444,19 @@ void Scene_Base::apply_add_dependency_parent_locked(const Renderable& child, con throw std::invalid_argument("dependency relationship endpoint is null"); validate_renderable_scene(*child); validate_renderable_scene(*parent); - if (!dependency_topology_.add_parent(child->renderable_id(), parent->renderable_id())) + if (!dependency_topology_.add_parent(child->d_func().renderable_id, + parent->d_func().renderable_id)) return; - child->invalidate_prepare(); + child->d_func().invalidate_prepare(); notify_model_dirty(); } void Scene_Base::apply_clear_dependency_parent_locked(const Renderable& child) { if (!child) throw std::invalid_argument("dependency child is null"); validate_renderable_scene(*child); - if (!dependency_topology_.clear_parents(child->renderable_id())) + if (!dependency_topology_.clear_parents(child->d_func().renderable_id)) return; - child->invalidate_prepare(); + child->d_func().invalidate_prepare(); notify_model_dirty(); } void Scene_Base::cleanup_detached_topology_locked() { @@ -467,7 +477,8 @@ void Scene_Base::validate_structure_locked() { if (!renderable) throw std::logic_error("scene contains null renderable owner"); validate_renderable_scene(*renderable); - if (!renderable->attached()) + if (!renderable->d_func().real_time_data_state->attached.load( + std::memory_order_acquire)) throw std::logic_error("scene contains renderable whose attached state is false"); if (!display_topology_.contains(id)) throw std::logic_error("display topology is missing attached renderable " + std::to_string(id)); @@ -502,33 +513,35 @@ void Scene_Base::validate_structure_locked() { std::abort(); } void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) { - if (!renderable.attached()) { - renderable.reset_render_graph(); + auto& renderable_data = renderable.d_func(); + if (!renderable_data.real_time_data_state->attached.load( + std::memory_order_acquire)) { + renderable_data.reset_render_graph(); return; } validate_renderable_scene(renderable); if (active_renderable_edit_scene_ == this) { - renderable.reset_render_graph(); + renderable_data.reset_render_graph(); notify_model_dirty(); return; } Renderable owner; { std::lock_guard lock(model_mutex_); - const auto iterator = renderables_.find(renderable.renderable_id()); + const auto iterator = renderables_.find(renderable_data.renderable_id); if (iterator == renderables_.end() || iterator->second.get() != &renderable) return; owner = iterator->second; } std::lock_guard lock(task_mutex_); if (!runtime_started_) { - owner->reset_render_graph(); + owner->d_func().reset_render_graph(); notify_model_dirty(); return; } enqueue_renderable_edit_locked([owner = std::move(owner)](Renderable_Editor&) { - owner->reset_render_graph(); - owner->notify_scene_model_dirty(); + owner->d_func().reset_render_graph(); + owner->d_func().notify_scene_model_dirty(); }); } void Scene_Base::publish_frame_state() { @@ -547,7 +560,7 @@ void Scene_Base::publish_frame_state() { for (const Renderable& renderable : renderables) { if (auto* state = dynamic_cast(renderable.get())) state->publish(); - renderable->publish_real_time_data(); + renderable->d_func().publish_real_time_data(); } } @@ -555,6 +568,15 @@ void Scene_Base::notify_model_dirty() noexcept { model_dirty_.store(true, std::memory_order_release); } +void Scene_Base::invalidate_renderables() { + std::lock_guard lock(model_mutex_); + for (const auto& [id, renderable] : renderables_) { + static_cast(id); + renderable->d_func().invalidate_prepare(); + } + notify_model_dirty(); +} + std::size_t Scene_Base::renderable_count() const { std::lock_guard lock(model_mutex_); return renderables_.size(); @@ -602,19 +624,23 @@ std::shared_ptr Scene_Base::snapshot_live_model() { snapshot->renderables.reserve(dependency_order.size()); for (const Renderable_Id id : dependency_order) { const Renderable& owner = renderables_.at(id); + auto& renderable_data = owner->d_func(); Renderable_Frame_State state; state.renderable_id = id; - { - std::lock_guard configuration_lock(owner->configuration_mutex_); - state.visible = owner->visible_; - state.configuration = owner->configuration_; - state.prepare_revision = owner->prepare_revision(); - state.prepared_revision = owner->prepared_revision(); - state.paint_revision = owner->paint_revision(); - state.painted_revision = owner->painted_revision(); - state.painted_prepare_revision = - owner->painted_prepare_revision_.load(std::memory_order_acquire); - } + state.visible = renderable_data.visible.load(std::memory_order_acquire); + state.configuration = renderable_data.configuration.load( + std::memory_order_acquire); + state.prepare_revision = renderable_data.prepare_revision.load( + std::memory_order_acquire); + state.prepared_revision = renderable_data.prepared_revision.load( + std::memory_order_acquire); + state.paint_revision = renderable_data.paint_revision.load( + std::memory_order_acquire); + state.painted_revision = renderable_data.painted_revision.load( + std::memory_order_acquire); + state.painted_prepare_revision = + renderable_data.painted_prepare_revision.load( + std::memory_order_acquire); const bool prepare_valid = state.prepare_revision != std::numeric_limits::max() && state.configuration.cache_enabled && @@ -625,7 +651,7 @@ std::shared_ptr Scene_Base::snapshot_live_model() { state.painted_prepare_revision == state.prepared_revision; state.prepare_required = !prepare_valid; state.paint_required = !paint_valid; - state.render_graph = owner->render_graph(); + state.render_graph = renderable_data.render_graph_snapshot(); const auto& parents = dependency_topology_.node(id).parents; state.dependency_parent_ids.assign(parents.begin(), parents.end()); state.owner_ = owner; @@ -809,7 +835,8 @@ void Scene_Base::shutdown() noexcept { std::lock_guard lock(model_mutex_); for (const auto& [id, renderable] : renderables_) { static_cast(id); - renderable->real_time_data_state_->attached.store(false, std::memory_order_release); + renderable->d_func().real_time_data_state->attached.store( + false, std::memory_order_release); } } scene_lifetime_->invalidate(); @@ -1017,18 +1044,20 @@ std::shared_ptr Scene_Base::compile_render_plan( for (const Renderable_Id id : snapshot.display_order) { const std::size_t index = indices.at(id); const auto& state = snapshot.renderables[index]; - graph.nodes.push_back({state.owner_->composite_node_id_, id, + const Render_Node_Id composite_node_id = + state.owner_->d_func().composite_node_id; + graph.nodes.push_back({composite_node_id, id, "Composite", Render_Node_Kind::composite, 0}); - functions.emplace(state.owner_->composite_node_id_, + functions.emplace(composite_node_id, Execution_Binding{state.owner_, index, Composite_Render_Node_Function{ [compositor](const Composite_Render_Context& context) { compositor->composite(context); }}}); - append_edge(previous, state.owner_->composite_node_id_); - previous = state.owner_->composite_node_id_; + append_edge(previous, composite_node_id); + previous = composite_node_id; for (const Render_Node_Id paint : active[index].paint_terminals) - append_edge(paint, state.owner_->composite_node_id_); + append_edge(paint, composite_node_id); } } @@ -1152,10 +1181,10 @@ void Scene_Base::execute_taskflow(Render_Task& task) { for (const auto& state : snapshot.renderables) { if (state.prepare_required) - state.owner_->mark_prepared(state.prepare_revision); + state.owner_->d_func().mark_prepared(state.prepare_revision); if (state.paint_required) - state.owner_->mark_painted(state.paint_revision, - state.prepare_revision); + state.owner_->d_func().mark_painted(state.paint_revision, + state.prepare_revision); } const auto completed = frame->complete_render( capture ? render_clock_now_ns() : 0); @@ -1185,7 +1214,7 @@ void Scene_Base::execute_taskflow(Render_Task& task) { void Scene_Base::validate_renderable_scene( const Renderable_Base& renderable) const { - if (renderable.real_time_data_state_->scene_lifetime.get() != + if (renderable.d_func().real_time_data_state->scene_lifetime.get() != scene_lifetime_.get()) throw std::invalid_argument("renderable belongs to another scene"); } @@ -1194,9 +1223,7 @@ bool Scene_Base::is_renderable_attached_locked( const Renderable& renderable) const { if (!renderable) return false; - const auto iterator = renderables_.find(renderable->renderable_id()); + const auto iterator = renderables_.find(renderable->d_func().renderable_id); return iterator != renderables_.end() && iterator->second.get() == renderable.get(); } - - diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index 69041e4..ef12b32 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -20,6 +20,7 @@ #include "renderive/base/memory/Memory_Resource.hpp" #include "renderive/capture/Capture.hpp" #include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp" +#include "renderive/renderable/Renderable_Graph_Builder.hpp" #include "renderive/renderable/base/Renderable_Base.hpp" #include "renderive/renderable/base/Renderive_Owner.hpp" #include "renderive/render_graph/Render_Plan.hpp" @@ -164,6 +165,7 @@ protected: std::unique_lock lock_render_idle(); [[nodiscard]] bool is_render_worker_thread() const noexcept; bool consume_model_dirty() noexcept; + void invalidate_renderables(); void shutdown() noexcept; private: diff --git a/render_2D/plottable/Afterglow.cpp b/render_2D/plottable/Afterglow.cpp index 1a5ccdd..880ca6a 100644 --- a/render_2D/plottable/Afterglow.cpp +++ b/render_2D/plottable/Afterglow.cpp @@ -3,8 +3,10 @@ #include "Plottable_Real_Time_Data.h" #include "../render/Blend2D_Cache.h" #include "../renderable/Render_Partition.h" +#include "../renderable/Renderable_p.h" #include #include +#include namespace renderive::detail { namespace { using Afterglow_History = Plottable_History_Real_Time_Data, std::deque>>; @@ -22,40 +24,45 @@ struct Afterglow_Prepare_Buffer { bool valid{}; }; } -struct Afterglow_Control::Impl { - Impl(Afterglow_Control& owner, renderive_Owner frequency, renderive_Owner power) - : frequency_axis(std::move(frequency)), power_axis(std::move(power)), history(owner) {} +struct Afterglow_Control::Impl : Renderable::Impl { + Impl(renderive_Owner frequency, + renderive_Owner power) + : frequency_axis(std::move(frequency)), + power_axis(std::move(power)) {} renderive_Owner frequency_axis; renderive_Owner power_axis; - Afterglow_History history; + std::optional history; Adaptive_Render_Partitioner partitioner; Afterglow_Prepare_Buffer prepare_buffer; }; Afterglow_Control::Afterglow_Control(const Afterglow_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(frequency_axis), std::move(power_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(frequency_axis), std::move(power_axis))) { + d_func().history.emplace(*this); +} Afterglow_Control::~Afterglow_Control() = default; std::size_t Afterglow_Control::history_count() const { - return impl_->history.size(); + return d_func().history->size(); } std::size_t Afterglow_Control::latest_spectrum_point_count() const { - const auto history = impl_->history.snapshot(); + const auto history = d_func().history->snapshot(); return history.empty() ? 0 : history.back().size(); } std::size_t Afterglow_Control::rendered_cell_count() const { const auto state = properties(); - const auto history = impl_->history.snapshot(); + const auto history = d_func().history->snapshot(); if (history.empty()) return 0; const int width = std::min(state.frequency_point_size.get(), static_cast(history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() - : std::max(1, static_cast(impl_->power_axis->transform().pixel_length)); + : std::max(1, static_cast(d_func().power_axis->transform().pixel_length)); return width > 0 && height > 0 ? static_cast(width) * static_cast(height) : 0; } void Afterglow_Control::append_spectrum(std::span values) { if (get<&Afterglow_Properties::frequency_point_size>() <= 0) set<&Afterglow_Properties::frequency_point_size>(static_cast(values.size())); - impl_->history.update({values.begin(), values.end()}, 64); + d_func().history->update({values.begin(), values.end()}, 64); render_graph_changed(); } void Afterglow_Control::append_spectrum(std::pmr::vector&& values) { @@ -66,20 +73,20 @@ void Afterglow_Control::publish() { } void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& history = view.get(impl_->history); + const auto& history = view.get(*d_func().history); const int width = history.empty() ? 0 : std::min(state.frequency_point_size.get(), static_cast(history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() - : std::max(1, static_cast(impl_->power_axis->transform(view).pixel_length)); + : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); const std::size_t work_size = width > 0 && height > 0 ? static_cast(width) * static_cast(height) : 0; - const int partition_count = impl_->partitioner.graph_partition_count( + const int partition_count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( @@ -88,10 +95,10 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - impl_->prepare_buffer.history.size()); + d_func().prepare_buffer.history.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - impl_->prepare_buffer.work_size / - std::max(1, impl_->prepare_buffer.active_partitions)); + d_func().prepare_buffer.work_size / + std::max(1, d_func().prepare_buffer.active_partitions)); } }); std::vector accumulation; @@ -104,11 +111,11 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { accumulate_partition(index); if (context.metrics) { const auto range = render_partition_range( - static_cast(impl_->prepare_buffer.source_width), - index, impl_->prepare_buffer.active_partitions); + static_cast(d_func().prepare_buffer.source_width), + index, d_func().prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, (range.last - range.first) * - impl_->prepare_buffer.source_height); + d_func().prepare_buffer.source_height); } }); builder.precede(prepare, task); @@ -131,20 +138,20 @@ void Afterglow_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { } void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& history = view.get(impl_->history); + const auto& history = view.get(*d_func().history); const int width = history.empty() ? 0 : std::min(state.frequency_point_size.get(), static_cast(history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() - : std::max(1, static_cast(impl_->power_axis->transform(view).pixel_length)); + : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); const std::size_t work_size = width > 0 && height > 0 ? static_cast(width) * static_cast(height) : 0; - const int partition_count = impl_->partitioner.graph_partition_count( + const int partition_count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto paint_image = add_paint_task( @@ -153,7 +160,7 @@ void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { paint_render_frame(painter); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, - impl_->prepare_buffer.work_size); + d_func().prepare_buffer.work_size); }); if (partition_count == 0) { builder.precede(builder.find("prepare"), paint_image); @@ -166,8 +173,8 @@ void Afterglow_Control::build_paint_graph(Renderable_Graph_Builder& builder) { void Afterglow_Control::prepare_render_frame(const Render_State_View& view, int graph_partition_count) { const auto& state = render_properties(view); - const auto& history = view.get(impl_->history); - auto& output = impl_->prepare_buffer; + const auto& history = view.get(*d_func().history); + auto& output = d_func().prepare_buffer; output = {}; output.properties = state; output.history.assign(history.begin(), history.end()); @@ -176,11 +183,11 @@ void Afterglow_Control::prepare_render_frame(const Render_State_View& view, const int width = std::min(state.frequency_point_size.get(), static_cast(history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() - : std::max(1, static_cast(impl_->power_axis->transform(view).pixel_length)); + : std::max(1, static_cast(d_func().power_axis->transform(view).pixel_length)); if (width <= 0 || height <= 0) return; - const auto layout = axis_raster_layout(impl_->frequency_axis->transform(view), - impl_->power_axis->transform(view), + const auto layout = axis_raster_layout(d_func().frequency_axis->transform(view), + d_func().power_axis->transform(view), state.frequency_range, state.power_range, width, height); if (!layout.valid()) @@ -191,13 +198,13 @@ void Afterglow_Control::prepare_render_frame(const Render_State_View& view, output.work_size = static_cast(width) * height; output.intensity.resize(output.work_size); output.pixels.resize(output.work_size); - output.active_partitions = impl_->partitioner.begin(graph_partition_count, + output.active_partitions = d_func().partitioner.begin(graph_partition_count, output.work_size); output.valid = true; } void Afterglow_Control::accumulate_partition(int partition_index) { - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -226,14 +233,14 @@ void Afterglow_Control::accumulate_partition(int partition_index) { } void Afterglow_Control::normalize_render_frame() { - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; if (output.valid) output.maximum = std::max(1.0, *std::max_element(output.intensity.begin(), output.intensity.end())); } void Afterglow_Control::color_partition(int partition_index) { - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -248,7 +255,7 @@ void Afterglow_Control::color_partition(int partition_index) { } void Afterglow_Control::paint_render_frame(Painter& painter) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid) return; painter.heatmap(output.layout.target, output.layout.width, output.layout.height, @@ -257,11 +264,11 @@ void Afterglow_Control::paint_render_frame(Painter& painter) { void Afterglow_Control::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid || !is_visible()) return; const auto& state = output.properties; - if (impl_->partitioner.finish( + if (d_func().partitioner.finish( state.partition_mode, output.active_partitions, target_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), output.work_size, 4096)) diff --git a/render_2D/plottable/Afterglow.h b/render_2D/plottable/Afterglow.h index c4de2bf..620383a 100644 --- a/render_2D/plottable/Afterglow.h +++ b/render_2D/plottable/Afterglow.h @@ -39,7 +39,6 @@ protected: void build_paint_graph(Renderable_Graph_Builder& builder) override; private: struct Impl; - std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); void accumulate_partition(int partition_index); void normalize_render_frame(); diff --git a/render_2D/plottable/Constellation_Diagram.cpp b/render_2D/plottable/Constellation_Diagram.cpp index a9e3a2a..8425940 100644 --- a/render_2D/plottable/Constellation_Diagram.cpp +++ b/render_2D/plottable/Constellation_Diagram.cpp @@ -2,11 +2,13 @@ #include "Plottable_Real_Time_Data.h" #include "Heatmap_Utils.h" #include "../render/Blend2D_Cache.h" +#include "../renderable/Renderable_p.h" #include #include #include #include #include +#include namespace renderive { namespace detail { namespace { @@ -16,39 +18,42 @@ struct Timed_Point { }; using Constellation_History = Plottable_History_Real_Time_Data>; } -struct Constellation_Diagram_Control::Impl { +struct Constellation_Diagram_Control::Impl : Renderable::Impl { struct Prepare_Buffer { std::vector anchors; std::vector points; }; - Impl(Constellation_Diagram_Control& owner, renderive_Owner i, renderive_Owner q) - : i_axis(std::move(i)), q_axis(std::move(q)), points(owner) {} + Impl(renderive_Owner i, renderive_Owner q) + : i_axis(std::move(i)), q_axis(std::move(q)) {} renderive_Owner i_axis; renderive_Owner q_axis; - Constellation_History points; + std::optional points; Prepare_Buffer prepare_buffer; }; Constellation_Diagram_Control::Constellation_Diagram_Control(const Constellation_Diagram_Properties& properties, renderive_Owner i_axis, renderive_Owner q_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(i_axis), std::move(q_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(i_axis), std::move(q_axis))) { + d_func().points.emplace(*this); +} Constellation_Diagram_Control::~Constellation_Diagram_Control() = default; void Constellation_Diagram_Control::append_point(PointF point) { const auto now = std::chrono::steady_clock::now(); const int lifetime = get<&Constellation_Diagram_Properties::point_lifetime_ms>(); const auto cutoff = now - std::chrono::milliseconds(lifetime); const auto cutoff_ns = std::chrono::duration_cast(cutoff.time_since_epoch()).count(); - impl_->points.update({point, now}); - impl_->points.discard_before_time_ns(static_cast(cutoff_ns)); + d_func().points->update({point, now}); + d_func().points->discard_before_time_ns(static_cast(cutoff_ns)); changed(); } std::size_t Constellation_Diagram_Control::point_count() const { - return impl_->points.size(); + return d_func().points->size(); } void Constellation_Diagram_Control::fit_square_to_axes() { const auto state = properties(); const double side = std::max(state.i_range.size(), state.q_range.size()); - impl_->i_axis->set<&Axis_Properties::coordinates>( + d_func().i_axis->set<&Axis_Properties::coordinates>( Range{state.i_range.center() - side * 0.5, state.i_range.center() + side * 0.5}); - impl_->q_axis->set<&Axis_Properties::coordinates>( + d_func().q_axis->set<&Axis_Properties::coordinates>( Range{state.q_range.center() + side * 0.5, state.q_range.center() - side * 0.5}); } void Constellation_Diagram_Control::publish() { @@ -58,10 +63,10 @@ void Constellation_Diagram_Control::prepare_frame( const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; const auto& state = render_properties(view); - const auto& points = view.get(impl_->points); - const Axis_Transform x = impl_->i_axis->transform(view); - const Axis_Transform y = impl_->q_axis->transform(view); - auto& output = impl_->prepare_buffer; + const auto& points = view.get(*d_func().points); + const Axis_Transform x = d_func().i_axis->transform(view); + const Axis_Transform y = d_func().q_axis->transform(view); + auto& output = d_func().prepare_buffer; output = {}; const int count = static_cast(state.type); const double radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; @@ -81,7 +86,7 @@ void Constellation_Diagram_Control::prepare_frame( } void Constellation_Diagram_Control::paint(Painter& painter, const Paint_Render_Context& context) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; const auto& view = context.frame.render_state; const auto& state = render_properties(view); for (const PointF point : output.anchors) diff --git a/render_2D/plottable/Constellation_Diagram.h b/render_2D/plottable/Constellation_Diagram.h index 51eb466..295131d 100644 --- a/render_2D/plottable/Constellation_Diagram.h +++ b/render_2D/plottable/Constellation_Diagram.h @@ -29,7 +29,6 @@ protected: void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; - std::unique_ptr impl_; void publish() override; }; } diff --git a/render_2D/plottable/Frequency_Trace.cpp b/render_2D/plottable/Frequency_Trace.cpp index c11d565..e2d07c3 100644 --- a/render_2D/plottable/Frequency_Trace.cpp +++ b/render_2D/plottable/Frequency_Trace.cpp @@ -2,38 +2,44 @@ #include "Plottable_Real_Time_Data.h" #include "Heatmap_Utils.h" #include "../render/Blend2D_Cache.h" +#include "../renderable/Renderable_p.h" #include #include +#include #include namespace renderive { namespace detail { namespace { using Frequency_Trace_History = Plottable_History_Real_Time_Data, std::deque>>; } -struct Frequency_Trace_Control::Impl { +struct Frequency_Trace_Control::Impl : Renderable::Impl { struct Prepare_Buffer { std::vector points; }; - Impl(Frequency_Trace_Control& owner, renderive_Owner time, renderive_Owner value) - : time_axis(std::move(time)), value_axis(std::move(value)), samples(owner) {} + Impl(renderive_Owner time, renderive_Owner value) + : time_axis(std::move(time)), value_axis(std::move(value)) {} renderive_Owner time_axis; renderive_Owner value_axis; - Frequency_Trace_History samples; + std::optional samples; Prepare_Buffer prepare_buffer; }; Frequency_Trace_Control::Frequency_Trace_Control(const Frequency_Trace_Properties& properties, renderive_Owner time_axis, renderive_Owner value_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(time_axis), std::move(value_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(time_axis), std::move(value_axis))) { + d_func().samples.emplace(*this); +} Frequency_Trace_Control::~Frequency_Trace_Control() = default; void Frequency_Trace_Control::append_sample(int tick, double value) { - const int limit = std::max(2, impl_->time_axis->get<&Time_Axis_Properties::visible_count>()); - impl_->samples.update({tick, value}, static_cast(limit)); + auto& d = d_func(); + const int limit = std::max(2, d.time_axis->get<&Time_Axis_Properties::visible_count>()); + d.samples->update({tick, value}, static_cast(limit)); changed(); } void Frequency_Trace_Control::append_sample(Time_Of_Day time, double value) { - append_sample(impl_->time_axis->append_time(time), value); + append_sample(d_func().time_axis->append_time(time), value); } std::size_t Frequency_Trace_Control::sample_count() const { - return impl_->samples.size(); + return d_func().samples->size(); } std::size_t Frequency_Trace_Control::rendered_point_count() const { const std::size_t count = sample_count(); @@ -45,13 +51,14 @@ void Frequency_Trace_Control::publish() { void Frequency_Trace_Control::prepare_frame(const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; const auto& state = render_properties(view); - const auto& samples = view.get(impl_->samples); - auto& output = impl_->prepare_buffer; + auto& d = d_func(); + const auto& samples = view.get(*d.samples); + auto& output = d.prepare_buffer; output = {}; if (samples.size() < 2) return; - const Axis_Transform x = impl_->time_axis->transform(view); - const Axis_Transform y = impl_->value_axis->transform(view); + const Axis_Transform x = d.time_axis->transform(view); + const Axis_Transform y = d.value_axis->transform(view); output.points.reserve(samples.size()); for (const auto& [tick, value] : samples) output.points.push_back(mapped_point(x, tick, y, value)); @@ -59,7 +66,8 @@ void Frequency_Trace_Control::prepare_frame(const Prepare_Render_Context& contex void Frequency_Trace_Control::paint(Painter& painter, const Paint_Render_Context& context) { const auto& view = context.frame.render_state; - painter.polyline(impl_->prepare_buffer.points, render_properties(view).pen); + painter.polyline(d_func().prepare_buffer.points, + render_properties(view).pen); } } } diff --git a/render_2D/plottable/Frequency_Trace.h b/render_2D/plottable/Frequency_Trace.h index 2f745b6..57aa327 100644 --- a/render_2D/plottable/Frequency_Trace.h +++ b/render_2D/plottable/Frequency_Trace.h @@ -19,7 +19,6 @@ protected: void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; - std::unique_ptr impl_; void publish() override; }; } diff --git a/render_2D/plottable/Plottable.h b/render_2D/plottable/Plottable.h index 0150a14..5a37b76 100644 --- a/render_2D/plottable/Plottable.h +++ b/render_2D/plottable/Plottable.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace renderive { class Abs_Axis; @@ -64,6 +65,13 @@ public: : Base(properties, With_Observer{ State_Observer(Renderable_State_Observer(this))}) {} + template + Plottable_State(const Properties& properties, + std::unique_ptr implementation) + : Base(properties, + With_Observer{ + State_Observer(Renderable_State_Observer(this))}, + std::move(implementation), true) {} template Value> Plottable_State& set(Value&& value) { Base::template set(std::forward(value)); diff --git a/render_2D/plottable/Selection_Rectangle_Overlay.cpp b/render_2D/plottable/Selection_Rectangle_Overlay.cpp index 7a83828..7374e9f 100644 --- a/render_2D/plottable/Selection_Rectangle_Overlay.cpp +++ b/render_2D/plottable/Selection_Rectangle_Overlay.cpp @@ -2,9 +2,11 @@ #include "Plottable_Real_Time_Data.h" #include "Heatmap_Utils.h" #include "../render/Blend2D_Cache.h" +#include "../renderable/Renderable_p.h" #include #include #include +#include namespace renderive { namespace detail { namespace { @@ -19,7 +21,7 @@ RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& secon return mapped_rect(first, second, first.coordinate_range, second.coordinate_range); } } -struct Selection_Rectangle_Overlay_Control::Impl { +struct Selection_Rectangle_Overlay_Control::Impl : Renderable::Impl { struct Prepared_Region { RectF pixels; std::string label; @@ -29,34 +31,40 @@ struct Selection_Rectangle_Overlay_Control::Impl { RectF active_selection; bool selecting{}; }; - Impl(Selection_Rectangle_Overlay_Control& owner, renderive_Owner horizontal, renderive_Owner vertical) - : horizontal_axis(std::move(horizontal)), vertical_axis(std::move(vertical)), regions(owner) {} + Impl(renderive_Owner horizontal, + renderive_Owner vertical) + : horizontal_axis(std::move(horizontal)), + vertical_axis(std::move(vertical)) {} renderive_Owner horizontal_axis; renderive_Owner vertical_axis; - Plottable_History_Real_Time_Data> regions; + std::optional>> regions; Selection_Interaction_State interaction; Prepare_Buffer prepare_buffer; }; Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(const Selection_Rectangle_Overlay_Properties& properties, renderive_Owner horizontal_axis, renderive_Owner vertical_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(horizontal_axis), std::move(vertical_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(horizontal_axis), std::move(vertical_axis))) { + d_func().regions.emplace(*this); +} Selection_Rectangle_Overlay_Control::~Selection_Rectangle_Overlay_Control() = default; std::vector Selection_Rectangle_Overlay_Control::selected_regions() const { - return impl_->regions.snapshot(); + return d_func().regions->snapshot(); } void Selection_Rectangle_Overlay_Control::clear_selected_regions() { - impl_->regions.clear(); - impl_->interaction.update([](Selection_Interaction& interaction) { + d_func().regions->clear(); + d_func().interaction.update([](Selection_Interaction& interaction) { interaction.selecting = false; }); changed(); } void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { - const RectF content = axis_content_rect(impl_->horizontal_axis->transform(), impl_->vertical_axis->transform()); + const RectF content = axis_content_rect(d_func().horizontal_axis->transform(), d_func().vertical_axis->transform()); if(event.type == Event_Type::Pointer_Press) { const auto& pointer = static_cast(event); if(pointer.button != Mouse_Button::Left || !content.contains(pointer.position)) return; - impl_->interaction.update([&](Selection_Interaction& interaction) { + d_func().interaction.update([&](Selection_Interaction& interaction) { interaction.selecting = true; interaction.selection_start = interaction.selection_current = pointer.position; }); @@ -67,7 +75,7 @@ void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { if(event.type == Event_Type::Pointer_Move) { const auto& pointer = static_cast(event); bool selecting{}; - impl_->interaction.update([&](Selection_Interaction& interaction) { + d_func().interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; if(selecting) interaction.selection_current = pointer.position; @@ -83,7 +91,7 @@ void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { const auto& pointer = static_cast(event); PointF start; bool selecting{}; - impl_->interaction.update([&](Selection_Interaction& interaction) { + d_func().interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; if(!selecting) return; @@ -93,32 +101,32 @@ void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { }); if(!selecting) return; - const Axis_Transform horizontal = impl_->horizontal_axis->transform(); - const Axis_Transform vertical = impl_->vertical_axis->transform(); + const Axis_Transform horizontal = d_func().horizontal_axis->transform(); + const Axis_Transform vertical = d_func().vertical_axis->transform(); const double first_start = horizontal.point_to_coord(start); const double second_start = vertical.point_to_coord(start); const RectF region{first_start, second_start, horizontal.point_to_coord(pointer.position) - first_start, vertical.point_to_coord(pointer.position) - second_start}; if(std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9) - impl_->regions.update(region.normalized()); + d_func().regions->update(region.normalized()); event.accept(); changed(); } void Selection_Rectangle_Overlay_Control::publish() { publish_properties(); - impl_->interaction.publish(); + d_func().interaction.publish(); } void Selection_Rectangle_Overlay_Control::prepare_frame( const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; const auto& state = render_properties(view); - const auto& interaction = view.get(impl_->interaction); - const auto horizontal = impl_->horizontal_axis->transform(view); - const auto vertical = impl_->vertical_axis->transform(view); - auto& output = impl_->prepare_buffer; + const auto& interaction = view.get(d_func().interaction); + const auto horizontal = d_func().horizontal_axis->transform(view); + const auto vertical = d_func().vertical_axis->transform(view); + auto& output = d_func().prepare_buffer; output = {}; - for (const RectF& region : view.get(impl_->regions)) { + for (const RectF& region : view.get(*d_func().regions)) { const RectF pixels = mapped_rect(horizontal, vertical, {region.x, region.right()}, {region.y, region.bottom()}); @@ -135,7 +143,7 @@ void Selection_Rectangle_Overlay_Control::prepare_frame( } void Selection_Rectangle_Overlay_Control::paint( Painter& painter, const Paint_Render_Context& context) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; const auto& view = context.frame.render_state; const auto& state = render_properties(view); for (const auto& region : output.regions) { diff --git a/render_2D/plottable/Selection_Rectangle_Overlay.h b/render_2D/plottable/Selection_Rectangle_Overlay.h index 93675b2..8b38160 100644 --- a/render_2D/plottable/Selection_Rectangle_Overlay.h +++ b/render_2D/plottable/Selection_Rectangle_Overlay.h @@ -21,7 +21,6 @@ protected: void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; - std::unique_ptr impl_; void publish() override; }; } diff --git a/render_2D/plottable/Spectrum.cpp b/render_2D/plottable/Spectrum.cpp index eccdbfb..8675ba8 100644 --- a/render_2D/plottable/Spectrum.cpp +++ b/render_2D/plottable/Spectrum.cpp @@ -3,9 +3,11 @@ #include "Plottable_Real_Time_Data.h" #include "../render/Blend2D_Cache.h" #include "../renderable/Render_Partition.h" +#include "../renderable/Renderable_p.h" #include #include #include +#include #include namespace renderive::detail { namespace { @@ -136,25 +138,30 @@ double spectrum_power_at(const Spectrum_Properties& properties, const Spectrum_F return frame.samples[lower] * (1.0 - fraction) + frame.samples[upper] * fraction; } } -struct Spectrum_Control::Impl { - Impl(Spectrum_Control& owner, renderive_Owner frequency, renderive_Owner power) - : frequency_axis(std::move(frequency)), power_axis(std::move(power)), frame(owner) {} +struct Spectrum_Control::Impl : Renderable::Impl { + Impl(renderive_Owner frequency, + renderive_Owner power) + : frequency_axis(std::move(frequency)), + power_axis(std::move(power)) {} renderive_Owner frequency_axis; renderive_Owner power_axis; - Plottable_Latest_Real_Time_Data frame; + std::optional> frame; Spectrum_Interaction_State interaction; std::mutex frame_update_mutex; Adaptive_Render_Partitioner partitioner; Spectrum_Prepare_Buffer prepare_buffer; }; Spectrum_Control::Spectrum_Control(const Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(frequency_axis), std::move(power_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(frequency_axis), std::move(power_axis))) { + d_func().frame.emplace(*this); +} Spectrum_Control::~Spectrum_Control() = default; void Spectrum_Control::update_samples(std::span values) { if(get<&Spectrum_Properties::frequency_point_size>() <= 0) set<&Spectrum_Properties::frequency_point_size>(static_cast(values.size())); - std::lock_guard lock(impl_->frame_update_mutex); - Spectrum_Frame frame = impl_->frame.snapshot().value_or(Spectrum_Frame{}); + std::lock_guard lock(d_func().frame_update_mutex); + Spectrum_Frame frame = d_func().frame->snapshot().value_or(Spectrum_Frame{}); frame.samples.assign(values.begin(), values.end()); if(frame.maxima.size() != values.size()) frame.maxima.assign(values.begin(), values.end()); @@ -166,36 +173,36 @@ void Spectrum_Control::update_samples(std::span values) { else for(std::size_t index = 0; index < values.size(); ++index) frame.minima[index] = std::min(frame.minima[index], values[index]); - impl_->frame.update(std::move(frame)); + d_func().frame->update(std::move(frame)); render_graph_changed(); } void Spectrum_Control::update_samples(std::pmr::vector&& values) { update_samples(std::span(values.data(), values.size())); } std::size_t Spectrum_Control::sample_count() const { - const auto frame = impl_->frame.snapshot(); + const auto frame = d_func().frame->snapshot(); return frame ? frame->samples.size() : 0; } std::size_t Spectrum_Control::rendered_point_count() const { const auto state = properties(); - const auto frame = impl_->frame.snapshot(); - return frame ? curve_points(frame->samples, state.frequency_range, impl_->frequency_axis->transform(), impl_->power_axis->transform(), state.visible_range_only, state.interpolation_mode).size() : 0; + const auto frame = d_func().frame->snapshot(); + return frame ? curve_points(frame->samples, state.frequency_range, d_func().frequency_axis->transform(), d_func().power_axis->transform(), state.visible_range_only, state.interpolation_mode).size() : 0; } double Spectrum_Control::power_at(double frequency, bool& ok) const { const auto state = properties(); - const auto frame = impl_->frame.snapshot(); + const auto frame = d_func().frame->snapshot(); return frame ? spectrum_power_at(state, *frame, frequency, ok) : (ok = false, 0.0); } void Spectrum_Control::add_custom_marker(double frequency) { add_custom_line_marker(frequency); } void Spectrum_Control::add_custom_line_marker(double frequency) { - impl_->interaction.update([frequency](Spectrum_Interaction& interaction) { interaction.markers.push_back(frequency); }); + d_func().interaction.update([frequency](Spectrum_Interaction& interaction) { interaction.markers.push_back(frequency); }); changed(); } void Spectrum_Control::remove_custom_marker(double frequency) { bool removed{}; - impl_->interaction.update([&](Spectrum_Interaction& interaction) { + d_func().interaction.update([&](Spectrum_Interaction& interaction) { if(interaction.markers.empty()) return; auto closest = std::min_element(interaction.markers.begin(), interaction.markers.end(), [frequency](double left, double right) { return std::abs(left - frequency) < std::abs(right - frequency); }); @@ -212,7 +219,7 @@ void Spectrum_Control::remove_custom_marker(double frequency) { } void Spectrum_Control::remove_selected_marker() { bool removed{}; - impl_->interaction.update([&](Spectrum_Interaction& interaction) { + d_func().interaction.update([&](Spectrum_Interaction& interaction) { if(interaction.selected_marker < 0 || interaction.selected_marker >= static_cast(interaction.markers.size())) return; interaction.markers.erase(interaction.markers.begin() + interaction.selected_marker); @@ -223,24 +230,24 @@ void Spectrum_Control::remove_selected_marker() { changed(); } void Spectrum_Control::clear_custom_markers() { - impl_->interaction.update([](Spectrum_Interaction& interaction) { + d_func().interaction.update([](Spectrum_Interaction& interaction) { interaction.markers.clear(); interaction.selected_marker = -1; }); changed(); } int Spectrum_Control::selectable_line_marker_count() const { - return impl_->interaction.read([](const Spectrum_Interaction& interaction) { return static_cast(interaction.markers.size()); }); + return d_func().interaction.read([](const Spectrum_Interaction& interaction) { return static_cast(interaction.markers.size()); }); } int Spectrum_Control::selected_marker_index() const { - return impl_->interaction.get<&Spectrum_Interaction::selected_marker>(); + return d_func().interaction.get<&Spectrum_Interaction::selected_marker>(); } void Spectrum_Control::set_selected_marker_index(int index) { - impl_->interaction.update([index](Spectrum_Interaction& interaction) { interaction.selected_marker = index >= 0 && index < static_cast(interaction.markers.size()) ? index : -1; }); + d_func().interaction.update([index](Spectrum_Interaction& interaction) { interaction.selected_marker = index >= 0 && index < static_cast(interaction.markers.size()) ? index : -1; }); changed(); } void Spectrum_Control::select_next_marker() { - impl_->interaction.update([](Spectrum_Interaction& interaction) { + d_func().interaction.update([](Spectrum_Interaction& interaction) { if(interaction.markers.empty()) interaction.selected_marker = -1; else @@ -249,7 +256,7 @@ void Spectrum_Control::select_next_marker() { changed(); } void Spectrum_Control::select_previous_marker() { - impl_->interaction.update([](Spectrum_Interaction& interaction) { + d_func().interaction.update([](Spectrum_Interaction& interaction) { if(interaction.markers.empty()) interaction.selected_marker = -1; else @@ -261,11 +268,11 @@ void Spectrum_Control::clear_marker_selection() { set_selected_marker_index(-1); } double Spectrum_Control::marker_frequency(int index) const { - return impl_->interaction.read([index](const Spectrum_Interaction& interaction) { return index >= 0 && index < static_cast(interaction.markers.size()) ? interaction.markers[index] : 0.0; }); + return d_func().interaction.read([index](const Spectrum_Interaction& interaction) { return index >= 0 && index < static_cast(interaction.markers.size()) ? interaction.markers[index] : 0.0; }); } void Spectrum_Control::set_marker_frequency(int index, double frequency) { bool updated{}; - impl_->interaction.update([&](Spectrum_Interaction& interaction) { + d_func().interaction.update([&](Spectrum_Interaction& interaction) { if(index < 0 || index >= static_cast(interaction.markers.size())) return; interaction.markers[index] = frequency; @@ -281,23 +288,23 @@ void Spectrum_Control::set_current_marker_frequency(double frequency) { } void Spectrum_Control::handle_event(const Event& event) { bool updated{}; - impl_->interaction.update([&](Spectrum_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); + d_func().interaction.update([&](Spectrum_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); if(updated) changed(); } void Spectrum_Control::publish() { publish_properties(); - impl_->interaction.publish(); + d_func().interaction.publish(); } void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& published_frame = view.get(impl_->frame); + const auto& published_frame = view.get(*d_func().frame); const std::size_t work_size = published_frame && published_frame->samples.size() > 1 ? published_frame->samples.size() - 1 : 0; - const int partition_count = impl_->partitioner.graph_partition_count( + const int partition_count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); const auto prepare = add_prepare_task( @@ -306,10 +313,10 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - impl_->prepare_buffer.frame.samples.size()); + d_func().prepare_buffer.frame.samples.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - impl_->prepare_buffer.work_size / - std::max(1, impl_->prepare_buffer.active_partitions)); + d_func().prepare_buffer.work_size / + std::max(1, d_func().prepare_buffer.active_partitions)); } }); for (int index = 0; index < partition_count; ++index) { @@ -320,8 +327,8 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_partition(index); if (context.metrics) { const auto range = render_partition_range( - impl_->prepare_buffer.work_size, index, - impl_->prepare_buffer.active_partitions); + d_func().prepare_buffer.work_size, index, + d_func().prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, range.last - range.first); } @@ -331,13 +338,13 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { } void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& published_frame = view.get(impl_->frame); + const auto& published_frame = view.get(*d_func().frame); const std::size_t work_size = published_frame && published_frame->samples.size() > 1 ? published_frame->samples.size() - 1 : 0; - const int partition_count = impl_->partitioner.graph_partition_count( + const int partition_count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); const auto paint = add_paint_task( @@ -350,7 +357,7 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { paint_overlay(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::primitive_count, - impl_->prepare_buffer.work_size); + d_func().prepare_buffer.work_size); }); if (partition_count == 0) builder.precede(builder.find("prepare"), paint); @@ -362,20 +369,20 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) { void Spectrum_Control::prepare_render_frame(const Render_State_View& view, int graph_partition_count) { - const auto& published_frame = view.get(impl_->frame); - auto& output = impl_->prepare_buffer; + const auto& published_frame = view.get(*d_func().frame); + auto& output = d_func().prepare_buffer; output = {}; output.properties = render_properties(view); if (published_frame) output.frame = *published_frame; - output.interaction = view.get(impl_->interaction); - output.frequency_axis = impl_->frequency_axis->transform(view); - output.power_axis = impl_->power_axis->transform(view); + output.interaction = view.get(d_func().interaction); + output.frequency_axis = d_func().frequency_axis->transform(view); + output.power_axis = d_func().power_axis->transform(view); output.valid = axes_are_orthogonal(output.frequency_axis, output.power_axis); output.work_size = published_frame && published_frame->samples.size() > 1 ? published_frame->samples.size() - 1 : 0; - output.active_partitions = impl_->partitioner.begin(graph_partition_count, + output.active_partitions = d_func().partitioner.begin(graph_partition_count, output.work_size); output.partitions.resize(static_cast(graph_partition_count)); if (!output.valid) @@ -449,7 +456,7 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view, } void Spectrum_Control::prepare_partition(int partition_index) { - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -483,7 +490,7 @@ void Spectrum_Control::prepare_partition(int partition_index) { void Spectrum_Control::paint_partition(Painter& painter, int partition_index, const Render_State_View& view) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& partition = output.partitions[static_cast(partition_index)]; @@ -496,7 +503,7 @@ void Spectrum_Control::paint_partition(Painter& painter, int partition_index, void Spectrum_Control::paint_background(Painter& painter, const Render_State_View& view) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid || output.sweep_region.empty()) return; painter.rect(output.sweep_region, Pen{.style = Line_Style::None}, @@ -505,7 +512,7 @@ void Spectrum_Control::paint_background(Painter& painter, void Spectrum_Control::paint_overlay(Painter& painter, const Render_State_View& view) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid) return; const auto& paint_state = render_properties(view); @@ -534,11 +541,11 @@ void Spectrum_Control::paint_overlay(Painter& painter, void Spectrum_Control::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid || !is_visible()) return; const auto& state = output.properties; - if (impl_->partitioner.finish( + if (d_func().partitioner.finish( state.partition_mode, output.active_partitions, target_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), output.work_size, 128)) diff --git a/render_2D/plottable/Spectrum.h b/render_2D/plottable/Spectrum.h index 236f9ff..fb646ce 100644 --- a/render_2D/plottable/Spectrum.h +++ b/render_2D/plottable/Spectrum.h @@ -71,7 +71,6 @@ protected: void build_paint_graph(Renderable_Graph_Builder& builder) override; private: struct Impl; - std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); void prepare_partition(int partition_index); void paint_partition(Painter& painter, int partition_index, diff --git a/render_2D/plottable/Sweep_Spectrum.cpp b/render_2D/plottable/Sweep_Spectrum.cpp index 1cc108c..9de3eb5 100644 --- a/render_2D/plottable/Sweep_Spectrum.cpp +++ b/render_2D/plottable/Sweep_Spectrum.cpp @@ -2,9 +2,11 @@ #include "Curve_Sampling.h" #include "Plottable_Real_Time_Data.h" #include "../render/Blend2D_Cache.h" +#include "../renderable/Renderable_p.h" #include #include #include +#include namespace renderive { namespace detail { namespace { @@ -16,38 +18,41 @@ std::vector flatten(const std::deque>& blocks) { return values; } } -struct Sweep_Spectrum_Control::Impl { +struct Sweep_Spectrum_Control::Impl : Renderable::Impl { struct Prepare_Buffer { std::vector points; PointF current_first; PointF current_second; bool valid{}; }; - Impl(Sweep_Spectrum_Control& owner, renderive_Owner frequency, renderive_Owner power) - : frequency_axis(std::move(frequency)), power_axis(std::move(power)), blocks(owner) {} + Impl(renderive_Owner frequency, renderive_Owner power) + : frequency_axis(std::move(frequency)), power_axis(std::move(power)) {} renderive_Owner frequency_axis; renderive_Owner power_axis; - Sweep_Spectrum_History blocks; + std::optional blocks; Prepare_Buffer prepare_buffer; }; Sweep_Spectrum_Control::Sweep_Spectrum_Control(const Sweep_Spectrum_Properties& properties, renderive_Owner frequency_axis, renderive_Owner power_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(frequency_axis), std::move(power_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(frequency_axis), std::move(power_axis))) { + d_func().blocks.emplace(*this); +} Sweep_Spectrum_Control::~Sweep_Spectrum_Control() = default; void Sweep_Spectrum_Control::append_block(std::span values) { if(get<&Sweep_Spectrum_Properties::bins_per_block>() <= 0) set<&Sweep_Spectrum_Properties::bins_per_block>(static_cast(values.size())); const int limit = get<&Sweep_Spectrum_Properties::block_count>(); - impl_->blocks.update({values.begin(), values.end()}, static_cast(limit)); + d_func().blocks->update({values.begin(), values.end()}, static_cast(limit)); changed(); } void Sweep_Spectrum_Control::append_block(std::pmr::vector&& values) { append_block(std::span(values.data(), values.size())); } std::size_t Sweep_Spectrum_Control::stored_block_count() const { - return impl_->blocks.size(); + return d_func().blocks->size(); } std::size_t Sweep_Spectrum_Control::stored_point_count() const { - const auto blocks = impl_->blocks.snapshot(); + const auto blocks = d_func().blocks->snapshot(); std::size_t count{}; for(const auto& block : blocks) count += block.size(); @@ -55,25 +60,25 @@ std::size_t Sweep_Spectrum_Control::stored_point_count() const { } std::size_t Sweep_Spectrum_Control::rendered_point_count() const { const auto state = properties(); - const auto values = flatten(impl_->blocks.snapshot()); - return curve_points(values, state.frequency_range, impl_->frequency_axis->transform(), impl_->power_axis->transform(), state.visible_range_only, state.interpolation_mode).size(); + const auto values = flatten(d_func().blocks->snapshot()); + return curve_points(values, state.frequency_range, d_func().frequency_axis->transform(), d_func().power_axis->transform(), state.visible_range_only, state.interpolation_mode).size(); } void Sweep_Spectrum_Control::publish() { publish_properties(); const int limit = get<&Sweep_Spectrum_Properties::block_count>(); - impl_->blocks.retain_latest(static_cast(limit)); + d_func().blocks->retain_latest(static_cast(limit)); } void Sweep_Spectrum_Control::prepare_frame(const Prepare_Render_Context& context) { const auto& view = context.frame.render_state; const auto& state = render_properties(view); - const auto& blocks = view.get(impl_->blocks); + const auto& blocks = view.get(*d_func().blocks); const auto values = flatten(blocks); - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; output = {}; if (values.size() < 2) return; - const Axis_Transform x = impl_->frequency_axis->transform(view); - const Axis_Transform y = impl_->power_axis->transform(view); + const Axis_Transform x = d_func().frequency_axis->transform(view); + const Axis_Transform y = d_func().power_axis->transform(view); output.points = curve_points(values, state.frequency_range, x, y, state.visible_range_only, state.interpolation_mode); const double completed = std::min(1.0, static_cast(blocks.size()) / state.block_count.get()); @@ -84,7 +89,7 @@ void Sweep_Spectrum_Control::prepare_frame(const Prepare_Render_Context& context } void Sweep_Spectrum_Control::paint(Painter& painter, const Paint_Render_Context& context) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid) return; const auto& view = context.frame.render_state; diff --git a/render_2D/plottable/Sweep_Spectrum.h b/render_2D/plottable/Sweep_Spectrum.h index e8c6341..a1d5ac6 100644 --- a/render_2D/plottable/Sweep_Spectrum.h +++ b/render_2D/plottable/Sweep_Spectrum.h @@ -32,7 +32,6 @@ protected: void paint(Painter& painter, const Paint_Render_Context& context) override; private: struct Impl; - std::unique_ptr impl_; void publish() override; }; } diff --git a/render_2D/plottable/Waterfall.cpp b/render_2D/plottable/Waterfall.cpp index ad2340e..c0d3e06 100644 --- a/render_2D/plottable/Waterfall.cpp +++ b/render_2D/plottable/Waterfall.cpp @@ -2,9 +2,11 @@ #include "Heatmap_Utils.h" #include "Plottable_Real_Time_Data.h" #include "../renderable/Render_Partition.h" +#include "../renderable/Renderable_p.h" #include #include #include +#include #include namespace renderive::detail { namespace { @@ -56,41 +58,45 @@ std::size_t waterfall_work_size(const Waterfall_Properties& state, : 0; } } -struct Waterfall_Control::Impl { - Impl(Waterfall_Control& owner, renderive_Owner frequency, renderive_Owner time) - : frequency_axis(std::move(frequency)), time_axis(std::move(time)), rows(owner) {} +struct Waterfall_Control::Impl : Renderable::Impl { + Impl(renderive_Owner frequency, + renderive_Owner time) + : frequency_axis(std::move(frequency)), time_axis(std::move(time)) {} renderive_Owner frequency_axis; renderive_Owner time_axis; - Waterfall_History rows; + std::optional rows; Waterfall_Interaction_State interaction; Adaptive_Render_Partitioner partitioner; Waterfall_Prepare_Buffer prepare_buffer; }; Waterfall_Control::Waterfall_Control(const Waterfall_Properties& properties, renderive_Owner frequency_axis, renderive_Owner time_axis) - : Plottable_State(properties), impl_(std::make_unique(*this, std::move(frequency_axis), std::move(time_axis))) {} + : Plottable_State(properties, std::make_unique( + std::move(frequency_axis), std::move(time_axis))) { + d_func().rows.emplace(*this); +} Waterfall_Control::~Waterfall_Control() = default; void Waterfall_Control::append_row(int tick, std::span values) { const std::size_t limit = static_cast( - std::max(2, impl_->time_axis->get<&Time_Axis_Properties::visible_count>())); + std::max(2, d_func().time_axis->get<&Time_Axis_Properties::visible_count>())); if(get<&Waterfall_Properties::frequency_bin_count>() <= 0) set<&Waterfall_Properties::frequency_bin_count>(static_cast(values.size())); - impl_->rows.update({tick, {values.begin(), values.end()}}, limit); + d_func().rows->update({tick, {values.begin(), values.end()}}, limit); render_graph_changed(); } void Waterfall_Control::append_row(int tick, std::pmr::vector&& values) { append_row(tick, std::span(values.data(), values.size())); } void Waterfall_Control::append_row(Time_Of_Day time, std::span values) { - append_row(impl_->time_axis->append_time(time), values); + append_row(d_func().time_axis->append_time(time), values); } void Waterfall_Control::append_row(Time_Of_Day time, std::pmr::vector&& values) { append_row(time, std::span(values.data(), values.size())); } std::size_t Waterfall_Control::row_count() const { - return impl_->rows.size(); + return d_func().rows->size(); } std::size_t Waterfall_Control::stored_point_count() const { - const auto rows = impl_->rows.snapshot(); + const auto rows = d_func().rows->snapshot(); std::size_t count{}; for(const auto& row : rows) count += row.values.size(); @@ -98,7 +104,7 @@ std::size_t Waterfall_Control::stored_point_count() const { } std::size_t Waterfall_Control::rendered_cell_count() const { const auto state = properties(); - const auto rows = impl_->rows.snapshot(); + const auto rows = d_func().rows->snapshot(); if(rows.empty()) return 0; const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); @@ -106,29 +112,29 @@ std::size_t Waterfall_Control::rendered_cell_count() const { return 0; const auto columns = frequency_columns( state.frequency_range, - impl_->frequency_axis->get<&Axis_Properties::coordinates>(), + d_func().frequency_axis->get<&Axis_Properties::coordinates>(), source_width, state.visible_range_only); return columns ? static_cast(columns->last - columns->first + 1) * rows.size() : 0; } void Waterfall_Control::handle_event(const Event& event) { bool updated{}; - impl_->interaction.update([&](Waterfall_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); + d_func().interaction.update([&](Waterfall_Interaction& interaction) { updated = update_hover_tooltip(interaction.tooltip, event); }); if(updated) changed(); } void Waterfall_Control::publish() { publish_properties(); - impl_->interaction.publish(); + d_func().interaction.publish(); } void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& rows = view.get(impl_->rows); + const auto& rows = view.get(*d_func().rows); const std::size_t work_size = waterfall_work_size( - state, rows, impl_->frequency_axis->transform(view)); - const int partition_count = impl_->partitioner.graph_partition_count( + state, rows, d_func().frequency_axis->transform(view)); + const int partition_count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = add_prepare_task( @@ -137,10 +143,10 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { prepare_render_frame(context.frame.render_state, partition_count); if (context.metrics) { context.metrics->set(Node_Metric_Kind::input_count, - impl_->prepare_buffer.rows.size()); + d_func().prepare_buffer.rows.size()); context.metrics->set(Node_Metric_Kind::chunk_size, - impl_->prepare_buffer.work_size / - std::max(1, impl_->prepare_buffer.active_partitions)); + d_func().prepare_buffer.work_size / + std::max(1, d_func().prepare_buffer.active_partitions)); } }); std::vector partitions; @@ -153,8 +159,8 @@ void Waterfall_Control::build_prepare_graph(Renderable_Graph_Builder& builder) { render_partition(index); if (context.metrics) { const auto range = render_partition_range( - impl_->prepare_buffer.work_size, index, - impl_->prepare_buffer.active_partitions); + d_func().prepare_buffer.work_size, index, + d_func().prepare_buffer.active_partitions); context.metrics->set(Node_Metric_Kind::prepared_cells, range.last - range.first); } @@ -170,14 +176,14 @@ void Waterfall_Control::build_paint_graph(Renderable_Graph_Builder& builder) { paint_render_frame(painter, context.frame.render_state); if (context.metrics) context.metrics->set(Node_Metric_Kind::pixel_count, - impl_->prepare_buffer.work_size); + d_func().prepare_buffer.work_size); }); - const auto view = render_state_view(); + const auto view = d_func().render_state_view(); const auto state = properties(); - const auto& rows = view.get(impl_->rows); + const auto& rows = view.get(*d_func().rows); const std::size_t work_size = waterfall_work_size( - state, rows, impl_->frequency_axis->transform(view)); - const int count = impl_->partitioner.graph_partition_count( + state, rows, d_func().frequency_axis->transform(view)); + const int count = d_func().partitioner.graph_partition_count( state.partition_mode, state.partition_count.get(), static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); if (count == 0) { @@ -192,20 +198,20 @@ void Waterfall_Control::build_paint_graph(Renderable_Graph_Builder& builder) { void Waterfall_Control::prepare_render_frame(const Render_State_View& view, int graph_partition_count) { const auto& state = render_properties(view); - const auto& rows = view.get(impl_->rows); - auto& output = impl_->prepare_buffer; + const auto& rows = view.get(*d_func().rows); + auto& output = d_func().prepare_buffer; output = {}; output.properties = state; output.rows.assign(rows.begin(), rows.end()); - output.interaction = view.get(impl_->interaction); + output.interaction = view.get(d_func().interaction); if (rows.empty()) return; const int source_width = std::min(state.frequency_bin_count.get(), static_cast(std::min_element(rows.begin(), rows.end(), [](const auto& left, const auto& right) { return left.values.size() < right.values.size(); })->values.size())); const int height = static_cast(rows.size()); if (source_width <= 0 || height <= 0) return; - const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view); - const Axis_Transform time_axis = impl_->time_axis->transform(view); + const Axis_Transform frequency_axis = d_func().frequency_axis->transform(view); + const Axis_Transform time_axis = d_func().time_axis->transform(view); const auto columns = frequency_columns(state.frequency_range, frequency_axis.coordinate_range, source_width, state.visible_range_only); if (!columns) @@ -226,7 +232,7 @@ void Waterfall_Control::prepare_render_frame(const Render_State_View& view, output.source_height = height; output.work_size = static_cast(width) * height; output.pixels.resize(output.work_size); - output.active_partitions = impl_->partitioner.begin(graph_partition_count, + output.active_partitions = d_func().partitioner.begin(graph_partition_count, output.work_size); if (state.tooltip_enabled && output.interaction.tooltip.active && output.layout.target.contains(output.interaction.tooltip.position)) { @@ -243,7 +249,7 @@ void Waterfall_Control::prepare_render_frame(const Render_State_View& view, } void Waterfall_Control::render_partition(int partition_index) { - auto& output = impl_->prepare_buffer; + auto& output = d_func().prepare_buffer; if (!output.valid || partition_index >= output.active_partitions) return; const auto& state = output.properties; @@ -263,7 +269,7 @@ void Waterfall_Control::render_partition(int partition_index) { void Waterfall_Control::paint_render_frame(Painter& painter, const Render_State_View& view) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid) return; const auto& paint_state = render_properties(view); @@ -280,11 +286,11 @@ void Waterfall_Control::paint_render_frame(Painter& painter, void Waterfall_Control::render_frame_completed( std::uint64_t target_interval_ns) { - const auto& output = impl_->prepare_buffer; + const auto& output = d_func().prepare_buffer; if (!output.valid || !is_visible()) return; const auto& state = output.properties; - if (impl_->partitioner.finish( + if (d_func().partitioner.finish( state.partition_mode, output.active_partitions, target_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), output.work_size, 4096)) diff --git a/render_2D/plottable/Waterfall.h b/render_2D/plottable/Waterfall.h index e45e9c5..37ce577 100644 --- a/render_2D/plottable/Waterfall.h +++ b/render_2D/plottable/Waterfall.h @@ -47,7 +47,6 @@ protected: void build_paint_graph(Renderable_Graph_Builder& builder) override; private: struct Impl; - std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); void paint_render_frame(Painter& painter, const Render_State_View& state); void render_frame_completed(std::uint64_t target_interval_ns) override; diff --git a/render_2D/renderable/Renderable.cpp b/render_2D/renderable/Renderable.cpp index 55dfd99..87ba45b 100644 --- a/render_2D/renderable/Renderable.cpp +++ b/render_2D/renderable/Renderable.cpp @@ -1,4 +1,5 @@ #include "Renderable.h" +#include "Renderable_p.h" #include #include "../render/Blend2D_Cache.h" @@ -7,62 +8,57 @@ namespace renderive { -Renderable_Observation Renderable_Observer::observation() const noexcept { - std::lock_guard lock(mutex_); - return observation_; -} - -void Renderable_Observer::observe_state(Renderable_Observer_Event event, - std::uint64_t time_ns, - std::uint64_t cache_update_count, - std::uint64_t publish_count) noexcept { - std::lock_guard lock(mutex_); - observation_.event = event; - observation_.event_time_ns = time_ns; - observation_.cache_update_count = cache_update_count; - observation_.publish_count = publish_count; -} - Renderable::Renderable(bool cache_enabled) - : ::Renderable_Base({.cache_enabled = cache_enabled}) {} + : ::Renderable_Base(std::make_unique(), + {.cache_enabled = cache_enabled}) {} Renderable::~Renderable() = default; Renderable_Cache_Mode Renderable::get_cache_mode() const noexcept { - return configuration().cache_enabled + return d_func().configuration.load(std::memory_order_acquire).cache_enabled ? Renderable_Cache_Mode::Local_Pixel : Renderable_Cache_Mode::Direct; } void Renderable::set_cache_mode(Renderable_Cache_Mode mode) { - set_configuration({.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel}); + d_func().set_configuration( + {.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel}); } std::string Renderable::object_name() const { - std::lock_guard lock(metadata_mutex_); - return object_name_; + const auto& d = d_func(); + std::lock_guard lock(d.metadata_mutex); + return d.object_name; } void Renderable::set_object_name(std::string name) { - std::lock_guard lock(metadata_mutex_); - object_name_ = std::move(name); + auto& d = d_func(); + std::lock_guard lock(d.metadata_mutex); + d.object_name = std::move(name); } Renderable_Observation Renderable::observation() const noexcept { - return observer_.observation(); + const auto& d = d_func(); + std::lock_guard lock(d.observation_mutex); + return d.observation; } void Renderable::observe_state(Renderable_Observer_Event event, std::uint64_t time_ns, std::uint64_t cache_update_count, std::uint64_t publish_count) noexcept { - observer_.observe_state(event, time_ns, cache_update_count, publish_count); + auto& d = d_func(); + std::lock_guard lock(d.observation_mutex); + d.observation.event = event; + d.observation.event_time_ns = time_ns; + d.observation.cache_update_count = cache_update_count; + d.observation.publish_count = publish_count; } void Renderable::build_prepare_graph(Renderable_Graph_Builder& builder) { add_prepare_task(builder, "prepare", "Prepare", [this](const Prepare_Render_Context& context) { - prepare_frame(context); + d_func().execute_prepare(context); }); } @@ -103,17 +99,35 @@ Renderable_Graph_Builder::Task Renderable::add_paint_task( } void Renderable::changed() noexcept { - invalidate_prepare(); - notify_scene_model_dirty(); + d_func().invalidate_prepare(); + d_func().notify_scene_model_dirty(); } void Renderable::paint_changed() noexcept { - invalidate_paint(); - notify_scene_model_dirty(); + d_func().invalidate_paint(); + d_func().notify_scene_model_dirty(); } void Renderable::render_graph_changed() { - rebuild_render_graph(); + d_func().rebuild_render_graph(); +} + +Renderable& Renderable::Impl::renderable() noexcept { + return static_cast(owner()); +} + +void Renderable::Impl::build_prepare_graph( + Renderable_Graph_Builder& builder) { + renderable().build_prepare_graph(builder); +} + +void Renderable::Impl::build_paint_graph( + Renderable_Graph_Builder& builder) { + renderable().build_paint_graph(builder); +} + +void Renderable::Impl::prepare(const Prepare_Render_Context& context) { + renderable().prepare_frame(context); } } // namespace renderive diff --git a/render_2D/renderable/Renderable.h b/render_2D/renderable/Renderable.h index cceee80..9fe8b2c 100644 --- a/render_2D/renderable/Renderable.h +++ b/render_2D/renderable/Renderable.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -41,17 +42,6 @@ struct Renderable_Observation { std::uint64_t publish_count{}; }; -class LIB_DECL Renderable_Observer final { -public: - [[nodiscard]] Renderable_Observation observation() const noexcept; - void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns, - std::uint64_t cache_update_count, - std::uint64_t publish_count) noexcept; -private: - mutable std::mutex mutex_; - Renderable_Observation observation_; -}; - class LIB_DECL Renderable : public ::Renderable_Base { public: explicit Renderable(bool cache_enabled = true); @@ -65,6 +55,15 @@ public: [[nodiscard]] Renderable_Observation observation() const noexcept; protected: + class Impl; + + template + explicit Renderable(std::unique_ptr implementation, + bool cache_enabled) + : ::Renderable_Base( + std::move(implementation), + {.cache_enabled = cache_enabled}) {} + using Prepare_Task_Function = std::function; using Paint_Task_Function = @@ -73,8 +72,8 @@ protected: virtual void prepare_frame(const Prepare_Render_Context& context) = 0; virtual void paint(detail::Painter& painter, const Paint_Render_Context& context) = 0; - virtual void build_prepare_graph(Renderable_Graph_Builder& builder) override; - virtual void build_paint_graph(Renderable_Graph_Builder& builder) override; + virtual void build_prepare_graph(Renderable_Graph_Builder& builder); + virtual void build_paint_graph(Renderable_Graph_Builder& builder); Renderable_Graph_Builder::Task add_prepare_task(Renderable_Graph_Builder& builder, std::string logical_key, std::string name, @@ -92,9 +91,6 @@ private: std::uint64_t cache_update_count, std::uint64_t publish_count) noexcept; friend class detail::Renderable_State_Observer; - mutable std::mutex metadata_mutex_; - std::string object_name_; - Renderable_Observer observer_; }; namespace detail { diff --git a/render_2D/renderable/Renderable_p.h b/render_2D/renderable/Renderable_p.h new file mode 100644 index 0000000..ff5101c --- /dev/null +++ b/render_2D/renderable/Renderable_p.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Renderable.h" + +#include +#include + +#include + +namespace renderive { + +class Renderable::Impl : public ::Renderable_Base::Impl { +public: + mutable std::mutex metadata_mutex; + std::string object_name; + mutable std::mutex observation_mutex; + Renderable_Observation observation; + +protected: + void build_prepare_graph(Renderable_Graph_Builder& builder) override; + void build_paint_graph(Renderable_Graph_Builder& builder) override; + void prepare(const Prepare_Render_Context& context) override; + +private: + [[nodiscard]] Renderable& renderable() noexcept; +}; + +} // namespace renderive diff --git a/render_2D/scene/Scene.h b/render_2D/scene/Scene.h index 7518558..5123e72 100644 --- a/render_2D/scene/Scene.h +++ b/render_2D/scene/Scene.h @@ -239,10 +239,7 @@ public: if (static_cast(*this).template get<&Scene_State::viewport>() == viewport) return; static_cast(*this).template set<&Scene_State::viewport>(viewport); - const auto topology = this->topology_snapshot(); - for (const auto& renderable : topology.renderables) - const_cast<::Renderable_Base&>(*renderable).invalidate_prepare(); - this->notify_model_dirty(); + this->invalidate_renderables(); } [[nodiscard]] Size viewport_size() const noexcept { const auto viewport = static_cast(*this).template get<&Scene_State::viewport>(); diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index 5701bda..393c8c9 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -147,9 +147,9 @@ public: [[nodiscard]] nlohmann::json controls() const { return { {"resources", controls_.resources()}, - {"observers", controls_.observers(root_->scene())}, - {"render_plan", controls_.render_plan(root_->scene())}, - {"performance_capture", gallery_performance_capture_json(root_->scene())} + {"observers", controls_.observers(plot_)}, + {"render_plan", controls_.render_plan(plot_)}, + {"performance_capture", gallery_performance_capture_json(plot_)} }; } [[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept { @@ -271,10 +271,10 @@ public: last_action_result_.clear(); recognized = true; if (request.id == "capture_next_frame") { - const auto state = root_->scene().capture_state(); + const auto state = plot_.capture_state(); if (state.enabled()) return "A performance capture session is already active"; - const auto session_id = root_->scene().capture_next_frame(); + const auto session_id = plot_.capture_next_frame(); last_action_result_ = "capture session=" + std::to_string(session_id); return "Capture next render frame requested"; } @@ -284,10 +284,10 @@ public: return invalid_argument(recognized); const std::size_t count = static_cast( std::clamp(std::llround(*argument), 1LL, 1000LL)); - const auto state = root_->scene().capture_state(); + const auto state = plot_.capture_state(); if (state.enabled()) return "A performance capture session is already active"; - const auto session_id = root_->scene().capture_frames(count); + const auto session_id = plot_.capture_frames(count); last_action_result_ = "capture session=" + std::to_string(session_id) + ", frames=" + std::to_string(count); return "Consecutive render frame capture requested"; @@ -561,8 +561,8 @@ public: {"kernel_observer", std::move(observer_data)}, {"consumer_feedback", std::move(consumer_feedback_data)}, {"client_performance", std::move(client_performance_data)}, - {"renderable_observers", controls_.observers(root_->scene())}, - {"performance_capture", gallery_performance_capture_json(root_->scene())}, + {"renderable_observers", controls_.observers(plot_)}, + {"performance_capture", gallery_performance_capture_json(plot_)}, {"last_action_result", last_action_result_} }; if (primary_) {