feat: FPS Limiter (#1446)

* Add interpolation frame rate cap

* wip: reworked framelimiter

Based on my testing this is a bit more stable in frametimes.

* wip: efficiency improvement + windows build fix

Significantly improve efficiency by using a hybrid approach.

* wip: UI changes

* wip: end frame AFTER limiting

* wip: remove unused include

* wip: minor ui code change

Makes it easier to remove/add presets

* Simplify Limiter UI

- Change enableFrameInterpolation to an enum with off/capped/unlimited values
- Simplify the UI to use 2 settings (unlock framerate + a max value entry)

* wip: slight limiter simplification

* wip: implement review suggestions

* wip: fix syntax error

* wip: revert enum order + replace old checks

* Fix compile error

---------

Co-authored-by: SailorSnoW <sailorsnow@pm.me>
Co-authored-by: Loïs <49660929+SailorSnoW@users.noreply.github.com>
Co-authored-by: SuperDude88 <82904174+SuperDude88@users.noreply.github.com>
Co-authored-by: Luke Street <luke@street.dev>
This commit is contained in:
Ash
2026-05-18 04:11:32 +02:00
committed by GitHub
parent 66c5cb1dae
commit 2da6590657
32 changed files with 207 additions and 59 deletions
+7 -7
View File
@@ -370,28 +370,28 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast<std::chrono::nanosecond
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) {
const auto sleepTime = limiter.SleepTime(targetNs);
const auto sleepTime = limiter.Sleep(targetNs);
dusk::frameUsagePct =
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(targetNs));
limiter.Sleep(targetNs);
}
#endif
static void waitForTick(u32 p1, u16 p2) {
#if TARGET_PC
if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) {
dusk::frameUsagePct = 0.f;
return;
static Limiter limiter;
if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) {
dusk::frameUsagePct = 0.f;
return;
}
if (dusk::getTransientSettings().skipFrameRateLimit) {
p1 = OS_TIMER_CLOCK / 120;
}
#if TARGET_PC
if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) {
return;
}
#endif
ZoneScopedC(tracy::Color::DimGray);
#endif
@@ -655,7 +655,7 @@ value_or_fun:
value:
#if TARGET_PC
if (dusk::getSettings().game.enableFrameInterpolation && u <= 5 &&
if (dusk::frame_interp::is_enabled() && u <= 5 &&
(operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12))
{
dusk::frame_interp::request_presentation_sync();
@@ -666,7 +666,7 @@ value:
value_n:
#if TARGET_PC
if (dusk::getSettings().game.enableFrameInterpolation &&
if (dusk::frame_interp::is_enabled() &&
(pN == TAdaptor_camera::sauVariableValue_3_POSITION_XYZ || pN == TAdaptor_camera::sauVariableValue_3_TARGET_POSITION_XYZ) &&
(operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12))
{