/ src / Ryujinx.Graphics.Vulkan / VulkanRenderer.cs
VulkanRenderer.cs
   1  using Ryujinx.Common.Configuration;
   2  using Ryujinx.Common.Logging;
   3  using Ryujinx.Graphics.GAL;
   4  using Ryujinx.Graphics.Shader;
   5  using Ryujinx.Graphics.Shader.Translation;
   6  using Ryujinx.Graphics.Vulkan.MoltenVK;
   7  using Ryujinx.Graphics.Vulkan.Queries;
   8  using Silk.NET.Vulkan;
   9  using Silk.NET.Vulkan.Extensions.EXT;
  10  using Silk.NET.Vulkan.Extensions.KHR;
  11  using System;
  12  using System.Collections.Generic;
  13  using System.Runtime.InteropServices;
  14  using Format = Ryujinx.Graphics.GAL.Format;
  15  using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
  16  using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
  17  
  18  namespace Ryujinx.Graphics.Vulkan
  19  {
  20      public sealed class VulkanRenderer : IRenderer
  21      {
  22          private VulkanInstance _instance;
  23          private SurfaceKHR _surface;
  24          private VulkanPhysicalDevice _physicalDevice;
  25          private Device _device;
  26          private WindowBase _window;
  27  
  28          private bool _initialized;
  29  
  30          internal FormatCapabilities FormatCapabilities { get; private set; }
  31          internal HardwareCapabilities Capabilities;
  32  
  33          internal Vk Api { get; private set; }
  34          internal KhrSurface SurfaceApi { get; private set; }
  35          internal KhrSwapchain SwapchainApi { get; private set; }
  36          internal ExtConditionalRendering ConditionalRenderingApi { get; private set; }
  37          internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; }
  38          internal KhrPushDescriptor PushDescriptorApi { get; private set; }
  39          internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
  40          internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
  41          internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; }
  42  
  43          internal uint QueueFamilyIndex { get; private set; }
  44          internal Queue Queue { get; private set; }
  45          internal Queue BackgroundQueue { get; private set; }
  46          internal object BackgroundQueueLock { get; private set; }
  47          internal object QueueLock { get; private set; }
  48  
  49          internal MemoryAllocator MemoryAllocator { get; private set; }
  50          internal HostMemoryAllocator HostMemoryAllocator { get; private set; }
  51          internal CommandBufferPool CommandBufferPool { get; private set; }
  52          internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
  53          internal BackgroundResources BackgroundResources { get; private set; }
  54          internal Action<Action> InterruptAction { get; private set; }
  55          internal SyncManager SyncManager { get; private set; }
  56  
  57          internal BufferManager BufferManager { get; private set; }
  58  
  59          internal HashSet<ShaderCollection> Shaders { get; }
  60          internal HashSet<ITexture> Textures { get; }
  61          internal HashSet<SamplerHolder> Samplers { get; }
  62  
  63          private VulkanDebugMessenger _debugMessenger;
  64          private Counters _counters;
  65  
  66          private PipelineFull _pipeline;
  67  
  68          internal HelperShader HelperShader { get; private set; }
  69          internal PipelineFull PipelineInternal => _pipeline;
  70  
  71          internal BarrierBatch Barriers { get; private set; }
  72  
  73          public IPipeline Pipeline => _pipeline;
  74  
  75          public IWindow Window => _window;
  76  
  77          private readonly Func<Instance, Vk, SurfaceKHR> _getSurface;
  78          private readonly Func<string[]> _getRequiredExtensions;
  79          private readonly string _preferredGpuId;
  80  
  81          private int[] _pdReservedBindings;
  82          private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 };
  83          private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 };
  84  
  85          internal Vendor Vendor { get; private set; }
  86          internal bool IsAmdWindows { get; private set; }
  87          internal bool IsIntelWindows { get; private set; }
  88          internal bool IsAmdGcn { get; private set; }
  89          internal bool IsNvidiaPreTuring { get; private set; }
  90          internal bool IsIntelArc { get; private set; }
  91          internal bool IsQualcommProprietary { get; private set; }
  92          internal bool IsMoltenVk { get; private set; }
  93          internal bool IsTBDR { get; private set; }
  94          internal bool IsSharedMemory { get; private set; }
  95  
  96          public string GpuVendor { get; private set; }
  97          public string GpuDriver { get; private set; }
  98          public string GpuRenderer { get; private set; }
  99          public string GpuVersion { get; private set; }
 100  
 101          public bool PreferThreading => true;
 102  
 103          public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
 104  
 105          public VulkanRenderer(Vk api, Func<Instance, Vk, SurfaceKHR> surfaceFunc, Func<string[]> requiredExtensionsFunc, string preferredGpuId)
 106          {
 107              _getSurface = surfaceFunc;
 108              _getRequiredExtensions = requiredExtensionsFunc;
 109              _preferredGpuId = preferredGpuId;
 110              Api = api;
 111              Shaders = new HashSet<ShaderCollection>();
 112              Textures = new HashSet<ITexture>();
 113              Samplers = new HashSet<SamplerHolder>();
 114  
 115              if (OperatingSystem.IsMacOS())
 116              {
 117                  MVKInitialization.Initialize();
 118  
 119                  // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
 120                  IsMoltenVk = true;
 121              }
 122          }
 123  
 124          private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex)
 125          {
 126              FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice);
 127  
 128              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtConditionalRendering conditionalRenderingApi))
 129              {
 130                  ConditionalRenderingApi = conditionalRenderingApi;
 131              }
 132  
 133              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
 134              {
 135                  ExtendedDynamicStateApi = extendedDynamicStateApi;
 136              }
 137  
 138              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
 139              {
 140                  PushDescriptorApi = pushDescriptorApi;
 141              }
 142  
 143              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtTransformFeedback transformFeedbackApi))
 144              {
 145                  TransformFeedbackApi = transformFeedbackApi;
 146              }
 147  
 148              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
 149              {
 150                  DrawIndirectCountApi = drawIndirectCountApi;
 151              }
 152  
 153              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
 154              {
 155                  DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
 156              }
 157  
 158              if (maxQueueCount >= 2)
 159              {
 160                  Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
 161                  BackgroundQueue = backgroundQueue;
 162                  BackgroundQueueLock = new object();
 163              }
 164  
 165              PhysicalDeviceProperties2 properties2 = new()
 166              {
 167                  SType = StructureType.PhysicalDeviceProperties2,
 168              };
 169  
 170              PhysicalDeviceSubgroupProperties propertiesSubgroup = new()
 171              {
 172                  SType = StructureType.PhysicalDeviceSubgroupProperties,
 173                  PNext = properties2.PNext,
 174              };
 175  
 176              properties2.PNext = &propertiesSubgroup;
 177  
 178              PhysicalDeviceBlendOperationAdvancedPropertiesEXT propertiesBlendOperationAdvanced = new()
 179              {
 180                  SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt,
 181              };
 182  
 183              bool supportsBlendOperationAdvanced = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_blend_operation_advanced");
 184  
 185              if (supportsBlendOperationAdvanced)
 186              {
 187                  propertiesBlendOperationAdvanced.PNext = properties2.PNext;
 188                  properties2.PNext = &propertiesBlendOperationAdvanced;
 189              }
 190  
 191              bool supportsTransformFeedback = _physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName);
 192  
 193              PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new()
 194              {
 195                  SType = StructureType.PhysicalDeviceTransformFeedbackPropertiesExt,
 196              };
 197  
 198              if (supportsTransformFeedback)
 199              {
 200                  propertiesTransformFeedback.PNext = properties2.PNext;
 201                  properties2.PNext = &propertiesTransformFeedback;
 202              }
 203  
 204              PhysicalDevicePortabilitySubsetPropertiesKHR propertiesPortabilitySubset = new()
 205              {
 206                  SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr,
 207              };
 208  
 209              bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName);
 210  
 211              PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR()
 212              {
 213                  SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr
 214              };
 215  
 216              if (supportsPushDescriptors)
 217              {
 218                  propertiesPushDescriptor.PNext = properties2.PNext;
 219                  properties2.PNext = &propertiesPushDescriptor;
 220              }
 221  
 222              PhysicalDeviceFeatures2 features2 = new()
 223              {
 224                  SType = StructureType.PhysicalDeviceFeatures2,
 225              };
 226  
 227              PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart = new()
 228              {
 229                  SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
 230              };
 231  
 232              PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new()
 233              {
 234                  SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
 235              };
 236  
 237              PhysicalDeviceShaderFloat16Int8FeaturesKHR featuresShaderInt8 = new()
 238              {
 239                  SType = StructureType.PhysicalDeviceShaderFloat16Int8Features,
 240              };
 241  
 242              PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor = new()
 243              {
 244                  SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
 245              };
 246  
 247              PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new()
 248              {
 249                  SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
 250              };
 251  
 252              PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
 253              {
 254                  SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
 255              };
 256  
 257              PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
 258              {
 259                  SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
 260              };
 261  
 262              PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
 263              {
 264                  SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
 265              };
 266  
 267              if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
 268              {
 269                  features2.PNext = &featuresPrimitiveTopologyListRestart;
 270              }
 271  
 272              if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
 273              {
 274                  featuresRobustness2.PNext = features2.PNext;
 275                  features2.PNext = &featuresRobustness2;
 276              }
 277  
 278              if (_physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8"))
 279              {
 280                  featuresShaderInt8.PNext = features2.PNext;
 281                  features2.PNext = &featuresShaderInt8;
 282              }
 283  
 284              if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
 285              {
 286                  featuresCustomBorderColor.PNext = features2.PNext;
 287                  features2.PNext = &featuresCustomBorderColor;
 288              }
 289  
 290              bool supportsDepthClipControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control");
 291  
 292              if (supportsDepthClipControl)
 293              {
 294                  featuresDepthClipControl.PNext = features2.PNext;
 295                  features2.PNext = &featuresDepthClipControl;
 296              }
 297  
 298              bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout");
 299  
 300              if (supportsAttachmentFeedbackLoop)
 301              {
 302                  featuresAttachmentFeedbackLoop.PNext = features2.PNext;
 303                  features2.PNext = &featuresAttachmentFeedbackLoop;
 304              }
 305  
 306              bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state");
 307  
 308              if (supportsDynamicAttachmentFeedbackLoop)
 309              {
 310                  featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext;
 311                  features2.PNext = &featuresDynamicAttachmentFeedbackLoop;
 312              }
 313  
 314              bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
 315  
 316              if (usePortability)
 317              {
 318                  propertiesPortabilitySubset.PNext = properties2.PNext;
 319                  properties2.PNext = &propertiesPortabilitySubset;
 320  
 321                  featuresPortabilitySubset.PNext = features2.PNext;
 322                  features2.PNext = &featuresPortabilitySubset;
 323              }
 324  
 325              Api.GetPhysicalDeviceProperties2(_physicalDevice.PhysicalDevice, &properties2);
 326              Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
 327  
 328              var portabilityFlags = PortabilitySubsetFlags.None;
 329              uint vertexBufferAlignment = 1;
 330  
 331              if (usePortability)
 332              {
 333                  vertexBufferAlignment = propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment;
 334  
 335                  portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans;
 336                  portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode;
 337                  portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView;
 338                  portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
 339              }
 340  
 341              bool supportsCustomBorderColor = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
 342                                               featuresCustomBorderColor.CustomBorderColors &&
 343                                               featuresCustomBorderColor.CustomBorderColorWithoutFormat;
 344  
 345              ref var properties = ref properties2.Properties;
 346  
 347              var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties);
 348  
 349              Vendor = VendorUtils.FromId(properties.VendorID);
 350  
 351              IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows();
 352              IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows();
 353              IsTBDR =
 354                  Vendor == Vendor.Apple ||
 355                  Vendor == Vendor.Qualcomm ||
 356                  Vendor == Vendor.ARM ||
 357                  Vendor == Vendor.Broadcom ||
 358                  Vendor == Vendor.ImgTec;
 359  
 360              GpuVendor = VendorUtils.GetNameFromId(properties.VendorID);
 361              GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ?
 362                  VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred.
 363  
 364              fixed (byte* deviceName = properties.DeviceName)
 365              {
 366                  GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)deviceName);
 367              }
 368  
 369              GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
 370  
 371              IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
 372  
 373              if (Vendor == Vendor.Nvidia)
 374              {
 375                  var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
 376  
 377                  if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber))
 378                  {
 379                      IsNvidiaPreTuring = gpuNumber < 2000;
 380                  }
 381                  else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
 382                  {
 383                      IsNvidiaPreTuring = true;
 384                  }
 385              }
 386              else if (Vendor == Vendor.Intel)
 387              {
 388                  IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
 389              }
 390  
 391              IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
 392  
 393              ulong minResourceAlignment = Math.Max(
 394                  Math.Max(
 395                      properties.Limits.MinStorageBufferOffsetAlignment,
 396                      properties.Limits.MinUniformBufferOffsetAlignment),
 397                  properties.Limits.MinTexelBufferOffsetAlignment
 398              );
 399  
 400              SampleCountFlags supportedSampleCounts =
 401                  properties.Limits.FramebufferColorSampleCounts &
 402                  properties.Limits.FramebufferDepthSampleCounts &
 403                  properties.Limits.FramebufferStencilSampleCounts;
 404  
 405              Capabilities = new HardwareCapabilities(
 406                  _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
 407                  supportsCustomBorderColor,
 408                  supportsBlendOperationAdvanced,
 409                  propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap,
 410                  propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor,
 411                  propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor,
 412                  _physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
 413                  _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
 414                  _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
 415                  features2.Features.ShaderFloat64,
 416                  featuresShaderInt8.ShaderInt8,
 417                  _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
 418                  features2.Features.ShaderStorageImageMultisample,
 419                  _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
 420                  _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
 421                  features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
 422                  featuresRobustness2.NullDescriptor || IsMoltenVk,
 423                  supportsPushDescriptors && !IsMoltenVk,
 424                  propertiesPushDescriptor.MaxPushDescriptors,
 425                  featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
 426                  featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
 427                  supportsTransformFeedback,
 428                  propertiesTransformFeedback.TransformFeedbackQueries,
 429                  features2.Features.OcclusionQueryPrecise,
 430                  _physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
 431                  _physicalDevice.PhysicalDeviceFeatures.GeometryShader,
 432                  _physicalDevice.PhysicalDeviceFeatures.TessellationShader,
 433                  _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
 434                  _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
 435                  supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
 436                  supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
 437                  supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
 438                  propertiesSubgroup.SubgroupSize,
 439                  supportedSampleCounts,
 440                  portabilityFlags,
 441                  vertexBufferAlignment,
 442                  properties.Limits.SubTexelPrecisionBits,
 443                  minResourceAlignment);
 444  
 445              IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice);
 446  
 447              MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
 448  
 449              Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
 450              HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
 451  
 452              CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
 453  
 454              PipelineLayoutCache = new PipelineLayoutCache();
 455  
 456              BackgroundResources = new BackgroundResources(this, _device);
 457  
 458              BufferManager = new BufferManager(this, _device);
 459  
 460              SyncManager = new SyncManager(this, _device);
 461              _pipeline = new PipelineFull(this, _device);
 462              _pipeline.Initialize();
 463  
 464              HelperShader = new HelperShader(this, _device);
 465  
 466              Barriers = new BarrierBatch(this);
 467  
 468              _counters = new Counters(this, _device, _pipeline);
 469          }
 470  
 471          private void SetupContext(GraphicsDebugLevel logLevel)
 472          {
 473              _instance = VulkanInitialization.CreateInstance(Api, logLevel, _getRequiredExtensions());
 474              _debugMessenger = new VulkanDebugMessenger(Api, _instance.Instance, logLevel);
 475  
 476              if (Api.TryGetInstanceExtension(_instance.Instance, out KhrSurface surfaceApi))
 477              {
 478                  SurfaceApi = surfaceApi;
 479              }
 480  
 481              _surface = _getSurface(_instance.Instance, Api);
 482              _physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(Api, _instance, _surface, _preferredGpuId);
 483  
 484              var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(Api, _physicalDevice, _surface, out uint maxQueueCount);
 485  
 486              _device = VulkanInitialization.CreateDevice(Api, _physicalDevice, queueFamilyIndex, maxQueueCount);
 487  
 488              if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrSwapchain swapchainApi))
 489              {
 490                  SwapchainApi = swapchainApi;
 491              }
 492  
 493              Api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue);
 494              Queue = queue;
 495              QueueLock = new object();
 496  
 497              LoadFeatures(maxQueueCount, queueFamilyIndex);
 498  
 499              QueueFamilyIndex = queueFamilyIndex;
 500  
 501              _window = new Window(this, _surface, _physicalDevice.PhysicalDevice, _device);
 502  
 503              _initialized = true;
 504          }
 505  
 506          internal int[] GetPushDescriptorReservedBindings(bool isOgl)
 507          {
 508              // The first call of this method determines what push descriptor layout is used for all shaders on this renderer.
 509              // This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors.
 510              if (_pdReservedBindings == null)
 511              {
 512                  if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2)
 513                  {
 514                      _pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn;
 515                  }
 516                  else
 517                  {
 518                      _pdReservedBindings = Array.Empty<int>();
 519                  }
 520              }
 521  
 522              return _pdReservedBindings;
 523          }
 524  
 525          public BufferHandle CreateBuffer(int size, BufferAccess access)
 526          {
 527              return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), access.HasFlag(BufferAccess.Stream));
 528          }
 529  
 530          public BufferHandle CreateBuffer(nint pointer, int size)
 531          {
 532              return BufferManager.CreateHostImported(this, pointer, size);
 533          }
 534  
 535          public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
 536          {
 537              return BufferManager.CreateSparse(this, storageBuffers);
 538          }
 539  
 540          public IImageArray CreateImageArray(int size, bool isBuffer)
 541          {
 542              return new ImageArray(this, size, isBuffer);
 543          }
 544  
 545          public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
 546          {
 547              bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
 548  
 549              if (info.State.HasValue || isCompute)
 550              {
 551                  return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.FromCache);
 552              }
 553  
 554              return new ShaderCollection(this, _device, sources, info.ResourceLayout);
 555          }
 556  
 557          internal ShaderCollection CreateProgramWithMinimalLayout(ShaderSource[] sources, ResourceLayout resourceLayout, SpecDescription[] specDescription = null)
 558          {
 559              return new ShaderCollection(this, _device, sources, resourceLayout, specDescription, isMinimal: true);
 560          }
 561  
 562          public ISampler CreateSampler(SamplerCreateInfo info)
 563          {
 564              return new SamplerHolder(this, _device, info);
 565          }
 566  
 567          public ITexture CreateTexture(TextureCreateInfo info)
 568          {
 569              if (info.Target == Target.TextureBuffer)
 570              {
 571                  return new TextureBuffer(this, info);
 572              }
 573  
 574              return CreateTextureView(info);
 575          }
 576  
 577          public ITextureArray CreateTextureArray(int size, bool isBuffer)
 578          {
 579              return new TextureArray(this, size, isBuffer);
 580          }
 581  
 582          internal TextureView CreateTextureView(TextureCreateInfo info)
 583          {
 584              // This should be disposed when all views are destroyed.
 585              var storage = CreateTextureStorage(info);
 586              return storage.CreateView(info, 0, 0);
 587          }
 588  
 589          internal TextureStorage CreateTextureStorage(TextureCreateInfo info)
 590          {
 591              return new TextureStorage(this, _device, info);
 592          }
 593  
 594          public void DeleteBuffer(BufferHandle buffer)
 595          {
 596              BufferManager.Delete(buffer);
 597          }
 598  
 599          internal void FlushAllCommands()
 600          {
 601              _pipeline?.FlushCommandsImpl();
 602          }
 603  
 604          internal void RegisterFlush()
 605          {
 606              SyncManager.RegisterFlush();
 607  
 608              // Periodically free unused regions of the staging buffer to avoid doing it all at once.
 609              BufferManager.StagingBuffer.FreeCompleted();
 610          }
 611  
 612          public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
 613          {
 614              return BufferManager.GetData(buffer, offset, size);
 615          }
 616  
 617          public unsafe Capabilities GetCapabilities()
 618          {
 619              FormatFeatureFlags compressedFormatFeatureFlags =
 620                  FormatFeatureFlags.SampledImageBit |
 621                  FormatFeatureFlags.SampledImageFilterLinearBit |
 622                  FormatFeatureFlags.BlitSrcBit |
 623                  FormatFeatureFlags.TransferSrcBit |
 624                  FormatFeatureFlags.TransferDstBit;
 625  
 626              bool supportsBc123CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 627                  Format.Bc1RgbaSrgb,
 628                  Format.Bc1RgbaUnorm,
 629                  Format.Bc2Srgb,
 630                  Format.Bc2Unorm,
 631                  Format.Bc3Srgb,
 632                  Format.Bc3Unorm);
 633  
 634              bool supportsBc45CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 635                  Format.Bc4Snorm,
 636                  Format.Bc4Unorm,
 637                  Format.Bc5Snorm,
 638                  Format.Bc5Unorm);
 639  
 640              bool supportsBc67CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 641                  Format.Bc6HSfloat,
 642                  Format.Bc6HUfloat,
 643                  Format.Bc7Srgb,
 644                  Format.Bc7Unorm);
 645  
 646              bool supportsEtc2CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 647                  Format.Etc2RgbaSrgb,
 648                  Format.Etc2RgbaUnorm,
 649                  Format.Etc2RgbPtaSrgb,
 650                  Format.Etc2RgbPtaUnorm,
 651                  Format.Etc2RgbSrgb,
 652                  Format.Etc2RgbUnorm);
 653  
 654              bool supports5BitComponentFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 655                  Format.R5G6B5Unorm,
 656                  Format.R5G5B5A1Unorm,
 657                  Format.R5G5B5X1Unorm,
 658                  Format.B5G6R5Unorm,
 659                  Format.B5G5R5A1Unorm,
 660                  Format.A1B5G5R5Unorm);
 661  
 662              bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 663                  Format.R4G4B4A4Unorm);
 664  
 665              bool supportsAstcFormats = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
 666                  Format.Astc4x4Unorm,
 667                  Format.Astc5x4Unorm,
 668                  Format.Astc5x5Unorm,
 669                  Format.Astc6x5Unorm,
 670                  Format.Astc6x6Unorm,
 671                  Format.Astc8x5Unorm,
 672                  Format.Astc8x6Unorm,
 673                  Format.Astc8x8Unorm,
 674                  Format.Astc10x5Unorm,
 675                  Format.Astc10x6Unorm,
 676                  Format.Astc10x8Unorm,
 677                  Format.Astc10x10Unorm,
 678                  Format.Astc12x10Unorm,
 679                  Format.Astc12x12Unorm,
 680                  Format.Astc4x4Srgb,
 681                  Format.Astc5x4Srgb,
 682                  Format.Astc5x5Srgb,
 683                  Format.Astc6x5Srgb,
 684                  Format.Astc6x6Srgb,
 685                  Format.Astc8x5Srgb,
 686                  Format.Astc8x6Srgb,
 687                  Format.Astc8x8Srgb,
 688                  Format.Astc10x5Srgb,
 689                  Format.Astc10x6Srgb,
 690                  Format.Astc10x8Srgb,
 691                  Format.Astc10x10Srgb,
 692                  Format.Astc12x10Srgb,
 693                  Format.Astc12x12Srgb);
 694  
 695              PhysicalDeviceVulkan12Features featuresVk12 = new()
 696              {
 697                  SType = StructureType.PhysicalDeviceVulkan12Features,
 698              };
 699  
 700              PhysicalDeviceFeatures2 features2 = new()
 701              {
 702                  SType = StructureType.PhysicalDeviceFeatures2,
 703                  PNext = &featuresVk12,
 704              };
 705  
 706              Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
 707  
 708              var limits = _physicalDevice.PhysicalDeviceProperties.Limits;
 709              var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex];
 710  
 711              SystemMemoryType memoryType;
 712  
 713              if (IsSharedMemory)
 714              {
 715                  memoryType = SystemMemoryType.UnifiedMemory;
 716              }
 717              else
 718              {
 719                  memoryType = Vendor == Vendor.Nvidia ?
 720                      SystemMemoryType.DedicatedMemorySlowStorage :
 721                      SystemMemoryType.DedicatedMemory;
 722              }
 723  
 724              return new Capabilities(
 725                  api: TargetApi.Vulkan,
 726                  GpuVendor,
 727                  memoryType: memoryType,
 728                  hasFrontFacingBug: IsIntelWindows,
 729                  hasVectorIndexingBug: IsQualcommProprietary,
 730                  needsFragmentOutputSpecialization: IsMoltenVk,
 731                  reduceShaderPrecision: IsMoltenVk,
 732                  supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
 733                  supportsBc123Compression: supportsBc123CompressionFormat,
 734                  supportsBc45Compression: supportsBc45CompressionFormat,
 735                  supportsBc67Compression: supportsBc67CompressionFormat,
 736                  supportsEtc2Compression: supportsEtc2CompressionFormat,
 737                  supports3DTextureCompression: true,
 738                  supportsBgraFormat: true,
 739                  supportsR4G4Format: false,
 740                  supportsR4G4B4A4Format: supportsR4G4B4A4Format,
 741                  supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(),
 742                  supportsSnormBufferTextureFormat: true,
 743                  supports5BitComponentFormat: supports5BitComponentFormat,
 744                  supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit),
 745                  supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced,
 746                  supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock,
 747                  supportsFragmentShaderOrderingIntel: false,
 748                  supportsGeometryShader: Capabilities.SupportsGeometryShader,
 749                  supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
 750                  supportsTransformFeedback: Capabilities.SupportsTransformFeedback,
 751                  supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
 752                  supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer,
 753                  supportsMismatchingViewFormat: true,
 754                  supportsCubemapView: !IsAmdGcn,
 755                  supportsNonConstantTextureOffset: false,
 756                  supportsQuads: false,
 757                  supportsSeparateSampler: true,
 758                  supportsShaderBallot: false,
 759                  supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
 760                  supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
 761                  supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
 762                  supportsTextureShadowLod: false,
 763                  supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
 764                  supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
 765                  supportsViewportMask: Capabilities.SupportsViewportArray2,
 766                  supportsViewportSwizzle: false,
 767                  supportsIndirectParameters: true,
 768                  supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
 769                  uniformBufferSetIndex: PipelineBase.UniformSetIndex,
 770                  storageBufferSetIndex: PipelineBase.StorageSetIndex,
 771                  textureSetIndex: PipelineBase.TextureSetIndex,
 772                  imageSetIndex: PipelineBase.ImageSetIndex,
 773                  extraSetBaseIndex: PipelineBase.DescriptorSetLayouts,
 774                  maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts),
 775                  maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
 776                  maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
 777                  maximumTexturesPerStage: Constants.MaxTexturesPerStage,
 778                  maximumImagesPerStage: Constants.MaxImagesPerStage,
 779                  maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize,
 780                  maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
 781                  shaderSubgroupSize: (int)Capabilities.SubgroupSize,
 782                  storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
 783                  textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
 784                  gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
 785                  maximumGpuMemory: GetTotalGPUMemory());
 786          }
 787  
 788          private ulong GetTotalGPUMemory()
 789          {
 790              ulong totalMemory = 0;
 791  
 792              Api.GetPhysicalDeviceMemoryProperties(_physicalDevice.PhysicalDevice, out PhysicalDeviceMemoryProperties memoryProperties);
 793  
 794              for (int i = 0; i < memoryProperties.MemoryHeapCount; i++)
 795              {
 796                  var heap = memoryProperties.MemoryHeaps[i];
 797                  if ((heap.Flags & MemoryHeapFlags.DeviceLocalBit) == MemoryHeapFlags.DeviceLocalBit)
 798                  {
 799                      totalMemory += heap.Size;
 800                  }
 801              }
 802  
 803              return totalMemory;
 804          }
 805  
 806          public HardwareInfo GetHardwareInfo()
 807          {
 808              return new HardwareInfo(GpuVendor, GpuRenderer, GpuDriver);
 809          }
 810  
 811          /// <summary>
 812          /// Gets the available Vulkan devices using the default Vulkan API
 813          /// object returned by <see cref="Vk.GetApi()"/>
 814          /// </summary>
 815          /// <returns></returns>
 816          public static DeviceInfo[] GetPhysicalDevices()
 817          {
 818              try
 819              {
 820                  return VulkanInitialization.GetSuitablePhysicalDevices(Vk.GetApi());
 821              }
 822              catch (Exception ex)
 823              {
 824                  Logger.Error?.PrintMsg(LogClass.Gpu, $"Error querying Vulkan devices: {ex.Message}");
 825  
 826                  return Array.Empty<DeviceInfo>();
 827              }
 828          }
 829  
 830          public static DeviceInfo[] GetPhysicalDevices(Vk api)
 831          {
 832              try
 833              {
 834                  return VulkanInitialization.GetSuitablePhysicalDevices(api);
 835              }
 836              catch (Exception)
 837              {
 838                  // If we got an exception here, Vulkan is most likely not supported.
 839                  return Array.Empty<DeviceInfo>();
 840              }
 841          }
 842  
 843          private static string ParseStandardVulkanVersion(uint version)
 844          {
 845              return $"{version >> 22}.{(version >> 12) & 0x3FF}.{version & 0xFFF}";
 846          }
 847  
 848          private static string ParseDriverVersion(ref PhysicalDeviceProperties properties)
 849          {
 850              uint driverVersionRaw = properties.DriverVersion;
 851  
 852              // NVIDIA differ from the standard here and uses a different format.
 853              if (properties.VendorID == 0x10DE)
 854              {
 855                  return $"{(driverVersionRaw >> 22) & 0x3FF}.{(driverVersionRaw >> 14) & 0xFF}.{(driverVersionRaw >> 6) & 0xFF}.{driverVersionRaw & 0x3F}";
 856              }
 857  
 858              return ParseStandardVulkanVersion(driverVersionRaw);
 859          }
 860  
 861          internal PrimitiveTopology TopologyRemap(PrimitiveTopology topology)
 862          {
 863              return topology switch
 864              {
 865                  PrimitiveTopology.Quads => PrimitiveTopology.Triangles,
 866                  PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip,
 867                  PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans)
 868                      ? PrimitiveTopology.Triangles
 869                      : topology,
 870                  _ => topology,
 871              };
 872          }
 873  
 874          internal bool TopologyUnsupported(PrimitiveTopology topology)
 875          {
 876              return topology switch
 877              {
 878                  PrimitiveTopology.Quads => true,
 879                  PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans),
 880                  _ => false,
 881              };
 882          }
 883  
 884          private void PrintGpuInformation()
 885          {
 886              Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
 887              Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB");
 888          }
 889  
 890          public void Initialize(GraphicsDebugLevel logLevel)
 891          {
 892              SetupContext(logLevel);
 893  
 894              PrintGpuInformation();
 895          }
 896  
 897          internal bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
 898          {
 899              if (Capabilities.VertexBufferAlignment > 1)
 900              {
 901                  alignment = (int)Capabilities.VertexBufferAlignment;
 902  
 903                  return true;
 904              }
 905              else if (Vendor != Vendor.Nvidia)
 906              {
 907                  // Vulkan requires that vertex attributes are globally aligned by their component size,
 908                  // so buffer strides that don't divide by the largest scalar element are invalid.
 909                  // Guest applications do this, NVIDIA GPUs are OK with it, others are not.
 910  
 911                  alignment = attrScalarAlignment;
 912  
 913                  return true;
 914              }
 915  
 916              alignment = 1;
 917  
 918              return false;
 919          }
 920  
 921          public void PreFrame()
 922          {
 923              SyncManager.Cleanup();
 924          }
 925  
 926          public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
 927          {
 928              return _counters.QueueReport(type, resultHandler, divisor, hostReserved);
 929          }
 930  
 931          public void ResetCounter(CounterType type)
 932          {
 933              _counters.QueueReset(type);
 934          }
 935  
 936          public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
 937          {
 938              BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate);
 939          }
 940  
 941          public void UpdateCounters()
 942          {
 943              _counters.Update();
 944          }
 945  
 946          public void ResetCounterPool()
 947          {
 948              _counters.ResetCounterPool();
 949          }
 950  
 951          public void ResetFutureCounters(CommandBuffer cmd, int count)
 952          {
 953              _counters?.ResetFutureCounters(cmd, count);
 954          }
 955  
 956          public void BackgroundContextAction(Action action, bool alwaysBackground = false)
 957          {
 958              action();
 959          }
 960  
 961          public void CreateSync(ulong id, bool strict)
 962          {
 963              SyncManager.Create(id, strict);
 964          }
 965  
 966          public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info)
 967          {
 968              throw new NotImplementedException();
 969          }
 970  
 971          public void WaitSync(ulong id)
 972          {
 973              SyncManager.Wait(id);
 974          }
 975  
 976          public ulong GetCurrentSync()
 977          {
 978              return SyncManager.GetCurrent();
 979          }
 980  
 981          public void SetInterruptAction(Action<Action> interruptAction)
 982          {
 983              InterruptAction = interruptAction;
 984          }
 985  
 986          public void Screenshot()
 987          {
 988              _window.ScreenCaptureRequested = true;
 989          }
 990  
 991          public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
 992          {
 993              ScreenCaptured?.Invoke(this, bitmap);
 994          }
 995  
 996          public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
 997          {
 998              return !(IsMoltenVk || IsQualcommProprietary);
 999          }
1000  
1001          public unsafe void Dispose()
1002          {
1003              if (!_initialized)
1004              {
1005                  return;
1006              }
1007  
1008              CommandBufferPool.Dispose();
1009              BackgroundResources.Dispose();
1010              _counters.Dispose();
1011              _window.Dispose();
1012              HelperShader.Dispose();
1013              _pipeline.Dispose();
1014              BufferManager.Dispose();
1015              PipelineLayoutCache.Dispose();
1016              Barriers.Dispose();
1017  
1018              MemoryAllocator.Dispose();
1019  
1020              foreach (var shader in Shaders)
1021              {
1022                  shader.Dispose();
1023              }
1024  
1025              foreach (var texture in Textures)
1026              {
1027                  texture.Release();
1028              }
1029  
1030              foreach (var sampler in Samplers)
1031              {
1032                  sampler.Dispose();
1033              }
1034  
1035              SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
1036  
1037              Api.DestroyDevice(_device, null);
1038  
1039              _debugMessenger.Dispose();
1040  
1041              // Last step destroy the instance
1042              _instance.Dispose();
1043          }
1044  
1045          public bool PrepareHostMapping(nint address, ulong size)
1046          {
1047              return Capabilities.SupportsHostImportedMemory &&
1048                  HostMemoryAllocator.TryImport(BufferManager.HostImportedBufferMemoryRequirements, BufferManager.DefaultBufferMemoryFlags, address, size);
1049          }
1050      }
1051  }