Fallout from #2014. Implementation matches that of DXVK.
The SetTargetFrameRate function is called with:
A positive value if there's a DXVK app profile (or user config). Hopefully won't ever be relevant for us since this is only really used to work around broken games that don't understand the concept of high-refresh monitors and just break at >60 FPS.
A negative value if the app is running in fullscreen mode (i.e. app calls SetFullscreenState(TRUE)) and presents with SyncInterval > 0. The target frame rate is equal to the refresh rate of the selected display mode, and the intention is to only engage the frame rate limiter if we can't do a mode switch and display at a higher rate. Again mostly relevant for older games, but also makes things like the "fullscreen" mode and refresh rate options in Horizon Zero Dawn work as intended.
A value of 0.0 otherwise, in which case there is no limit.
The algorithm itself is fairly simple and intends to roughly emulate the behaviour of running on a lower refresh rate display:
Let Δt = 1 / |target_frame_rate| and t_f be the time stamp at which vkWaitForPresentKHR completes.
Each frame has a window [t0..t1) during which we want the frame to be presented, with t1 = t0 + Δt.
if t_f < t1, the frame is on time and we advance the next frame's window by setting t0_next = t1.
if also t_f < t0, sleep until t0.
if t_f >= t1, the current frame was delayed and we restart the frame window by setting t0_next = t_f + Δt.
Sleeping needs to be reasonably accurate to not mess up frame pacing, which is why we intentionally ask to be woken up a few milliseconds early and busy-wait for the remainder of the duration time. The downside here is power consumption, but the only way to really improve on that would be to do the SpecialK thing of abusing the monitorx / mwaitx instruction on Ryzen CPUs, which isn't trivial since they require cpuid checks and operate in units of TSC ticks.
Fallout from #2014. Implementation matches that of DXVK.
The
SetTargetFrameRate
function is called with:SetFullscreenState(TRUE)
) and presents with SyncInterval > 0. The target frame rate is equal to the refresh rate of the selected display mode, and the intention is to only engage the frame rate limiter if we can't do a mode switch and display at a higher rate. Again mostly relevant for older games, but also makes things like the "fullscreen" mode and refresh rate options in Horizon Zero Dawn work as intended.The algorithm itself is fairly simple and intends to roughly emulate the behaviour of running on a lower refresh rate display:
Δt = 1 / |target_frame_rate|
andt_f
be the time stamp at whichvkWaitForPresentKHR
completes.[t0..t1)
during which we want the frame to be presented, witht1 = t0 + Δt
.t_f < t1
, the frame is on time and we advance the next frame's window by settingt0_next = t1
.t_f < t0
, sleep untilt0
.t_f >= t1
, the current frame was delayed and we restart the frame window by settingt0_next = t_f + Δt
.Sleeping needs to be reasonably accurate to not mess up frame pacing, which is why we intentionally ask to be woken up a few milliseconds early and busy-wait for the remainder of the duration time. The downside here is power consumption, but the only way to really improve on that would be to do the SpecialK thing of abusing the
monitorx
/mwaitx
instruction on Ryzen CPUs, which isn't trivial since they require cpuid checks and operate in units of TSC ticks.