aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Vulkan/VulkanInitialization.cs')
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanInitialization.cs596
1 files changed, 596 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
new file mode 100644
index 00000000..889ce7e2
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -0,0 +1,596 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.EXT;
+using Silk.NET.Vulkan.Extensions.KHR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ public unsafe static class VulkanInitialization
+ {
+ private const uint InvalidIndex = uint.MaxValue;
+ private const string AppName = "Ryujinx.Graphics.Vulkan";
+ private const int QueuesCount = 2;
+
+ public static string[] DesirableExtensions { get; } = new string[]
+ {
+ ExtConditionalRendering.ExtensionName,
+ ExtExtendedDynamicState.ExtensionName,
+ KhrDrawIndirectCount.ExtensionName,
+ KhrPushDescriptor.ExtensionName,
+ "VK_EXT_custom_border_color",
+ "VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
+ "VK_EXT_fragment_shader_interlock",
+ "VK_EXT_index_type_uint8",
+ "VK_EXT_robustness2",
+ "VK_EXT_shader_subgroup_ballot",
+ "VK_EXT_subgroup_size_control",
+ "VK_NV_geometry_shader_passthrough"
+ };
+
+ public static string[] RequiredExtensions { get; } = new string[]
+ {
+ KhrSwapchain.ExtensionName,
+ "VK_EXT_shader_subgroup_vote",
+ ExtTransformFeedback.ExtensionName
+ };
+
+ private static string[] _excludedMessages = new string[]
+ {
+ // NOTE: Done on purpose right now.
+ "UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
+ // TODO: Figure out if fixable
+ "VUID-vkCmdDrawIndexed-None-04584",
+ // TODO: Might be worth looking into making this happy to possibly optimize copies.
+ "UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
+ // TODO: Fix this, it's causing too much noise right now.
+ "VUID-VkSubpassDependency-srcSubpass-00867"
+ };
+
+ internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
+ {
+ var enabledLayers = new List<string>();
+
+ void AddAvailableLayer(string layerName)
+ {
+ uint layerPropertiesCount;
+
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
+
+ LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount];
+
+ fixed (LayerProperties* pLayerProperties = layerProperties)
+ {
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
+
+ for (int i = 0; i < layerPropertiesCount; i++)
+ {
+ string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
+
+ if (currentLayerName == layerName)
+ {
+ enabledLayers.Add(layerName);
+ return;
+ }
+ }
+ }
+
+ Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
+ }
+
+ if (logLevel != GraphicsDebugLevel.None)
+ {
+ AddAvailableLayer("VK_LAYER_KHRONOS_validation");
+ }
+
+ var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray();
+
+ var appName = Marshal.StringToHGlobalAnsi(AppName);
+
+ var applicationInfo = new ApplicationInfo
+ {
+ PApplicationName = (byte*)appName,
+ ApplicationVersion = 1,
+ PEngineName = (byte*)appName,
+ EngineVersion = 1,
+ ApiVersion = Vk.Version12.Value
+ };
+
+ IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
+ IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count];
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
+ }
+
+ for (int i = 0; i < enabledLayers.Count; i++)
+ {
+ ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]);
+ }
+
+ var instanceCreateInfo = new InstanceCreateInfo
+ {
+ SType = StructureType.InstanceCreateInfo,
+ PApplicationInfo = &applicationInfo,
+ PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
+ PpEnabledLayerNames = (byte**)ppEnabledLayers,
+ EnabledExtensionCount = (uint)enabledExtensions.Length,
+ EnabledLayerCount = (uint)enabledLayers.Count
+ };
+
+ api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
+
+ Marshal.FreeHGlobal(appName);
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledExtensions[i]);
+ }
+
+ for (int i = 0; i < enabledLayers.Count; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledLayers[i]);
+ }
+
+ CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
+
+ return instance;
+ }
+
+ private unsafe static uint DebugReport(
+ uint flags,
+ DebugReportObjectTypeEXT objectType,
+ ulong @object,
+ nuint location,
+ int messageCode,
+ byte* layerPrefix,
+ byte* message,
+ void* userData)
+ {
+ var msg = Marshal.PtrToStringAnsi((IntPtr)message);
+
+ foreach (string excludedMessagePart in _excludedMessages)
+ {
+ if (msg.Contains(excludedMessagePart))
+ {
+ return 0;
+ }
+ }
+
+ DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags;
+
+ if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt))
+ {
+ Logger.Error?.Print(LogClass.Gpu, msg);
+ //throw new Exception(msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt))
+ {
+ Logger.Warning?.Print(LogClass.Gpu, msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt))
+ {
+ Logger.Info?.Print(LogClass.Gpu, msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt))
+ {
+ Logger.Warning?.Print(LogClass.Gpu, msg);
+ }
+ else
+ {
+ Logger.Debug?.Print(LogClass.Gpu, msg);
+ }
+
+ return 0;
+ }
+
+ internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface, string preferredGpuId)
+ {
+ uint physicalDeviceCount;
+
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
+
+ PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
+
+ fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
+ {
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
+ }
+
+ // First we try to pick the the user preferred GPU.
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
+ {
+ return physicalDevices[i];
+ }
+ }
+
+ // If we fail to do that, just use the first compatible GPU.
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ if (IsSuitableDevice(api, physicalDevices[i], surface))
+ {
+ return physicalDevices[i];
+ }
+ }
+
+ throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
+ }
+
+ internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
+ {
+ var appName = Marshal.StringToHGlobalAnsi(AppName);
+
+ var applicationInfo = new ApplicationInfo
+ {
+ PApplicationName = (byte*)appName,
+ ApplicationVersion = 1,
+ PEngineName = (byte*)appName,
+ EngineVersion = 1,
+ ApiVersion = Vk.Version12.Value
+ };
+
+ var instanceCreateInfo = new InstanceCreateInfo
+ {
+ SType = StructureType.InstanceCreateInfo,
+ PApplicationInfo = &applicationInfo,
+ PpEnabledExtensionNames = null,
+ PpEnabledLayerNames = null,
+ EnabledExtensionCount = 0,
+ EnabledLayerCount = 0
+ };
+
+ api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
+
+ Marshal.FreeHGlobal(appName);
+
+ uint physicalDeviceCount;
+
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
+
+ PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
+
+ fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
+ {
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
+ }
+
+ DeviceInfo[] devices = new DeviceInfo[physicalDevices.Length];
+
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ var physicalDevice = physicalDevices[i];
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+
+ devices[i] = new DeviceInfo(
+ StringFromIdPair(properties.VendorID, properties.DeviceID),
+ VendorUtils.GetNameFromId(properties.VendorID),
+ Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName),
+ properties.DeviceType == PhysicalDeviceType.DiscreteGpu);
+ }
+
+ api.DestroyInstance(instance, null);
+
+ return devices;
+ }
+
+ public static string StringFromIdPair(uint vendorId, uint deviceId)
+ {
+ return $"0x{vendorId:X}_0x{deviceId:X}";
+ }
+
+ private static bool IsPreferredAndSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
+ {
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+
+ if (StringFromIdPair(properties.VendorID, properties.DeviceID) != preferredGpuId)
+ {
+ return false;
+ }
+
+ return IsSuitableDevice(api, physicalDevice, surface);
+ }
+
+ private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface)
+ {
+ int extensionMatches = 0;
+ uint propertiesCount;
+
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
+
+ ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
+
+ fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
+ {
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
+
+ for (int i = 0; i < propertiesCount; i++)
+ {
+ string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
+
+ if (RequiredExtensions.Contains(extensionName))
+ {
+ extensionMatches++;
+ }
+ }
+ }
+
+ return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
+ }
+
+ internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
+ {
+ const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
+
+ var khrSurface = new KhrSurface(api.Context);
+
+ uint propertiesCount;
+
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
+
+ QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount];
+
+ fixed (QueueFamilyProperties* pProperties = properties)
+ {
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
+ }
+
+ for (uint index = 0; index < propertiesCount; index++)
+ {
+ var queueFlags = properties[index].QueueFlags;
+
+ khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
+
+ if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
+ {
+ queueCount = properties[index].QueueCount;
+ return index;
+ }
+ }
+
+ queueCount = 0;
+ return InvalidIndex;
+ }
+
+ public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount)
+ {
+ if (queueCount > QueuesCount)
+ {
+ queueCount = QueuesCount;
+ }
+
+ float* queuePriorities = stackalloc float[(int)queueCount];
+
+ for (int i = 0; i < queueCount; i++)
+ {
+ queuePriorities[i] = 1f;
+ }
+
+ var queueCreateInfo = new DeviceQueueCreateInfo()
+ {
+ SType = StructureType.DeviceQueueCreateInfo,
+ QueueFamilyIndex = queueFamilyIndex,
+ QueueCount = queueCount,
+ PQueuePriorities = queuePriorities
+ };
+
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+ bool useRobustBufferAccess = VendorUtils.FromId(properties.VendorID) == Vendor.Nvidia;
+
+ var supportedFeatures = api.GetPhysicalDeviceFeature(physicalDevice);
+
+ var features = new PhysicalDeviceFeatures()
+ {
+ DepthBiasClamp = true,
+ DepthClamp = true,
+ DualSrcBlend = true,
+ FragmentStoresAndAtomics = true,
+ GeometryShader = true,
+ ImageCubeArray = true,
+ IndependentBlend = true,
+ LogicOp = true,
+ MultiViewport = true,
+ PipelineStatisticsQuery = true,
+ SamplerAnisotropy = true,
+ ShaderClipDistance = true,
+ ShaderFloat64 = supportedFeatures.ShaderFloat64,
+ ShaderImageGatherExtended = true,
+ // ShaderStorageImageReadWithoutFormat = true,
+ // ShaderStorageImageWriteWithoutFormat = true,
+ TessellationShader = true,
+ VertexPipelineStoresAndAtomics = true,
+ RobustBufferAccess = useRobustBufferAccess
+ };
+
+ void* pExtendedFeatures = null;
+
+ var featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
+ PNext = pExtendedFeatures,
+ TransformFeedback = true
+ };
+
+ pExtendedFeatures = &featuresTransformFeedback;
+
+ var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
+ PNext = pExtendedFeatures,
+ NullDescriptor = true
+ };
+
+ pExtendedFeatures = &featuresRobustness2;
+
+ var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
+ PNext = pExtendedFeatures,
+ ExtendedDynamicState = supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)
+ };
+
+ pExtendedFeatures = &featuresExtendedDynamicState;
+
+ var featuresVk11 = new PhysicalDeviceVulkan11Features()
+ {
+ SType = StructureType.PhysicalDeviceVulkan11Features,
+ PNext = pExtendedFeatures,
+ ShaderDrawParameters = true
+ };
+
+ pExtendedFeatures = &featuresVk11;
+
+ var featuresVk12 = new PhysicalDeviceVulkan12Features()
+ {
+ SType = StructureType.PhysicalDeviceVulkan12Features,
+ PNext = pExtendedFeatures,
+ DescriptorIndexing = supportedExtensions.Contains("VK_EXT_descriptor_indexing"),
+ DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName)
+ };
+
+ pExtendedFeatures = &featuresVk12;
+
+ PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
+
+ if (supportedExtensions.Contains("VK_EXT_index_type_uint8"))
+ {
+ featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
+ PNext = pExtendedFeatures,
+ IndexTypeUint8 = true
+ };
+
+ pExtendedFeatures = &featuresIndexU8;
+ }
+
+ PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
+
+ if (supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"))
+ {
+ featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
+ PNext = pExtendedFeatures,
+ FragmentShaderPixelInterlock = true
+ };
+
+ pExtendedFeatures = &featuresFragmentShaderInterlock;
+ }
+
+ PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
+
+ if (supportedExtensions.Contains("VK_EXT_subgroup_size_control"))
+ {
+ featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt,
+ PNext = pExtendedFeatures,
+ SubgroupSizeControl = true
+ };
+
+ pExtendedFeatures = &featuresSubgroupSizeControl;
+ }
+
+ var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
+
+ IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
+ }
+
+ var deviceCreateInfo = new DeviceCreateInfo()
+ {
+ SType = StructureType.DeviceCreateInfo,
+ PNext = pExtendedFeatures,
+ QueueCreateInfoCount = 1,
+ PQueueCreateInfos = &queueCreateInfo,
+ PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
+ EnabledExtensionCount = (uint)enabledExtensions.Length,
+ PEnabledFeatures = &features
+ };
+
+ api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledExtensions[i]);
+ }
+
+ return device;
+ }
+
+ public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice)
+ {
+ uint propertiesCount;
+
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
+
+ ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
+
+ fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
+ {
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
+ }
+
+ return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
+ }
+
+ internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
+ {
+ return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
+ }
+
+ internal unsafe static void CreateDebugCallbacks(
+ Vk api,
+ GraphicsDebugLevel logLevel,
+ Instance instance,
+ out ExtDebugReport debugReport,
+ out DebugReportCallbackEXT debugReportCallback)
+ {
+ debugReport = default;
+
+ if (logLevel != GraphicsDebugLevel.None)
+ {
+ if (!api.TryGetInstanceExtension(instance, out debugReport))
+ {
+ debugReportCallback = default;
+ return;
+ }
+
+ var flags = logLevel switch
+ {
+ GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
+ GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
+ GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
+ DebugReportFlagsEXT.DebugReportWarningBitExt |
+ DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
+ DebugReportFlagsEXT.DebugReportErrorBitExt |
+ DebugReportFlagsEXT.DebugReportDebugBitExt,
+ _ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
+ };
+ var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
+ {
+ SType = StructureType.DebugReportCallbackCreateInfoExt,
+ Flags = flags,
+ PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
+ };
+
+ debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
+ }
+ else
+ {
+ debugReportCallback = default;
+ }
+ }
+ }
+}