Преглед изворни кода

Vulkan: Add workarounds for MoltenVK (#4202)

* Add MVK basics.

* Use appropriate output attribute types

* 4kb vertex alignment, bunch of fixes

* Add reduced shader precision mode for mvk.

* Disable ASTC on MVK for now

* Only request robustnes2 when it is available.

* It's just the one feature actually

* Add triangle fan conversion

* Allow NullDescriptor on MVK for some reason.

* Force safe blit on MoltenVK

* Use ASTC only when formats are all available.

* Disable multilevel 3d texture views

* Filter duplicate render targets (on backend)

* Add Automatic MoltenVK Configuration

* Do not create color attachment views with formats that are not RT compatible

* Make sure that the host format matches the vertex shader input types for invalid/unknown guest formats

* FIx rebase for Vertex Attrib State

* Fix 4b alignment for vertex

* Use asynchronous queue submits for MVK

* Ensure color clear shader has correct output type

* Update MoltenVK config

* Always use MoltenVK workarounds on MacOS

* Make MVK supersede all vendors

* Fix rebase

* Various fixes on rebase

* Get portability flags from extension

* Fix some minor rebasing issues

* Style change

* Use LibraryImport for MVKConfiguration

* Rename MoltenVK vendor to Apple

Intel and AMD GPUs on moltenvk report with the those vendors - only apple silicon reports with vendor 0x106B.

* Fix features2 rebase conflict

* Rename fragment output type

* Add missing check for fragment output types

Might have caused the crash in MK8

* Only do fragment output specialization on MoltenVK

* Avoid copy when passing capabilities

* Self feedback

* Address feedback

Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: nastys <nastys@users.noreply.github.com>
riperiperi пре 3 година
родитељ
комит
8fa248ceb4
36 измењених фајлова са 736 додато и 58 уклоњено
  1. 6 0
      Ryujinx.Graphics.GAL/Capabilities.cs
  2. 47 0
      Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs
  3. 10 0
      Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
  4. 21 2
      Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
  5. 1 1
      Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
  6. 7 1
      Ryujinx.Graphics.Gpu/GpuChannel.cs
  7. 6 0
      Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
  8. 6 0
      Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
  9. 2 0
      Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
  10. 9 1
      Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
  11. 5 0
      Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
  12. 2 0
      Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
  13. 23 3
      Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
  14. 6 6
      Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
  15. 2 2
      Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
  16. 5 0
      Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
  17. 24 4
      Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
  18. 19 0
      Ryujinx.Graphics.Shader/IGpuAccessor.cs
  19. 9 1
      Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
  20. 19 0
      Ryujinx.Graphics.Vulkan/FramebufferParams.cs
  21. 17 1
      Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
  22. 44 5
      Ryujinx.Graphics.Vulkan/HelperShader.cs
  23. 104 0
      Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
  24. 31 0
      Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
  25. 84 10
      Ryujinx.Graphics.Vulkan/PipelineBase.cs
  26. 1 0
      Ryujinx.Graphics.Vulkan/PipelineFull.cs
  27. 0 0
      Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag
  28. 9 0
      Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag
  29. 9 0
      Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag
  30. 63 1
      Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
  31. 1 1
      Ryujinx.Graphics.Vulkan/TextureStorage.cs
  32. 24 5
      Ryujinx.Graphics.Vulkan/TextureView.cs
  33. 7 0
      Ryujinx.Graphics.Vulkan/Vendor.cs
  34. 2 2
      Ryujinx.Graphics.Vulkan/VertexBufferState.cs
  35. 21 6
      Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
  36. 90 6
      Ryujinx.Graphics.Vulkan/VulkanRenderer.cs

+ 6 - 0
Ryujinx.Graphics.GAL/Capabilities.cs

@@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.GAL
 
 
         public readonly bool HasFrontFacingBug;
         public readonly bool HasFrontFacingBug;
         public readonly bool HasVectorIndexingBug;
         public readonly bool HasVectorIndexingBug;
+        public readonly bool NeedsFragmentOutputSpecialization;
+        public readonly bool ReduceShaderPrecision;
 
 
         public readonly bool SupportsAstcCompression;
         public readonly bool SupportsAstcCompression;
         public readonly bool SupportsBc123Compression;
         public readonly bool SupportsBc123Compression;
@@ -49,6 +51,8 @@ namespace Ryujinx.Graphics.GAL
             string vendorName,
             string vendorName,
             bool hasFrontFacingBug,
             bool hasFrontFacingBug,
             bool hasVectorIndexingBug,
             bool hasVectorIndexingBug,
+            bool needsFragmentOutputSpecialization,
+            bool reduceShaderPrecision,
             bool supportsAstcCompression,
             bool supportsAstcCompression,
             bool supportsBc123Compression,
             bool supportsBc123Compression,
             bool supportsBc45Compression,
             bool supportsBc45Compression,
@@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.GAL
             VendorName = vendorName;
             VendorName = vendorName;
             HasFrontFacingBug = hasFrontFacingBug;
             HasFrontFacingBug = hasFrontFacingBug;
             HasVectorIndexingBug = hasVectorIndexingBug;
             HasVectorIndexingBug = hasVectorIndexingBug;
+            NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
+            ReduceShaderPrecision = reduceShaderPrecision;
             SupportsAstcCompression = supportsAstcCompression;
             SupportsAstcCompression = supportsAstcCompression;
             SupportsBc123Compression = supportsBc123Compression;
             SupportsBc123Compression = supportsBc123Compression;
             SupportsBc45Compression = supportsBc45Compression;
             SupportsBc45Compression = supportsBc45Compression;

+ 47 - 0
Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common.Memory;
 using Ryujinx.Common.Memory;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Types;
 using Ryujinx.Graphics.Gpu.Shader;
 using Ryujinx.Graphics.Gpu.Shader;
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader;
 
 
@@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
     /// </summary>
     /// </summary>
     internal class SpecializationStateUpdater
     internal class SpecializationStateUpdater
     {
     {
+        private readonly GpuContext _context;
         private GpuChannelGraphicsState _graphics;
         private GpuChannelGraphicsState _graphics;
         private GpuChannelPoolState _pool;
         private GpuChannelPoolState _pool;
 
 
@@ -18,6 +20,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
 
         private bool _changed;
         private bool _changed;
 
 
+        /// <summary>
+        /// Creates a new instance of the specialization state updater class.
+        /// </summary>
+        /// <param name="context">GPU context</param>
+        public SpecializationStateUpdater(GpuContext context)
+        {
+            _context = context;
+        }
+
         /// <summary>
         /// <summary>
         /// Signal that the specialization state has changed.
         /// Signal that the specialization state has changed.
         /// </summary>
         /// </summary>
@@ -232,6 +243,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Updates the type of the outputs produced by the fragment shader based on the current render target state.
+        /// </summary>
+        /// <param name="rtControl">The render target control register</param>
+        /// <param name="state">The color attachment state</param>
+        public void SetFragmentOutputTypes(RtControl rtControl, ref Array8<RtColorState> state)
+        {
+            bool changed = false;
+            int count = rtControl.UnpackCount();
+
+            for (int index = 0; index < Constants.TotalRenderTargets; index++)
+            {
+                int rtIndex = rtControl.UnpackPermutationIndex(index);
+
+                var colorState = state[rtIndex];
+
+                if (index < count && StateUpdater.IsRtEnabled(colorState))
+                {
+                    Format format = colorState.Format.Convert().Format;
+
+                    AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
+
+                    if (type != _graphics.FragmentOutputTypes[index])
+                    {
+                        _graphics.FragmentOutputTypes[index] = type;
+                        changed = true;
+                    }
+                }
+            }
+
+            if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization)
+            {
+                Signal();
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
         /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
         /// </summary>
         /// </summary>

+ 10 - 0
Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs

@@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
             _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
         }
         }
 
 
+        /// <summary>
+        /// Check if the given register group is dirty without clearing it.
+        /// </summary>
+        /// <param name="groupIndex">Index of the group to check</param>
+        /// <returns>True if dirty, false otherwise</returns>
+        public bool IsDirty(int groupIndex)
+        {
+            return (_dirtyMask & (1UL << groupIndex)) != 0;
+        }
+
         /// <summary>
         /// <summary>
         /// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
         /// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
         /// </summary>
         /// </summary>

+ 21 - 2
Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs

@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
         public const int ScissorStateIndex = 16;
         public const int ScissorStateIndex = 16;
         public const int VertexBufferStateIndex = 0;
         public const int VertexBufferStateIndex = 0;
         public const int PrimitiveRestartStateIndex = 12;
         public const int PrimitiveRestartStateIndex = 12;
+        public const int RenderTargetStateIndex = 27;
 
 
         private readonly GpuContext _context;
         private readonly GpuContext _context;
         private readonly GpuChannel _channel;
         private readonly GpuChannel _channel;
@@ -264,6 +265,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 _prevTfEnable = false;
                 _prevTfEnable = false;
             }
             }
 
 
+            if (_updateTracker.IsDirty(RenderTargetStateIndex))
+            {
+                UpdateRenderTargetSpecialization();
+            }
+
             _updateTracker.Update(ulong.MaxValue);
             _updateTracker.Update(ulong.MaxValue);
 
 
             // If any state that the shader depends on changed,
             // If any state that the shader depends on changed,
@@ -526,12 +532,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Updates specialization state based on render target state.
+        /// </summary>
+        public void UpdateRenderTargetSpecialization()
+        {
+            _currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState);
+        }
+
         /// <summary>
         /// <summary>
         /// Checks if a render target color buffer is used.
         /// Checks if a render target color buffer is used.
         /// </summary>
         /// </summary>
         /// <param name="colorState">Color buffer information</param>
         /// <param name="colorState">Color buffer information</param>
         /// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
         /// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
-        private static bool IsRtEnabled(RtColorState colorState)
+        internal static bool IsRtEnabled(RtColorState colorState)
         {
         {
             // Colors are disabled by writing 0 to the format.
             // Colors are disabled by writing 0 to the format.
             return colorState.Format != 0 && colorState.WidthOrStride != 0;
             return colorState.Format != 0 && colorState.WidthOrStride != 0;
@@ -893,7 +907,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 {
                 {
                     Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
                     Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
 
 
-                    format = Format.R32G32B32A32Float;
+                    format = vertexAttrib.UnpackType() switch
+                    {
+                        VertexAttribType.Sint => Format.R32G32B32A32Sint,
+                        VertexAttribType.Uint => Format.R32G32B32A32Uint,
+                        _ => Format.R32G32B32A32Float
+                    };
                 }
                 }
 
 
                 vertexAttribs[index] = new VertexAttribDescriptor(
                 vertexAttribs[index] = new VertexAttribDescriptor(

+ 1 - 1
Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs

@@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
 
             _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
             _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
 
 
-            var spec = new SpecializationStateUpdater();
+            var spec = new SpecializationStateUpdater(context);
             var drawState = new DrawState();
             var drawState = new DrawState();
 
 
             _drawManager = new DrawManager(context, channel, _state, drawState, spec);
             _drawManager = new DrawManager(context, channel, _state, drawState, spec);

+ 7 - 1
Ryujinx.Graphics.Gpu/GpuChannel.cs

@@ -1,4 +1,5 @@
-using Ryujinx.Graphics.Gpu.Engine.GPFifo;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.GPFifo;
 using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.Memory;
 using Ryujinx.Graphics.Gpu.Memory;
 using System;
 using System;
@@ -31,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu
         /// </summary>
         /// </summary>
         internal MemoryManager MemoryManager => _memoryManager;
         internal MemoryManager MemoryManager => _memoryManager;
 
 
+        /// <summary>
+        /// Host hardware capabilities from the GPU context.
+        /// </summary>
+        internal ref Capabilities Capabilities => ref _context.Capabilities;
+
         /// <summary>
         /// <summary>
         /// Creates a new instance of a GPU channel.
         /// Creates a new instance of a GPU channel.
         /// </summary>
         /// </summary>

+ 6 - 0
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs

@@ -107,6 +107,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             return _oldSpecState.GraphicsState.AttributeTypes[location];
             return _oldSpecState.GraphicsState.AttributeTypes[location];
         }
         }
 
 
+        /// <inheritdoc/>
+        public AttributeType QueryFragmentOutputType(int location)
+        {
+            return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
         public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
 
 

+ 6 - 0
Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs

@@ -113,6 +113,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
             return _state.GraphicsState.AttributeTypes[location];
             return _state.GraphicsState.AttributeTypes[location];
         }
         }
 
 
+        /// <inheritdoc/>
+        public AttributeType QueryFragmentOutputType(int location)
+        {
+            return _state.GraphicsState.FragmentOutputTypes[location];
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
         public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
 
 

+ 2 - 0
Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs

@@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             };
             };
         }
         }
 
 
+        public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
+
         public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
         public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
 
 
         public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
         public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;

+ 9 - 1
Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs

@@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         /// </summary>
         public bool HasUnalignedStorageBuffer;
         public bool HasUnalignedStorageBuffer;
 
 
+        /// <summary>
+        /// Type of the fragment shader outputs.
+        /// </summary>
+        public Array8<AttributeType> FragmentOutputTypes;
+
         /// <summary>
         /// <summary>
         /// Creates a new GPU graphics state.
         /// Creates a new GPU graphics state.
         /// </summary>
         /// </summary>
@@ -105,6 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
         /// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
         /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
         /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
         /// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
         /// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
+        /// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
         public GpuChannelGraphicsState(
         public GpuChannelGraphicsState(
             bool earlyZForce,
             bool earlyZForce,
             PrimitiveTopology topology,
             PrimitiveTopology topology,
@@ -120,7 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             float alphaTestReference,
             float alphaTestReference,
             ref Array32<AttributeType> attributeTypes,
             ref Array32<AttributeType> attributeTypes,
             bool hasConstantBufferDrawParameters,
             bool hasConstantBufferDrawParameters,
-            bool hasUnalignedStorageBuffer)
+            bool hasUnalignedStorageBuffer,
+            ref Array8<AttributeType> fragmentOutputTypes)
         {
         {
             EarlyZForce = earlyZForce;
             EarlyZForce = earlyZForce;
             Topology = topology;
             Topology = topology;
@@ -137,6 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             AttributeTypes = attributeTypes;
             AttributeTypes = attributeTypes;
             HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
             HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
             HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
             HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
+            FragmentOutputTypes = fragmentOutputTypes;
         }
         }
     }
     }
 }
 }

+ 5 - 0
Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs

@@ -530,6 +530,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return false;
                 return false;
             }
             }
 
 
+            if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
+            {
+                return false;
+            }
+
             return Matches(channel, ref poolState, checkTextures, isCompute: false);
             return Matches(channel, ref poolState, checkTextures, isCompute: false);
         }
         }
 
 

+ 2 - 0
Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs

@@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.OpenGL
                 vendorName: GpuVendor,
                 vendorName: GpuVendor,
                 hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
                 hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
                 hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
                 hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
+                needsFragmentOutputSpecialization: false,
+                reduceShaderPrecision: false,
                 supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
                 supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
                 supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
                 supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
                 supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
                 supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,

+ 23 - 3
Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs

@@ -346,12 +346,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             {
             {
                 string name = context.OperandManager.DeclareLocal(decl);
                 string name = context.OperandManager.DeclareLocal(decl);
 
 
-                context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
+                context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
             }
             }
         }
         }
 
 
-        public static string GetVarTypeName(AggregateType type, bool precise = true)
+        public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
         {
         {
+            if (context.Config.GpuAccessor.QueryHostReducedPrecision())
+            {
+                precise = false;
+            }
+
             return type switch
             return type switch
             {
             {
                 AggregateType.Void => "void",
                 AggregateType.Void => "void",
@@ -666,7 +671,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
             }
             else
             else
             {
             {
-                context.AppendLine($"layout (location = {attr}) out vec4 {name};");
+                string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
+                    context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
+                    {
+                        AttributeType.Sint => "ivec4",
+                        AttributeType.Uint => "uvec4",
+                        _ => "vec4"
+                    };
+
+                if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
+                {
+                    context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
+                }
+                else
+                {
+                    context.AppendLine($"layout (location = {attr}) out {type} {name};");
+                }
             }
             }
         }
         }
 
 

+ 6 - 6
Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             {
             {
                 for (int i = 1; i < info.Functions.Count; i++)
                 for (int i = 1; i < info.Functions.Count; i++)
                 {
                 {
-                    context.AppendLine($"{GetFunctionSignature(info.Functions[i])};");
+                    context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};");
                 }
                 }
 
 
                 context.AppendLine();
                 context.AppendLine();
@@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         {
         {
             context.CurrentFunction = function;
             context.CurrentFunction = function;
 
 
-            context.AppendLine(GetFunctionSignature(function, funcName));
+            context.AppendLine(GetFunctionSignature(context, function, funcName));
             context.EnterScope();
             context.EnterScope();
 
 
             Declarations.DeclareLocals(context, function);
             Declarations.DeclareLocals(context, function);
@@ -54,23 +54,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             context.LeaveScope();
             context.LeaveScope();
         }
         }
 
 
-        private static string GetFunctionSignature(StructuredFunction function, string funcName = null)
+        private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null)
         {
         {
             string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
             string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
 
 
             for (int i = 0; i < function.InArguments.Length; i++)
             for (int i = 0; i < function.InArguments.Length; i++)
             {
             {
-                args[i] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
+                args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
             }
             }
 
 
             for (int i = 0; i < function.OutArguments.Length; i++)
             for (int i = 0; i < function.OutArguments.Length; i++)
             {
             {
                 int j = i + function.InArguments.Length;
                 int j = i + function.InArguments.Length;
 
 
-                args[j] = $"out {Declarations.GetVarTypeName(function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
+                args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
             }
             }
 
 
-            return $"{Declarations.GetVarTypeName(function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
+            return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
         }
         }
 
 
         private static void PrintBlock(CodeGenContext context, AstBlock block)
         private static void PrintBlock(CodeGenContext context, AstBlock block)

+ 2 - 2
Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs

@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 
 
                         if ((outputType & AggregateType.ElementCountMask) != 0)
                         if ((outputType & AggregateType.ElementCountMask) != 0)
                         {
                         {
-                            return $"{Declarations.GetVarTypeName(outputType, precise: false)}({imageConst})";
+                            return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
                         }
                         }
 
 
                         return imageConst;
                         return imageConst;
@@ -513,7 +513,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 
 
                     if ((outputType & AggregateType.ElementCountMask) != 0)
                     if ((outputType & AggregateType.ElementCountMask) != 0)
                     {
                     {
-                        return $"{Declarations.GetVarTypeName(outputType, precise: false)}({scalarValue})";
+                        return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
                     }
                     }
                 }
                 }
 
 

+ 5 - 0
Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs

@@ -577,6 +577,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
                     context.Decorate(spvVar, Decoration.Patch);
                     context.Decorate(spvVar, Decoration.Patch);
                 }
                 }
 
 
+                if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
+                {
+                    context.Decorate(spvVar, Decoration.Invariant);
+                }
+
                 context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
                 context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
 
 
                 if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
                 if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)

+ 24 - 4
Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs

@@ -2194,13 +2194,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
             if (operation.Inst.HasFlag(Instruction.FP64))
             if (operation.Inst.HasFlag(Instruction.FP64))
             {
             {
                 var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
                 var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
-                context.Decorate(result, Decoration.NoContraction);
+
+                if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+                {
+                    context.Decorate(result, Decoration.NoContraction);
+                }
+
                 return new OperationResult(AggregateType.FP64, result);
                 return new OperationResult(AggregateType.FP64, result);
             }
             }
             else if (operation.Inst.HasFlag(Instruction.FP32))
             else if (operation.Inst.HasFlag(Instruction.FP32))
             {
             {
                 var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
                 var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
-                context.Decorate(result, Decoration.NoContraction);
+
+                if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+                {
+                    context.Decorate(result, Decoration.NoContraction);
+                }
+
                 return new OperationResult(AggregateType.FP32, result);
                 return new OperationResult(AggregateType.FP32, result);
             }
             }
             else
             else
@@ -2255,13 +2265,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
             if (operation.Inst.HasFlag(Instruction.FP64))
             if (operation.Inst.HasFlag(Instruction.FP64))
             {
             {
                 var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
                 var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
-                context.Decorate(result, Decoration.NoContraction);
+
+                if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+                {
+                    context.Decorate(result, Decoration.NoContraction);
+                }
+
                 return new OperationResult(AggregateType.FP64, result);
                 return new OperationResult(AggregateType.FP64, result);
             }
             }
             else if (operation.Inst.HasFlag(Instruction.FP32))
             else if (operation.Inst.HasFlag(Instruction.FP32))
             {
             {
                 var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
                 var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
-                context.Decorate(result, Decoration.NoContraction);
+
+                if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+                {
+                    context.Decorate(result, Decoration.NoContraction);
+                }
+
                 return new OperationResult(AggregateType.FP32, result);
                 return new OperationResult(AggregateType.FP32, result);
             }
             }
             else
             else

+ 19 - 0
Ryujinx.Graphics.Shader/IGpuAccessor.cs

@@ -114,6 +114,16 @@ namespace Ryujinx.Graphics.Shader
             return index;
             return index;
         }
         }
 
 
+        /// <summary>
+        /// Queries output type for fragment shaders.
+        /// </summary>
+        /// <param name="location">Location of the framgent output</param>
+        /// <returns>Output location</returns>
+        AttributeType QueryFragmentOutputType(int location)
+        {
+            return AttributeType.Float;
+        }
+
         /// <summary>
         /// <summary>
         /// Queries Local Size X for compute shaders.
         /// Queries Local Size X for compute shaders.
         /// </summary>
         /// </summary>
@@ -186,6 +196,15 @@ namespace Ryujinx.Graphics.Shader
             return false;
             return false;
         }
         }
 
 
+        /// <summary>
+        /// Queries host about whether to reduce precision to improve performance.
+        /// </summary>
+        /// <returns>True if precision is limited to vertex position, false otherwise</returns>
+        bool QueryHostReducedPrecision()
+        {
+            return false;
+        }
+
         /// <summary>
         /// <summary>
         /// Queries host about the presence of the FrontFacing built-in variable bug.
         /// Queries host about the presence of the FrontFacing built-in variable bug.
         /// </summary>
         /// </summary>

+ 9 - 1
Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs

@@ -128,7 +128,15 @@ namespace Ryujinx.Graphics.Shader.Translation
             }
             }
             else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
             else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
             {
             {
-                return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false);
+                int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
+                var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
+                {
+                    AttributeType.Sint => AggregateType.S32,
+                    AttributeType.Uint => AggregateType.U32,
+                    _ => AggregateType.FP32
+                };
+
+                return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
             }
             }
             else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
             else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
             {
             {

+ 19 - 0
Ryujinx.Graphics.Vulkan/FramebufferParams.cs

@@ -140,6 +140,25 @@ namespace Ryujinx.Graphics.Vulkan
             return _attachments[index];
             return _attachments[index];
         }
         }
 
 
+        public ComponentType GetAttachmentComponentType(int index)
+        {
+            if (_colors != null && (uint)index < _colors.Length)
+            {
+                var format = _colors[index].Info.Format;
+
+                if (format.IsSint())
+                {
+                    return ComponentType.SignedInteger;
+                }
+                else if (format.IsUint())
+                {
+                    return ComponentType.UnsignedInteger;
+                }
+            }
+
+            return ComponentType.Float;
+        }
+
         public bool IsValidColorAttachment(int bindIndex)
         public bool IsValidColorAttachment(int bindIndex)
         {
         {
             return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
             return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;

+ 17 - 1
Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs

@@ -1,7 +1,20 @@
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
+using System;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
 {
 {
+    [Flags]
+    enum PortabilitySubsetFlags
+    {
+        None = 0,
+
+        VertexBufferAlignment4B = 1,
+        NoTriangleFans = 1 << 1,
+        NoPointMode = 1 << 2,
+        No3DImageView = 1 << 3,
+        NoLodBias = 1 << 4
+    }
+
     readonly struct HardwareCapabilities
     readonly struct HardwareCapabilities
     {
     {
         public readonly bool SupportsIndexTypeUint8;
         public readonly bool SupportsIndexTypeUint8;
@@ -23,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
         public readonly uint MaxSubgroupSize;
         public readonly uint MaxSubgroupSize;
         public readonly ShaderStageFlags RequiredSubgroupSizeStages;
         public readonly ShaderStageFlags RequiredSubgroupSizeStages;
         public readonly SampleCountFlags SupportedSampleCounts;
         public readonly SampleCountFlags SupportedSampleCounts;
+        public readonly PortabilitySubsetFlags PortabilitySubset;
 
 
         public HardwareCapabilities(
         public HardwareCapabilities(
             bool supportsIndexTypeUint8,
             bool supportsIndexTypeUint8,
@@ -43,7 +57,8 @@ namespace Ryujinx.Graphics.Vulkan
             uint minSubgroupSize,
             uint minSubgroupSize,
             uint maxSubgroupSize,
             uint maxSubgroupSize,
             ShaderStageFlags requiredSubgroupSizeStages,
             ShaderStageFlags requiredSubgroupSizeStages,
-            SampleCountFlags supportedSampleCounts)
+            SampleCountFlags supportedSampleCounts,
+            PortabilitySubsetFlags portabilitySubset)
         {
         {
             SupportsIndexTypeUint8 = supportsIndexTypeUint8;
             SupportsIndexTypeUint8 = supportsIndexTypeUint8;
             SupportsCustomBorderColor = supportsCustomBorderColor;
             SupportsCustomBorderColor = supportsCustomBorderColor;
@@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
             MaxSubgroupSize = maxSubgroupSize;
             MaxSubgroupSize = maxSubgroupSize;
             RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
             RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
             SupportedSampleCounts = supportedSampleCounts;
             SupportedSampleCounts = supportedSampleCounts;
+            PortabilitySubset = portabilitySubset;
         }
         }
     }
     }
 }
 }

+ 44 - 5
Ryujinx.Graphics.Vulkan/HelperShader.cs

@@ -9,6 +9,13 @@ using VkFormat = Silk.NET.Vulkan.Format;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
 {
 {
+    enum ComponentType
+    {
+        Float,
+        SignedInteger,
+        UnsignedInteger
+    }
+
     class HelperShader : IDisposable
     class HelperShader : IDisposable
     {
     {
         private const int UniformBufferAlignment = 256;
         private const int UniformBufferAlignment = 256;
@@ -18,7 +25,9 @@ namespace Ryujinx.Graphics.Vulkan
         private readonly ISampler _samplerNearest;
         private readonly ISampler _samplerNearest;
         private readonly IProgram _programColorBlit;
         private readonly IProgram _programColorBlit;
         private readonly IProgram _programColorBlitClearAlpha;
         private readonly IProgram _programColorBlitClearAlpha;
-        private readonly IProgram _programColorClear;
+        private readonly IProgram _programColorClearF;
+        private readonly IProgram _programColorClearSI;
+        private readonly IProgram _programColorClearUI;
         private readonly IProgram _programStrideChange;
         private readonly IProgram _programStrideChange;
         private readonly IProgram _programConvertIndexBuffer;
         private readonly IProgram _programConvertIndexBuffer;
         private readonly IProgram _programConvertIndirectData;
         private readonly IProgram _programConvertIndirectData;
@@ -63,10 +72,22 @@ namespace Ryujinx.Graphics.Vulkan
                 Array.Empty<int>(),
                 Array.Empty<int>(),
                 Array.Empty<int>());
                 Array.Empty<int>());
 
 
-            _programColorClear = gd.CreateProgramWithMinimalLayout(new[]
+            _programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
+            {
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+            });
+
+            _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
+            {
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+            });
+
+            _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
                 new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
                 new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
             });
             });
 
 
             var strideChangeBindings = new ShaderBindings(
             var strideChangeBindings = new ShaderBindings(
@@ -242,6 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
             int dstWidth,
             int dstWidth,
             int dstHeight,
             int dstHeight,
             VkFormat dstFormat,
             VkFormat dstFormat,
+            ComponentType type,
             Rectangle<int> scissor)
             Rectangle<int> scissor)
         {
         {
             const int ClearColorBufferSize = 16;
             const int ClearColorBufferSize = 16;
@@ -273,7 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             scissors[0] = scissor;
             scissors[0] = scissor;
 
 
-            _pipeline.SetProgram(_programColorClear);
+            IProgram program;
+
+            if (type == ComponentType.SignedInteger)
+            {
+                program = _programColorClearSI;
+            }
+            else if (type == ComponentType.UnsignedInteger)
+            {
+                program = _programColorClearUI;
+            }
+            else
+            {
+                program = _programColorClearF;
+            }
+
+            _pipeline.SetProgram(program);
             _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
             _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
             _pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
             _pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
             _pipeline.SetViewports(viewports, false);
             _pipeline.SetViewports(viewports, false);
@@ -948,7 +985,9 @@ namespace Ryujinx.Graphics.Vulkan
             {
             {
                 _programColorBlitClearAlpha.Dispose();
                 _programColorBlitClearAlpha.Dispose();
                 _programColorBlit.Dispose();
                 _programColorBlit.Dispose();
-                _programColorClear.Dispose();
+                _programColorClearF.Dispose();
+                _programColorClearSI.Dispose();
+                _programColorClearUI.Dispose();
                 _programStrideChange.Dispose();
                 _programStrideChange.Dispose();
                 _programConvertIndexBuffer.Dispose();
                 _programConvertIndexBuffer.Dispose();
                 _programConvertIndirectData.Dispose();
                 _programConvertIndirectData.Dispose();

+ 104 - 0
Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs

@@ -0,0 +1,104 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan.MoltenVK
+{
+    enum MVKConfigLogLevel : int
+    {
+        None = 0,
+        Error = 1,
+        Warning = 2,
+        Info = 3,
+        Debug = 4
+    }
+
+    enum MVKConfigTraceVulkanCalls : int
+    {
+        None = 0,
+        Enter = 1,
+        EnterExit = 2,
+        Duration = 3
+    }
+
+    enum MVKConfigAutoGPUCaptureScope : int
+    {
+        None = 0,
+        Device = 1,
+        Frame = 2
+    }
+
+    [Flags]
+    enum MVKConfigAdvertiseExtensions : int
+    {
+        All = 0x00000001,
+        MoltenVK = 0x00000002,
+        WSI = 0x00000004,
+        Portability = 0x00000008
+    }
+
+    enum MVKVkSemaphoreSupportStyle : int
+    {
+        MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0,
+        MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1,
+        MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2,
+        MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3,
+        MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF
+    }
+
+    readonly struct Bool32
+    {
+        uint Value { get; }
+
+        public Bool32(uint value)
+        {
+            Value = value;
+        }
+
+        public Bool32(bool value)
+        {
+            Value = value ? 1u : 0u;
+        }
+
+        public static implicit operator bool(Bool32 val) => val.Value == 1;
+        public static implicit operator Bool32(bool val) => new Bool32(val);
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct MVKConfiguration
+    {
+        public Bool32 DebugMode;
+        public Bool32 ShaderConversionFlipVertexY;
+        public Bool32 SynchronousQueueSubmits;
+        public Bool32 PrefillMetalCommandBuffers;
+        public uint MaxActiveMetalCommandBuffersPerQueue;
+        public Bool32 SupportLargeQueryPools;
+        public Bool32 PresentWithCommandBuffer;
+        public Bool32 SwapchainMagFilterUseNearest;
+        public ulong MetalCompileTimeout;
+        public Bool32 PerformanceTracking;
+        public uint PerformanceLoggingFrameCount;
+        public Bool32 DisplayWatermark;
+        public Bool32 SpecializedQueueFamilies;
+        public Bool32 SwitchSystemGPU;
+        public Bool32 FullImageViewSwizzle;
+        public uint DefaultGPUCaptureScopeQueueFamilyIndex;
+        public uint DefaultGPUCaptureScopeQueueIndex;
+        public Bool32 FastMathEnabled;
+        public MVKConfigLogLevel LogLevel;
+        public MVKConfigTraceVulkanCalls TraceVulkanCalls;
+        public Bool32 ForceLowPowerGPU;
+        public Bool32 SemaphoreUseMTLFence;
+        public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle;
+        public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope;
+        public IntPtr AutoGPUCaptureOutputFilepath;
+        public Bool32 Texture1DAs2D;
+        public Bool32 PreallocateDescriptors;
+        public Bool32 UseCommandPooling;
+        public Bool32 UseMTLHeap;
+        public Bool32 LogActivityPerformanceInline;
+        public uint ApiVersionToAdvertise;
+        public MVKConfigAdvertiseExtensions AdvertiseExtensions;
+        public Bool32 ResumeLostDevice;
+        public Bool32 UseMetalArgumentBuffers;
+    }
+}

+ 31 - 0
Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs

@@ -0,0 +1,31 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.Versioning;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan.MoltenVK
+{
+    [SupportedOSPlatform("macos")]
+    public static partial class MVKInitialization
+    {
+        [LibraryImport("libMoltenVK.dylib")]
+        private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
+
+        [LibraryImport("libMoltenVK.dylib")]
+        private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
+
+        public static void Initialize()
+        {
+            var configSize = (IntPtr)Marshal.SizeOf<MVKConfiguration>();
+
+            vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize);
+
+            config.UseMetalArgumentBuffers = true;
+
+            config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
+            config.SynchronousQueueSubmits = false;
+
+            vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
+        }
+    }
+}

+ 84 - 10
Ryujinx.Graphics.Vulkan/PipelineBase.cs

@@ -2,6 +2,7 @@
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
 using System;
 using System;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
@@ -50,6 +51,11 @@ namespace Ryujinx.Graphics.Vulkan
         private Auto<DisposableRenderPass> _renderPass;
         private Auto<DisposableRenderPass> _renderPass;
         private int _writtenAttachmentCount;
         private int _writtenAttachmentCount;
 
 
+        private bool _framebufferUsingColorWriteMask;
+
+        private ITexture[] _preMaskColors;
+        private ITexture _preMaskDepthStencil;
+
         private readonly DescriptorSetUpdater _descriptorSetUpdater;
         private readonly DescriptorSetUpdater _descriptorSetUpdater;
 
 
         private IndexBufferState _indexBuffer;
         private IndexBufferState _indexBuffer;
@@ -905,24 +911,37 @@ namespace Ryujinx.Graphics.Vulkan
                 }
                 }
             }
             }
 
 
-            SignalStateChange();
-
-            if (writtenAttachments != _writtenAttachmentCount)
+            if (_framebufferUsingColorWriteMask)
             {
             {
-                SignalAttachmentChange();
-                _writtenAttachmentCount = writtenAttachments;
+                SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
+            }
+            else
+            {
+                SignalStateChange();
+
+                if (writtenAttachments != _writtenAttachmentCount)
+                {
+                    SignalAttachmentChange();
+                    _writtenAttachmentCount = writtenAttachments;
+                }
             }
             }
         }
         }
 
 
-        public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+        private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
         {
         {
             FramebufferParams?.UpdateModifications();
             FramebufferParams?.UpdateModifications();
-            CreateFramebuffer(colors, depthStencil);
+            CreateFramebuffer(colors, depthStencil, filterWriteMasked);
             CreateRenderPass();
             CreateRenderPass();
             SignalStateChange();
             SignalStateChange();
             SignalAttachmentChange();
             SignalAttachmentChange();
         }
         }
 
 
+        public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+        {
+            _framebufferUsingColorWriteMask = false;
+            SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
+        }
+
         public void SetRenderTargetScale(float scale)
         public void SetRenderTargetScale(float scale)
         {
         {
             _renderScale[0].X = scale;
             _renderScale[0].X = scale;
@@ -1102,7 +1121,7 @@ namespace Ryujinx.Graphics.Vulkan
 
 
                         int vbSize = vertexBuffer.Buffer.Size;
                         int vbSize = vertexBuffer.Buffer.Size;
 
 
-                        if (Gd.Vendor == Vendor.Amd && vertexBuffer.Stride > 0)
+                        if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
                         {
                         {
                             // AMD has a bug where if offset + stride * count is greater than
                             // AMD has a bug where if offset + stride * count is greater than
                             // the size, then the last attribute will have the wrong value.
                             // the size, then the last attribute will have the wrong value.
@@ -1119,7 +1138,8 @@ namespace Ryujinx.Graphics.Vulkan
 
 
                         buffer.Dispose();
                         buffer.Dispose();
 
 
-                        if ((vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
+                        if (!Gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B) &&
+                            (vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
                         {
                         {
                             buffer = new VertexBufferState(
                             buffer = new VertexBufferState(
                                 vb,
                                 vb,
@@ -1259,8 +1279,62 @@ namespace Ryujinx.Graphics.Vulkan
             _currentPipelineHandle = 0;
             _currentPipelineHandle = 0;
         }
         }
 
 
-        private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil)
+        private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
         {
         {
+            if (filterWriteMasked)
+            {
+                // TBDR GPUs don't work properly if the same attachment is bound to multiple targets,
+                // due to each attachment being a copy of the real attachment, rather than a direct write.
+
+                // Just try to remove duplicate attachments.
+                // Save a copy of the array to rebind when mask changes.
+
+                void maskOut()
+                {
+                    if (!_framebufferUsingColorWriteMask)
+                    {
+                        _preMaskColors = colors.ToArray();
+                        _preMaskDepthStencil = depthStencil;
+                    }
+
+                    // If true, then the framebuffer must be recreated when the mask changes.
+                    _framebufferUsingColorWriteMask = true;
+                }
+
+                // Look for textures that are masked out.
+
+                for (int i = 0; i < colors.Length; i++)
+                {
+                    if (colors[i] == null)
+                    {
+                        continue;
+                    }
+
+                    ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
+
+                    for (int j = 0; j < i; j++)
+                    {
+                        // Check each binding for a duplicate binding before it.
+
+                        if (colors[i] == colors[j])
+                        {
+                            // Prefer the binding with no write mask.
+                            ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j];
+                            if (vkBlend.ColorWriteMask == 0)
+                            {
+                                colors[i] = null;
+                                maskOut();
+                            }
+                            else if (vkBlend2.ColorWriteMask == 0)
+                            {
+                                colors[j] = null;
+                                maskOut();
+                            }
+                        }
+                    }
+                }
+            }
+
             FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
             FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
             UpdatePipelineAttachmentFormats();
             UpdatePipelineAttachmentFormats();
         }
         }

+ 1 - 0
Ryujinx.Graphics.Vulkan/PipelineFull.cs

@@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
                     (int)FramebufferParams.Width,
                     (int)FramebufferParams.Width,
                     (int)FramebufferParams.Height,
                     (int)FramebufferParams.Height,
                     FramebufferParams.AttachmentFormats[index],
                     FramebufferParams.AttachmentFormats[index],
+                    FramebufferParams.GetAttachmentComponentType(index),
                     ClearScissor);
                     ClearScissor);
             }
             }
             else
             else

+ 0 - 0
Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag → Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag


+ 9 - 0
Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag

@@ -0,0 +1,9 @@
+#version 450 core
+
+layout (location = 0) in vec4 clear_colour;
+layout (location = 0) out ivec4 colour;
+
+void main()
+{
+    colour = floatBitsToInt(clear_colour);
+}

+ 9 - 0
Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag

@@ -0,0 +1,9 @@
+#version 450 core
+
+layout (location = 0) in vec4 clear_colour;
+layout (location = 0) out uvec4 colour;
+
+void main()
+{
+    colour = floatBitsToUint(clear_colour);
+}

+ 63 - 1
Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs

@@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
             0x3C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
             0x3C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
         };
         };
 
 
-        public static readonly byte[] ColorClearFragmentShaderSource = new byte[]
+        public static readonly byte[] ColorClearFFragmentShaderSource = new byte[]
         {
         {
             0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00,
             0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
@@ -459,6 +459,68 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
             0x0C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
             0x0C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
         };
         };
 
 
+        public static readonly byte[] ColorClearSIFragmentShaderSource = new byte[]
+        {
+            0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+            0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+            0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
+            0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
+            0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+            0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+            0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+        };
+
+        public static readonly byte[] ColorClearUIFragmentShaderSource = new byte[]
+        {
+            0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+            0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+            0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
+            0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
+            0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+            0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+            0x0D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0x16, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+        };
+
         public static readonly byte[] ColorClearVertexShaderSource = new byte[]
         public static readonly byte[] ColorClearVertexShaderSource = new byte[]
         {
         {
             0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x36, 0x00, 0x00, 0x00,
             0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x36, 0x00, 0x00, 0x00,

+ 1 - 1
Ryujinx.Graphics.Vulkan/TextureStorage.cs

@@ -106,7 +106,7 @@ namespace Ryujinx.Graphics.Vulkan
                 flags |= ImageCreateFlags.CreateCubeCompatibleBit;
                 flags |= ImageCreateFlags.CreateCubeCompatibleBit;
             }
             }
 
 
-            if (type == ImageType.Type3D)
+            if (type == ImageType.Type3D && !gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
             {
             {
                 flags |= ImageCreateFlags.Create2DArrayCompatibleBit;
                 flags |= ImageCreateFlags.Create2DArrayCompatibleBit;
             }
             }

+ 24 - 5
Ryujinx.Graphics.Vulkan/TextureView.cs

@@ -94,8 +94,14 @@ namespace Ryujinx.Graphics.Vulkan
             var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
             var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
             var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
             var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
 
 
-            unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType)
+            unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags = 0)
             {
             {
+                var usage = new ImageViewUsageCreateInfo()
+                {
+                    SType = StructureType.ImageViewUsageCreateInfo,
+                    Usage = usageFlags
+                };
+
                 var imageCreateInfo = new ImageViewCreateInfo()
                 var imageCreateInfo = new ImageViewCreateInfo()
                 {
                 {
                     SType = StructureType.ImageViewCreateInfo,
                     SType = StructureType.ImageViewCreateInfo,
@@ -103,7 +109,8 @@ namespace Ryujinx.Graphics.Vulkan
                     ViewType = viewType,
                     ViewType = viewType,
                     Format = format,
                     Format = format,
                     Components = cm,
                     Components = cm,
-                    SubresourceRange = sr
+                    SubresourceRange = sr,
+                    PNext = usageFlags == 0 ? null : &usage
                 };
                 };
 
 
                 gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
                 gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@@ -124,9 +131,21 @@ namespace Ryujinx.Graphics.Vulkan
             // Framebuffer attachments also require 3D textures to be bound as 2D array.
             // Framebuffer attachments also require 3D textures to be bound as 2D array.
             if (info.Target == Target.Texture3D)
             if (info.Target == Target.Texture3D)
             {
             {
-                subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+                if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
+                {
+                    if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil()))
+                    {
+                        subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1);
 
 
-                _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
+                        _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2D, ImageUsageFlags.ColorAttachmentBit);
+                    }
+                }
+                else
+                {
+                    subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+
+                    _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
+                }
             }
             }
 
 
             Valid = true;
             Valid = true;
@@ -353,7 +372,7 @@ namespace Ryujinx.Graphics.Vulkan
             }
             }
 
 
             if (VulkanConfiguration.UseSlowSafeBlitOnAmd &&
             if (VulkanConfiguration.UseSlowSafeBlitOnAmd &&
-                _gd.Vendor == Vendor.Amd &&
+                (_gd.Vendor == Vendor.Amd || _gd.IsMoltenVk) &&
                 src.Info.Target == Target.Texture2D &&
                 src.Info.Target == Target.Texture2D &&
                 dst.Info.Target == Target.Texture2D &&
                 dst.Info.Target == Target.Texture2D &&
                 !dst.Info.Format.IsDepthOrStencil())
                 !dst.Info.Format.IsDepthOrStencil())

+ 7 - 0
Ryujinx.Graphics.Vulkan/Vendor.cs

@@ -5,9 +5,12 @@ namespace Ryujinx.Graphics.Vulkan
     enum Vendor
     enum Vendor
     {
     {
         Amd,
         Amd,
+        ImgTec,
         Intel,
         Intel,
         Nvidia,
         Nvidia,
+        ARM,
         Qualcomm,
         Qualcomm,
+        Apple,
         Unknown
         Unknown
     }
     }
 
 
@@ -21,7 +24,10 @@ namespace Ryujinx.Graphics.Vulkan
             return id switch
             return id switch
             {
             {
                 0x1002 => Vendor.Amd,
                 0x1002 => Vendor.Amd,
+                0x1010 => Vendor.ImgTec,
+                0x106B => Vendor.Apple,
                 0x10DE => Vendor.Nvidia,
                 0x10DE => Vendor.Nvidia,
+                0x13B5 => Vendor.ARM,
                 0x8086 => Vendor.Intel,
                 0x8086 => Vendor.Intel,
                 0x5143 => Vendor.Qualcomm,
                 0x5143 => Vendor.Qualcomm,
                 _ => Vendor.Unknown
                 _ => Vendor.Unknown
@@ -34,6 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
             {
             {
                 0x1002 => "AMD",
                 0x1002 => "AMD",
                 0x1010 => "ImgTec",
                 0x1010 => "ImgTec",
+                0x106B => "Apple",
                 0x10DE => "NVIDIA",
                 0x10DE => "NVIDIA",
                 0x13B5 => "ARM",
                 0x13B5 => "ARM",
                 0x1AE0 => "Google",
                 0x1AE0 => "Google",

+ 2 - 2
Ryujinx.Graphics.Vulkan/VertexBufferState.cs

@@ -82,9 +82,9 @@ namespace Ryujinx.Graphics.Vulkan
                         }
                         }
 
 
                         _buffer = autoBuffer;
                         _buffer = autoBuffer;
-                    }
 
 
-                    state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
+                        state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
+                    }
 
 
                     return;
                     return;
                 }
                 }

+ 21 - 6
Ryujinx.Graphics.Vulkan/VulkanInitialization.cs

@@ -389,6 +389,18 @@ namespace Ryujinx.Graphics.Vulkan
                 features2.PNext = &featuresCustomBorderColorSupported;
                 features2.PNext = &featuresCustomBorderColorSupported;
             }
             }
 
 
+            PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+            {
+                SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
+            };
+
+            if (supportedExtensions.Contains("VK_EXT_robustness2"))
+            {
+                supportedFeaturesRobustness2.PNext = features2.PNext;
+
+                features2.PNext = &supportedFeaturesRobustness2;
+            }
+
             api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
             api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
 
 
             var supportedFeatures = features2.Features;
             var supportedFeatures = features2.Features;
@@ -428,14 +440,17 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             pExtendedFeatures = &featuresTransformFeedback;
             pExtendedFeatures = &featuresTransformFeedback;
 
 
-            var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+            if (supportedExtensions.Contains("VK_EXT_robustness2"))
             {
             {
-                SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
-                PNext = pExtendedFeatures,
-                NullDescriptor = true
-            };
+                var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+                {
+                    SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
+                    PNext = pExtendedFeatures,
+                    NullDescriptor = supportedFeaturesRobustness2.NullDescriptor
+                };
 
 
-            pExtendedFeatures = &featuresRobustness2;
+                pExtendedFeatures = &featuresRobustness2;
+            }
 
 
             var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
             var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
             {
             {

+ 90 - 6
Ryujinx.Graphics.Vulkan/VulkanRenderer.cs

@@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader.Translation;
 using Ryujinx.Graphics.Shader.Translation;
+using Ryujinx.Graphics.Vulkan.MoltenVK;
 using Ryujinx.Graphics.Vulkan.Queries;
 using Ryujinx.Graphics.Vulkan.Queries;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan.Extensions.EXT;
 using Silk.NET.Vulkan.Extensions.EXT;
@@ -77,6 +78,8 @@ namespace Ryujinx.Graphics.Vulkan
         internal bool IsAmdWindows { get; private set; }
         internal bool IsAmdWindows { get; private set; }
         internal bool IsIntelWindows { get; private set; }
         internal bool IsIntelWindows { get; private set; }
         internal bool IsAmdGcn { get; private set; }
         internal bool IsAmdGcn { get; private set; }
+        internal bool IsMoltenVk { get; private set; }
+        internal bool IsTBDR { get; private set; }
         public string GpuVendor { get; private set; }
         public string GpuVendor { get; private set; }
         public string GpuRenderer { get; private set; }
         public string GpuRenderer { get; private set; }
         public string GpuVersion { get; private set; }
         public string GpuVersion { get; private set; }
@@ -93,6 +96,14 @@ namespace Ryujinx.Graphics.Vulkan
             Shaders = new HashSet<ShaderCollection>();
             Shaders = new HashSet<ShaderCollection>();
             Textures = new HashSet<ITexture>();
             Textures = new HashSet<ITexture>();
             Samplers = new HashSet<SamplerHolder>();
             Samplers = new HashSet<SamplerHolder>();
+
+            if (OperatingSystem.IsMacOS())
+            {
+                MVKInitialization.Initialize();
+
+                // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
+                IsMoltenVk = true;
+            }
         }
         }
 
 
         private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
         private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
@@ -161,7 +172,10 @@ namespace Ryujinx.Graphics.Vulkan
                 properties2.PNext = &propertiesTransformFeedback;
                 properties2.PNext = &propertiesTransformFeedback;
             }
             }
 
 
-            Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
+            PhysicalDevicePortabilitySubsetPropertiesKHR propertiesPortabilitySubset = new PhysicalDevicePortabilitySubsetPropertiesKHR()
+            {
+                SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr
+            };
 
 
             PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
             PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
             {
             {
@@ -183,6 +197,11 @@ namespace Ryujinx.Graphics.Vulkan
                 SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
                 SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
             };
             };
 
 
+            PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
+            {
+                SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
+            };
+
             if (supportedExtensions.Contains("VK_EXT_robustness2"))
             if (supportedExtensions.Contains("VK_EXT_robustness2"))
             {
             {
                 features2.PNext = &featuresRobustness2;
                 features2.PNext = &featuresRobustness2;
@@ -200,8 +219,31 @@ namespace Ryujinx.Graphics.Vulkan
                 features2.PNext = &featuresCustomBorderColor;
                 features2.PNext = &featuresCustomBorderColor;
             }
             }
 
 
+            bool usePortability = supportedExtensions.Contains("VK_KHR_portability_subset");
+
+            if (usePortability)
+            {
+                propertiesPortabilitySubset.PNext = properties2.PNext;
+                properties2.PNext = &propertiesPortabilitySubset;
+
+                featuresPortabilitySubset.PNext = features2.PNext;
+                features2.PNext = &featuresPortabilitySubset;
+            }
+
+            Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
             Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
             Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
 
 
+            var portabilityFlags = PortabilitySubsetFlags.None;
+
+            if (usePortability)
+            {
+                portabilityFlags |= propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment > 1 ? PortabilitySubsetFlags.VertexBufferAlignment4B : 0;
+                portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans;
+                portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode;
+                portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView;
+                portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
+            }
+
             bool customBorderColorSupported = supportedExtensions.Contains("VK_EXT_custom_border_color") &&
             bool customBorderColorSupported = supportedExtensions.Contains("VK_EXT_custom_border_color") &&
                                               featuresCustomBorderColor.CustomBorderColors &&
                                               featuresCustomBorderColor.CustomBorderColors &&
                                               featuresCustomBorderColor.CustomBorderColorWithoutFormat;
                                               featuresCustomBorderColor.CustomBorderColorWithoutFormat;
@@ -224,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
                 supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
                 supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
                 supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
                 supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
                 features2.Features.MultiViewport,
                 features2.Features.MultiViewport,
-                featuresRobustness2.NullDescriptor,
+                featuresRobustness2.NullDescriptor || IsMoltenVk,
                 supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
                 supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
                 supportsTransformFeedback,
                 supportsTransformFeedback,
                 propertiesTransformFeedback.TransformFeedbackQueries,
                 propertiesTransformFeedback.TransformFeedbackQueries,
@@ -232,7 +274,8 @@ namespace Ryujinx.Graphics.Vulkan
                 propertiesSubgroupSizeControl.MinSubgroupSize,
                 propertiesSubgroupSizeControl.MinSubgroupSize,
                 propertiesSubgroupSizeControl.MaxSubgroupSize,
                 propertiesSubgroupSizeControl.MaxSubgroupSize,
                 propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
                 propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
-                supportedSampleCounts);
+                supportedSampleCounts,
+                portabilityFlags);
 
 
             MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
             MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
 
 
@@ -413,6 +456,36 @@ namespace Ryujinx.Graphics.Vulkan
             bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
             bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
                 GAL.Format.R4G4B4A4Unorm);
                 GAL.Format.R4G4B4A4Unorm);
 
 
+            bool supportsAstcFormats = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
+                GAL.Format.Astc4x4Unorm,
+                GAL.Format.Astc5x4Unorm,
+                GAL.Format.Astc5x5Unorm,
+                GAL.Format.Astc6x5Unorm,
+                GAL.Format.Astc6x6Unorm,
+                GAL.Format.Astc8x5Unorm,
+                GAL.Format.Astc8x6Unorm,
+                GAL.Format.Astc8x8Unorm,
+                GAL.Format.Astc10x5Unorm,
+                GAL.Format.Astc10x6Unorm,
+                GAL.Format.Astc10x8Unorm,
+                GAL.Format.Astc10x10Unorm,
+                GAL.Format.Astc12x10Unorm,
+                GAL.Format.Astc12x12Unorm,
+                GAL.Format.Astc4x4Srgb,
+                GAL.Format.Astc5x4Srgb,
+                GAL.Format.Astc5x5Srgb,
+                GAL.Format.Astc6x5Srgb,
+                GAL.Format.Astc6x6Srgb,
+                GAL.Format.Astc8x5Srgb,
+                GAL.Format.Astc8x6Srgb,
+                GAL.Format.Astc8x8Srgb,
+                GAL.Format.Astc10x5Srgb,
+                GAL.Format.Astc10x6Srgb,
+                GAL.Format.Astc10x8Srgb,
+                GAL.Format.Astc10x10Srgb,
+                GAL.Format.Astc12x10Srgb,
+                GAL.Format.Astc12x12Srgb);
+
             PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
             PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
             {
             {
                 SType = StructureType.PhysicalDeviceVulkan12Features
                 SType = StructureType.PhysicalDeviceVulkan12Features
@@ -434,7 +507,9 @@ namespace Ryujinx.Graphics.Vulkan
                 GpuVendor,
                 GpuVendor,
                 hasFrontFacingBug: IsIntelWindows,
                 hasFrontFacingBug: IsIntelWindows,
                 hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
                 hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
-                supportsAstcCompression: features2.Features.TextureCompressionAstcLdr,
+                needsFragmentOutputSpecialization: IsMoltenVk,
+                reduceShaderPrecision: IsMoltenVk,
+                supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
                 supportsBc123Compression: supportsBc123CompressionFormat,
                 supportsBc123Compression: supportsBc123CompressionFormat,
                 supportsBc45Compression: supportsBc45CompressionFormat,
                 supportsBc45Compression: supportsBc45CompressionFormat,
                 supportsBc67Compression: supportsBc67CompressionFormat,
                 supportsBc67Compression: supportsBc67CompressionFormat,
@@ -515,12 +590,13 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
             IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
             IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
             IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+            IsTBDR = IsMoltenVk || Vendor == Vendor.Qualcomm || Vendor == Vendor.ARM || Vendor == Vendor.ImgTec;
 
 
             GpuVendor = vendorName;
             GpuVendor = vendorName;
             GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);
             GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);
             GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
             GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
 
 
-            IsAmdGcn = Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
+            IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
 
 
             Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
             Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
         }
         }
@@ -531,6 +607,7 @@ namespace Ryujinx.Graphics.Vulkan
             {
             {
                 GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles,
                 GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles,
                 GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip,
                 GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip,
+                GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) ? GAL.PrimitiveTopology.Triangles : topology,
                 _ => topology
                 _ => topology
             };
             };
         }
         }
@@ -540,6 +617,7 @@ namespace Ryujinx.Graphics.Vulkan
             return topology switch
             return topology switch
             {
             {
                 GAL.PrimitiveTopology.Quads => true,
                 GAL.PrimitiveTopology.Quads => true,
+                GAL.PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans),
                 _ => false
                 _ => false
             };
             };
         }
         }
@@ -553,7 +631,13 @@ namespace Ryujinx.Graphics.Vulkan
 
 
         public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
         public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
         {
         {
-            if (Vendor != Vendor.Nvidia)
+            if (Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B))
+            {
+                alignment = 4;
+
+                return true;
+            }
+            else if (Vendor != Vendor.Nvidia)
             {
             {
                 // Vulkan requires that vertex attributes are globally aligned by their component size,
                 // Vulkan requires that vertex attributes are globally aligned by their component size,
                 // so buffer strides that don't divide by the largest scalar element are invalid.
                 // so buffer strides that don't divide by the largest scalar element are invalid.