Skip to content
Open
89 changes: 89 additions & 0 deletions osu.Desktop/LowLatency/NVAPIDirect3D11LowLatencyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#pragma warning disable IDE1006 // Naming rule violation

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using osu.Framework.Graphics.Rendering.LowLatency;

namespace osu.Desktop.LowLatency
{
/// <summary>
/// Provider for NVIDIA's NVAPI (Reflex) low latency features.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SupportedOSPlatform("windows")]
internal sealed class NVAPIDirect3D11LowLatencyProvider : IDirect3D11LowLatencyProvider
{
public bool IsAvailable { get; private set; }

private IntPtr _deviceHandle;

/// <summary>
/// Initialize the NVAPI low latency provider with a native device handle. Ensure NVAPI is available before calling this method.
/// </summary>
/// <param name="nativeDeviceHandle">An <see cref="IntPtr"/> to the handle of the D3D11 device.</param>
/// <exception cref="InvalidOperationException">Throws an exception if NVAPI is unavailable, or the device handle provided was invalid.</exception>
public void Initialize(IntPtr nativeDeviceHandle)
{
_deviceHandle = nativeDeviceHandle;
IsAvailable = NVAPI.Available && _deviceHandle != IntPtr.Zero;

if (!IsAvailable)
throw new InvalidOperationException("NVAPI is not available or the provided device handle is invalid.");
}

/// <summary>
/// Set the low latency mode.
/// </summary>
/// <param name="mode">The <see cref="LatencyMode"/> to use.</param>
/// <exception cref="InvalidOperationException">Throws an exception if an attempt to set the low latency mode was unsuccessful.</exception>
public void SetMode(LatencyMode mode)
{
if (!IsAvailable || _deviceHandle == IntPtr.Zero)
return;

bool enable = mode != LatencyMode.Off;
bool boost = mode == LatencyMode.Boost;
var status = NVAPI.SetSleepModeHelper(_deviceHandle, enable, boost, boost, 0);

if (status != NvStatus.OK)
throw new InvalidOperationException($"Failed to set NVAPI low latency (Sleep) mode: {status}");
}

/// <summary>
/// Set a latency marker for the current frame.
/// </summary>
/// <remarks>WARNING: Do not log any errors that come from this method, they should be ignored as this method runs in a realtime environment.</remarks>
/// <param name="marker">The <see cref="LatencyMarker"/> to set.</param>
/// <param name="frameId">The frame number this marker is for.</param>
/// <exception cref="InvalidOperationException">Throws an exception if the attempt to set the marker was unsuccessful. Please ensure this exception is ignored.</exception>
public void SetMarker(LatencyMarker marker, ulong frameId)
{
if (!IsAvailable || _deviceHandle == IntPtr.Zero)
return;

var status = NVAPI.SetLatencyMarkerHelper(_deviceHandle, (uint)marker, frameId);

if (status != NvStatus.OK)
throw new InvalidOperationException($"Failed to set NVAPI latency marker: {status}");
}

/// <summary>
/// Ensure this is called once per frame, at the start of the Update phase, to allow NVAPI to manage frame sleep timing.
/// </summary>
/// <exception cref="InvalidOperationException">Throws an exception if the Sleep attempt was unsuccessful.</exception>
public void FrameSleep()
{
if (!IsAvailable || _deviceHandle == IntPtr.Zero)
return;

var status = NVAPI.FrameSleepHelper(_deviceHandle);

if (status != NvStatus.OK)
throw new InvalidOperationException($"Failed to perform NVAPI frame sleep: {status}");
}
}
}
82 changes: 82 additions & 0 deletions osu.Desktop/NVAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ internal static class NVAPI

private static readonly SaveSettingsDelegate SaveSettings;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate NvStatus SetSleepModeDelegate(IntPtr pDevice, ref NvSetSleepModeParams pParams);

private static readonly SetSleepModeDelegate SetSleepMode;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate NvStatus SetLatencyMarkerDelegate(IntPtr pDevice, ref NvFrameMarkerParams pParams);

private static readonly SetLatencyMarkerDelegate SetLatencyMarker;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate NvStatus SleepDelegate(IntPtr pDevice);

private static readonly SleepDelegate Sleep;

public static NvStatus Status { get; private set; } = NvStatus.OK;
public static bool Available { get; private set; }

Expand Down Expand Up @@ -338,6 +353,36 @@ private static bool setSetting(NvSettingID settingId, uint settingValue)
return !checkError(SaveSettings(sessionHandle), nameof(SaveSettings));
}

internal static NvStatus SetSleepModeHelper(IntPtr devicePtr, bool enable, bool boost, bool useMarkersOptimize, uint minimumIntervalUs)
{
if (!Available)
return NvStatus.NO_IMPLEMENTATION;

NvSetSleepModeParams sleepParams = new NvSetSleepModeParams
{
version = NvSetSleepModeParams.Stride,
bLowLatencyMode = enable ? (byte)1 : (byte)0,
bLowLatencyBoost = boost ? (byte)1 : (byte)0,
minimumIntervalUs = 0,
bUseMarkersToOptimize = useMarkersOptimize ? (byte)1 : (byte)0,
};

return SetSleepMode(devicePtr, ref sleepParams);
}

internal static NvStatus SetLatencyMarkerHelper(IntPtr devicePtr, uint marker, ulong frameId)
{
var frameMarkerParams = new NvFrameMarkerParams
{
version = NvFrameMarkerParams.Stride,
frameID = frameId,
markerType = marker
};
return SetLatencyMarker(devicePtr, ref frameMarkerParams);
}

internal static NvStatus FrameSleepHelper(IntPtr devicePtr) => Sleep(devicePtr);

/// <summary>
/// Creates a session to access the driver configuration.
/// </summary>
Expand Down Expand Up @@ -400,6 +445,9 @@ static NVAPI()
getDelegate(0x7FA2173A, out EnumApplications);
getDelegate(0x4347A9DE, out CreateApplication);
getDelegate(0xFCBC7E14, out SaveSettings);
getDelegate(0xAC1CA9E0, out SetSleepMode);
getDelegate(0xD9984C05, out SetLatencyMarker);
getDelegate(0x852CD1D2, out Sleep);
}

if (createSession())
Expand All @@ -426,6 +474,40 @@ private static void getDelegate<T>(uint id, out T newDelegate) where T : class
private delegate NvStatus InitializeDelegate();
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NvSetSleepModeParams
{
public uint version;
public byte bLowLatencyMode;
public byte bLowLatencyBoost;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
private byte[] _pad0; // explicit padding to align next uint

public uint minimumIntervalUs; // NvU32

public byte bUseMarkersToOptimize; // NvBool (1 byte)

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 31)]
private byte[] rsvd; // reserved (must be zeroed)

public static uint Stride => (uint)Marshal.SizeOf(typeof(NvSetSleepModeParams)) | (1u << 16);
}

[StructLayout(LayoutKind.Sequential)]
internal struct NvFrameMarkerParams
{
public uint version;
public ulong frameID;
public uint markerType;
public ulong rsvd0;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)]
public byte[] rsvd;

public static uint Stride => (uint)Marshal.SizeOf(typeof(NvFrameMarkerParams)) | (1u << 16);
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct NvSetting
{
Expand Down
5 changes: 5 additions & 0 deletions osu.Desktop/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Runtime.Versioning;
using osu.Desktop.LegacyIpc;
using osu.Desktop.LowLatency;
using osu.Desktop.Windows;
using osu.Framework;
using osu.Framework.Development;
Expand Down Expand Up @@ -138,6 +139,10 @@ public static void Main(string[] args)
host.Run(new TournamentGame());
else
{
// Attempt to use the NVAPI Low Latency Provider. This should only succeed on systems with NVIDIA GPUs and the proper drivers installed.
if (NVAPI.Available)
host.SetLowLatencyProvider(new NVAPIDirect3D11LowLatencyProvider());

host.Run(new OsuGameDesktop(args)
{
IsFirstRun = isFirstRun
Expand Down
33 changes: 33 additions & 0 deletions osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Rendering.LowLatency;

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, MultiThreaded)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, MultiThreaded)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, SingleThread)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, SingleThread)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, MultiThreaded)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, MultiThreaded)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, SingleThread)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, SingleThread)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)

Check failure on line 9 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'LowLatency' does not exist in the namespace 'osu.Framework.Graphics.Rendering' (are you missing an assembly reference?)
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
Expand All @@ -21,12 +22,17 @@

private bool automaticRendererInUse;

private SettingsEnumDropdown<LatencyMode>? reflexSetting;

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, MultiThreaded)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, MultiThreaded)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, SingleThread)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, SingleThread)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, MultiThreaded)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, MultiThreaded)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, SingleThread)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, SingleThread)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 25 in osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'LatencyMode' could not be found (are you missing a using directive or an assembly reference?)

[BackgroundDependencyLoader]
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDialogOverlay? dialogOverlay, OsuGame? game, GameHost host)
{
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
automaticRendererInUse = renderer.Value == RendererType.Automatic;

var reflexMode = config.GetBindable<LatencyMode>(FrameworkSetting.LatencyMode);
var frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync);

Children = new Drawable[]
{
new RendererSettingsDropdown
Expand All @@ -51,13 +57,38 @@
LabelText = GraphicsSettingsStrings.ThreadingMode,
Current = config.GetBindable<ExecutionMode>(FrameworkSetting.ExecutionMode)
},
reflexSetting = new SettingsEnumDropdown<LatencyMode>
{
LabelText = "NVIDIA Reflex",
Current = reflexMode,
Keywords = new[] { @"nvidia", @"latency", @"reflex" },
TooltipText = "Reduces latency by leveraging the NVIDIA Reflex API on NVIDIA GPUs.\nRecommended to have On, turn Off only if experiencing issues."
},
new SettingsCheckbox
{
LabelText = GraphicsSettingsStrings.ShowFPS,
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
},
};

// Ensure NVIDIA reflex is turned off and hidden if the resolved renderer isn't Direct3D 11
if (host.ResolvedRenderer is not (RendererType.Deferred_Direct3D11 or RendererType.Direct3D11))
{
reflexMode.Value = LatencyMode.Off;
reflexSetting.Hide();
}

// Disable frame limiter if reflex is enabled and add notice when reflex boost is enabled
reflexMode.BindValueChanged(r =>
{
frameSyncMode.Disabled = r.NewValue != LatencyMode.Off;

reflexSetting.ClearNoticeText();

if (r.NewValue == LatencyMode.Boost)
setReflexBoostNotice();
}, true);

renderer.BindValueChanged(r =>
{
if (r.NewValue == host.ResolvedRenderer)
Expand All @@ -81,6 +112,8 @@
});
}

private void setReflexBoostNotice() => reflexSetting?.SetNoticeText("Boost increases GPU power consumption and may increase latency in some cases. Disable Boost if experiencing issues.", true);

private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
{
protected override OsuDropdown<RendererType> CreateDropdown() => new RendererDropdown();
Expand Down
Loading