#include "Plot_Core.h" #include "Plot_Core_Access.h" #include "../architecture/Renderable.h" #include "../architecture/Plot_Render_Context.h" #include "../architecture/Render_Time.h" #include "../architecture/Update_Completion.h" #include "../base/global.h" #include "../render/Canvas.h" #include "../render/Frame_Prepare_Subflow.h" #include "../render/Render_Frame_Snapshot.h" #include #include #include namespace renderive { class Plot_Core_Private; struct Frame_Render_Job { std::shared_ptr owner; std::uint64_t generation{}; Render_Frame_Surface frame_surface; Render_Output* buffer{}; std::shared_ptr lifecycle; Render_Frame_Snapshot render_snapshot; Renderable_Frame_Tree renderables; std::shared_ptr worker_stat; std::atomic_uint64_t prepared_output_count{0}; }; class Plot_Core_Private final : public std::enable_shared_from_this, public Frame_Flow_Host { public: explicit Plot_Core_Private(std::unique_ptr selected_flow) : flow(std::move(selected_flow)) {} ~Plot_Core_Private() = default; bool destroying() const { return !context || context->destroying.load(std::memory_order_acquire); } Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const override { return flow ? flow->runtime_snapshot(now_ns, destroying()) : Frame_Flow_Runtime_Snapshot{destroying(), false, false, false, false, now_ns}; } bool submit_render_once() { if (destroying()) 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_surface = flow ? flow->begin_render_frame() : Render_Frame_Surface{}; if (!frame_surface) return false; Render_Output* buffer = frame_surface.output; buffer->render_complete.store(false, std::memory_order_release); cancel_update_states(buffer->presented_update_states); if (flow) flow->drain_frame_update_states(buffer->presented_update_states); std::uint64_t render_role_enter_ns = buffer->metadata ? buffer->metadata->render_role_enter_ns : 0; buffer->metadata = std::make_shared(); auto frame_record = buffer->metadata; Frame_Lifecycle_Record& frame = *frame_record; std::uint64_t submit_time = steady_now_ns(); frame.frame_id = ++next_frame_id; frame.input_version = frame_surface.input_version; 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.viewport_version = context->viewport_version.load(std::memory_order_acquire); frame.dirty_reason_mask = pending_dirty_reason_mask.exchange(0, std::memory_order_acq_rel); frame.render_queue_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0; frame.render_begin_ns = submit_time; auto job = std::make_shared(); job->owner = shared_from_this(); job->generation = generation.load(std::memory_order_acquire); job->frame_surface = std::move(frame_surface); job->buffer = buffer; job->lifecycle = frame_record; job->worker_stat = std::make_shared(); job->render_snapshot.destroying_token = destroying_token; job->render_snapshot.size = size; job->render_snapshot.background_color = background_color(); job->render_snapshot.previous_frame_time_ns = previous_frame_time_ns; job->render_snapshot.frame_time_ns = submit_time; job->render_snapshot.frame_metadata = frame_record.get(); job->render_snapshot.frame_update_states = &buffer->presented_update_states; previous_frame_time_ns = submit_time; job->renderables = renderable_tree_snapshot(); job->render_snapshot.frame_tree = &job->renderables; if (!frame_tree_views_complete(job->renderables)) { frame.outcome = Frame_Outcome::Cancelled; if (flow) flow->cancel_render_frame(std::move(job->frame_surface)); supersede_update_states(buffer->presented_update_states); release_frame_views(job); collect_frame(frame_record); notify_render_finished(frame); return false; } auto& scheduler = Global::instance()->render_scheduler(); bool submitted = scheduler.try_submit_render_task(Render_Executor_Task{ plot_execution_id, frame.frame_id, Render_Task_Kind::Frame, Task{}, [job](Render_Task_Graph& graph, Render_Task_Graph_Node entry, Render_Task_Graph_Node exit) { auto prepare_begin = graph.emplace(Render_Task_Kind::Frame, Task([job]() { job->owner->begin_prepare_frame_job(job); })); auto prepare_dispatch = graph.emplace_subflow(Render_Task_Kind::Frame, [job](Render_Task_Subflow& subflow) { job->owner->dispatch_prepare_subflow(job, subflow); }); auto prepare_end = graph.emplace(Render_Task_Kind::Frame, Task([job]() { job->owner->end_prepare_frame_job(job); })); auto draw = graph.emplace(Render_Task_Kind::Primitive, Task([job]() { job->owner->draw_frame_job(job); })); auto finalize = graph.emplace(Render_Task_Kind::Frame, Task([job]() { job->owner->finalize_frame_job(job); })); graph.precede(entry, prepare_begin); graph.precede(prepare_begin, prepare_dispatch); graph.precede(prepare_dispatch, prepare_end); graph.precede(prepare_end, draw); graph.precede(draw, finalize); graph.precede(finalize, exit); }, Task([job]() { Global::instance()->render_scheduler().post([job]() { job->owner->complete_frame_job(job); }); }), job->worker_stat, true }); if (submitted) return true; frame.outcome = Frame_Outcome::Worker_Budget_Dropped; frame.worker.rejected_task_count++; if (flow) flow->discard_render_frame(std::move(job->frame_surface)); release_frame_views(job); collect_frame(frame_record); notify_render_finished(frame); return false; } static bool frame_tree_views_complete(const Renderable_Frame_Tree& tree) { return std::all_of(tree.begin(), tree.end(), [](const Renderable_Frame_Node& node) { return !node.frame_view_required || static_cast(node.frame_view); }); } static void release_frame_views(const std::shared_ptr& job) { if (!job) return; job->render_snapshot.frame_tree = nullptr; job->renderables.clear(); } static bool frame_job_valid(const std::shared_ptr& job) { return job && job->buffer && job->lifecycle && !job->render_snapshot.destroying(); } void begin_prepare_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job)) return; Frame_Lifecycle_Record& frame = *job->lifecycle; frame.prepare_begin_ns = steady_now_ns(); job->prepared_output_count.store(0, std::memory_order_release); } void dispatch_prepare_subflow(const std::shared_ptr& job, Render_Task_Subflow& subflow) { if (!frame_job_valid(job) || !job->lifecycle->prepare_begin_ns) return; for (const auto& node : job->renderables) { const auto& renderable = node.renderable; if (!renderable || !renderable->should_prepare()) continue; build_renderable_prepare(*renderable, subflow, job->render_snapshot, node.frame_view); job->prepared_output_count.fetch_add(1, std::memory_order_relaxed); } } void end_prepare_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job) || !job->lifecycle->prepare_begin_ns) return; Frame_Lifecycle_Record& frame = *job->lifecycle; if (job->render_snapshot.frame_update_states) { for (const auto& node : job->renderables) { if (node.renderable) drain_renderable_prepare_updates(*node.renderable, *job->render_snapshot.frame_update_states); } } frame.prepared_output_count = job->prepared_output_count.load(std::memory_order_acquire); frame.prepare_end_ns = steady_now_ns(); if (job->owner && job->owner->context) job->owner->context->first_prepare_data.store(false, std::memory_order_release); } void draw_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job) || !job->lifecycle->prepare_end_ns) return; Frame_Lifecycle_Record& frame = *job->lifecycle; job->buffer->render_complete.store(false, std::memory_order_release); job->buffer->image.resize(job->render_snapshot.size.width, job->render_snapshot.size.height); job->buffer->image.fill(job->render_snapshot.background_color); Canvas canvas(job->buffer->image); frame.draw_begin_ns = steady_now_ns(); if (!job->renderables.empty() && job->renderables.front().renderable) job->renderables.front().renderable->render_tree_snapshot(canvas, job->render_snapshot, job->renderables, 0); frame.draw_end_ns = steady_now_ns(); frame.render_end_ns = steady_now_ns(); } void finalize_frame_job(const std::shared_ptr& job) { if (!frame_job_valid(job) || !job->lifecycle->render_end_ns) return; Frame_Lifecycle_Record& frame = *job->lifecycle; for (const auto& update_state : job->buffer->presented_update_states) { if (update_state) update_state->complete_until(Update_Stage::Rendered); } job->buffer->render_complete.store(true, std::memory_order_release); } void execute_frame_job(const std::shared_ptr& job) { if (!job || !job->buffer || !job->lifecycle || job->render_snapshot.destroying()) return; begin_prepare_frame_job(job); Render_Task_Subflow no_subflow(nullptr); dispatch_prepare_subflow(job, no_subflow); end_prepare_frame_job(job); draw_frame_job(job); finalize_frame_job(job); } void complete_frame_job(const std::shared_ptr& job) { if (!job || !job->buffer || !job->lifecycle) return; Frame_Lifecycle_Record& frame = *job->lifecycle; if (job->worker_stat) frame.worker = *job->worker_stat; bool valid_generation = job->generation == generation.load(std::memory_order_acquire); bool valid_context = !destroying(); bool render_complete = job->buffer->render_complete.load(std::memory_order_acquire); if (!valid_generation || !valid_context || !frame.render_end_ns || !render_complete) { if (flow) flow->cancel_render_frame(std::move(job->frame_surface)); frame.outcome = Frame_Outcome::Cancelled; supersede_update_states(job->buffer->presented_update_states); release_frame_views(job); notify_render_finished(frame); return; } auto publish_result = flow ? flow->publish_rendered_frame(std::move(job->frame_surface), steady_now_ns()) : Render_Surface_Publish_Result{}; release_frame_views(job); if (publish_result.superseded_frame_record) { notify_frame_returned(publish_result.superseded_frame_record); } if (publish_result.dropped_frame_record) { collect_frame(publish_result.dropped_frame_record); } if (publish_result.request_present) request_present(publish_result.dirty_rect); notify_render_finished(frame); } 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)); } bool try_render_once() override { cancel_render_deadline(); return submit_render_once(); } void request_paint_from_middle() override { auto update_result = flow ? flow->request_present(steady_now_ns()) : Render_Surface_Publish_Result{}; if (update_result.request_present) request_present(update_result.dirty_rect); } void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) { if (flow) flow->notify_model_dirty(reason, now_ns); } void notify_viewport_changed(Size viewport, std::uint64_t now_ns) { if (flow) flow->notify_viewport_changed(viewport, now_ns); } void notify_render_finished(Frame_Lifecycle_Record& frame) { if (flow) flow->notify_render_finished(frame, steady_now_ns()); } void notify_started() { if (flow) flow->activate_view(steady_now_ns()); } void notify_paused() { if (flow) flow->deactivate_view(); } void notify_frame_returned(std::shared_ptr frame) { if (!frame) return; if (flow) flow->notify_frame_returned(*frame, steady_now_ns()); collect_frame(frame); } void notify_paint_finished() { if (flow) flow->notify_paint_finished(steady_now_ns()); } void schedule_render_deadline(std::uint64_t deadline_ns) override { if (deadline_ns <= steady_now_ns()) { cancel_render_deadline(); if (flow) flow->notify_timer_fired(steady_now_ns()); return; } if (scheduled_flow_deadline_ns && scheduled_flow_deadline_ns <= deadline_ns) return; scheduled_flow_deadline_ns = deadline_ns; std::uint64_t generation = ++flow_deadline_generation; auto guard = context; auto self = shared_from_this(); Global::instance()->render_scheduler().post_at(deadline_ns, [guard, self, generation, deadline_ns]() { if (!guard || guard->destroying.load(std::memory_order_acquire)) return; if (self->flow_deadline_generation != generation || self->scheduled_flow_deadline_ns != deadline_ns) return; self->scheduled_flow_deadline_ns = 0; if (self->flow) self->flow->notify_timer_fired(steady_now_ns()); }); } void cancel_render_deadline() override { scheduled_flow_deadline_ns = 0; ++flow_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_Frame_Tree renderable_tree_snapshot() const { Renderable_Frame_Tree 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(std::shared_ptr frame) { if (!frame) return; 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 flow; std::shared_ptr context = std::make_shared(); std::shared_ptr destroying_token = std::make_shared(false); std::shared_ptr root; std::vector> hover_targets; std::weak_ptr sink; std::atomic latest_input_time_ns{0}; std::atomic pending_input_count{0}; std::atomic pending_dirty_reason_mask{0}; std::uint64_t flow_deadline_generation{}; std::uint64_t scheduled_flow_deadline_ns{}; std::uint64_t next_frame_id{}; std::uint64_t previous_frame_time_ns{}; Plot_Execution_Id plot_execution_id{}; std::atomic generation{1}; }; namespace { Plot_Execution_Id next_plot_execution_id() { static std::atomic next_id{1}; return next_id.fetch_add(1, std::memory_order_acq_rel); } } // namespace Plot_Core::Plot_Core(std::unique_ptr flow) : d(std::make_shared(std::move(flow))) { d->plot_execution_id = next_plot_execution_id(); d->context->owner.store(this, std::memory_order_release); if (d->flow) d->flow->attach(*d); Global::instance()->render_scheduler().register_plot(this); } Plot_Core::~Plot_Core() { d->context->destroying.store(true, std::memory_order_release); d->destroying_token->store(true, std::memory_order_release); d->generation.fetch_add(1, std::memory_order_acq_rel); if (d->flow) d->flow->shutdown(); Global::instance()->render_scheduler().remove_plot(this); if (d->flow) d->flow->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::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(); notify_model_dirty(); } void Plot_Core::set_background_color(Color color) { d->context->background_rgba.store(premultiply(color), std::memory_order_release); notify_model_dirty(Dirty_Reason::Config); } Color Plot_Core::background_color() const { return d->background_color(); } void Plot_Core::set_viewport_size(Size size) { Size previous{ d->context->render_width.load(std::memory_order_acquire), d->context->render_height.load(std::memory_order_acquire) }; d->context->render_width.store(size.width, std::memory_order_release); d->context->render_height.store(size.height, std::memory_order_release); if (previous != size) d->context->viewport_version.fetch_add(1, std::memory_order_acq_rel); if (!size.empty()) d->context->first_resize.store(false, std::memory_order_release); if (previous != size) { d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(Dirty_Reason::Viewport), std::memory_order_acq_rel); d->post_scheduler_action([data = d, size]() { data->notify_viewport_changed(size, steady_now_ns()); }); } } 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::notify_model_dirty(Dirty_Reason reason) { if (d->destroying()) return; std::uint64_t now_ns = steady_now_ns(); if (d->flow) d->flow->advance_edit_version(); d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(reason), std::memory_order_acq_rel); 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, reason, now_ns]() { data->notify_model_dirty(reason, now_ns); }); } void Plot_Core::request_explicit_render() { if (d->destroying()) return; d->post_scheduler_action([data = d]() { data->notify_model_dirty(Dirty_Reason::Core_Data, steady_now_ns()); }); } void Plot_Core::activate_view() { if (d->destroying()) return; d->post_scheduler_action([data = d]() { data->notify_started(); }); } void Plot_Core::deactivate_view() { d->post_scheduler_action([data = d]() { data->notify_paused(); }); } bool Plot_Core::view_active() const { return d->flow && d->flow->view_active(); } void Plot_Core::set_presentation_sink(std::weak_ptr sink) { d->sink = std::move(sink); } std::unique_ptr Plot_Core::create_renderable_runtime(Renderable& owner) const { return d->flow ? d->flow->create_renderable_runtime(owner) : nullptr; } Present_Surface_Lease Plot_Core::begin_present() { Present_Surface_Lease result; if (!d->root || !d->flow) return result; std::shared_ptr returned_frame; result = d->flow->begin_present(steady_now_ns(), returned_frame); if (returned_frame) { d->post_scheduler_action([data = d, frame = std::move(returned_frame)]() mutable { data->notify_frame_returned(frame); }); } return result; } void Plot_Core::end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) { if (d->flow) { if (auto returned = d->flow->end_present(std::move(frame), paint_begin_ns, paint_end_ns)) d->collect_frame(returned); } d->post_scheduler_action([data = d]() mutable { data->notify_paint_finished(); }); } std::shared_ptr Plot_Core_Context_Access::render_context(Plot_Core& plot) { return plot.d ? plot.d->context : std::shared_ptr{}; } std::shared_ptr Plot_Core_Context_Access::render_context(const Plot_Core& plot) { return plot.d ? plot.d->context : std::shared_ptr{}; } } // namespace renderive