#include "Performance_Overlay.h" #include #include #include #include #include #include #include #include #include namespace renderive { namespace { std::string safe_filename(std::string value) { if (value.empty()) return "plot"; for (char& character : value) { const auto byte = static_cast(character); if (!(std::isalnum(byte) || character == '-' || character == '_')) character = '_'; } return value; } std::string fixed(double value, int precision = 2) { std::ostringstream stream; stream << std::fixed << std::setprecision(precision) << value; return stream.str(); } } // namespace struct Performance_Overlay::Impl { mutable std::mutex mutex; std::atomic_bool enabled{}; Performance_Overlay_Options options; std::string plot_name; std::shared_ptr snapshot; std::ofstream log; std::filesystem::path log_path; std::chrono::steady_clock::time_point previous_frame; }; Performance_Overlay::Performance_Overlay() : Performance_Overlay(Performance_Overlay_Options{}) {} Performance_Overlay::Performance_Overlay(Performance_Overlay_Options options) : impl_(std::make_unique()) { impl_->options = std::move(options); } Performance_Overlay::~Performance_Overlay() = default; void Performance_Overlay::set_enabled(bool enabled) { if (impl_->enabled.exchange(enabled, std::memory_order_acq_rel) == enabled) return; std::lock_guard lock(impl_->mutex); impl_->previous_frame = {}; if (!enabled) { impl_->log.close(); impl_->log_path.clear(); } } bool Performance_Overlay::enabled() const noexcept { return impl_->enabled.load(std::memory_order_acquire); } void Performance_Overlay::set_options(Performance_Overlay_Options options) { std::lock_guard lock(impl_->mutex); impl_->options = std::move(options); impl_->log.close(); impl_->log_path.clear(); } std::shared_ptr Performance_Overlay::display_snapshot() const { std::lock_guard lock(impl_->mutex); return impl_->snapshot; } void Performance_Overlay::set_plot_name(std::string name) { std::lock_guard lock(impl_->mutex); impl_->plot_name = std::move(name); impl_->log.close(); impl_->log_path.clear(); } void Performance_Overlay::record_frame(double render_duration_ms, const Low_Latency_Diagnostics& diagnostics, std::size_t renderable_count, Size viewport) { if (!enabled()) return; { std::lock_guard lock(impl_->mutex); if (!enabled()) return; const auto now = std::chrono::steady_clock::now(); double rendered_fps{}; if (impl_->previous_frame.time_since_epoch().count() != 0) { const auto seconds = std::chrono::duration(now - impl_->previous_frame).count(); if (seconds > 0.0) rendered_fps = 1.0 / seconds; } impl_->previous_frame = now; auto snapshot = std::make_shared(); snapshot->style = impl_->options.style; snapshot->viewport_size = viewport; snapshot->plot_name = impl_->plot_name; snapshot->frame_count = diagnostics.refresh.frame_count; snapshot->lines = { "Renderive Core2 / Kernel", "Plot: " + (impl_->plot_name.empty() ? std::string{"unnamed"} : impl_->plot_name), "Frame: " + std::to_string(snapshot->frame_count), "Render: " + fixed(render_duration_ms, 3) + " ms", "Observed: " + fixed(rendered_fps) + " FPS", "Limit: " + fixed(diagnostics.refresh.frequency_hz) + " FPS", "Kernel interval: " + fixed(diagnostics.refresh.next_refresh_interval_ns / 1'000'000.0, 3) + " ms", "Renderables: " + std::to_string(renderable_count) }; impl_->snapshot = std::move(snapshot); const auto& log_options = impl_->options.log; if (log_options.enabled && !log_options.directory.empty()) { std::error_code error; const std::filesystem::path directory(log_options.directory); std::filesystem::create_directories(directory, error); const auto path = directory / (safe_filename(log_options.session_name) + "_" + safe_filename(impl_->plot_name) + ".csv"); if (!error && (!impl_->log.is_open() || impl_->log_path != path)) { impl_->log.close(); const bool exists = std::filesystem::exists(path, error); impl_->log.open(path, std::ios::out | std::ios::app); impl_->log_path = path; if (impl_->log && !exists) impl_->log << "frame,render_ms,observed_fps,max_fps,renderables\n"; } if (impl_->log) { impl_->log << diagnostics.refresh.frame_count << ',' << render_duration_ms << ',' << rendered_fps << ',' << diagnostics.refresh.frequency_hz << ',' << renderable_count << '\n'; impl_->log.flush(); } } } } } // namespace renderive