#include "Plot_Core.h" #include "../plottable/Performance_Overlay.h" #include "../render/Blend2D_Cache.h" #include "../renderable/Renderable.h" #include #include #include #include #include #include #include #include namespace renderive { namespace { using Plot_Frame_Control = ::Low_Latency_Strategy<::Scene2D_Frame_Data>; using Plot_Scene = ::Scene2D_Context; Rect full_rect(Size size) { return {0, 0, std::max(0, size.width), std::max(0, size.height)}; } class Renderable_Group final : public Renderable { public: using Renderable::Renderable; private: void paint(detail::Painter&) override {} }; } // namespace struct Plot_Core::Impl { Impl() : scene(*memory_resource(Memory_Domain::Plot_Frame)) {} Plot_Scene scene; mutable std::mutex mutex; std::mutex initialization_mutex; std::weak_ptr presentation_sink; std::shared_ptr performance; Color background = Color::black(); Size viewport; std::atomic_bool active{}; std::atomic_bool dirty{true}; }; Plot_Core::Plot_Core() : impl_(std::make_unique()) {} Plot_Core::~Plot_Core() = default; void Plot_Core::init() { std::lock_guard initialization_lock(impl_->initialization_mutex); if (root_renderable()) return; auto root = renderive::make_shared(Memory_Domain::Renderable, *this, true); root->set_object_name("root"); impl_->scene.attach_renderable(root); notify_model_dirty(); } std::shared_ptr Plot_Core::root_renderable() const { const auto topology = impl_->scene.topology_snapshot(); for (const auto& relationship : topology.display) { if (relationship.parent) continue; auto base = std::const_pointer_cast<::Renderable_Base>(relationship.child); if (auto renderable = std::dynamic_pointer_cast(base)) return renderable; } return {}; } std::shared_ptr Plot_Core::create_renderable_node( const std::shared_ptr& parent, std::string object_name) { auto renderable = renderive::make_shared( Memory_Domain::Renderable, *this, true); renderable->set_object_name(std::move(object_name)); attach_renderable(renderable, parent); return renderable; } void Plot_Core::attach_renderable(const std::shared_ptr& renderable, const std::shared_ptr& parent) { if (!renderable) throw std::invalid_argument("renderable is null"); Renderable* parent_pointer = parent.get(); if (!parent_pointer) { auto root = root_renderable(); if (!root) throw std::logic_error("Plot_Core::init must be called before adding renderables"); parent_pointer = root.get(); } impl_->scene.attach_renderable(renderable); try { impl_->scene.set_display_parent(*renderable, parent_pointer); impl_->scene.set_dependency_parent(*renderable, parent_pointer); } catch (...) { impl_->scene.detach_renderable(*renderable); throw; } notify_model_dirty(); } void Plot_Core::remove_renderable(const std::shared_ptr& renderable) { if (!renderable || renderable == root_renderable()) return; impl_->scene.detach_renderable(*renderable); notify_model_dirty(); } void Plot_Core::set_background_color(Color color) { { std::lock_guard lock(impl_->mutex); if (impl_->background == color) return; impl_->background = color; } notify_model_dirty(); } Color Plot_Core::background_color() const noexcept { std::lock_guard lock(impl_->mutex); return impl_->background; } void Plot_Core::set_viewport_size(Size size) { size.width = std::max(0, size.width); size.height = std::max(0, size.height); { std::lock_guard lock(impl_->mutex); if (impl_->viewport == size) return; impl_->viewport = size; } const auto topology = impl_->scene.topology_snapshot(); for (const auto& renderable : topology.renderables) const_cast<::Renderable_Base&>(*renderable).invalidate_cache(); notify_model_dirty(); } Size Plot_Core::viewport_size() const noexcept { std::lock_guard lock(impl_->mutex); return impl_->viewport; } void Plot_Core::dispatch_event(const Event& event) { const auto topology = impl_->scene.topology_snapshot(); for (auto iterator = topology.display.rbegin(); iterator != topology.display.rend(); ++iterator) { auto base = std::const_pointer_cast<::Renderable_Base>(iterator->child); if (auto renderable = std::dynamic_pointer_cast(base)) { if (renderable->is_visible()) { if (auto handler = std::dynamic_pointer_cast(base)) handler->handle_event(event); } if (event.is_accepted()) break; } } } void Plot_Core::notify_model_dirty() noexcept { impl_->dirty.store(true, std::memory_order_release); } bool Plot_Core::render_frame(bool force) { if (!view_active() && !force) return false; const Size viewport = viewport_size(); if (viewport.empty()) return false; if (!impl_->dirty.exchange(false, std::memory_order_acq_rel) && !force) return false; const auto started = std::chrono::steady_clock::now(); try { { auto paint_frame = impl_->scene.frame_control.acquire_painter(); auto& scene_state = static_cast(impl_->scene); scene_state.template set<&::Scene2D_State::revision>(scene_state.state_revision() + 1); scene_state.publish(); } { auto render_frame = impl_->scene.frame_control.acquire_renderer(); impl_->scene.render(); impl_->scene.wait_for_render(); } } catch (...) { notify_model_dirty(); throw; } const auto finished = std::chrono::steady_clock::now(); const double duration_ms = std::chrono::duration(finished - started).count(); std::shared_ptr sink; std::shared_ptr overlay; { std::lock_guard lock(impl_->mutex); sink = impl_->presentation_sink.lock(); overlay = impl_->performance; } if (overlay) overlay->record_frame(duration_ms, diagnostics(), impl_->scene.renderable_count(), viewport); if (sink) sink->request_present(full_rect(viewport)); return true; } void Plot_Core::with_frame(const std::function& consumer) { if (!consumer) return; impl_->scene.with_final_color_cache([&consumer](const detail::Blend2D_Color_Cache& cache) { consumer(cache.view()); }); } void Plot_Core::activate_view() noexcept { impl_->active.store(true, std::memory_order_release); notify_model_dirty(); } void Plot_Core::deactivate_view() noexcept { impl_->active.store(false, std::memory_order_release); } bool Plot_Core::view_active() const noexcept { return impl_->active.load(std::memory_order_acquire); } void Plot_Core::set_max_render_fps(double fps) { if (!std::isfinite(fps) || fps <= 0.0) throw std::invalid_argument("maximum render FPS must be finite and positive"); impl_->scene.frame_control.set_frequency_hz(fps); notify_model_dirty(); } double Plot_Core::max_render_fps() const noexcept { return impl_->scene.frame_control.state().frequency_hz; } Low_Latency_Diagnostics Plot_Core::diagnostics() const { const auto state = impl_->scene.frame_control.state(); return { view_active(), {state.frequency_hz, state.completed_lifecycle_count, state.next_refresh_interval_ns} }; } Refresh_Control_Snapshot Plot_Core::refresh_feedback_snapshot() const { return diagnostics().refresh; } void Plot_Core::set_presentation_sink(std::weak_ptr sink) { std::lock_guard lock(impl_->mutex); impl_->presentation_sink = std::move(sink); } std::shared_ptr Plot_Core::performance_overlay() const { std::lock_guard lock(impl_->mutex); return impl_->performance; } void Plot_Core::set_performance_overlay(std::shared_ptr overlay) { { std::lock_guard lock(impl_->mutex); impl_->performance = std::move(overlay); } notify_model_dirty(); } ::Scene_Base& Plot_Core::kernel_scene() const noexcept { return impl_->scene; } void Plot_Core::set_renderable_cache(Renderable& renderable, Renderable_Cache_Mode mode) { impl_->scene.set_renderable_configuration( renderable, {.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel}); notify_model_dirty(); } } // namespace renderive