#include "Performance_Overlay_p.h" #include "../architecture/Plot_Render_Context.h" #include "../performance/Performance_Log_Service.h" #include "../plot/Plot_Core_Access.h" #include #include "../base/Task.h" #include "../plot/Plot_Core.h" namespace renderive { namespace { class Performance_Overlay_Service { public: ~Performance_Overlay_Service() { shutdown(); } bool try_submit(Task task) { if (!task || shutting_down.load(std::memory_order_acquire)) return false; auto topology = std::make_shared(); auto task_ptr = std::make_shared(std::move(task)); topology->emplace([task_ptr]() { (*task_ptr)(); }); executor.run(*topology, [topology]() {}); return true; } void shutdown() { if (shutting_down.exchange(true, std::memory_order_acq_rel)) return; executor.wait_for_all(); } private: tf::Executor executor{1}; std::atomic_bool shutting_down{false}; }; Performance_Overlay_Service& performance_background_service() { static Performance_Overlay_Service service; return service; } double duration_ms(std::uint64_t begin_ns, std::uint64_t end_ns) { return end_ns > begin_ns ? static_cast(end_ns - begin_ns) / 1000000.0 : 0.0; } double cache_ratio(std::uint64_t hit, std::uint64_t miss) { std::uint64_t total = hit + miss; return total ? static_cast(hit) / static_cast(total) : 0.0; } std::string source_text(Refresh_Limit_Source source) { switch (source) { case Refresh_Limit_Source::Unknown: return "unknown"; case Refresh_Limit_Source::Input: return "input"; case Refresh_Limit_Source::Render: return "render"; case Refresh_Limit_Source::Paint: return "paint"; case Refresh_Limit_Source::User: return "user"; } return "unknown"; } std::string outcome_text(Frame_Outcome outcome) { switch (outcome) { case Frame_Outcome::Presented: return "presented"; case Frame_Outcome::Superseded: return "superseded"; case Frame_Outcome::Render_Acquire_Dropped: return "render_acquire_dropped"; case Frame_Outcome::Publish_Dropped: return "publish_dropped"; case Frame_Outcome::Paint_Acquire_Dropped: return "paint_acquire_dropped"; case Frame_Outcome::Worker_Budget_Dropped: return "worker_budget_dropped"; case Frame_Outcome::Cancelled: return "cancelled"; } return "unknown"; } std::string bottleneck_text(Low_Latency_Bottleneck_State state) { switch (state) { case Low_Latency_Bottleneck_State::Unknown: return "unknown"; case Low_Latency_Bottleneck_State::Input_Limited: return "input"; case Low_Latency_Bottleneck_State::Producer_Limited: return "producer"; case Low_Latency_Bottleneck_State::Consumer_Limited: return "consumer"; case Low_Latency_Bottleneck_State::User_Limited: return "user"; } return "unknown"; } std::string production_mode_text(Low_Latency_Production_Mode mode) { switch (mode) { case Low_Latency_Production_Mode::Latency_Eager: return "latency_eager"; case Low_Latency_Production_Mode::Consumer_Paced: return "consumer_paced"; } return "latency_eager"; } std::string limit_reason_text(Low_Latency_Limit_Reason reason) { switch (reason) { case Low_Latency_Limit_Reason::User_Fps: return "user_fps"; case Low_Latency_Limit_Reason::Consumer_Capacity: return "consumer_capacity"; case Low_Latency_Limit_Reason::Render_Capacity: return "render_capacity"; case Low_Latency_Limit_Reason::No_Work: return "no_work"; case Low_Latency_Limit_Reason::Shutdown: return "shutdown"; } return "user_fps"; } std::string consumer_confidence_text(Consumer_Confidence confidence) { switch (confidence) { case Consumer_Confidence::Low: return "low"; case Consumer_Confidence::Medium: return "medium"; case Consumer_Confidence::High: return "high"; } return "low"; } static std::shared_ptr performance_overlay_owner(const std::shared_ptr& ctx) { if (!ctx) return {}; auto observer = std::atomic_load_explicit(&ctx->frame_lifecycle_observer, std::memory_order_acquire); return std::dynamic_pointer_cast(observer); } static Performance_Log_Plot_Identity performance_plot_identity(const std::shared_ptr& ctx) { Performance_Log_Plot_Identity identity; if (!ctx) return identity; identity.plot_id = ctx->performance_plot_id.load(std::memory_order_acquire); { std::lock_guard lock(ctx->performance_plot_name_mutex); identity.plot_name = ctx->performance_plot_name; } if (identity.plot_name.empty()) identity.plot_name = "plot_" + std::to_string(identity.plot_id); return identity; } static std::function performance_update_callback(const std::shared_ptr& ctx) { if (!ctx) return {}; std::lock_guard lock(ctx->performance_update_callback_mutex); return ctx->performance_update_callback; } static std::function performance_clipboard_callback(const std::shared_ptr& ctx) { if (!ctx) return {}; std::lock_guard lock(ctx->performance_clipboard_callback_mutex); return ctx->performance_clipboard_callback; } static std::string performance_snapshot_text(const Performance_Display_Snapshot& snapshot) { std::size_t size = 0; for (const auto& line : snapshot.lines) size += line.size() + 1; std::string text; text.reserve(size); for (std::size_t i = 0; i < snapshot.lines.size(); ++i) { if (i) text.push_back('\n'); text += snapshot.lines[i]; } return text; } static void request_performance_update(const std::shared_ptr& worker) { if (!worker) return; std::function callback; { std::lock_guard lock(worker->update_callback_mutex); callback = worker->update_callback; } if (callback) callback(); } } void schedule_performance_metrics_worker(const std::shared_ptr& state, bool request_repaint) { if (!state || state->cancelled.load(std::memory_order_acquire)) return; if (request_repaint) state->repaint_requested.store(true, std::memory_order_release); state->work_requested.store(true, std::memory_order_release); if (state->worker_active.exchange(true, std::memory_order_acq_rel)) return; if (!performance_background_service().try_submit(Task([state]() { Performance_Overlay_Private::run_metrics_worker(state); }))) { state->worker_active.store(false, std::memory_order_release); } } Performance_Overlay::Performance_Overlay() : private_data(std::make_unique()) {} Performance_Overlay::~Performance_Overlay() = default; Performance_Overlay_Private* Performance_Overlay::d() { return private_data.get(); } static std::shared_ptr attach_performance_overlay_impl(Plot_Core& plot, const Performance_Overlay_Options* options) { auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return {}; auto shower = performance_overlay_owner(ctx); if (shower) { shower->set_log_identity(performance_plot_identity(ctx)); if (options) shower->set_options(*options); return shower; } auto created_shower = renderive::make_shared(); created_shower->set_log_identity(performance_plot_identity(ctx)); if (options) created_shower->set_options(*options); created_shower->set_update_callback(performance_update_callback(ctx)); created_shower->set_clipboard_callback(performance_clipboard_callback(ctx)); std::atomic_store_explicit(&ctx->frame_lifecycle_observer, std::static_pointer_cast(created_shower), std::memory_order_release); return created_shower; } std::shared_ptr attach_performance_overlay(Plot_Core& plot) { return attach_performance_overlay_impl(plot, nullptr); } std::shared_ptr attach_performance_overlay(Plot_Core& plot, const Performance_Overlay_Options& options) { return attach_performance_overlay_impl(plot, &options); } std::shared_ptr performance_overlay(Plot_Core& plot) { return performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); } void set_performance_plot_name(Plot_Core& plot, std::string name) { auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; { std::lock_guard lock(ctx->performance_plot_name_mutex); ctx->performance_plot_name = std::move(name); } auto shower = performance_overlay_owner(ctx); if (shower) shower->set_log_identity(performance_plot_identity(ctx)); } void detach_performance_overlay(Plot_Core& plot) { auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; auto shower = performance_overlay_owner(ctx); std::atomic_store_explicit(&ctx->frame_lifecycle_observer, std::shared_ptr{}, std::memory_order_release); if (shower) shower->set_enabled(false); } bool performance_overlay_enabled(const Plot_Core& plot) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled(); } void set_performance_overlay_enabled(Plot_Core& plot, bool enabled) { if (!enabled) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (shower) shower->set_enabled(false); return; } auto shower = attach_performance_overlay(plot); if (shower) { shower->set_enabled(true); auto ctx = Plot_Core_Context_Access::render_context(plot); shower->set_viewport(plot.viewport_size(), ctx ? ctx->viewport_version.load(std::memory_order_acquire) : 0); } } std::shared_ptr performance_overlay_snapshot(const Plot_Core& plot) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (!shower || !shower->enabled()) return {}; return shower->display_snapshot(); } void set_performance_overlay_update_callback(Plot_Core& plot, std::function callback) { auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; { std::lock_guard lock(ctx->performance_update_callback_mutex); ctx->performance_update_callback = std::move(callback); } auto shower = performance_overlay_owner(ctx); if (shower) shower->set_update_callback(performance_update_callback(ctx)); } void set_performance_overlay_clipboard_callback(Plot_Core& plot, std::function callback) { auto ctx = Plot_Core_Context_Access::render_context(plot); if (!ctx) return; { std::lock_guard lock(ctx->performance_clipboard_callback_mutex); ctx->performance_clipboard_callback = std::move(callback); } auto shower = performance_overlay_owner(ctx); if (shower) shower->set_clipboard_callback(performance_clipboard_callback(ctx)); } void notify_performance_overlay_viewport_changed(Plot_Core& plot) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); if (!shower || !shower->enabled()) return; auto ctx = Plot_Core_Context_Access::render_context(plot); shower->set_viewport(plot.viewport_size(), ctx ? ctx->viewport_version.load(std::memory_order_acquire) : 0); } bool performance_overlay_handle_wheel(Plot_Core& plot, PointF position, double angle_delta_y, double pixel_delta_y) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_wheel(position, angle_delta_y, pixel_delta_y); } bool performance_overlay_handle_pointer_press(Plot_Core& plot, PointF position) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_press(position); } bool performance_overlay_handle_pointer_move(Plot_Core& plot, PointF position) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_move(position); } bool performance_overlay_handle_pointer_release(Plot_Core& plot, PointF position) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_release(position); } bool performance_overlay_handle_pointer_leave(Plot_Core& plot) { auto shower = performance_overlay_owner(Plot_Core_Context_Access::render_context(plot)); return shower && shower->enabled() && shower->handle_pointer_leave(); } void Performance_Overlay::set_enabled(bool enabled) { auto worker = d()->metrics_worker; d()->reset_button_states(); d()->enabled_value.store(enabled, std::memory_order_release); worker->enabled.store(enabled, std::memory_order_release); if (!enabled) { worker->repaint_requested.store(false, std::memory_order_release); worker->published_snapshot.store({}, std::memory_order_release); request_performance_update(worker); return; } worker->reset_requested.store(true, std::memory_order_release); worker->snapshot_rebuild_requested.store(true, std::memory_order_release); schedule_performance_metrics_worker(worker, true); } bool Performance_Overlay::enabled() const { return const_cast(this)->d()->enabled_value.load(std::memory_order_acquire); } std::shared_ptr Performance_Overlay::display_snapshot() const { return const_cast(this)->d()->metrics_worker->published_snapshot.load(std::memory_order_acquire); } void Performance_Overlay::set_options(Performance_Overlay_Options options) { auto worker = d()->metrics_worker; if (options.log.enabled) performance_log_service().configure(options.log); { std::lock_guard lock(worker->options_mutex); worker->options = std::move(options); } worker->snapshot_rebuild_requested.store(true, std::memory_order_release); if (enabled()) schedule_performance_metrics_worker(worker, true); } Performance_Overlay_Options Performance_Overlay::options() const { auto worker = const_cast(this)->d()->metrics_worker; std::lock_guard lock(worker->options_mutex); return worker->options; } void Performance_Overlay::set_log_options(Performance_Log_Options options) { Performance_Overlay_Options value = this->options(); value.log = std::move(options); set_options(std::move(value)); } Performance_Log_Options Performance_Overlay::log_options() const { return options().log; } bool Performance_Overlay::logging_enabled() const { return log_options().enabled; } bool Performance_Overlay::capture_enabled() const { return enabled() || logging_enabled(); } void Performance_Overlay::set_log_identity(Performance_Log_Plot_Identity identity) { d()->set_identity(std::move(identity)); } void Performance_Overlay::set_font(Font font) { Performance_Overlay_Options value = options(); value.font = std::move(font); set_options(std::move(value)); } void Performance_Overlay::set_foreground(Color color) { Performance_Overlay_Options value = options(); value.foreground = color; value.section_color = color; value.label_color = color; value.value_color = color; value.unit_color = color; set_options(std::move(value)); } void Performance_Overlay::set_background(Color color) { Performance_Overlay_Options value = options(); value.background = color; set_options(std::move(value)); } void Performance_Overlay::collect_frame(std::shared_ptr frame) { if (!frame) return; if (enabled()) d()->enqueue_frame_metric(frame); if (logging_enabled()) d()->enqueue_frame_log(frame); } void Performance_Overlay::collect_feedback_event(std::shared_ptr event) { if (!event || !logging_enabled()) return; d()->enqueue_feedback_log(event); } void Performance_Overlay::set_viewport(Size viewport, std::uint64_t version) { d()->metrics_worker->viewport_width.store(viewport.width, std::memory_order_release); d()->metrics_worker->viewport_height.store(viewport.height, std::memory_order_release); d()->metrics_worker->viewport_version.store(version, std::memory_order_release); d()->metrics_worker->snapshot_rebuild_requested.store(true, std::memory_order_release); schedule_performance_metrics_worker(d()->metrics_worker, true); } bool Performance_Overlay::handle_wheel(PointF position, double angle_delta_y, double pixel_delta_y) { auto snapshot = display_snapshot(); if (!snapshot || !snapshot->destination_rect.contains(position)) return false; double line_height = snapshot->line_height > 0.0 ? snapshot->line_height : d()->metrics_worker->last_line_height; double delta = 0.0; if (pixel_delta_y != 0.0) delta = -pixel_delta_y; else if (angle_delta_y != 0.0) delta = -(angle_delta_y / 120.0) * 3.0 * std::max(1.0, line_height); if (delta != 0.0) { performance_atomic_add(d()->metrics_worker->pending_wheel_delta_px, delta); schedule_performance_metrics_worker(d()->metrics_worker, true); } return true; } bool Performance_Overlay::handle_pointer_press(PointF position) { auto snapshot = display_snapshot(); if (!snapshot) return false; Button_Event_Result copy_result = d()->copy_button_interaction.pointer_press(position, snapshot->copy_button_rect); Button_Event_Result close_result = d()->close_button_interaction.pointer_press(position, snapshot->close_button_rect); d()->publish_button_states(copy_result.changed || close_result.changed); if (copy_result.consumed || close_result.consumed) return true; if (snapshot->max_scroll_offset <= 0.0) return false; if (snapshot->scroll_thumb_rect.contains(position)) { d()->metrics_worker->drag_anchor_y.store(position.y - snapshot->scroll_thumb_rect.y, std::memory_order_release); d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); d()->metrics_worker->drag_end_requested.store(false, std::memory_order_release); d()->metrics_worker->drag_active.store(true, std::memory_order_release); schedule_performance_metrics_worker(d()->metrics_worker, true); return true; } if (snapshot->scroll_track_rect.contains(position)) { int page = position.y < snapshot->scroll_thumb_rect.y ? -1 : 1; d()->metrics_worker->pending_page_delta.fetch_add(page, std::memory_order_acq_rel); schedule_performance_metrics_worker(d()->metrics_worker, true); return true; } return false; } bool Performance_Overlay::handle_pointer_move(PointF position) { if (d()->metrics_worker->drag_active.load(std::memory_order_acquire)) { d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); schedule_performance_metrics_worker(d()->metrics_worker, true); return true; } auto snapshot = display_snapshot(); if (!snapshot) return false; Button_Event_Result copy_result = d()->copy_button_interaction.pointer_move(position, snapshot->copy_button_rect); Button_Event_Result close_result = d()->close_button_interaction.pointer_move(position, snapshot->close_button_rect); d()->publish_button_states(copy_result.changed || close_result.changed); return copy_result.consumed || close_result.consumed; } bool Performance_Overlay::handle_pointer_release(PointF position) { auto snapshot = display_snapshot(); if (snapshot) { Button_Event_Result copy_result = d()->copy_button_interaction.pointer_release(position, snapshot->copy_button_rect); Button_Event_Result close_result = d()->close_button_interaction.pointer_release(position, snapshot->close_button_rect); d()->publish_button_states(copy_result.changed || close_result.changed); if (copy_result.clicked) d()->copy_to_clipboard(performance_snapshot_text(*snapshot)); if (close_result.clicked) set_enabled(false); if (copy_result.consumed || close_result.consumed) return true; } if (!d()->metrics_worker->drag_active.load(std::memory_order_acquire)) return false; d()->metrics_worker->drag_position_y.store(position.y, std::memory_order_release); d()->metrics_worker->drag_end_requested.store(true, std::memory_order_release); schedule_performance_metrics_worker(d()->metrics_worker, true); return true; } bool Performance_Overlay::handle_pointer_leave() { Button_Event_Result copy_result = d()->copy_button_interaction.pointer_leave(); Button_Event_Result close_result = d()->close_button_interaction.pointer_leave(); d()->publish_button_states(copy_result.changed || close_result.changed); return copy_result.consumed || close_result.consumed; } void Performance_Overlay::set_update_callback(std::function callback) { std::lock_guard lock(d()->metrics_worker->update_callback_mutex); d()->metrics_worker->update_callback = std::move(callback); } void Performance_Overlay::set_clipboard_callback(std::function callback) { d()->set_clipboard_callback(std::move(callback)); } void write_frame_performance_log(std::shared_ptr frame) { if (!frame) return; performance_log_service().enqueue_frame({}, std::move(frame)); } } // namespace renderive