ui: scale the auto frame cap margin with the display refresh rate

This commit is contained in:
mchughalex 2026-07-24 13:33:44 +01:00
parent 79761cbdd5
commit ae6ea8e925
3 changed files with 26 additions and 3 deletions

View file

@ -222,6 +222,15 @@ class Window {
// readable from any thread (frame pacers, settings UI). // readable from any thread (frame pacers, settings UI).
static float CachedDisplayRefreshHz(); static float CachedDisplayRefreshHz();
// Refresh-derived automatic frame cap for the given display refresh rate,
// 0 while the refresh is unknown. Presents run with tearing allowed, so on
// a VRR display any present arriving faster than the panel's minimum
// refresh period tears; the cap must sit far enough below the refresh rate
// that swap-to-present jitter cannot cross it. A fixed margin that is
// ample at 60 Hz is too thin at 144 Hz, so the margin scales with the
// refresh rate. Single source of the policy for pacers and settings UI.
static float AutoFrameCapHz(float refresh_hz);
// Round trips are not guaranteed to return the same results. // Round trips are not guaranteed to return the same results.
static constexpr uint32_t ConvertSizeDpi(uint32_t size, uint32_t new_dpi, uint32_t old_dpi) { static constexpr uint32_t ConvertSizeDpi(uint32_t size, uint32_t new_dpi, uint32_t old_dpi) {
// Always rounding up to prevent zero sizes (unless the input is zero) as // Always rounding up to prevent zero sizes (unless the input is zero) as

View file

@ -1351,10 +1351,12 @@ void SimpleSettingsDialog::BuildRows(std::vector<RowSpec>& rows, int category) {
"Auto tracks the current display and does exactly that. Frames above the " "Auto tracks the current display and does exactly that. Frames above the "
"refresh rate cannot be shown and only make steady motion judder."; "refresh rate cannot be shown and only make steady motion judder.";
if (FrameCapHasAuto()) { if (FrameCapHasAuto()) {
const float hz = rex::ui::Window::CachedDisplayRefreshHz(); // Same policy the guest pacer applies (see Window::AutoFrameCapHz).
const float auto_cap =
rex::ui::Window::AutoFrameCapHz(rex::ui::Window::CachedDisplayRefreshHz());
row.options.push_back( row.options.push_back(
hz >= 30.0f auto_cap > 0.0f
? "Auto (" + std::to_string(int(hz) - 4) + " FPS)" ? "Auto (" + std::to_string(int(auto_cap)) + " FPS)"
: std::string("Auto")); : std::string("Auto"));
} }
for (size_t i = 1; i < kFrameCapLabels.size(); ++i) { for (size_t i = 1; i < kFrameCapLabels.size(); ++i) {

View file

@ -11,6 +11,7 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <cmath>
#include <iterator> #include <iterator>
#include <rex/assert.h> #include <rex/assert.h>
@ -78,6 +79,17 @@ void Window::UpdateCachedDisplayRefresh() {
} }
} }
float Window::AutoFrameCapHz(float refresh_hz) {
if (refresh_hz < 30.0f) {
return 0.0f;
}
// 4 FPS of margin or 5% of the refresh rate, whichever is larger (see the
// declaration): 4 FPS is 6.7% of headroom at 60 Hz but only 0.2 ms of
// present slack at 144 Hz, within routine thread-scheduling jitter.
const float margin = std::max(4.0f, refresh_hz * 0.05f);
return std::floor(refresh_hz - margin);
}
Window::Window(WindowedAppContext& app_context, const std::string_view title, Window::Window(WindowedAppContext& app_context, const std::string_view title,
uint32_t desired_logical_width, uint32_t desired_logical_height) uint32_t desired_logical_width, uint32_t desired_logical_height)
: app_context_(app_context), : app_context_(app_context),