Skip to content

Conversation

@smallketchup82
Copy link

@smallketchup82 smallketchup82 commented Nov 10, 2025

This PR implements LowLatencyProvider, an interface that can be used by any latency reduction API to enable just-in-time rendering. Currently this is being implemented for use with NVIDIA Reflex, but it can easily be used with LatencyFlex as well.

Opening as a draft as this PR is still incomplete, pending the addition of frame limiting logic. However the current code is more or less good for review.

Copy link
Member

@Susko3 Susko3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frameCount is accessed in a thread-unsafe manner, this could mean that the tests done in ppy/osu#35678 might be invalid.

The way low latency providers are initialised and used with all the try-catch-ignore-don't log seems convoluted and prone to hiding problems. It also doesn't consider a game providing multiple different low latency providers for different renderers. I would remove all public methods from GameHost and instead allow games to supply a list of ILowLatencyProviders via HostOptions.


try
{
LatencyMark(LatencyMarker.RenderSubmitStart, frameCount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frameCount is set from the update thread and read here from the draw thread. This can cause a multitude of problems when running in multi-threaded:

  • calls to LatencyMark() in the same draw frame having different frame counts
  • frame count not being the one used when generating the buffer (as the update thread runs twice as fast)

To make this correct, the frameCount should be stored in the buffer.

Copy link
Author

@smallketchup82 smallketchup82 Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6163e21 (#6666) and 225a4e5 (#6666). Let me know if you had a different solution in mind.

I wonder if this might fix the frametime spikes I observed predominantly in the Reflex On and Boost modes.
image
(green is off, orange is on, blue is with boost)

{
bool IsAvailable { get; }

void Initialize(IntPtr nativeDeviceHandle);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this to IDirect3D11LowLatencyProvider, to avoid the problem of passing the wrong device handle.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5c02d43 (#6666)

}
}

public void SetLowLatencyMode(LatencyMode mode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be public, FrameworkSetting.LatencyMode can and should be set through the FrameworkConfigManager.

Copy link
Author

@smallketchup82 smallketchup82 Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f250d43 (#6666)

@smallketchup82
Copy link
Author

smallketchup82 commented Nov 13, 2025

Ahhh completely forgot about threading.. Pretty embarrassing, will go through and clean that up soon, along with the other review comments.

The way low latency providers are initialised and used with all the try-catch-ignore-don't log seems convoluted and prone to hiding problems.

I was going to make a small write-up in the osu-sided PR talking about this as I felt it was also noteworthy to speak about. I agree that sending exceptions on the Initialize, SetSleepMode, etc. to the void is not the right way to go about it, I'll change this to have those methods log errors to the console. But I am going to be very firm on the following: Any errors on the SetLatencyMarker methods must never handle errors. It's mostly a case of our hands being tied as a result of running this method in a realtime environment, and very much an intentional decision made by me.

Say we get an exception in SetLatencyMarker, here are our options:

  1. Throw the exception and not handle it, resulting in the game crashing
  2. Log the error, resulting in I/O which is bound to cause a stutter given the realtime environment
  3. Handle the exception by incrementing a FailedMarkers counter, allowing us to track if the method failed to run, but even this is overkill in my opinion

It's probably more than enough to ensure every other piece is working, and to blindly trust that SetLatencyMarker is working as expected, as any possible outcome other than sending the exception to the void is just additional per-frame overhead for a feature that isn't critical to the game's operations and serves only to try and optimize it wherever it can. Reflex can tank a couple missed marker calls, but players won't be very happy if their game crashes or stutters at random.

@smallketchup82 smallketchup82 marked this pull request as ready for review November 20, 2025 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants