1
0

VulkanContext.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using Avalonia.Platform;
  7. using Avalonia.Rendering.Composition;
  8. using Avalonia.Vulkan;
  9. using Silk.NET.Core;
  10. using Silk.NET.Core.Contexts;
  11. using Silk.NET.Core.Loader;
  12. using Silk.NET.Vulkan;
  13. using Silk.NET.Vulkan.Extensions.EXT;
  14. using Silk.NET.Vulkan.Extensions.KHR;
  15. using SilkNetDemo;
  16. using SkiaSharp;
  17. using D3DDevice = SharpDX.Direct3D11.Device;
  18. #pragma warning disable CS0162 // Unreachable code detected
  19. namespace GpuInterop.VulkanDemo;
  20. public unsafe class VulkanContext : IDisposable
  21. {
  22. public required Vk Api { get; init; }
  23. public required Instance Instance { get; init; }
  24. public required PhysicalDevice PhysicalDevice { get; init; }
  25. public required Device Device { get; init; }
  26. public required Queue Queue { get; init; }
  27. public required uint QueueFamilyIndex { get; init; }
  28. public required VulkanCommandBufferPool Pool { get; init; }
  29. public required GRContext? GrContext { get; init; }
  30. public required DescriptorPool DescriptorPool { get; init; }
  31. public required D3DDevice? D3DDevice { get; init; }
  32. public static (VulkanContext? result, string info) TryCreate(ICompositionGpuInterop gpuInterop)
  33. {
  34. using var appName = new ByteString("GpuInterop");
  35. using var engineName = new ByteString("Test");
  36. var applicationInfo = new ApplicationInfo
  37. {
  38. SType = StructureType.ApplicationInfo,
  39. PApplicationName = appName,
  40. ApiVersion = new Version32(1, 1, 0),
  41. PEngineName = appName,
  42. EngineVersion = new Version32(1, 0, 0),
  43. ApplicationVersion = new Version32(1, 0, 0)
  44. };
  45. var enabledExtensions = new List<string>()
  46. {
  47. "VK_KHR_get_physical_device_properties2"
  48. };
  49. if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  50. enabledExtensions.Add("VK_KHR_portability_enumeration");
  51. if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  52. {
  53. enabledExtensions.AddRange([
  54. "VK_KHR_external_memory_capabilities",
  55. "VK_KHR_external_semaphore_capabilities"
  56. ]);
  57. }
  58. var enabledLayers = new List<string>();
  59. Vk api = GetApi();
  60. enabledExtensions.Add("VK_EXT_debug_utils");
  61. if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
  62. enabledLayers.Add("VK_LAYER_KHRONOS_validation");
  63. Device device = default;
  64. DescriptorPool descriptorPool = default;
  65. VulkanCommandBufferPool? pool = null;
  66. GRContext? grContext = null;
  67. bool success = false;
  68. try
  69. {
  70. enabledLayers.Clear();
  71. using var pRequiredExtensions = new ByteStringList(enabledExtensions);
  72. using var pEnabledLayers = new ByteStringList(enabledLayers);
  73. api.CreateInstance(new InstanceCreateInfo
  74. {
  75. SType = StructureType.InstanceCreateInfo,
  76. PApplicationInfo = &applicationInfo,
  77. PpEnabledExtensionNames = pRequiredExtensions,
  78. EnabledExtensionCount = pRequiredExtensions.UCount,
  79. PpEnabledLayerNames = pEnabledLayers,
  80. EnabledLayerCount = pEnabledLayers.UCount,
  81. Flags = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? InstanceCreateFlags.EnumeratePortabilityBitKhr : default
  82. }, null, out var vkInstance).ThrowOnError();
  83. if (api.TryGetInstanceExtension(vkInstance, out ExtDebugUtils debugUtils))
  84. {
  85. var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT
  86. {
  87. SType = StructureType.DebugUtilsMessengerCreateInfoExt,
  88. MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
  89. DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
  90. DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
  91. MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
  92. DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
  93. DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
  94. PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
  95. };
  96. debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out _);
  97. }
  98. var requireDeviceExtensions = new List<string>();
  99. if(!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  100. {
  101. requireDeviceExtensions.AddRange([
  102. "VK_KHR_external_memory",
  103. "VK_KHR_external_semaphore"
  104. ]);
  105. };
  106. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  107. {
  108. if (!(gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
  109. .D3D11TextureGlobalSharedHandle)
  110. || gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
  111. .VulkanOpaqueNtHandle))
  112. )
  113. return (null, "Image sharing is not supported by the current backend");
  114. requireDeviceExtensions.Add(KhrExternalMemoryWin32.ExtensionName);
  115. requireDeviceExtensions.Add(KhrExternalSemaphoreWin32.ExtensionName);
  116. requireDeviceExtensions.Add("VK_KHR_dedicated_allocation");
  117. requireDeviceExtensions.Add("VK_KHR_get_memory_requirements2");
  118. }
  119. else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  120. {
  121. if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
  122. .IOSurfaceRef)
  123. )
  124. return (null, "Image sharing is not supported by the current backend");
  125. requireDeviceExtensions.AddRange(["VK_EXT_metal_objects", "VK_KHR_timeline_semaphore"]);
  126. }
  127. else
  128. {
  129. if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
  130. .VulkanOpaquePosixFileDescriptor)
  131. || !gpuInterop.SupportedSemaphoreTypes.Contains(KnownPlatformGraphicsExternalSemaphoreHandleTypes
  132. .VulkanOpaquePosixFileDescriptor)
  133. )
  134. return (null, "Image sharing is not supported by the current backend");
  135. requireDeviceExtensions.Add(KhrExternalMemoryFd.ExtensionName);
  136. requireDeviceExtensions.Add(KhrExternalSemaphoreFd.ExtensionName);
  137. }
  138. uint count = 0;
  139. api.EnumeratePhysicalDevices(vkInstance, ref count, null).ThrowOnError();
  140. var physicalDevices = stackalloc PhysicalDevice[(int)count];
  141. api.EnumeratePhysicalDevices(vkInstance, ref count, physicalDevices)
  142. .ThrowOnError();
  143. for (uint c = 0; c < count; c++)
  144. {
  145. if (requireDeviceExtensions.Any(ext => !api.IsDeviceExtensionPresent(physicalDevices[c], ext)))
  146. continue;
  147. var physicalDeviceIDProperties = new PhysicalDeviceIDProperties()
  148. {
  149. SType = StructureType.PhysicalDeviceIDProperties
  150. };
  151. var physicalDeviceProperties2 = new PhysicalDeviceProperties2()
  152. {
  153. SType = StructureType.PhysicalDeviceProperties2,
  154. PNext = &physicalDeviceIDProperties
  155. };
  156. api.GetPhysicalDeviceProperties2(physicalDevices[c], &physicalDeviceProperties2);
  157. if (gpuInterop.DeviceLuid != null && physicalDeviceIDProperties.DeviceLuidvalid)
  158. {
  159. if (!new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8)
  160. .SequenceEqual(gpuInterop.DeviceLuid))
  161. continue;
  162. }
  163. else if (gpuInterop.DeviceUuid != null)
  164. {
  165. if (!new Span<byte>(physicalDeviceIDProperties.DeviceUuid, 16)
  166. .SequenceEqual(gpuInterop.DeviceUuid))
  167. continue;
  168. }
  169. var physicalDevice = physicalDevices[c];
  170. var name = Marshal.PtrToStringAnsi(new IntPtr(physicalDeviceProperties2.Properties.DeviceName))!;
  171. uint queueFamilyCount = 0;
  172. api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, null);
  173. var familyProperties = stackalloc QueueFamilyProperties[(int)queueFamilyCount];
  174. api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
  175. for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
  176. {
  177. var family = familyProperties[queueFamilyIndex];
  178. if (!family.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
  179. continue;
  180. var queuePriorities = stackalloc float[(int)family.QueueCount];
  181. for (var i = 0; i < family.QueueCount; i++)
  182. queuePriorities[i] = 1f;
  183. var features = new PhysicalDeviceFeatures();
  184. var queueCreateInfo = new DeviceQueueCreateInfo
  185. {
  186. SType = StructureType.DeviceQueueCreateInfo,
  187. QueueFamilyIndex = queueFamilyIndex,
  188. QueueCount = family.QueueCount,
  189. PQueuePriorities = queuePriorities
  190. };
  191. using var pEnabledDeviceExtensions = new ByteStringList(requireDeviceExtensions);
  192. var deviceCreateInfo = new DeviceCreateInfo
  193. {
  194. SType = StructureType.DeviceCreateInfo,
  195. QueueCreateInfoCount = 1,
  196. PQueueCreateInfos = &queueCreateInfo,
  197. PpEnabledExtensionNames = pEnabledDeviceExtensions,
  198. EnabledExtensionCount = pEnabledDeviceExtensions.UCount,
  199. PEnabledFeatures = &features
  200. };
  201. api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out device)
  202. .ThrowOnError();
  203. api.GetDeviceQueue(device, queueFamilyIndex, 0, out var queue);
  204. var descriptorPoolSize = new DescriptorPoolSize
  205. {
  206. Type = DescriptorType.UniformBuffer, DescriptorCount = 16
  207. };
  208. var descriptorPoolInfo = new DescriptorPoolCreateInfo
  209. {
  210. SType = StructureType.DescriptorPoolCreateInfo,
  211. PoolSizeCount = 1,
  212. PPoolSizes = &descriptorPoolSize,
  213. MaxSets = 16,
  214. Flags = DescriptorPoolCreateFlags.FreeDescriptorSetBit
  215. };
  216. api.CreateDescriptorPool(device, &descriptorPoolInfo, null, out descriptorPool)
  217. .ThrowOnError();
  218. pool = new VulkanCommandBufferPool(api, device, queue, queueFamilyIndex);
  219. if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  220. {
  221. grContext = GRContext.CreateVulkan(new GRVkBackendContext
  222. {
  223. VkInstance = vkInstance.Handle,
  224. VkDevice = device.Handle,
  225. VkQueue = queue.Handle,
  226. GraphicsQueueIndex = queueFamilyIndex,
  227. VkPhysicalDevice = physicalDevice.Handle,
  228. GetProcedureAddress = (proc, _, _) =>
  229. {
  230. var rv = api.GetDeviceProcAddr(device, proc);
  231. if (rv != IntPtr.Zero)
  232. return rv;
  233. rv = api.GetInstanceProcAddr(vkInstance, proc);
  234. if (rv != IntPtr.Zero)
  235. return rv;
  236. return api.GetInstanceProcAddr(default, proc);
  237. }
  238. });
  239. if (grContext == null)
  240. return (null, "Can't create Skia GrContext, device is likely broken");
  241. }
  242. D3DDevice? d3dDevice = null;
  243. if (physicalDeviceIDProperties.DeviceLuidvalid &&
  244. RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
  245. !gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle)
  246. )
  247. d3dDevice = D3DMemoryHelper.CreateDeviceByLuid(
  248. new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8));
  249. success = true;
  250. return (new VulkanContext
  251. {
  252. Api = api,
  253. Device = device,
  254. Instance = vkInstance,
  255. PhysicalDevice = physicalDevice,
  256. Queue = queue,
  257. QueueFamilyIndex = queueFamilyIndex,
  258. Pool = pool,
  259. DescriptorPool = descriptorPool,
  260. GrContext = grContext,
  261. D3DDevice = d3dDevice
  262. }, name);
  263. }
  264. return (null, "No suitable device queue found");
  265. }
  266. return (null, "Suitable device not found");
  267. }
  268. catch (Exception e)
  269. {
  270. return (null, e.ToString());
  271. }
  272. finally
  273. {
  274. if (!success)
  275. {
  276. pool?.Dispose();
  277. if (descriptorPool.Handle != default)
  278. api.DestroyDescriptorPool(device, descriptorPool, null);
  279. if (device.Handle != default)
  280. api.DestroyDevice(device, null);
  281. }
  282. }
  283. }
  284. private static unsafe bool IsLayerAvailable(Vk api, string layerName)
  285. {
  286. uint layerPropertiesCount;
  287. api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
  288. var layerProperties = new LayerProperties[layerPropertiesCount];
  289. fixed (LayerProperties* pLayerProperties = layerProperties)
  290. {
  291. api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
  292. for (var i = 0; i < layerPropertiesCount; i++)
  293. {
  294. var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
  295. if (currentLayerName == layerName) return true;
  296. }
  297. }
  298. return false;
  299. }
  300. private static unsafe uint LogCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, DebugUtilsMessageTypeFlagsEXT messageTypes, DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
  301. {
  302. if (messageSeverity != DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt)
  303. {
  304. var message = Marshal.PtrToStringAnsi((nint)pCallbackData->PMessage);
  305. Console.WriteLine(message);
  306. }
  307. return Vk.False;
  308. }
  309. private const string MacVulkanSdkGlobalPath = "/usr/local/lib/libvulkan.dylib";
  310. class MacLibraryNameContainer : SearchPathContainer
  311. {
  312. public override string Windows64 { get; }
  313. public override string Windows86 { get; }
  314. public override string Linux { get; }
  315. public override string MacOS { get; } = MacVulkanSdkGlobalPath;
  316. }
  317. private static Vk GetApi()
  318. {
  319. if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || !File.Exists(MacVulkanSdkGlobalPath))
  320. return Vk.GetApi();
  321. var ctx = new MultiNativeContext(new INativeContext[2]
  322. {
  323. Vk.CreateDefaultContext(new MacLibraryNameContainer().GetLibraryName()),
  324. null!
  325. });
  326. var ret = new Vk(ctx);
  327. ctx.Contexts[1] = new LamdaNativeContext((Func<string, IntPtr>) ((x) =>
  328. {
  329. if (x.EndsWith("ProcAddr"))
  330. return IntPtr.Zero;
  331. IntPtr deviceProcAddr = (IntPtr) ret.GetDeviceProcAddr(ret.CurrentDevice.GetValueOrDefault(), x);
  332. return deviceProcAddr != IntPtr.Zero ? deviceProcAddr : (IntPtr) ret.GetInstanceProcAddr(ret.CurrentInstance.GetValueOrDefault(), x);
  333. }));
  334. return ret;
  335. }
  336. public void Dispose()
  337. {
  338. D3DDevice?.Dispose();
  339. GrContext?.Dispose();
  340. Pool.Dispose();
  341. Api.DestroyDescriptorPool(Device, DescriptorPool, null);
  342. Api.DestroyDevice(Device, null);
  343. }
  344. }