diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 26609ba59..25354aa71 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -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; } }