diff --git a/README.md b/README.md index c5925050..64c5455a 100644 --- a/README.md +++ b/README.md @@ -352,7 +352,8 @@ Parameters that are enabled by default have to be explicitly disabled. These (cu | `fps_limit` | Limit the apps framerate. Comma-separated list of one or more FPS values. `0` means unlimited | | `fps_only` | Show FPS only. ***Not meant to be used with other display params*** | | `fps_sampling_period=` | Time interval between two sampling points for gathering the FPS in milliseconds. Default is `500` | -| `fps_value=` | Choose the break points where `fps_color_change` changes colors between. E.g `60,144`, default is `30,60` | +| `fps_value` | Choose the break points where `fps_color_change` changes colors between. E.g `60,144`, default is `30,60` | +| `fps_metrics` | Takes a list of decimal values or the value avg, e.g `avg,0.001` | | `frame_count` | Display frame count | | `frametime` | Display frametime next to FPS text | | `fsr` | Display the status of FSR (only works in gamescope) | diff --git a/data/MangoHud.conf b/data/MangoHud.conf index 5c4b43cf..b2d87431 100644 --- a/data/MangoHud.conf +++ b/data/MangoHud.conf @@ -135,6 +135,8 @@ fps # fps_color=B22222,FDFD09,39F900 frametime # frame_count +## fps_metrics takes a list of decimal values or the value avg +# fps_metrics=avg,0.01 ### Display GPU throttling status based on Power, current, temp or "other" ## Only shows if throttling is currently happening diff --git a/src/fps_metrics.h b/src/fps_metrics.h new file mode 100644 index 00000000..985a745e --- /dev/null +++ b/src/fps_metrics.h @@ -0,0 +1,134 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct metric_t { + std::string name; + float value; + std::string display_name; +}; + +class fpsMetrics { + private: + std::vector> fps_stats; + std::thread thread; + std::mutex mtx; + std::condition_variable cv; + bool run = false; + bool thread_init = false; + bool terminate = false; + + void calculate(){ + thread_init = true; + while (!terminate){ + std::unique_lock lock(mtx); + cv.wait(lock, [this] { return run; }); + + std::vector sorted_values; + for (const auto& p : fps_stats) + sorted_values.push_back(p.second); + + std::sort(sorted_values.begin(), sorted_values.end()); + + auto it = metrics.begin(); + while (it != metrics.end()) { + if (it->name == "AVG") { + it->display_name = it->name; + if (!fps_stats.empty()) { + float sum = std::accumulate(fps_stats.begin(), fps_stats.end(), 0.0f, + [](float acc, const std::pair& p) { + return acc + p.second; + }); + it->value = sum / fps_stats.size(); + ++it; + } + } else { + try { + float val = std::stof(it->name); + if (val <= 0 || val >= 1 ) { + SPDLOG_DEBUG("Failed to use fps metric, it's out of range {}", it->name); + it = metrics.erase(it); + break; + } + float multiplied_val = val * 100; + std::ostringstream stream; + if (multiplied_val == static_cast(multiplied_val)) { + stream << std::fixed << std::setprecision(0) << multiplied_val << "%"; + } else { + stream << std::fixed << std::setprecision(1) << multiplied_val << "%"; + } + it->display_name = stream.str(); + uint64_t idx = val * sorted_values.size() - 1; + it->value = sorted_values[idx]; + ++it; + } catch (const std::invalid_argument& e) { + SPDLOG_DEBUG("Failed to use fps metric value {}", it->name); + it = metrics.erase(it); + } + } + } + + run = false; + } + } + + public: + std::vector metrics; + + fpsMetrics(std::vector values){ + // capitalize string + for (auto& val : values){ + for(char& c : val) { + c = std::toupper(static_cast(c)); + } + + metrics.push_back({val, 0.0f}); + } + + if (!thread_init){ + thread = std::thread(&fpsMetrics::calculate, this); + } + }; + + void update(uint64_t now, double fps){ + fps_stats.push_back({now, fps}); + // Calculate the cut-off nanotime (10 minutes ago) + uint64_t ten_minutes_ns = 600000000000ULL; // 10 minutes in nanoseconds + uint64_t cutoff_time_ns = os_time_get_nano() - ten_minutes_ns; + + // Removing elements older than 10 minutes + fps_stats.erase(std::remove_if(fps_stats.begin(), fps_stats.end(), + [cutoff_time_ns](const std::pair& p) { + return p.first < cutoff_time_ns; + }), + fps_stats.end()); + }; + + void update_thread(){ + { + std::lock_guard lock(mtx); + run = true; + } + cv.notify_one(); + } + + ~fpsMetrics(){ + terminate = true; + { + std::lock_guard lock(mtx); + run = true; + } + cv.notify_one(); + thread.join(); + } +}; + +extern std::unique_ptr fpsmetrics; \ No newline at end of file diff --git a/src/hud_elements.h b/src/hud_elements.h index a66d9afa..6f7594e2 100644 --- a/src/hud_elements.h +++ b/src/hud_elements.h @@ -84,6 +84,7 @@ class HudElements{ static void throttling_status(); static void exec_name(); static void duration(); + static void fps_metrics(); void convert_colors(const struct overlay_params& params); void convert_colors(bool do_conv, const struct overlay_params& params); diff --git a/src/overlay.cpp b/src/overlay.cpp index 9d7c4ccc..c528531f 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -23,7 +23,7 @@ #include "pci_ids.h" #include "iostats.h" #include "amdgpu.h" - +#include "fps_metrics.h" #ifdef __linux__ #include @@ -256,12 +256,15 @@ void update_hud_info_with_frametime(struct swapchain_stats& sw_stats, const stru #endif frametime = frametime_ms; fps = double(1000 / frametime_ms); + if (fpsmetrics) fpsmetrics->update(now, fps); if (elapsed >= params.fps_sampling_period) { if (!hw_update_thread) hw_update_thread = std::make_unique(); hw_update_thread->update(¶ms, vendorID); + if (fpsmetrics) fpsmetrics->update_thread(); + sw_stats.fps = 1000000000.0 * sw_stats.n_frames_since_update / elapsed; if (params.enabled[OVERLAY_PARAM_ENABLED_time]) { diff --git a/src/overlay_params.cpp b/src/overlay_params.cpp index 11e89ed3..657e9a8e 100644 --- a/src/overlay_params.cpp +++ b/src/overlay_params.cpp @@ -38,6 +38,9 @@ #include "dbus_info.h" #include "app/mangoapp.h" +#include "fps_metrics.h" + +std::unique_ptr fpsmetrics; #if __cplusplus >= 201703L @@ -414,6 +417,20 @@ parse_gl_size_query(const char *str) return GL_SIZE_DRAWABLE; } +static std::vector +parse_fps_metrics(const char *str){ + std::vector metrics; + auto tokens = str_tokenize(str); + for (auto& token : tokens) { + metrics.push_back(token); + } + + fpsmetrics.release(); + fpsmetrics = std::make_unique(metrics); + + return metrics; +} + #define parse_width(s) parse_unsigned(s) #define parse_height(s) parse_unsigned(s) #define parse_vsync(s) parse_unsigned(s) diff --git a/src/overlay_params.h b/src/overlay_params.h index b721df25..7e9e96a8 100644 --- a/src/overlay_params.h +++ b/src/overlay_params.h @@ -186,6 +186,7 @@ typedef unsigned long KeySym; OVERLAY_PARAM_CUSTOM(text_outline_thickness) \ OVERLAY_PARAM_CUSTOM(fps_text) \ OVERLAY_PARAM_CUSTOM(device_battery) \ + OVERLAY_PARAM_CUSTOM(fps_metrics) \ enum overlay_param_position { LAYER_POSITION_TOP_LEFT, @@ -306,6 +307,7 @@ struct overlay_params { unsigned text_outline_color; float text_outline_thickness; std::vector device_battery; + std::vector fps_metrics; }; const extern char *overlay_param_names[];