#include "Plot_Core.h" #include "Plot_Frame_Pipeline.h" #include "../architecture/Renderable.h" #include "../architecture/Render_Time.h" #include "../architecture/Update_Completion.h" #include "../base/global.h" #include "../render/Canvas.h" #include #include #include namespace renderive { class Plot_Core_Private final { public: explicit Plot_Core_Private(std::unique_ptr s) : strategy(std::move(s)) {} ~Plot_Core_Private() = default; bool render_enabled() const { return rendering.load(std::memory_order_acquire); } bool destroying() const { return destroying_flag.load(std::memory_order_acquire); } Refresh_Runtime_Snapshot refresh_runtime_snapshot(std::uint64_t now_ns = steady_now_ns()) const { return { render_enabled(), destroying(), pipeline.has_new_state(), pipeline.job_state != Plot_Render_Job_State::Idle, pipeline.middle_has_new_frame(), pipeline.paint_active(), now_ns }; } bool submit_render_once() { if (!rendering.load(std::memory_order_acquire) || destroying_flag.load(std::memory_order_acquire)) return false; if (!root) return false; Size size{ context->render_width.load(std::memory_order_acquire), context->render_height.load(std::memory_order_acquire) }; if (size.empty()) return false; auto frame_lease = pipeline.try_begin_render(); if (!frame_lease) return false; Plot_Frame* buffer = frame_lease.frame; cancel_update_states(buffer->presented_update_states); std::uint64_t render_role_enter_ns = buffer->metadata.render_role_enter_ns; buffer->metadata = {}; Frame_Lifecycle_Record& frame = buffer->metadata; std::uint64_t submit_time = steady_now_ns(); frame.frame_id = ++next_frame_id; frame.input_version = pipeline.edit_state_version.load(std::memory_order_acquire); frame.render_request_ns = submit_time; frame.render_role_enter_ns = render_role_enter_ns ? render_role_enter_ns : submit_time; frame.core_data_time_ns = latest_input_time_ns.load(std::memory_order_acquire); frame.raw_input_count = pending_input_count.exchange(0, std::memory_order_acq_rel); frame.pixel_width = static_cast(size.width); frame.pixel_height = static_cast(size.height); frame.wait_21_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; frame.render_begin_ns = submit_time; pipeline.job_state = Plot_Render_Job_State::Running; pipeline.render_state_version = frame.input_version; Plot_Render_Snapshot render_snapshot; render_snapshot.context = context; render_snapshot.size = size; render_snapshot.background_color = background_color(); render_snapshot.previous_frame_time_ns = previous_frame_time_ns; render_snapshot.frame_time_ns = submit_time; render_snapshot.frame_metadata = &frame; render_snapshot.frame_update_states = &buffer->presented_update_states; previous_frame_time_ns = submit_time; auto renderables = renderable_tree_snapshot(); frame.prepare_begin_ns = steady_now_ns(); for (const auto& node : renderables) { const auto& renderable = node.renderable; if (!renderable || !renderable->should_prepare()) continue; renderable->prepare_data(render_snapshot); ++frame.prepared_output_count; } frame.prepare_end_ns = steady_now_ns(); context->first_prepare_data.store(false, std::memory_order_release); buffer->image.resize(size.width, size.height); buffer->image.fill(render_snapshot.background_color); Canvas canvas(buffer->image); frame.draw_begin_ns = steady_now_ns(); if (!renderables.empty() && renderables.front().renderable) renderables.front().renderable->render_tree_snapshot(canvas, render_snapshot, renderables, 0); frame.draw_end_ns = steady_now_ns(); frame.render_end_ns = steady_now_ns(); buffer->version.store(frame.frame_id, std::memory_order_release); for (const auto& update_state : buffer->presented_update_states) { if (update_state) update_state->complete_until(Update_Stage::Rendered); } frame_lease.reset(); auto publish_result = pipeline.try_publish_rendered(steady_now_ns()); if (publish_result.superseded_middle) { Frame_Lifecycle_Record& superseded_frame = publish_result.superseded_middle->metadata; trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, &superseded_frame); collect_frame(superseded_frame); supersede_update_states(publish_result.superseded_middle->presented_update_states); } if (publish_result.dropped_frame) { Frame_Lifecycle_Record& dropped_frame = publish_result.dropped_frame->metadata; collect_frame(dropped_frame); supersede_update_states(publish_result.dropped_frame->presented_update_states); } pipeline.job_state = Plot_Render_Job_State::Idle; if (publish_result.request_present) request_present(publish_result.dirty_rect); trigger_refresh_strategy(Refresh_Trigger::Render_Finished, &frame); return true; } void post_scheduler_action(Task task) { if (!task) return; auto guard = context; auto action = [guard, task = std::move(task)]() mutable { if (!guard || guard->destroying.load(std::memory_order_acquire)) return; task(); }; auto& scheduler = Global::instance()->render_scheduler(); if (scheduler.is_scheduler_thread()) { action(); return; } scheduler.post(std::move(action)); } void trigger_refresh_strategy(Refresh_Trigger trigger, Frame_Lifecycle_Record* frame = nullptr) { if (!strategy) return; std::uint64_t now_ns = steady_now_ns(); Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); Refresh_Action action = strategy->on_trigger(trigger, runtime, frame); apply_refresh_action(action, runtime); } void notify_input_changed(std::uint64_t now_ns) { if (!strategy) return; Refresh_Runtime_Snapshot runtime = refresh_runtime_snapshot(now_ns); Refresh_Action action = strategy->on_trigger(Refresh_Trigger::Input_Dirty, runtime, nullptr); apply_refresh_action(action, runtime); } void notify_started() { if (!strategy_started) { strategy_started = true; trigger_refresh_strategy(Refresh_Trigger::Started); } else { trigger_refresh_strategy(Refresh_Trigger::Input_Dirty); } } void notify_paused() { invalidate_refresh_deadline(); if (!strategy_started) return; strategy_started = false; trigger_refresh_strategy(Refresh_Trigger::Paused); } void notify_frame_returned(Frame_Lifecycle_Record frame) { trigger_refresh_strategy(Refresh_Trigger::Frame_Returned, &frame); collect_frame(frame); } void apply_refresh_action(Refresh_Action action, const Refresh_Runtime_Snapshot& runtime) { if (action.request_paint) { auto update_result = pipeline.request_paint_update(steady_now_ns()); if (update_result.request_present) request_present(update_result.dirty_rect); } if (action.try_render) { invalidate_refresh_deadline(); submit_render_once(); return; } if (action.render_deadline_ns) { schedule_refresh_deadline(action.render_deadline_ns); return; } if (!runtime.enabled || runtime.destroying || !runtime.dirty) invalidate_refresh_deadline(); } void schedule_refresh_deadline(std::uint64_t deadline_ns) { if (deadline_ns <= steady_now_ns()) { invalidate_refresh_deadline(); trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); return; } if (scheduled_refresh_deadline_ns && scheduled_refresh_deadline_ns <= deadline_ns) return; scheduled_refresh_deadline_ns = deadline_ns; std::uint64_t generation = ++refresh_deadline_generation; auto guard = context; Global::instance()->render_scheduler().post_at(deadline_ns, [guard, self = this, generation, deadline_ns]() { if (!guard || guard->destroying.load(std::memory_order_acquire)) return; if (self->refresh_deadline_generation != generation || self->scheduled_refresh_deadline_ns != deadline_ns) return; self->scheduled_refresh_deadline_ns = 0; self->trigger_refresh_strategy(Refresh_Trigger::Timer_Fired); }); } void invalidate_refresh_deadline() { scheduled_refresh_deadline_ns = 0; ++refresh_deadline_generation; } void dispatch_event(const Event& event) { if (!root) return; switch (event.type) { case Event_Type::Pointer_Move: { const auto& pointer_event = static_cast(event); update_hover_targets(pointer_event); dispatch_to_renderables(root->hit_renderables(pointer_event.position), event); break; } case Event_Type::Pointer_Press: case Event_Type::Pointer_Release: { const auto& pointer_event = static_cast(event); dispatch_to_renderables(root->hit_renderables(pointer_event.position), event); break; } case Event_Type::Wheel: { const auto& wheel_event = static_cast(event); dispatch_to_renderables(root->hit_renderables(wheel_event.position), event); break; } case Event_Type::Key_Press: case Event_Type::Key_Release: dispatch_to_renderables(reverse_renderable_snapshot(), event); break; case Event_Type::Leave: clear_hover_targets(); dispatch_to_renderables(renderable_snapshot(), event); break; default: dispatch_to_renderables(renderable_snapshot(), event); break; } } std::vector> renderable_snapshot() const { std::vector> result; append_renderable_snapshot(result, root); return result; } Renderable_Tree_Snapshot renderable_tree_snapshot() const { Renderable_Tree_Snapshot result; if (root) root->append_tree_snapshot(result); return result; } std::vector> reverse_renderable_snapshot() const { std::vector> result = renderable_snapshot(); std::reverse(result.begin(), result.end()); return result; } void append_renderable_snapshot(std::vector>& result, const std::shared_ptr& renderable) const { if (!renderable) return; result.push_back(renderable); for (const auto& child : renderable->children_snapshot()) append_renderable_snapshot(result, child); } void dispatch_to_renderables(const std::vector>& renderables, const Event& event) { for (const auto& renderable : renderables) { if (!renderable || event.is_accepted()) break; renderable->plot_event(event); } } void update_hover_targets(const Pointer_Event& event) { Point pos{static_cast(event.position.x), static_cast(event.position.y)}; std::vector> hits = root->hit_renderables(event.position); for (const auto& old_target : hover_targets) { auto target = old_target.lock(); if (!target) continue; if (std::find(hits.begin(), hits.end(), target) == hits.end()) target->set_hover_state(pos, false); } hover_targets.clear(); hover_targets.reserve(hits.size()); for (const auto& target : hits) { if (!target) continue; target->set_hover_state(pos, true); hover_targets.push_back(target); } } void clear_hover_targets() { Point pos{}; for (const auto& old_target : hover_targets) { if (auto target = old_target.lock()) target->set_hover_state(pos, false); } hover_targets.clear(); } void request_present(Rect dirty_rect) { if (auto target = sink.lock()) target->request_present(dirty_rect); } void collect_frame(const Frame_Lifecycle_Record& frame) { auto observer = std::atomic_load_explicit(&context->frame_lifecycle_observer, std::memory_order_acquire); if (observer && observer->enabled()) observer->collect_frame(frame); } Color background_color() const { Pixel p = context->background_rgba.load(std::memory_order_acquire); return premultiplied_to_color(p); } std::unique_ptr strategy; std::shared_ptr context = std::make_shared(); std::shared_ptr root; std::vector> hover_targets; Plot_Frame_Pipeline pipeline; std::weak_ptr sink; std::atomic_bool rendering{false}; std::atomic_bool destroying_flag{false}; std::atomic latest_input_time_ns{0}; std::atomic pending_input_count{0}; bool strategy_started{}; std::uint64_t refresh_deadline_generation{}; std::uint64_t scheduled_refresh_deadline_ns{}; std::uint64_t next_frame_id{}; std::uint64_t previous_frame_time_ns{}; }; Plot_Core::Plot_Core(std::unique_ptr strategy) : d(std::make_unique(std::move(strategy))) { d->context->owner.store(this, std::memory_order_release); Global::instance()->render_scheduler().register_plot(this); } Plot_Core::~Plot_Core() { d->destroying_flag.store(true, std::memory_order_release); d->context->destroying.store(true, std::memory_order_release); Global::instance()->render_scheduler().remove_plot(this); d->pipeline.cancel_all_update_states(); d->context->owner.store(nullptr, std::memory_order_release); } void Plot_Core::init() { if (!d->root) { d->root = std::make_shared(); d->root->set_object_name("Root_Renderable"); d->root->init_root(this); } d->context->first_resize.store(true, std::memory_order_release); } std::shared_ptr Plot_Core::render_context() const { return d->context; } std::shared_ptr Plot_Core::root_renderable() const { return d->root; } std::shared_ptr Plot_Core::create_renderable_node( const std::shared_ptr& parent, std::string object_name) { if (!parent) return {}; auto node = std::make_shared(); node->set_object_name(std::move(object_name)); node->init(parent); return node; } void Plot_Core::remove_renderable(const std::shared_ptr& renderable) { if (!renderable) return; if (renderable == d->root) d->root.reset(); else renderable->detach_from_parent(); mark_render_state_dirty(); } void Plot_Core::set_background_color(Color color) { d->context->background_rgba.store(premultiply(color), std::memory_order_release); mark_render_state_dirty(); } Color Plot_Core::background_color() const { return d->background_color(); } void Plot_Core::set_viewport_size(Size size) { d->context->render_width.store(size.width, std::memory_order_release); d->context->render_height.store(size.height, std::memory_order_release); if (!size.empty()) d->context->first_resize.store(false, std::memory_order_release); } Size Plot_Core::viewport_size() const { return { d->context->render_width.load(std::memory_order_acquire), d->context->render_height.load(std::memory_order_acquire) }; } void Plot_Core::dispatch_event(const Event& event) { d->dispatch_event(event); } void Plot_Core::mark_render_state_dirty() { if (d->destroying_flag.load(std::memory_order_acquire)) return; std::uint64_t now_ns = steady_now_ns(); d->pipeline.mark_edit_state(); d->latest_input_time_ns.store(now_ns, std::memory_order_release); d->pending_input_count.fetch_add(1, std::memory_order_relaxed); d->post_scheduler_action([data = d.get(), now_ns]() { data->notify_input_changed(now_ns); }); } void Plot_Core::request_manual_refresh() { if (d->destroying_flag.load(std::memory_order_acquire)) return; d->post_scheduler_action([data = d.get()]() { data->trigger_refresh_strategy(Refresh_Trigger::Manual_Request); }); } void Plot_Core::start_render() { if (d->destroying_flag.load(std::memory_order_acquire)) return; d->rendering.store(true, std::memory_order_release); d->post_scheduler_action([data = d.get()]() { data->notify_started(); }); } void Plot_Core::pause_render() { d->rendering.store(false, std::memory_order_release); d->post_scheduler_action([data = d.get()]() { data->notify_paused(); }); } bool Plot_Core::is_rendering() const { return d->rendering.load(std::memory_order_acquire); } void Plot_Core::set_presentation_sink(std::weak_ptr sink) { d->sink = std::move(sink); } Present_Frame Plot_Core::begin_present() { Present_Frame result; if (!d->root) return result; auto consumed = d->pipeline.try_begin_paint(steady_now_ns()); if (consumed.has_returned_frame) { d->post_scheduler_action([data = d.get(), frame = consumed.returned_frame]() mutable { data->notify_frame_returned(frame); }); } result.is_new_frame = consumed.new_frame; result.update_was_pending = consumed.update_was_pending; if (consumed.lease) result.lease_ = std::make_unique(std::move(consumed.lease)); if (!result.new_frame() && result.request_was_pending()) { d->post_scheduler_action([data = d.get()]() { data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); }); } return result; } void Plot_Core::end_present(Present_Frame&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { if (frame.lease_ && frame.lease_->frame) { auto* rendered_frame = frame.lease_->frame; rendered_frame->metadata.paint_begin_ns = paint_begin_ns; rendered_frame->metadata.paint_end_ns = paint_end_ns; rendered_frame->metadata.wait_23_ns = paint_begin_ns > rendered_frame->metadata.transition_12_ns ? paint_begin_ns - rendered_frame->metadata.transition_12_ns : 0; complete_update_states(rendered_frame->presented_update_states, Update_Stage::Presented); } frame.lease_.reset(); d->post_scheduler_action([data = d.get()]() mutable { data->trigger_refresh_strategy(Refresh_Trigger::Paint_Finished); }); } } // namespace renderive