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 _logPathPtr;
|
||||
private static IntPtr _featuresPtr;
|
||||
private static IntPtr _projectIdPtr;
|
||||
private static IntPtr _engineVersionPtr;
|
||||
private static bool _initialized;
|
||||
private static bool _everInitialized;
|
||||
|
||||
@@ -208,6 +210,17 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
pref.RenderApi = RenderApiVulkan;
|
||||
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)
|
||||
{
|
||||
pref.Flags |= PrefFlagUseManualHooking;
|
||||
@@ -410,6 +423,18 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
_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)
|
||||
{
|
||||
NativeLibrary.Free(_interposerHandle);
|
||||
|
||||
@@ -15,9 +15,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public unsafe static class VulkanInitialization
|
||||
{
|
||||
private const uint InvalidIndex = uint.MaxValue;
|
||||
private const int InvalidDeviceIndex = -1;
|
||||
private static readonly uint _minimalVulkanVersion = Vk.Version11.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 int QueuesCount = 2;
|
||||
|
||||
@@ -136,6 +144,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -148,22 +160,93 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
// 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++)
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
nint appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
@@ -719,6 +802,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PhysicalDeviceSynchronization2Features featuresSync2;
|
||||
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)
|
||||
{
|
||||
featuresSync2 = new()
|
||||
@@ -731,6 +818,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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)
|
||||
{
|
||||
featuresOpticalFlow = new()
|
||||
@@ -771,40 +862,145 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
$"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 deviceCreateInfo = new()
|
||||
DeviceCreateInfo createInfo = new()
|
||||
{
|
||||
SType = StructureType.DeviceCreateInfo,
|
||||
PNext = pExtendedFeatures,
|
||||
QueueCreateInfoCount = useOpticalFlowQueue ? 2u : 1u,
|
||||
PQueueCreateInfos = queueCreateInfos,
|
||||
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
|
||||
EnabledExtensionCount = (uint)enabledExtensions.Length,
|
||||
PEnabledFeatures = &features,
|
||||
PNext = pNextChain,
|
||||
QueueCreateInfoCount = queueInfoCount,
|
||||
PQueueCreateInfos = queues,
|
||||
PpEnabledExtensionNames = (byte**)ppExtensions,
|
||||
EnabledExtensionCount = (uint)extensions.Length,
|
||||
PEnabledFeatures = enabledFeatures,
|
||||
};
|
||||
|
||||
Result result = api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out Device device);
|
||||
Result attempt = api.CreateDevice(physicalDevice.PhysicalDevice, in createInfo, null, out created);
|
||||
|
||||
if (result != Result.Success && useOpticalFlowQueue)
|
||||
for (int i = 0; i < extensions.Length; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(ppExtensions[i]);
|
||||
}
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
Result result = TryCreateDevice(
|
||||
enabledExtensions,
|
||||
pExtendedFeatures,
|
||||
useOpticalFlowQueue ? 2u : 1u,
|
||||
queueCreateInfos,
|
||||
&features,
|
||||
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 || opticalFlowSupported))
|
||||
{
|
||||
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.");
|
||||
|
||||
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();
|
||||
|
||||
@@ -136,6 +136,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (IsMoltenVk = OperatingSystem.IsMacOS())
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user