Fix DLSS on Windows: CUDA interop warm-up and NGX application identity #6
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.GraphicsDriver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Forces the NVIDIA driver to initialize its CUDA interop state, by creating and destroying
|
||||||
|
/// a throwaway Vulkan device that requests VK_NVX_binary_import.
|
||||||
|
///
|
||||||
|
/// This must run before the guest address space is reserved (guest process creation): once
|
||||||
|
/// that reservation exists, cuInit fails with CUDA_ERROR_OUT_OF_MEMORY and the driver
|
||||||
|
/// refuses the CUDA-backed device extensions (VK_NVX_binary_import, VK_NV_optical_flow) for
|
||||||
|
/// the rest of the process lifetime, which kills DLSS. Initialized early, the CUDA state
|
||||||
|
/// stays valid for the whole session.
|
||||||
|
///
|
||||||
|
/// Raw P/Invoke on purpose: it runs before any Silk.NET instance exists.
|
||||||
|
/// </summary>
|
||||||
|
public static class VkDiagProbe
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct InstanceCreateInfo
|
||||||
|
{
|
||||||
|
public int SType; public IntPtr PNext; public uint Flags; public IntPtr PApplicationInfo;
|
||||||
|
public uint EnabledLayerCount; public IntPtr PpEnabledLayerNames;
|
||||||
|
public uint EnabledExtensionCount; public IntPtr PpEnabledExtensionNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct DeviceQueueCreateInfo
|
||||||
|
{
|
||||||
|
public int SType; public IntPtr PNext; public uint Flags;
|
||||||
|
public uint QueueFamilyIndex; public uint QueueCount; public IntPtr PQueuePriorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct DeviceCreateInfo
|
||||||
|
{
|
||||||
|
public int SType; public IntPtr PNext; public uint Flags;
|
||||||
|
public uint QueueCreateInfoCount; public IntPtr PQueueCreateInfos;
|
||||||
|
public uint EnabledLayerCount; public IntPtr PpEnabledLayerNames;
|
||||||
|
public uint EnabledExtensionCount; public IntPtr PpEnabledExtensionNames;
|
||||||
|
public IntPtr PEnabledFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern int vkCreateInstance(ref InstanceCreateInfo ci, IntPtr alloc, out IntPtr instance);
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern void vkDestroyInstance(IntPtr instance, IntPtr alloc);
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern int vkEnumeratePhysicalDevices(IntPtr instance, ref uint count, IntPtr[] devices);
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern void vkGetPhysicalDeviceProperties(IntPtr dev, IntPtr props);
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern int vkCreateDevice(IntPtr physDev, ref DeviceCreateInfo ci, IntPtr alloc, out IntPtr device);
|
||||||
|
[DllImport("vulkan-1.dll")] private static extern void vkDestroyDevice(IntPtr device, IntPtr alloc);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the warm-up and returns a result string for the caller to log.
|
||||||
|
/// Never throws; a machine without an NVIDIA device reports that instead.
|
||||||
|
/// </summary>
|
||||||
|
public static string WarmUpCudaInterop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Probe();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return $"threw {e.GetType().Name}: {e.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Probe()
|
||||||
|
{
|
||||||
|
var ici = new InstanceCreateInfo { SType = 1 };
|
||||||
|
int r = vkCreateInstance(ref ici, IntPtr.Zero, out IntPtr instance);
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
return $"vkCreateInstance failed: {r}";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
vkEnumeratePhysicalDevices(instance, ref count, null);
|
||||||
|
IntPtr[] devs = new IntPtr[count];
|
||||||
|
vkEnumeratePhysicalDevices(instance, ref count, devs);
|
||||||
|
|
||||||
|
IntPtr nvidia = IntPtr.Zero;
|
||||||
|
IntPtr props = Marshal.AllocHGlobal(4096);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (IntPtr d in devs)
|
||||||
|
{
|
||||||
|
vkGetPhysicalDeviceProperties(d, props);
|
||||||
|
if ((uint)Marshal.ReadInt32(props, 8) == 0x10DE)
|
||||||
|
{
|
||||||
|
nvidia = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nvidia == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return "no NVIDIA device";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProbeDevice(nvidia);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
vkDestroyInstance(instance, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ProbeDevice(IntPtr nvidia)
|
||||||
|
{
|
||||||
|
IntPtr priority = Marshal.AllocHGlobal(4);
|
||||||
|
Marshal.Copy(new float[] { 1.0f }, 0, priority, 1);
|
||||||
|
|
||||||
|
var qci = new DeviceQueueCreateInfo { SType = 2, QueueFamilyIndex = 0, QueueCount = 1, PQueuePriorities = priority };
|
||||||
|
IntPtr pQci = Marshal.AllocHGlobal(Marshal.SizeOf<DeviceQueueCreateInfo>());
|
||||||
|
Marshal.StructureToPtr(qci, pQci, false);
|
||||||
|
|
||||||
|
IntPtr extName = Marshal.StringToHGlobalAnsi("VK_NVX_binary_import");
|
||||||
|
IntPtr extArr = Marshal.AllocHGlobal(IntPtr.Size);
|
||||||
|
Marshal.WriteIntPtr(extArr, 0, extName);
|
||||||
|
|
||||||
|
var dci = new DeviceCreateInfo
|
||||||
|
{
|
||||||
|
SType = 3, QueueCreateInfoCount = 1, PQueueCreateInfos = pQci,
|
||||||
|
EnabledExtensionCount = 1, PpEnabledExtensionNames = extArr,
|
||||||
|
};
|
||||||
|
|
||||||
|
int dr = vkCreateDevice(nvidia, ref dci, IntPtr.Zero, out IntPtr device);
|
||||||
|
|
||||||
|
if (dr == 0)
|
||||||
|
{
|
||||||
|
vkDestroyDevice(device, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(extName);
|
||||||
|
Marshal.FreeHGlobal(extArr);
|
||||||
|
Marshal.FreeHGlobal(pQci);
|
||||||
|
Marshal.FreeHGlobal(priority);
|
||||||
|
|
||||||
|
return $"VK_NVX_binary_import alone -> {(dr == 0 ? "OK" : dr == -3 ? "ErrorInitializationFailed" : dr.ToString())}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -161,6 +161,8 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
|||||||
private static IntPtr _interposerHandle;
|
private static IntPtr _interposerHandle;
|
||||||
private static IntPtr _logPathPtr;
|
private static IntPtr _logPathPtr;
|
||||||
private static IntPtr _featuresPtr;
|
private static IntPtr _featuresPtr;
|
||||||
|
private static IntPtr _projectIdPtr;
|
||||||
|
private static IntPtr _engineVersionPtr;
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
private static bool _everInitialized;
|
private static bool _everInitialized;
|
||||||
|
|
||||||
@@ -208,6 +210,17 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
|||||||
pref.RenderApi = RenderApiVulkan;
|
pref.RenderApi = RenderApiVulkan;
|
||||||
pref.Flags = PrefFlagDisableCLStateTracking;
|
pref.Flags = PrefFlagDisableCLStateTracking;
|
||||||
|
|
||||||
|
// NGX refuses to initialize without a valid application identity ("Please provide
|
||||||
|
// correct application id when calling slInit" in sl.log), which disables DLSS
|
||||||
|
// entirely. 100721531 is the placeholder id from NVIDIA's Streamline sample; with
|
||||||
|
// that id, Streamline requires a projectId GUID and engine version for the
|
||||||
|
// NGX-with-project-id init path instead.
|
||||||
|
pref.ApplicationId = 100721531;
|
||||||
|
_projectIdPtr = Marshal.StringToHGlobalAnsi("2a90c1c2-7fbf-4626-8b57-0d9a2072b1cd");
|
||||||
|
pref.ProjectId = _projectIdPtr;
|
||||||
|
_engineVersionPtr = Marshal.StringToHGlobalAnsi("1.0.0");
|
||||||
|
pref.EngineVersion = _engineVersionPtr;
|
||||||
|
|
||||||
if (loadFrameGeneration)
|
if (loadFrameGeneration)
|
||||||
{
|
{
|
||||||
pref.Flags |= PrefFlagUseManualHooking;
|
pref.Flags |= PrefFlagUseManualHooking;
|
||||||
@@ -410,6 +423,18 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
|||||||
_featuresPtr = IntPtr.Zero;
|
_featuresPtr = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_projectIdPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(_projectIdPtr);
|
||||||
|
_projectIdPtr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_engineVersionPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(_engineVersionPtr);
|
||||||
|
_engineVersionPtr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
if (_interposerHandle != IntPtr.Zero)
|
if (_interposerHandle != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
NativeLibrary.Free(_interposerHandle);
|
NativeLibrary.Free(_interposerHandle);
|
||||||
|
|||||||
@@ -15,9 +15,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public unsafe static class VulkanInitialization
|
public unsafe static class VulkanInitialization
|
||||||
{
|
{
|
||||||
private const uint InvalidIndex = uint.MaxValue;
|
private const uint InvalidIndex = uint.MaxValue;
|
||||||
|
private const int InvalidDeviceIndex = -1;
|
||||||
private static readonly uint _minimalVulkanVersion = Vk.Version11.Value;
|
private static readonly uint _minimalVulkanVersion = Vk.Version11.Value;
|
||||||
private static readonly uint _minimalInstanceVulkanVersion = Vk.Version12.Value;
|
private static readonly uint _minimalInstanceVulkanVersion = Vk.Version12.Value;
|
||||||
private static readonly uint _maximumVulkanVersion = Vk.Version12.Value;
|
// VK_MAKE_API_VERSION(0, 1, 3, 0). Opt-in through RYUJINX_VK_API13, because some NVIDIA
|
||||||
|
// extensions are refused when the instance only requests Vulkan 1.2.
|
||||||
|
private const uint VulkanVersion13 = (1u << 22) | (3u << 12);
|
||||||
|
|
||||||
|
private static readonly uint _maximumVulkanVersion =
|
||||||
|
Environment.GetEnvironmentVariable("RYUJINX_VK_API13") is "1" or "true" or "on"
|
||||||
|
? VulkanVersion13
|
||||||
|
: Vk.Version12.Value;
|
||||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||||
private const int QueuesCount = 2;
|
private const int QueuesCount = 2;
|
||||||
|
|
||||||
@@ -136,6 +144,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
result.ThrowOnError();
|
result.ThrowOnError();
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
$"Vulkan instance: requested API {(_maximumVulkanVersion >> 22) & 0x7F}.{(_maximumVulkanVersion >> 12) & 0x3FF}, " +
|
||||||
|
$"loader reports {(instance.InstanceVersion.Value >> 22) & 0x7F}.{(instance.InstanceVersion.Value >> 12) & 0x3FF}.");
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,22 +160,93 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
|
if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
$"GPU selection: using preferred device \"{physicalDevices[i].DeviceName}\" ({physicalDevices[i].Id}).");
|
||||||
|
|
||||||
return physicalDevices[i];
|
return physicalDevices[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we fail to do that, just use the first compatible GPU.
|
if (!string.IsNullOrEmpty(preferredGpuId))
|
||||||
|
{
|
||||||
|
LogPreferredDeviceSkipped(physicalDevices, preferredGpuId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we fail to do that, prefer a discrete GPU over an integrated one, so that a
|
||||||
|
// machine with both does not silently fall back to the integrated device.
|
||||||
|
int firstSuitable = InvalidDeviceIndex;
|
||||||
|
|
||||||
for (int i = 0; i < physicalDevices.Length; i++)
|
for (int i = 0; i < physicalDevices.Length; i++)
|
||||||
{
|
{
|
||||||
if (IsSuitableDevice(api, physicalDevices[i], surface))
|
if (!IsSuitableDevice(api, physicalDevices[i], surface))
|
||||||
{
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (physicalDevices[i].PhysicalDeviceProperties.DeviceType == PhysicalDeviceType.DiscreteGpu)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
$"GPU selection: falling back to discrete device \"{physicalDevices[i].DeviceName}\" ({physicalDevices[i].Id}).");
|
||||||
|
|
||||||
return physicalDevices[i];
|
return physicalDevices[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (firstSuitable == InvalidDeviceIndex)
|
||||||
|
{
|
||||||
|
firstSuitable = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No discrete GPU passed the checks, so use the first compatible GPU instead.
|
||||||
|
if (firstSuitable != InvalidDeviceIndex)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"GPU selection: no suitable discrete GPU found, falling back to \"{physicalDevices[firstSuitable].DeviceName}\" " +
|
||||||
|
$"({physicalDevices[firstSuitable].Id}, {physicalDevices[firstSuitable].PhysicalDeviceProperties.DeviceType}).");
|
||||||
|
|
||||||
|
return physicalDevices[firstSuitable];
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
|
throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void LogPreferredDeviceSkipped(VulkanPhysicalDevice[] physicalDevices, string preferredGpuId)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < physicalDevices.Length; i++)
|
||||||
|
{
|
||||||
|
if (physicalDevices[i].Id != preferredGpuId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string missingExtension = null;
|
||||||
|
|
||||||
|
foreach (string requiredExtension in _requiredExtensions)
|
||||||
|
{
|
||||||
|
if (!physicalDevices[i].IsDeviceExtensionPresent(requiredExtension))
|
||||||
|
{
|
||||||
|
missingExtension = requiredExtension;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string why = missingExtension != null
|
||||||
|
? $"it does not support the required extension {missingExtension}"
|
||||||
|
: "no queue family supports graphics, compute and presentation on this window's surface";
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"GPU selection: preferred device \"{physicalDevices[i].DeviceName}\" ({preferredGpuId}) was found but rejected because {why}.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string enumerated = string.Join(", ", physicalDevices.Select(x => $"\"{x.DeviceName}\" ({x.Id})"));
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"GPU selection: preferred device {preferredGpuId} was not found among the {physicalDevices.Length} enumerated device(s): {enumerated}.");
|
||||||
|
}
|
||||||
|
|
||||||
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
|
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
|
||||||
{
|
{
|
||||||
nint appName = Marshal.StringToHGlobalAnsi(AppName);
|
nint appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||||
@@ -719,6 +802,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PhysicalDeviceSynchronization2Features featuresSync2;
|
PhysicalDeviceSynchronization2Features featuresSync2;
|
||||||
PhysicalDeviceOpticalFlowFeaturesNV featuresOpticalFlow;
|
PhysicalDeviceOpticalFlowFeaturesNV featuresOpticalFlow;
|
||||||
|
|
||||||
|
// Remember the chain head before the DLSS-only feature structs are linked in, so that
|
||||||
|
// the staged fallback in CreateDevice can retry without them.
|
||||||
|
void* pFeaturesBaseline = pExtendedFeatures;
|
||||||
|
|
||||||
if (sync2Supported)
|
if (sync2Supported)
|
||||||
{
|
{
|
||||||
featuresSync2 = new()
|
featuresSync2 = new()
|
||||||
@@ -731,6 +818,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
pExtendedFeatures = &featuresSync2;
|
pExtendedFeatures = &featuresSync2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remember the chain head before the optical flow feature is linked in, so that the
|
||||||
|
// fallback in CreateDevice can drop that feature without rebuilding the whole chain.
|
||||||
|
void* pFeaturesWithoutOpticalFlow = pExtendedFeatures;
|
||||||
|
|
||||||
if (opticalFlowSupported)
|
if (opticalFlowSupported)
|
||||||
{
|
{
|
||||||
featuresOpticalFlow = new()
|
featuresOpticalFlow = new()
|
||||||
@@ -771,40 +862,145 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
$"featureQuery: sync2Supported={sync2Supported}, opticalFlowSupported={opticalFlowSupported}.");
|
$"featureQuery: sync2Supported={sync2Supported}, opticalFlowSupported={opticalFlowSupported}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
nint* ppEnabledExtensions = stackalloc nint[enabledExtensions.Length];
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation: requesting {enabledExtensions.Length} extension(s): {string.Join(", ", enabledExtensions)}.");
|
||||||
|
|
||||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
Result TryCreateDevice(
|
||||||
|
string[] extensions,
|
||||||
|
void* pNextChain,
|
||||||
|
uint queueInfoCount,
|
||||||
|
DeviceQueueCreateInfo* queues,
|
||||||
|
PhysicalDeviceFeatures* enabledFeatures,
|
||||||
|
out Device created)
|
||||||
{
|
{
|
||||||
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
|
nint* ppExtensions = stackalloc nint[extensions.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < extensions.Length; i++)
|
||||||
|
{
|
||||||
|
ppExtensions[i] = Marshal.StringToHGlobalAnsi(extensions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceCreateInfo createInfo = new()
|
||||||
|
{
|
||||||
|
SType = StructureType.DeviceCreateInfo,
|
||||||
|
PNext = pNextChain,
|
||||||
|
QueueCreateInfoCount = queueInfoCount,
|
||||||
|
PQueueCreateInfos = queues,
|
||||||
|
PpEnabledExtensionNames = (byte**)ppExtensions,
|
||||||
|
EnabledExtensionCount = (uint)extensions.Length,
|
||||||
|
PEnabledFeatures = enabledFeatures,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result attempt = api.CreateDevice(physicalDevice.PhysicalDevice, in createInfo, null, out created);
|
||||||
|
|
||||||
|
for (int i = 0; i < extensions.Length; i++)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(ppExtensions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attempt;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceCreateInfo deviceCreateInfo = new()
|
Result result = TryCreateDevice(
|
||||||
{
|
enabledExtensions,
|
||||||
SType = StructureType.DeviceCreateInfo,
|
pExtendedFeatures,
|
||||||
PNext = pExtendedFeatures,
|
useOpticalFlowQueue ? 2u : 1u,
|
||||||
QueueCreateInfoCount = useOpticalFlowQueue ? 2u : 1u,
|
queueCreateInfos,
|
||||||
PQueueCreateInfos = queueCreateInfos,
|
&features,
|
||||||
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
|
out Device device);
|
||||||
EnabledExtensionCount = (uint)enabledExtensions.Length,
|
|
||||||
PEnabledFeatures = &features,
|
|
||||||
};
|
|
||||||
|
|
||||||
Result result = api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out Device device);
|
// Stage 2: drop the dedicated optical-flow queue, the opticalFlow feature and the
|
||||||
|
// VK_NV_optical_flow extension together.
|
||||||
if (result != Result.Success && useOpticalFlowQueue)
|
if (result != Result.Success && (useOpticalFlowQueue || opticalFlowSupported))
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu,
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
$"NVOFA: device creation with the dedicated optical-flow queue failed ({result}); retrying without it. " +
|
$"Device creation failed ({result}) with optical flow enabled; retrying with the dedicated queue, " +
|
||||||
|
"the opticalFlow feature and the VK_NV_optical_flow extension all removed. " +
|
||||||
"DLSS and Frame Generation stay on; hardware optical flow (NvOpticalFlow) is disabled.");
|
"DLSS and Frame Generation stay on; hardware optical flow (NvOpticalFlow) is disabled.");
|
||||||
|
|
||||||
opticalFlowQueueFamilyIndex = uint.MaxValue;
|
opticalFlowQueueFamilyIndex = uint.MaxValue;
|
||||||
deviceCreateInfo.QueueCreateInfoCount = 1;
|
|
||||||
result = api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out device);
|
result = TryCreateDevice(
|
||||||
|
enabledExtensions.Where(x => x != "VK_NV_optical_flow").ToArray(),
|
||||||
|
pFeaturesWithoutOpticalFlow,
|
||||||
|
1,
|
||||||
|
queueCreateInfos,
|
||||||
|
&features,
|
||||||
|
out device);
|
||||||
|
|
||||||
|
if (result == Result.Success)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
"Device creation: succeeded once optical flow was removed, so the driver rejected the dedicated " +
|
||||||
|
"optical-flow queue, the opticalFlow feature or the VK_NV_optical_flow extension.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
// Stage 3: probe each extension DLSS adds on its own, then keep every one the driver
|
||||||
|
// accepts. This preserves as much DLSS capability as the driver allows, instead of
|
||||||
|
// dropping the whole set because one member of it is refused.
|
||||||
|
if (result != Result.Success && Dlss.DlssIntegration.IsEnabled)
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal(ppEnabledExtensions[i]);
|
string[] baselineExtensions = enabledExtensions.Except(Dlss.DlssIntegration.DeviceExtensions).ToArray();
|
||||||
|
string[] dlssExtensions = enabledExtensions.Except(baselineExtensions).ToArray();
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation still failed ({result}); probing each of the {dlssExtensions.Length} DLSS device " +
|
||||||
|
"extensions on its own to find which one the driver refuses.");
|
||||||
|
|
||||||
|
List<string> accepted = [];
|
||||||
|
|
||||||
|
foreach (string candidate in dlssExtensions)
|
||||||
|
{
|
||||||
|
Result probe = TryCreateDevice(
|
||||||
|
baselineExtensions.Append(candidate).ToArray(),
|
||||||
|
pFeaturesBaseline,
|
||||||
|
1,
|
||||||
|
queueCreateInfos,
|
||||||
|
&features,
|
||||||
|
out Device probeDevice);
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation probe: baseline + {candidate} -> {(probe == Result.Success ? "OK" : probe.ToString())}.");
|
||||||
|
|
||||||
|
if (probe == Result.Success)
|
||||||
|
{
|
||||||
|
accepted.Add(candidate);
|
||||||
|
api.DestroyDevice(probeDevice, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] rejected = dlssExtensions.Except(accepted).ToArray();
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation: the driver refuses {rejected.Length} extension(s) [{string.Join(", ", rejected)}]; " +
|
||||||
|
$"retrying with the {accepted.Count} accepted one(s) kept [{string.Join(", ", accepted)}].");
|
||||||
|
|
||||||
|
opticalFlowQueueFamilyIndex = uint.MaxValue;
|
||||||
|
|
||||||
|
// Keep the synchronization2 feature only when its extension survived the probe.
|
||||||
|
bool keepSync2 = sync2Supported && accepted.Contains("VK_KHR_synchronization2");
|
||||||
|
|
||||||
|
result = TryCreateDevice(
|
||||||
|
baselineExtensions.Concat(accepted).ToArray(),
|
||||||
|
keepSync2 ? pFeaturesWithoutOpticalFlow : pFeaturesBaseline,
|
||||||
|
1,
|
||||||
|
queueCreateInfos,
|
||||||
|
&features,
|
||||||
|
out device);
|
||||||
|
|
||||||
|
if (result == Result.Success)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation: succeeded without [{string.Join(", ", rejected)}]. " +
|
||||||
|
"DLSS features that depend on those extensions will not be available in this session.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Gpu,
|
||||||
|
$"Device creation: still failed ({result}) even with only the accepted extensions, so the cause " +
|
||||||
|
"lies outside the extensions DLSS adds.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.ThrowOnError();
|
result.ThrowOnError();
|
||||||
|
|||||||
@@ -136,6 +136,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
if (IsMoltenVk = OperatingSystem.IsMacOS())
|
if (IsMoltenVk = OperatingSystem.IsMacOS())
|
||||||
MVKInitialization.Initialize();
|
MVKInitialization.Initialize();
|
||||||
|
|
||||||
|
// The renderer is created before the guest address space is reserved. The NVIDIA
|
||||||
|
// driver's CUDA interop must initialize before that reservation exists, or it fails
|
||||||
|
// with CUDA_ERROR_OUT_OF_MEMORY and vkCreateDevice then refuses VK_NVX_binary_import
|
||||||
|
// and VK_NV_optical_flow (both CUDA-backed) for the rest of the session, which kills
|
||||||
|
// DLSS. Warmed up here, the state stays valid after the reservation.
|
||||||
|
if (Dlss.DlssIntegration.IsEnabled && OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
string warmUp = Ryujinx.Common.GraphicsDriver.VkDiagProbe.WarmUpCudaInterop();
|
||||||
|
Logger.Info?.Print(LogClass.Gpu, $"DLSS: CUDA interop warm-up: {warmUp}.");
|
||||||
|
}
|
||||||
|
|
||||||
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
|
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user