Skip to content

Conversation

@netborg-afps
Copy link

@netborg-afps netborg-afps commented Jan 29, 2025

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

@Torston420
Copy link

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

@doitsujin
Copy link
Owner

doitsujin commented Feb 17, 2025

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.

@netborg-afps
Copy link
Author

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.

This reverts commit efeb15e, because ordering guarantees were broken, that notifyGpuPresentEnd should happen after notifyGpuPresentBegin, which in turn lead to wrong latency measurements in case vkWaitForPresent was skipped.
@netborg-afps netborg-afps force-pushed the low-latency-framepacing-PR branch from ccf01e8 to 8e2a509 Compare February 19, 2025 19:48
@netborg-afps
Copy link
Author

@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

In practice, this change affects oversubscribed threading situations where waking up the "dxvk-queue" thread potentially can cause delays in the 100s of microseconds. For a lot of situations this change isn't affecting measurements in a meaningful way. Possibly affects AMD where vkQueueSubmit execution time is non-zero.
In d3d9 there were situations where the first frameId was 22, although in d3d11 it always started at 17. This did cause issues especially when waiting for fences which didn't get signaled for these frameIds.
Possibly can be optimized more, but just changing these numbers already had a huge effect, especially for games having a small number of submissions to begin with.
Not sure if this does anything, but better be safe to correctly track when the first succeeding Cs will get executed.
…ency frame pacing

Stutters less this way because we increase the sensitivity to mark frames as outliers, so that they don't get used for predicting the next frame. The actual "optimal" threshold is still to be fine-tuned, but this one worked really well.
…iable"

This reverts commit c802bdf and makes small adjustments.
Until we have a proper synchronization in place between emitting Cs triggered by the app thread, and fetching them from the queue, to measure the CsThread-caused delay, this config option is still useful for running some rare CsThread-limited games.
@Ice-IX
Copy link

Ice-IX commented Mar 1, 2025

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

@netborg-afps
Copy link
Author

@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.

@Laitinlok
Copy link

The latency is much better with the patches compared to ll2

@Ph42oN
Copy link

Ph42oN commented Nov 14, 2025

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.

@netborg-afps
Copy link
Author

netborg-afps commented Nov 14, 2025

@Ph42oN How did you test? It should be noted that for some reason, the latency display of upstream dxvk is broken since this patch (9ed43a6) 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.

@Ph42oN
Copy link

Ph42oN commented Nov 14, 2025

@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.

@netborg-afps
Copy link
Author

netborg-afps commented Nov 14, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants