Files
Renderive/render_2D/plottable/Performance_Overlay.cpp
T
2026-08-13 00:35:00 +08:00

154 lines
5.3 KiB
C++

#include "Performance_Overlay.h"
#include <algorithm>
#include <atomic>
#include <cctype>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <sstream>
namespace renderive {
namespace {
std::string safe_filename(std::string value) {
if (value.empty())
return "plot";
for (char& character : value) {
const auto byte = static_cast<unsigned char>(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<const Performance_Display_Snapshot> 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>()) {
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<const Performance_Display_Snapshot>
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<double>(now - impl_->previous_frame).count();
if (seconds > 0.0)
rendered_fps = 1.0 / seconds;
}
impl_->previous_frame = now;
auto snapshot = std::make_shared<Performance_Display_Snapshot>();
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