[dxvk] Add low-latency frame pacing
Similar to what is being offered by D3D drivers, this low-latency mode aims to reduce latency with minimal impact in fps.
https://github.com/netborg-afps/dxvk/releases
Source SDK MP 2013 hates the low-latency setting, in PF2 and TF2C I'm stuck at around 130fps with horrid framepacing compared to max-frame-latency's around 600 in PF2
Source SDK MP 2013 hates the low-latency setting, in PF2 and TF2C I'm stuck at around 130fps with horrid framepacing compared to max-frame-latency's around 600 in PF2
FWIW, current master already has a low-latency setting (dxvk.latencySleep = True in the config), which was basically just fallout from adding Reflex support recently. Might as well try that.
The fundamental drawbacks of doing this at a DXVK level don't really change though, specifically that doing this in games with any sort of internal threading does either very little or nothing at all, depending on where the bottlenecks are in the given engine and how it synchronizes rendering with game logic.
It works to the extent that it aligns the render thread with GPU work in such a way that the first GPU submission on the CPU timeline happens roughly when the previous frame's rendering work completes on the GPU timeline (bit more complicated than that since we want to avoid the GPU going idle during CPU-heavy parts as well, but that's the basic idea), which can help reduce latency to an extent, but we can't align game logic with rendering in the same way that built-in Reflex would.
This is also why I don't see any good reason to accept a PR that essentially duplicates things that are already there, and we're also not really going to advertize it much as a feature because its usefulness is so limited in practice.
Source SDK MP 2013 hates the low-latency setting, in PF2 and TF2C I'm stuck at around 130fps with horrid framepacing compared to max-frame-latency's around 600 in PF2
You can try out this build if you are interested: https://github.com/netborg-afps/dxvk/actions/runs/13122023952 which is a step into making it more compatible with a lot of games, but is far from complete. I'll update this PR soon with a complete rework that is rebased on current master.
Although @doitsujin is right in a sense, that not all games profit from such a pacing, a lot of games I care about absolutely need it, and many more will profit from it as well. The aforementioned dxvk.latencySleep = True isn't really reducing latency (yet?) to the level I'm targeting.
@Torston420 How about this reworked version? Although I haven't tested this particular game, I'm positive this will suit TF2C pretty well, as I haven't seen it break on any game yet. https://github.com/netborg-afps/dxvk/releases/tag/low-latency-framepacing-2.5.3-v2
Netborg's fork appears to reduce latency in games with inbuilt framerate limiters, like Supreme Commander: Forged Alliance, which is supposedly outside the scope of the the master branch's low-latency setting, per the documentation:
Controls latency sleep and Nvidia Reflex support. Supported values:
- True: Enables built-in latency reduction based on internal timings. This assumes that input sampling for any given frame happens after the D3D9 or DXGI Present call returns; games that render and present asynchronously will not behave as intended. Similarly, this will not have any effect in games with built-in frame rate limiters, or if an external limiter (such as MangoHud) is used. In some games, enabling this may reduce performance or lead to less consistent frame pacing. The implementation will either use VK_NV_low_latency2 if supported by the driver, or a custom algorithm.
dxvk.latencySleep = Auto
@Ice-IX Well, I've been debugging why certain parts of a level had more latency than others which didn't make sense to me. Optimizing the flush heuristic for the low-latency use-case solved this and generally improved the latency in some games by up to 20%, which is basically what you are experiencing. I'm sure this should also be checked for Reflex.
We essentially need to look for things like this, at places where delays happen, which is not only when a frame needs to wait for the GPU to become ready, although this is the most discussed common case.
Other than that, there shouldn't be any differences between the different pacing strategies when the game itself does the limiting. Note that the Render latency hud display is pretty meaningless in this case.
The latency is much better with the patches compared to ll2
This is interesting as to me latency is important. But i saw it causing performance issues in some cases, while latencySleep hasn't caused any issues for me, and in my testing i didn't notice improved latency over tweaked dxvk with these options: dxgi.maxFrameLatency = 1 dxvk.latencySleep = True dxvk.latencyTolerance = 0
Also dxvk fps limiter must be used for latencySleep to work properly, using other fps limiters latencySleep didn't improve over just maxFrameLatency=1.
But anyway i will do some measurements with arduino + photodiode to compare later. In measurements i did before, dxvk.conf tweaks made pretty big difference.
@Ph42oN How did you test? It should be noted that for some reason, the latency display of upstream dxvk is broken since this patch (https://github.com/doitsujin/dxvk/commit/9ed43a60a3397957f44a47ade7efcedbab5bf9f8) which I needed to revert. It leads to calling notifyGpuPresentEnd() directly after vkQueuePresentKHR() got called, which can cause also other issues, like polling the CPU with vkWaitForPresentKHR() when v-sync is enabled on fixed refresh, and it affects starting times of the next frame when v-sync is disabled.
In my own testing I came to the conclusion that the latencySleep variant generated up to 35% more latency than mine, and normal max-frame-latency-1 generated up to 74% more latency, when being GPU bound. (Edit: looked up some recent measurements and corrected percentages, results were 5.4 vs. 7.3 vs. 9.4 ms all at 210 fps)
I know that fps is currently dropping by about 10% (depends on game) compared to max-frame-latency, but this is mainly because I tuned the flush heuristic to flush more often, which is beneficial for games which only create a few GPU submissions and generally helps to get a more consistent latency per frame. But especially games which have created many GPU submissions already are being hit by that. Feel free to also test a version when removing these lines
GpuFlushTracker::m_minPendingSubmissions = 1; GpuFlushTracker::m_minChunkCount = 1;
which should solve the fps issue, but has the disadvantage described before. I plan on redesigning the GPU submission scheduling to integrate into the pacing logic, but this seems to be quite a task and won't be finished quickly.
When using dxvk fps limiting, the average latencies aren't that much different between my method and upstream dxvk, but the reason I made my own limiter is because single frames can be laggy with upstream. I'll comment on this in more detail, but essentially it is reacting slower. It is smooth though.
@netborg-afps This is based on how it feels, so being too small to notice is possibility, but i have felt improvement with latencySleep over just maxFrameLatency=1. I didn't test much yet, maybe i would find some improvement with more testing. Performance issue i told about is game which has some weird fps drops, it gets those fps drops much more often with this framePace option, but other than that performance is similar.
Edit: It seems that the performance issue is gone by using option dxvk.lowLatencyOffset = -10000. Not sure if this is exactly best value for it but the issue was still there with -1000.
Dropping frames is on purpose when detecting the previous one is proceeding slower than expected (you generally have 2 frames being processed simultaneously), as queued up frames are causing lag in the first place and lead to an unstable mouse input feel in those situations.
Curious what you will find out with your arduino + photodiode setup. I've checked latency regularly with my monitor's built-in Reflex-Analyzer.
Edit: Adding more detail
In case you have visible stutters, these are typically CPU-sided stalls taking 3-5 ms or more. When those stutters go away with dxvk.lowLatencyOffset = -10000, this indicates that the algorithm will take the branch at
https://github.com/netborg-afps/dxvk/blob/81acbcabaedd723670c05ab83e947c5d995f0670/src/dxvk/framepacer/dxvk_framepacer_mode_low_latency.h#L110
whereas otherwise it will wait until the previous frame has used up its GPU runtime budget, so it can expectedly be synced with the next frame without going into GPU buffering (or going into GPU buffering by the amount given by -dxvk.lowLatencyOffset). Stutter situations will then halt starting the next frame until the condition is satisfied within
https://github.com/netborg-afps/dxvk/blob/81acbcabaedd723670c05ab83e947c5d995f0670/src/dxvk/framepacer/dxvk_gpu_progress.h#L107
When adding this GPU-Progress feature with the last release, I certainly also had smoothness in mind, which it greatly improved by making the prediction more precise for when a frame will finish rendering. On the other hand, this also means, those kind of stutters are possibly being magnified, for example when the previous frame stalls before its first/second/etc. call to vkQueueSubmit2().
As said, I certainly prefer these stutters being visible like that instead of being transformed into "mouse lag stutters" as most drivers typically do, and most competitive gamers I have spoken with agreed to perceive it the same way. It's probably possible to cut these stutters down by some amount of buffering. I'll probably add this later on as a separate mode.