v1.2.2: stability update. Aims at a DLSS startup crash reported on some NVIDIA GPUs, where device creation rejected the dedicated optical-flow queue and the emulator closed a few seconds after launching a game. It now continues without that queue instead of closing, so DLSS and Frame Generation keep working; the optical-flow accelerator falls back to software. We could not reproduce it on our own hardware, so this is built from user logs, and the build records the optical-flow queue family details to help confirm. All opt-in, stock behavior by default.

This commit is contained in:
2026-07-14 20:47:14 -04:00
parent dd0b9872e1
commit 95106fcb4a
@@ -302,6 +302,20 @@ namespace Ryujinx.Graphics.Vulkan
if (i != queueFamilyIndex && ((uint)physicalDevice.QueueFamilyProperties[i].QueueFlags & 0x100u) != 0)
{
opticalFlowQueueFamilyIndex = i;
// Diagnostic (NVOFA): dump the chosen optical-flow family's properties. When a user's
// vkCreateDevice REJECTS this dedicated queue (the INITIALIZATION_FAILED some NVIDIA
// configs hit), their log then shows WHAT is different about their OFA family
// (queueCount / flags / timestampValidBits) versus a working one -- the lever for a
// real fix instead of the queue-drop fallback. One Info line; the driver version is
// printed by PrintGpuInformation right after.
QueueFamilyProperties ofaProps = physicalDevice.QueueFamilyProperties[i];
QueueFamilyProperties gfxProps = physicalDevice.QueueFamilyProperties[queueFamilyIndex];
Logger.Info?.Print(LogClass.Gpu,
$"NVOFA: optical-flow queue family {i} selected -- queueCount={ofaProps.QueueCount}, " +
$"flags=0x{(uint)ofaProps.QueueFlags:X}, timestampValidBits={ofaProps.TimestampValidBits}; " +
$"graphics family {queueFamilyIndex} queueCount={gfxProps.QueueCount}; total families={physicalDevice.QueueFamilyProperties.Length}.");
break;
}
}
@@ -423,6 +437,29 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
}
PhysicalDeviceSynchronization2Features supportedFeaturesSync2 = new()
{
SType = StructureType.PhysicalDeviceSynchronization2Features,
};
PhysicalDeviceOpticalFlowFeaturesNV supportedFeaturesOpticalFlow = new()
{
SType = StructureType.PhysicalDeviceOpticalFlowFeaturesNV,
};
// Query support for the NVOFA (optical flow) features, so we only ENABLE them below when the
// device actually advertises them. Every other feature block already does this; this pair was
// the lone exception. Chained only when DLSS is on and the extension is present, so non-DLSS
// device creation is left unchanged.
if (Dlss.DlssIntegration.IsEnabled && physicalDevice.IsDeviceExtensionPresent("VK_NV_optical_flow"))
{
supportedFeaturesSync2.PNext = features2.PNext;
features2.PNext = &supportedFeaturesSync2;
supportedFeaturesOpticalFlow.PNext = features2.PNext;
features2.PNext = &supportedFeaturesOpticalFlow;
}
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
@@ -637,14 +674,47 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
}
// NVOFA (B2): the DEDICATED optical-flow queue (a 2nd VkDeviceQueue on the NVIDIA OFA family)
// is OUR OWN addition, used only by NvOpticalFlow for hardware motion vectors. Some NVIDIA
// GPU/driver combos reject a device created with it (VK_ERROR_INITIALIZATION_FAILED at
// vkCreateDevice), which killed the renderer at startup for users who turned DLSS on. We now
// (a) enable the OFA / sync2 FEATURES only when the device reports them supported (queried
// above), and (b) if creation WITH the dedicated queue fails, retry WITHOUT that queue (see the
// device-creation block below). The extension and features stay enabled either way: Streamline's
// DLSS-G (Frame Generation) needs them and submits its own optical flow on the GRAPHICS queue,
// not ours; and NvOpticalFlow falls back to its Lucas-Kanade estimator on its own when the queue
// is absent (its _queueReady stays false). RYUJINX_DLSS_NO_OFA forces the no-queue path so it can
// be validated on hardware that does NOT crash (e.g. an RTX 5090).
bool forceNoOpticalFlowQueue =
Environment.GetEnvironmentVariable("RYUJINX_DLSS_NO_OFA") is "1" or "true" or "on";
bool opticalFlowExtPresent =
Dlss.DlssIntegration.IsEnabled && physicalDevice.IsDeviceExtensionPresent("VK_NV_optical_flow");
bool sync2Supported = opticalFlowExtPresent && supportedFeaturesSync2.Synchronization2;
bool opticalFlowSupported = opticalFlowExtPresent && supportedFeaturesOpticalFlow.OpticalFlow;
// Whether to request OUR dedicated OFA queue. Gated on support + a distinct OFA family existing
// + not forced off. If we won't use it, drop the family index so the caller skips the queue.
bool useOpticalFlowQueue =
!forceNoOpticalFlowQueue && opticalFlowQueueFamilyIndex != uint.MaxValue && sync2Supported && opticalFlowSupported;
if (!useOpticalFlowQueue && opticalFlowQueueFamilyIndex != uint.MaxValue)
{
string why = forceNoOpticalFlowQueue ? "forced off (RYUJINX_DLSS_NO_OFA)" :
!sync2Supported ? "synchronization2 feature not supported" :
"opticalFlow feature not supported";
Logger.Info?.Print(LogClass.Gpu, $"NVOFA: dedicated optical-flow queue not requested ({why}); DLSS/FG unaffected, NvOpticalFlow uses Lucas-Kanade.");
opticalFlowQueueFamilyIndex = uint.MaxValue;
}
PhysicalDeviceSynchronization2Features featuresSync2;
PhysicalDeviceOpticalFlowFeaturesNV featuresOpticalFlow;
if (Dlss.DlssIntegration.IsEnabled && physicalDevice.IsDeviceExtensionPresent("VK_NV_optical_flow"))
// Enable the OFA / sync2 features whenever the device supports them -- KEPT even if we drop our
// dedicated queue, because Streamline's DLSS-G relies on them. synchronization2 is also a hard
// dependency of VK_NV_optical_flow.
if (sync2Supported)
{
// NVOFA (B2): VK_NV_optical_flow needs synchronization2 as a dependency. Enable both
// features, only when DLSS is on and the device advertises the extension, so the default
// (non-DLSS) device creation is left byte-identical to upstream.
featuresSync2 = new()
{
SType = StructureType.PhysicalDeviceSynchronization2Features,
@@ -653,7 +723,10 @@ namespace Ryujinx.Graphics.Vulkan
};
pExtendedFeatures = &featuresSync2;
}
if (opticalFlowSupported)
{
featuresOpticalFlow = new()
{
SType = StructureType.PhysicalDeviceOpticalFlowFeaturesNV,
@@ -681,24 +754,44 @@ namespace Ryujinx.Graphics.Vulkan
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
}
// The extensions and the feature (pNext) chain are IDENTICAL across both attempts; only our
// dedicated OFA queue (the 2nd DeviceQueueCreateInfo) is dropped on the fallback attempt.
DeviceCreateInfo deviceCreateInfo = new()
{
SType = StructureType.DeviceCreateInfo,
PNext = pExtendedFeatures,
QueueCreateInfoCount = queueCreateInfoCount,
QueueCreateInfoCount = useOpticalFlowQueue ? 2u : 1u,
PQueueCreateInfos = queueCreateInfos,
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
EnabledExtensionCount = (uint)enabledExtensions.Length,
PEnabledFeatures = &features,
};
api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out Device device).ThrowOnError();
Result result = api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out Device device);
if (result != Result.Success && useOpticalFlowQueue)
{
// Some NVIDIA GPU/driver combos reject the device when our dedicated optical-flow queue is
// requested (seen as VK_ERROR_INITIALIZATION_FAILED). Retry WITHOUT that queue so DLSS -- and
// Streamline's Frame Generation, which uses the graphics queue and keeps the extension --
// still run; NvOpticalFlow falls back to Lucas-Kanade. The result code names the reason in
// the affected user's log.
Logger.Warning?.Print(LogClass.Gpu,
$"NVOFA: device creation with the dedicated optical-flow queue failed ({result}); retrying without it. " +
"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);
}
for (int i = 0; i < enabledExtensions.Length; i++)
{
Marshal.FreeHGlobal(ppEnabledExtensions[i]);
}
result.ThrowOnError();
return device;
}
}