1
0

VulkanContext.cs 17 KB

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