154 lines
5.3 KiB
C++
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
|