/ src / Ryujinx.Graphics.Vulkan / VulkanInitialization.cs
VulkanInitialization.cs
  1  using Ryujinx.Common.Configuration;
  2  using Ryujinx.Common.Logging;
  3  using Ryujinx.Graphics.GAL;
  4  using Silk.NET.Vulkan;
  5  using Silk.NET.Vulkan.Extensions.EXT;
  6  using Silk.NET.Vulkan.Extensions.KHR;
  7  using System;
  8  using System.Collections.Generic;
  9  using System.Linq;
 10  using System.Runtime.InteropServices;
 11  
 12  namespace Ryujinx.Graphics.Vulkan
 13  {
 14      public unsafe static class VulkanInitialization
 15      {
 16          private const uint InvalidIndex = uint.MaxValue;
 17          private static readonly uint _minimalVulkanVersion = Vk.Version11.Value;
 18          private static readonly uint _minimalInstanceVulkanVersion = Vk.Version12.Value;
 19          private static readonly uint _maximumVulkanVersion = Vk.Version12.Value;
 20          private const string AppName = "Ryujinx.Graphics.Vulkan";
 21          private const int QueuesCount = 2;
 22  
 23          private static readonly string[] _desirableExtensions = {
 24              ExtConditionalRendering.ExtensionName,
 25              ExtExtendedDynamicState.ExtensionName,
 26              ExtTransformFeedback.ExtensionName,
 27              KhrDrawIndirectCount.ExtensionName,
 28              KhrPushDescriptor.ExtensionName,
 29              ExtExternalMemoryHost.ExtensionName,
 30              "VK_EXT_blend_operation_advanced",
 31              "VK_EXT_custom_border_color",
 32              "VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
 33              "VK_EXT_fragment_shader_interlock",
 34              "VK_EXT_index_type_uint8",
 35              "VK_EXT_primitive_topology_list_restart",
 36              "VK_EXT_robustness2",
 37              "VK_EXT_shader_stencil_export",
 38              "VK_KHR_shader_float16_int8",
 39              "VK_EXT_shader_subgroup_ballot",
 40              "VK_NV_geometry_shader_passthrough",
 41              "VK_NV_viewport_array2",
 42              "VK_EXT_depth_clip_control",
 43              "VK_KHR_portability_subset", // As per spec, we should enable this if present.
 44              "VK_EXT_4444_formats",
 45              "VK_KHR_8bit_storage",
 46              "VK_KHR_maintenance2",
 47              "VK_EXT_attachment_feedback_loop_layout",
 48              "VK_EXT_attachment_feedback_loop_dynamic_state",
 49          };
 50  
 51          private static readonly string[] _requiredExtensions = {
 52              KhrSwapchain.ExtensionName,
 53          };
 54  
 55          internal static VulkanInstance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions)
 56          {
 57              var enabledLayers = new List<string>();
 58  
 59              var instanceExtensions = VulkanInstance.GetInstanceExtensions(api);
 60              var instanceLayers = VulkanInstance.GetInstanceLayers(api);
 61  
 62              void AddAvailableLayer(string layerName)
 63              {
 64                  if (instanceLayers.Contains(layerName))
 65                  {
 66                      enabledLayers.Add(layerName);
 67                  }
 68                  else
 69                  {
 70                      Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
 71                  }
 72              }
 73  
 74              if (logLevel != GraphicsDebugLevel.None)
 75              {
 76                  AddAvailableLayer("VK_LAYER_KHRONOS_validation");
 77              }
 78  
 79              var enabledExtensions = requiredExtensions;
 80  
 81              if (instanceExtensions.Contains("VK_EXT_debug_utils"))
 82              {
 83                  enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
 84              }
 85  
 86              var appName = Marshal.StringToHGlobalAnsi(AppName);
 87  
 88              var applicationInfo = new ApplicationInfo
 89              {
 90                  PApplicationName = (byte*)appName,
 91                  ApplicationVersion = 1,
 92                  PEngineName = (byte*)appName,
 93                  EngineVersion = 1,
 94                  ApiVersion = _maximumVulkanVersion,
 95              };
 96  
 97              IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
 98              IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count];
 99  
100              for (int i = 0; i < enabledExtensions.Length; i++)
101              {
102                  ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
103              }
104  
105              for (int i = 0; i < enabledLayers.Count; i++)
106              {
107                  ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]);
108              }
109  
110              var instanceCreateInfo = new InstanceCreateInfo
111              {
112                  SType = StructureType.InstanceCreateInfo,
113                  PApplicationInfo = &applicationInfo,
114                  PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
115                  PpEnabledLayerNames = (byte**)ppEnabledLayers,
116                  EnabledExtensionCount = (uint)enabledExtensions.Length,
117                  EnabledLayerCount = (uint)enabledLayers.Count,
118              };
119  
120              Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var instance);
121  
122              Marshal.FreeHGlobal(appName);
123  
124              for (int i = 0; i < enabledExtensions.Length; i++)
125              {
126                  Marshal.FreeHGlobal(ppEnabledExtensions[i]);
127              }
128  
129              for (int i = 0; i < enabledLayers.Count; i++)
130              {
131                  Marshal.FreeHGlobal(ppEnabledLayers[i]);
132              }
133  
134              result.ThrowOnError();
135  
136              return instance;
137          }
138  
139          internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(Vk api, VulkanInstance instance, SurfaceKHR surface, string preferredGpuId)
140          {
141              instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError();
142  
143              // First we try to pick the user preferred GPU.
144              for (int i = 0; i < physicalDevices.Length; i++)
145              {
146                  if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
147                  {
148                      return physicalDevices[i];
149                  }
150              }
151  
152              // If we fail to do that, just use the first compatible GPU.
153              for (int i = 0; i < physicalDevices.Length; i++)
154              {
155                  if (IsSuitableDevice(api, physicalDevices[i], surface))
156                  {
157                      return physicalDevices[i];
158                  }
159              }
160  
161              throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
162          }
163  
164          internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
165          {
166              var appName = Marshal.StringToHGlobalAnsi(AppName);
167  
168              var applicationInfo = new ApplicationInfo
169              {
170                  PApplicationName = (byte*)appName,
171                  ApplicationVersion = 1,
172                  PEngineName = (byte*)appName,
173                  EngineVersion = 1,
174                  ApiVersion = _maximumVulkanVersion,
175              };
176  
177              var instanceCreateInfo = new InstanceCreateInfo
178              {
179                  SType = StructureType.InstanceCreateInfo,
180                  PApplicationInfo = &applicationInfo,
181                  PpEnabledExtensionNames = null,
182                  PpEnabledLayerNames = null,
183                  EnabledExtensionCount = 0,
184                  EnabledLayerCount = 0,
185              };
186  
187              Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var rawInstance);
188  
189              Marshal.FreeHGlobal(appName);
190  
191              result.ThrowOnError();
192  
193              using VulkanInstance instance = rawInstance;
194  
195              // We currently assume that the instance is compatible with Vulkan 1.2
196              // TODO: Remove this once we relax our initialization codepaths.
197              if (instance.InstanceVersion < _minimalInstanceVulkanVersion)
198              {
199                  return Array.Empty<DeviceInfo>();
200              }
201  
202              instance.EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices).ThrowOnError();
203  
204              List<DeviceInfo> deviceInfos = new();
205  
206              foreach (VulkanPhysicalDevice physicalDevice in physicalDevices)
207              {
208                  if (physicalDevice.PhysicalDeviceProperties.ApiVersion < _minimalVulkanVersion)
209                  {
210                      continue;
211                  }
212  
213                  deviceInfos.Add(physicalDevice.ToDeviceInfo());
214              }
215  
216              return deviceInfos.ToArray();
217          }
218  
219          private static bool IsPreferredAndSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
220          {
221              if (physicalDevice.Id != preferredGpuId)
222              {
223                  return false;
224              }
225  
226              return IsSuitableDevice(api, physicalDevice, surface);
227          }
228  
229          private static bool IsSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface)
230          {
231              int extensionMatches = 0;
232  
233              foreach (string requiredExtension in _requiredExtensions)
234              {
235                  if (physicalDevice.IsDeviceExtensionPresent(requiredExtension))
236                  {
237                      extensionMatches++;
238                  }
239              }
240  
241              return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
242          }
243  
244          internal static uint FindSuitableQueueFamily(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
245          {
246              const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
247  
248              var khrSurface = new KhrSurface(api.Context);
249  
250              for (uint index = 0; index < physicalDevice.QueueFamilyProperties.Length; index++)
251              {
252                  ref QueueFamilyProperties property = ref physicalDevice.QueueFamilyProperties[index];
253  
254                  khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice.PhysicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
255  
256                  if (property.QueueFlags.HasFlag(RequiredFlags) && surfaceSupported)
257                  {
258                      queueCount = property.QueueCount;
259  
260                      return index;
261                  }
262              }
263  
264              queueCount = 0;
265  
266              return InvalidIndex;
267          }
268  
269          internal static Device CreateDevice(Vk api, VulkanPhysicalDevice physicalDevice, uint queueFamilyIndex, uint queueCount)
270          {
271              if (queueCount > QueuesCount)
272              {
273                  queueCount = QueuesCount;
274              }
275  
276              float* queuePriorities = stackalloc float[(int)queueCount];
277  
278              for (int i = 0; i < queueCount; i++)
279              {
280                  queuePriorities[i] = 1f;
281              }
282  
283              var queueCreateInfo = new DeviceQueueCreateInfo
284              {
285                  SType = StructureType.DeviceQueueCreateInfo,
286                  QueueFamilyIndex = queueFamilyIndex,
287                  QueueCount = queueCount,
288                  PQueuePriorities = queuePriorities,
289              };
290  
291              bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia;
292  
293              PhysicalDeviceFeatures2 features2 = new()
294              {
295                  SType = StructureType.PhysicalDeviceFeatures2,
296              };
297  
298              PhysicalDeviceVulkan11Features supportedFeaturesVk11 = new()
299              {
300                  SType = StructureType.PhysicalDeviceVulkan11Features,
301                  PNext = features2.PNext,
302              };
303  
304              features2.PNext = &supportedFeaturesVk11;
305  
306              PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new()
307              {
308                  SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
309                  PNext = features2.PNext,
310              };
311  
312              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
313              {
314                  features2.PNext = &supportedFeaturesCustomBorderColor;
315              }
316  
317              PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new()
318              {
319                  SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
320                  PNext = features2.PNext,
321              };
322  
323              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
324              {
325                  features2.PNext = &supportedFeaturesPrimitiveTopologyListRestart;
326              }
327  
328              PhysicalDeviceTransformFeedbackFeaturesEXT supportedFeaturesTransformFeedback = new()
329              {
330                  SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
331                  PNext = features2.PNext,
332              };
333  
334              if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
335              {
336                  features2.PNext = &supportedFeaturesTransformFeedback;
337              }
338  
339              PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new()
340              {
341                  SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
342              };
343  
344              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
345              {
346                  supportedFeaturesRobustness2.PNext = features2.PNext;
347  
348                  features2.PNext = &supportedFeaturesRobustness2;
349              }
350  
351              PhysicalDeviceDepthClipControlFeaturesEXT supportedFeaturesDepthClipControl = new()
352              {
353                  SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
354                  PNext = features2.PNext,
355              };
356  
357              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control"))
358              {
359                  features2.PNext = &supportedFeaturesDepthClipControl;
360              }
361  
362              PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new()
363              {
364                  SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
365                  PNext = features2.PNext,
366              };
367  
368              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"))
369              {
370                  features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout;
371              }
372  
373              PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new()
374              {
375                  SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
376                  PNext = features2.PNext,
377              };
378  
379              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"))
380              {
381                  features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
382              }
383  
384              PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
385              {
386                  SType = StructureType.PhysicalDeviceVulkan12Features,
387                  PNext = features2.PNext,
388              };
389  
390              features2.PNext = &supportedPhysicalDeviceVulkan12Features;
391  
392              api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
393  
394              var supportedFeatures = features2.Features;
395  
396              var features = new PhysicalDeviceFeatures
397              {
398                  DepthBiasClamp = supportedFeatures.DepthBiasClamp,
399                  DepthClamp = supportedFeatures.DepthClamp,
400                  DualSrcBlend = supportedFeatures.DualSrcBlend,
401                  FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
402                  GeometryShader = supportedFeatures.GeometryShader,
403                  ImageCubeArray = supportedFeatures.ImageCubeArray,
404                  IndependentBlend = supportedFeatures.IndependentBlend,
405                  LogicOp = supportedFeatures.LogicOp,
406                  OcclusionQueryPrecise = supportedFeatures.OcclusionQueryPrecise,
407                  MultiViewport = supportedFeatures.MultiViewport,
408                  PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
409                  SamplerAnisotropy = supportedFeatures.SamplerAnisotropy,
410                  ShaderClipDistance = supportedFeatures.ShaderClipDistance,
411                  ShaderFloat64 = supportedFeatures.ShaderFloat64,
412                  ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended,
413                  ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample,
414                  ShaderStorageImageReadWithoutFormat = supportedFeatures.ShaderStorageImageReadWithoutFormat,
415                  ShaderStorageImageWriteWithoutFormat = supportedFeatures.ShaderStorageImageWriteWithoutFormat,
416                  TessellationShader = supportedFeatures.TessellationShader,
417                  VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
418                  RobustBufferAccess = useRobustBufferAccess,
419                  SampleRateShading = supportedFeatures.SampleRateShading,
420              };
421  
422              void* pExtendedFeatures = null;
423  
424              PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback;
425  
426              if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
427              {
428                  featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT
429                  {
430                      SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
431                      PNext = pExtendedFeatures,
432                      TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback,
433                  };
434  
435                  pExtendedFeatures = &featuresTransformFeedback;
436              }
437  
438              PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart;
439  
440              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
441              {
442                  featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT
443                  {
444                      SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
445                      PNext = pExtendedFeatures,
446                      PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
447                      PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
448                  };
449  
450                  pExtendedFeatures = &featuresPrimitiveTopologyListRestart;
451              }
452  
453              PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
454  
455              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
456              {
457                  featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT
458                  {
459                      SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
460                      PNext = pExtendedFeatures,
461                      NullDescriptor = supportedFeaturesRobustness2.NullDescriptor,
462                  };
463  
464                  pExtendedFeatures = &featuresRobustness2;
465              }
466  
467              var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT
468              {
469                  SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
470                  PNext = pExtendedFeatures,
471                  ExtendedDynamicState = physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
472              };
473  
474              pExtendedFeatures = &featuresExtendedDynamicState;
475  
476              var featuresVk11 = new PhysicalDeviceVulkan11Features
477              {
478                  SType = StructureType.PhysicalDeviceVulkan11Features,
479                  PNext = pExtendedFeatures,
480                  ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters,
481              };
482  
483              pExtendedFeatures = &featuresVk11;
484  
485              var featuresVk12 = new PhysicalDeviceVulkan12Features
486              {
487                  SType = StructureType.PhysicalDeviceVulkan12Features,
488                  PNext = pExtendedFeatures,
489                  DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
490                  DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
491                  UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
492                  UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
493                  StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
494              };
495  
496              pExtendedFeatures = &featuresVk12;
497  
498              PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
499  
500              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"))
501              {
502                  featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT
503                  {
504                      SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
505                      PNext = pExtendedFeatures,
506                      IndexTypeUint8 = true,
507                  };
508  
509                  pExtendedFeatures = &featuresIndexU8;
510              }
511  
512              PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
513  
514              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"))
515              {
516                  featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT
517                  {
518                      SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
519                      PNext = pExtendedFeatures,
520                      FragmentShaderPixelInterlock = true,
521                  };
522  
523                  pExtendedFeatures = &featuresFragmentShaderInterlock;
524              }
525  
526              PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
527  
528              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
529                  supportedFeaturesCustomBorderColor.CustomBorderColors &&
530                  supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat)
531              {
532                  featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT
533                  {
534                      SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
535                      PNext = pExtendedFeatures,
536                      CustomBorderColors = true,
537                      CustomBorderColorWithoutFormat = true,
538                  };
539  
540                  pExtendedFeatures = &featuresCustomBorderColor;
541              }
542  
543              PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl;
544  
545              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") &&
546                  supportedFeaturesDepthClipControl.DepthClipControl)
547              {
548                  featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT
549                  {
550                      SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
551                      PNext = pExtendedFeatures,
552                      DepthClipControl = true,
553                  };
554  
555                  pExtendedFeatures = &featuresDepthClipControl;
556              }
557  
558              PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
559  
560              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
561                  supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
562              {
563                  featuresAttachmentFeedbackLoopLayout = new()
564                  {
565                      SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
566                      PNext = pExtendedFeatures,
567                      AttachmentFeedbackLoopLayout = true,
568                  };
569  
570                  pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout;
571              }
572  
573              PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
574  
575              if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
576                  supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
577              {
578                  featuresDynamicAttachmentFeedbackLoopLayout = new()
579                  {
580                      SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
581                      PNext = pExtendedFeatures,
582                      AttachmentFeedbackLoopDynamicState = true,
583                  };
584  
585                  pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
586              }
587  
588              var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
589  
590              IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
591  
592              for (int i = 0; i < enabledExtensions.Length; i++)
593              {
594                  ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
595              }
596  
597              var deviceCreateInfo = new DeviceCreateInfo
598              {
599                  SType = StructureType.DeviceCreateInfo,
600                  PNext = pExtendedFeatures,
601                  QueueCreateInfoCount = 1,
602                  PQueueCreateInfos = &queueCreateInfo,
603                  PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
604                  EnabledExtensionCount = (uint)enabledExtensions.Length,
605                  PEnabledFeatures = &features,
606              };
607  
608              api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
609  
610              for (int i = 0; i < enabledExtensions.Length; i++)
611              {
612                  Marshal.FreeHGlobal(ppEnabledExtensions[i]);
613              }
614  
615              return device;
616          }
617      }
618  }