Sfoglia il codice sorgente

Replace ShaderBindings with new ResourceLayout structure for Vulkan (#5025)

* Introduce ResourceLayout

* Part 1: Use new ResourceSegments array on UpdateAndBind

* Part 2: Use ResourceLayout to build PipelineLayout

* Delete old code

* XML docs

* Fix shader cache load NRE

* Fix typo
gdkchan 2 anni fa
parent
commit
5626f2ca1c
24 ha cambiato i file con 1044 aggiunte e 674 eliminazioni
  1. 179 0
      src/Ryujinx.Graphics.GAL/ResourceLayout.cs
  2. 0 24
      src/Ryujinx.Graphics.GAL/ShaderBindings.cs
  3. 5 2
      src/Ryujinx.Graphics.GAL/ShaderInfo.cs
  4. 3 5
      src/Ryujinx.Graphics.GAL/ShaderSource.cs
  5. 3 6
      src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
  6. 3 10
      src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
  7. 1 16
      src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs
  8. 3 3
      src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
  9. 9 25
      src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
  10. 260 0
      src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
  11. 96 119
      src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
  12. 15 17
      src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
  13. 6 7
      src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
  14. 23 23
      src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
  15. 51 1
      src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
  16. 87 111
      src/Ryujinx.Graphics.Vulkan/HelperShader.cs
  17. 73 24
      src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs
  18. 12 14
      src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
  19. 44 227
      src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
  20. 22 0
      src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs
  21. 67 0
      src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs
  22. 0 3
      src/Ryujinx.Graphics.Vulkan/Shader.cs
  23. 76 30
      src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
  24. 6 7
      src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs

+ 179 - 0
src/Ryujinx.Graphics.GAL/ResourceLayout.cs

@@ -0,0 +1,179 @@
+using System;
+using System.Collections.ObjectModel;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public enum ResourceType : byte
+    {
+        UniformBuffer,
+        StorageBuffer,
+        Texture,
+        Sampler,
+        TextureAndSampler,
+        Image,
+        BufferTexture,
+        BufferImage
+    }
+
+    public enum ResourceAccess : byte
+    {
+        None = 0,
+        Read = 1,
+        Write = 2,
+        ReadWrite = Read | Write
+    }
+
+    [Flags]
+    public enum ResourceStages : byte
+    {
+        None = 0,
+        Compute = 1 << 0,
+        Vertex = 1 << 1,
+        TessellationControl = 1 << 2,
+        TessellationEvaluation = 1 << 3,
+        Geometry = 1 << 4,
+        Fragment = 1 << 5
+    }
+
+    public readonly struct ResourceDescriptor : IEquatable<ResourceDescriptor>
+    {
+        public int Binding { get; }
+        public int Count { get; }
+        public ResourceType Type { get; }
+        public ResourceStages Stages { get; }
+
+        public ResourceDescriptor(int binding, int count, ResourceType type, ResourceStages stages)
+        {
+            Binding = binding;
+            Count = count;
+            Type = type;
+            Stages = stages;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(Binding, Count, Type, Stages);
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is ResourceDescriptor other && Equals(other);
+        }
+
+        public bool Equals(ResourceDescriptor other)
+        {
+            return Binding == other.Binding && Count == other.Count && Type == other.Type && Stages == other.Stages;
+        }
+    }
+
+    public readonly struct ResourceUsage : IEquatable<ResourceUsage>
+    {
+        public int Binding { get; }
+        public ResourceType Type { get; }
+        public ResourceStages Stages { get; }
+        public ResourceAccess Access { get; }
+
+        public ResourceUsage(int binding, ResourceType type, ResourceStages stages, ResourceAccess access)
+        {
+            Binding = binding;
+            Type = type;
+            Stages = stages;
+            Access = access;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(Binding, Type, Stages, Access);
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is ResourceUsage other && Equals(other);
+        }
+
+        public bool Equals(ResourceUsage other)
+        {
+            return Binding == other.Binding && Type == other.Type && Stages == other.Stages && Access == other.Access;
+        }
+    }
+
+    public readonly struct ResourceDescriptorCollection
+    {
+        public ReadOnlyCollection<ResourceDescriptor> Descriptors { get; }
+
+        public ResourceDescriptorCollection(ReadOnlyCollection<ResourceDescriptor> descriptors)
+        {
+            Descriptors = descriptors;
+        }
+
+        public override int GetHashCode()
+        {
+            HashCode hasher = new HashCode();
+
+            if (Descriptors != null)
+            {
+                foreach (var descriptor in Descriptors)
+                {
+                    hasher.Add(descriptor);
+                }
+            }
+
+            return hasher.ToHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is ResourceDescriptorCollection other && Equals(other);
+        }
+
+        public bool Equals(ResourceDescriptorCollection other)
+        {
+            if ((Descriptors == null) != (other.Descriptors == null))
+            {
+                return false;
+            }
+
+            if (Descriptors != null)
+            {
+                if (Descriptors.Count != other.Descriptors.Count)
+                {
+                    return false;
+                }
+
+                for (int index = 0; index < Descriptors.Count; index++)
+                {
+                    if (!Descriptors[index].Equals(other.Descriptors[index]))
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+
+    public readonly struct ResourceUsageCollection
+    {
+        public ReadOnlyCollection<ResourceUsage> Usages { get; }
+
+        public ResourceUsageCollection(ReadOnlyCollection<ResourceUsage> usages)
+        {
+            Usages = usages;
+        }
+    }
+
+    public readonly struct ResourceLayout
+    {
+        public ReadOnlyCollection<ResourceDescriptorCollection> Sets { get; }
+        public ReadOnlyCollection<ResourceUsageCollection> SetUsages { get; }
+
+        public ResourceLayout(
+            ReadOnlyCollection<ResourceDescriptorCollection> sets,
+            ReadOnlyCollection<ResourceUsageCollection> setUsages)
+        {
+            Sets = sets;
+            SetUsages = setUsages;
+        }
+    }
+}

+ 0 - 24
src/Ryujinx.Graphics.GAL/ShaderBindings.cs

@@ -1,24 +0,0 @@
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.GAL
-{
-    public readonly struct ShaderBindings
-    {
-        public IReadOnlyCollection<int> UniformBufferBindings { get; }
-        public IReadOnlyCollection<int> StorageBufferBindings { get; }
-        public IReadOnlyCollection<int> TextureBindings { get; }
-        public IReadOnlyCollection<int> ImageBindings { get; }
-
-        public ShaderBindings(
-            IReadOnlyCollection<int> uniformBufferBindings,
-            IReadOnlyCollection<int> storageBufferBindings,
-            IReadOnlyCollection<int> textureBindings,
-            IReadOnlyCollection<int> imageBindings)
-        {
-            UniformBufferBindings = uniformBufferBindings;
-            StorageBufferBindings = storageBufferBindings;
-            TextureBindings = textureBindings;
-            ImageBindings = imageBindings;
-        }
-    }
-}

+ 5 - 2
src/Ryujinx.Graphics.GAL/ShaderInfo.cs

@@ -3,19 +3,22 @@ namespace Ryujinx.Graphics.GAL
     public struct ShaderInfo
     public struct ShaderInfo
     {
     {
         public int FragmentOutputMap { get; }
         public int FragmentOutputMap { get; }
+        public ResourceLayout ResourceLayout { get; }
         public ProgramPipelineState? State { get; }
         public ProgramPipelineState? State { get; }
         public bool FromCache { get; set; }
         public bool FromCache { get; set; }
 
 
-        public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state, bool fromCache = false)
+        public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
         {
         {
             FragmentOutputMap = fragmentOutputMap;
             FragmentOutputMap = fragmentOutputMap;
+            ResourceLayout = resourceLayout;
             State = state;
             State = state;
             FromCache = fromCache;
             FromCache = fromCache;
         }
         }
 
 
-        public ShaderInfo(int fragmentOutputMap, bool fromCache = false)
+        public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
         {
         {
             FragmentOutputMap = fragmentOutputMap;
             FragmentOutputMap = fragmentOutputMap;
+            ResourceLayout = resourceLayout;
             State = null;
             State = null;
             FromCache = fromCache;
             FromCache = fromCache;
         }
         }

+ 3 - 5
src/Ryujinx.Graphics.GAL/ShaderSource.cs

@@ -7,24 +7,22 @@ namespace Ryujinx.Graphics.GAL
     {
     {
         public string Code { get; }
         public string Code { get; }
         public byte[] BinaryCode { get; }
         public byte[] BinaryCode { get; }
-        public ShaderBindings Bindings { get; }
         public ShaderStage Stage { get; }
         public ShaderStage Stage { get; }
         public TargetLanguage Language { get; }
         public TargetLanguage Language { get; }
 
 
-        public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
+        public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
         {
         {
             Code = code;
             Code = code;
             BinaryCode = binaryCode;
             BinaryCode = binaryCode;
-            Bindings = bindings;
             Stage = stage;
             Stage = stage;
             Language = language;
             Language = language;
         }
         }
 
 
-        public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
+        public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
         {
         {
         }
         }
 
 
-        public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
+        public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
         {
         {
         }
         }
     }
     }

+ 3 - 6
src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs

@@ -368,12 +368,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
 
 
                         if (hostCode != null)
                         if (hostCode != null)
                         {
                         {
-                            bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
-                            int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
-
-                            ShaderInfo shaderInfo = specState.PipelineState.HasValue
-                                ? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
-                                : new ShaderInfo(fragmentOutputMap, fromCache: true);
+                            ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(context, shaders, specState.PipelineState);
 
 
                             IProgram hostProgram;
                             IProgram hostProgram;
 
 
@@ -385,6 +380,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
                             }
                             }
                             else
                             else
                             {
                             {
+                                bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
+
                                 hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
                                 hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
                             }
                             }
 
 

+ 3 - 10
src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs

@@ -491,23 +491,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
             {
             {
                 ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
                 ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
 
 
-                int fragmentOutputMap = -1;
+                ShaderInfoBuilder shaderInfoBuilder = new ShaderInfoBuilder(_context);
 
 
                 for (int index = 0; index < compilation.TranslatedStages.Length; index++)
                 for (int index = 0; index < compilation.TranslatedStages.Length; index++)
                 {
                 {
                     ShaderProgram shader = compilation.TranslatedStages[index];
                     ShaderProgram shader = compilation.TranslatedStages[index];
                     shaderSources[index] = CreateShaderSource(shader);
                     shaderSources[index] = CreateShaderSource(shader);
-
-                    if (shader.Info.Stage == ShaderStage.Fragment)
-                    {
-                        fragmentOutputMap = shader.Info.FragmentOutputMap;
-                    }
+                    shaderInfoBuilder.AddStageInfo(shader.Info);
                 }
                 }
 
 
-                ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
-                    ? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
-                    : new ShaderInfo(fragmentOutputMap, fromCache: true);
-
+                ShaderInfo shaderInfo = shaderInfoBuilder.Build(compilation.SpecializationState.PipelineState, fromCache: true);
                 IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
                 IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
                 CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
                 CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
 
 

+ 1 - 16
src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs

@@ -42,25 +42,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
                 int binaryCodeLength = reader.ReadInt32();
                 int binaryCodeLength = reader.ReadInt32();
                 byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
                 byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
 
 
-                output.Add(new ShaderSource(binaryCode, GetBindings(stages, stage), stage, TargetLanguage.Spirv));
+                output.Add(new ShaderSource(binaryCode, stage, TargetLanguage.Spirv));
             }
             }
 
 
             return output.ToArray();
             return output.ToArray();
         }
         }
-
-        private static ShaderBindings GetBindings(CachedShaderStage[] stages, ShaderStage stage)
-        {
-            for (int i = 0; i < stages.Length; i++)
-            {
-                CachedShaderStage currentStage = stages[i];
-
-                if (currentStage?.Info != null && currentStage.Info.Stage == stage)
-                {
-                    return ShaderCache.GetBindings(currentStage.Info);
-                }
-            }
-
-            return new ShaderBindings(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
-        }
     }
     }
 }
 }

+ 3 - 3
src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs

@@ -110,16 +110,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
                 Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
             }
             }
 
 
-            return GetStageIndex() * (int)maxPerStage + index;
+            return GetStageIndex(_stageIndex) * (int)maxPerStage + index;
         }
         }
 
 
-        private int GetStageIndex()
+        public static int GetStageIndex(int stageIndex)
         {
         {
             // This is just a simple remapping to ensure that most frequently used shader stages
             // This is just a simple remapping to ensure that most frequently used shader stages
             // have the lowest binding numbers.
             // have the lowest binding numbers.
             // This is useful because if we need to run on a system with a low limit on the bindings,
             // This is useful because if we need to run on a system with a low limit on the bindings,
             // then we can still get most games working as the most common shaders will have low binding numbers.
             // then we can still get most games working as the most common shaders will have low binding numbers.
-            return _stageIndex switch
+            return stageIndex switch
             {
             {
                 4 => 1, // Fragment
                 4 => 1, // Fragment
                 3 => 2, // Geometry
                 3 => 2, // Geometry

+ 9 - 25
src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs

@@ -219,12 +219,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
             GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
             GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
 
 
             TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
             TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
-
             TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
             TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
 
 
             ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
             ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
-
-            IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
+            ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
+            IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
 
 
             cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
             cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
 
 
@@ -363,6 +362,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
 
             TranslatorContext previousStage = null;
             TranslatorContext previousStage = null;
 
 
+            ShaderInfoBuilder infoBuilder = new ShaderInfoBuilder(_context);
+
             for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
             for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
             {
             {
                 TranslatorContext currentStage = translatorContexts[stageIndex + 1];
                 TranslatorContext currentStage = translatorContexts[stageIndex + 1];
@@ -398,6 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     if (program != null)
                     if (program != null)
                     {
                     {
                         shaderSources.Add(CreateShaderSource(program));
                         shaderSources.Add(CreateShaderSource(program));
+                        infoBuilder.AddStageInfo(program.Info);
                     }
                     }
 
 
                     previousStage = currentStage;
                     previousStage = currentStage;
@@ -414,8 +416,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
 
             ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
             ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
 
 
-            int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
-            IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
+            ShaderInfo info = infoBuilder.Build(pipeline);
+
+            IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
 
 
             gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
             gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
 
 
@@ -466,7 +469,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <returns>Shader source</returns>
         /// <returns>Shader source</returns>
         public static ShaderSource CreateShaderSource(ShaderProgram program)
         public static ShaderSource CreateShaderSource(ShaderProgram program)
         {
         {
-            return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
+            return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -717,25 +720,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
             };
             };
         }
         }
 
 
-        /// <summary>
-        /// Gets information about the bindings used by a shader program.
-        /// </summary>
-        /// <param name="info">Shader program information to get the information from</param>
-        /// <returns>Shader bindings</returns>
-        public static ShaderBindings GetBindings(ShaderProgramInfo info)
-        {
-            var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
-            var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
-            var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
-            var imageBindings = info.Images.Select(x => x.Binding).ToArray();
-
-            return new ShaderBindings(
-                uniformBufferBindings,
-                storageBufferBindings,
-                textureBindings,
-                imageBindings);
-        }
-
         /// <summary>
         /// <summary>
         /// Creates shader translation options with the requested graphics API and flags.
         /// Creates shader translation options with the requested graphics API and flags.
         /// The shader language is choosen based on the current configuration and graphics API.
         /// The shader language is choosen based on the current configuration and graphics API.

+ 260 - 0
src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs

@@ -0,0 +1,260 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+    /// <summary>
+    /// Shader info structure builder.
+    /// </summary>
+    class ShaderInfoBuilder
+    {
+        private const int TotalSets = 4;
+
+        private const int UniformSetIndex = 0;
+        private const int StorageSetIndex = 1;
+        private const int TextureSetIndex = 2;
+        private const int ImageSetIndex = 3;
+
+        private const ResourceStages SupportBufferStags =
+            ResourceStages.Compute |
+            ResourceStages.Vertex |
+            ResourceStages.Fragment;
+
+        private readonly GpuContext _context;
+
+        private int _fragmentOutputMap;
+
+        private readonly List<ResourceDescriptor>[] _resourceDescriptors;
+        private readonly List<ResourceUsage>[] _resourceUsages;
+
+        /// <summary>
+        /// Creates a new shader info builder.
+        /// </summary>
+        /// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
+        public ShaderInfoBuilder(GpuContext context)
+        {
+            _context = context;
+
+            _fragmentOutputMap = -1;
+
+            _resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
+            _resourceUsages = new List<ResourceUsage>[TotalSets];
+
+            for (int index = 0; index < TotalSets; index++)
+            {
+                _resourceDescriptors[index] = new();
+                _resourceUsages[index] = new();
+            }
+
+            AddDescriptor(SupportBufferStags, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
+        }
+
+        /// <summary>
+        /// Adds information from a given shader stage.
+        /// </summary>
+        /// <param name="info">Shader stage information</param>
+        public void AddStageInfo(ShaderProgramInfo info)
+        {
+            if (info.Stage == ShaderStage.Fragment)
+            {
+                _fragmentOutputMap = info.FragmentOutputMap;
+            }
+
+            int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch
+            {
+                ShaderStage.TessellationControl => 1,
+                ShaderStage.TessellationEvaluation => 2,
+                ShaderStage.Geometry => 3,
+                ShaderStage.Fragment => 4,
+                _ => 0
+            });
+
+            ResourceStages stages = info.Stage switch
+            {
+                ShaderStage.Compute => ResourceStages.Compute,
+                ShaderStage.Vertex => ResourceStages.Vertex,
+                ShaderStage.TessellationControl => ResourceStages.TessellationControl,
+                ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
+                ShaderStage.Geometry => ResourceStages.Geometry,
+                ShaderStage.Fragment => ResourceStages.Fragment,
+                _ => ResourceStages.None
+            };
+
+            int uniformsPerStage = (int)_context.Capabilities.MaximumUniformBuffersPerStage;
+            int storagesPerStage = (int)_context.Capabilities.MaximumStorageBuffersPerStage;
+            int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage;
+            int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage;
+
+            int uniformBinding = 1 + stageIndex * uniformsPerStage;
+            int storageBinding = stageIndex * storagesPerStage;
+            int textureBinding = stageIndex * texturesPerStage * 2;
+            int imageBinding = stageIndex * imagesPerStage * 2;
+
+            AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
+            AddArrayDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
+            AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
+            AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
+
+            AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
+            AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
+            AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
+            AddUsage(info.Images, stages, ImageSetIndex, isImage: true);
+        }
+
+        /// <summary>
+        /// Adds a resource descriptor to the list of descriptors.
+        /// </summary>
+        /// <param name="stages">Shader stages where the resource is used</param>
+        /// <param name="type">Type of the resource</param>
+        /// <param name="setIndex">Descriptor set number where the resource will be bound</param>
+        /// <param name="binding">Binding number where the resource will be bound</param>
+        /// <param name="count">Number of resources bound at the binding location</param>
+        private void AddDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
+        {
+            for (int index = 0; index < count; index++)
+            {
+                _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding + index, 1, type, stages));
+            }
+        }
+
+        /// <summary>
+        /// Adds two interleaved groups of resources to the list of descriptors.
+        /// </summary>
+        /// <param name="stages">Shader stages where the resource is used</param>
+        /// <param name="type">Type of the first interleaved resource</param>
+        /// <param name="type2">Type of the second interleaved resource</param>
+        /// <param name="setIndex">Descriptor set number where the resource will be bound</param>
+        /// <param name="binding">Binding number where the resource will be bound</param>
+        /// <param name="count">Number of resources bound at the binding location</param>
+        private void AddDualDescriptor(ResourceStages stages, ResourceType type, ResourceType type2, int setIndex, int binding, int count)
+        {
+            AddDescriptor(stages, type, setIndex, binding, count);
+            AddDescriptor(stages, type2, setIndex, binding + count, count);
+        }
+
+        /// <summary>
+        /// Adds an array resource to the list of descriptors.
+        /// </summary>
+        /// <param name="stages">Shader stages where the resource is used</param>
+        /// <param name="type">Type of the resource</param>
+        /// <param name="setIndex">Descriptor set number where the resource will be bound</param>
+        /// <param name="binding">Binding number where the resource will be bound</param>
+        /// <param name="count">Number of resources bound at the binding location</param>
+        private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
+        {
+            _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages));
+        }
+
+        /// <summary>
+        /// Adds buffer usage information to the list of usages.
+        /// </summary>
+        /// <param name="buffers">Buffers to be added</param>
+        /// <param name="stages">Stages where the buffers are used</param>
+        /// <param name="setIndex">Descriptor set index where the buffers will be bound</param>
+        /// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
+        private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, int setIndex, bool isStorage)
+        {
+            foreach (BufferDescriptor buffer in buffers)
+            {
+                _resourceUsages[setIndex].Add(new ResourceUsage(
+                    buffer.Binding,
+                    isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
+                    stages,
+                    buffer.Flags.HasFlag(BufferUsageFlags.Write) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
+            }
+        }
+
+        /// <summary>
+        /// Adds texture usage information to the list of usages.
+        /// </summary>
+        /// <param name="textures">Textures to be added</param>
+        /// <param name="stages">Stages where the textures are used</param>
+        /// <param name="setIndex">Descriptor set index where the textures will be bound</param>
+        /// <param name="isImage">True for images, false for textures</param>
+        private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
+        {
+            foreach (TextureDescriptor texture in textures)
+            {
+                bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
+
+                ResourceType type = isBuffer
+                    ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
+                    : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
+
+                _resourceUsages[setIndex].Add(new ResourceUsage(
+                    texture.Binding,
+                    type,
+                    stages,
+                    texture.Flags.HasFlag(TextureUsageFlags.ImageStore) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
+            }
+        }
+
+        /// <summary>
+        /// Creates a new shader information structure from the added information.
+        /// </summary>
+        /// <param name="pipeline">Optional pipeline state for background shader compilation</param>
+        /// <param name="fromCache">Indicates if the shader comes from a disk cache</param>
+        /// <returns>Shader information</returns>
+        public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
+        {
+            var descriptors = new ResourceDescriptorCollection[TotalSets];
+            var usages = new ResourceUsageCollection[TotalSets];
+
+            for (int index = 0; index < TotalSets; index++)
+            {
+                descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
+                usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
+            }
+
+            ResourceLayout resourceLayout = new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
+
+            if (pipeline.HasValue)
+            {
+                return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
+            }
+            else
+            {
+                return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
+            }
+        }
+
+        /// <summary>
+        /// Builds shader information for shaders from the disk cache.
+        /// </summary>
+        /// <param name="context">GPU context that owns the shaders</param>
+        /// <param name="programs">Shaders from the disk cache</param>
+        /// <param name="pipeline">Optional pipeline for background compilation</param>
+        /// <returns>Shader information</returns>
+        public static ShaderInfo BuildForCache(GpuContext context, IEnumerable<CachedShaderStage> programs, ProgramPipelineState? pipeline)
+        {
+            ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
+
+            foreach (CachedShaderStage program in programs)
+            {
+                if (program?.Info != null)
+                {
+                    builder.AddStageInfo(program.Info);
+                }
+            }
+
+            return builder.Build(pipeline, fromCache: true);
+        }
+
+        /// <summary>
+        /// Builds shader information for a compute shader.
+        /// </summary>
+        /// <param name="context">GPU context that owns the shader</param>
+        /// <param name="info">Compute shader information</param>
+        /// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
+        /// <returns>Shader information</returns>
+        public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
+        {
+            ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
+
+            builder.AddStageInfo(info);
+
+            return builder.Build(null, fromCache);
+        }
+    }
+}

+ 96 - 119
src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs

@@ -372,8 +372,9 @@ namespace Ryujinx.Graphics.Vulkan
         private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
         private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
         {
         {
             var program = _program;
             var program = _program;
-            int stagesCount = program.Bindings[setIndex].Length;
-            if (stagesCount == 0 && setIndex != PipelineBase.UniformSetIndex)
+            var bindingSegments = program.BindingSegments[setIndex];
+
+            if (bindingSegments.Length == 0 && setIndex != PipelineBase.UniformSetIndex)
             {
             {
                 return;
                 return;
             }
             }
@@ -410,125 +411,113 @@ namespace Ryujinx.Graphics.Vulkan
                 }
                 }
             }
             }
 
 
-            for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
+            foreach (ResourceBindingSegment segment in bindingSegments)
             {
             {
-                var stageBindings = program.Bindings[setIndex][stageIndex];
-                int bindingsCount = stageBindings.Length;
-                int count;
+                int binding = segment.Binding;
+                int count = segment.Count;
 
 
-                for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
+                if (setIndex == PipelineBase.UniformSetIndex)
                 {
                 {
-                    int binding = stageBindings[bindingIndex];
-                    count = 1;
-
-                    while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
+                    for (int i = 0; i < count; i++)
                     {
                     {
-                        count++;
-                    }
+                        int index = binding + i;
 
 
-                    if (setIndex == PipelineBase.UniformSetIndex)
-                    {
-                        for (int i = 0; i < count; i++)
+                        if (!_uniformSet[index])
                         {
                         {
-                            int index = binding + i;
-
-                            if (!_uniformSet[index])
-                            {
-                                UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
+                            UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
 
 
-                                _uniformSet[index] = true;
-                            }
+                            _uniformSet[index] = true;
                         }
                         }
-
-                        ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
-                        dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
                     }
                     }
-                    else if (setIndex == PipelineBase.StorageSetIndex)
+
+                    ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
+                    dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
+                }
+                else if (setIndex == PipelineBase.StorageSetIndex)
+                {
+                    for (int i = 0; i < count; i++)
                     {
                     {
-                        for (int i = 0; i < count; i++)
-                        {
-                            int index = binding + i;
+                        int index = binding + i;
 
 
-                            if (!_storageSet[index])
-                            {
-                                UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
+                        if (!_storageSet[index])
+                        {
+                            UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
 
 
-                                _storageSet[index] = true;
-                            }
+                            _storageSet[index] = true;
                         }
                         }
+                    }
 
 
-                        ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
-                        if (program.HasMinimalLayout)
-                        {
-                            dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
-                        }
-                        else
-                        {
-                            dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
-                        }
+                    ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
+                    if (program.HasMinimalLayout)
+                    {
+                        dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
                     }
                     }
-                    else if (setIndex == PipelineBase.TextureSetIndex)
+                    else
                     {
                     {
-                        if (((uint)binding % (Constants.MaxTexturesPerStage * 2)) < Constants.MaxTexturesPerStage || program.HasMinimalLayout)
-                        {
-                            Span<DescriptorImageInfo> textures = _textures;
-
-                            for (int i = 0; i < count; i++)
-                            {
-                                ref var texture = ref textures[i];
+                        dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
+                    }
+                }
+                else if (setIndex == PipelineBase.TextureSetIndex)
+                {
+                    if (segment.Type != ResourceType.BufferTexture)
+                    {
+                        Span<DescriptorImageInfo> textures = _textures;
 
 
-                                texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
-                                texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
+                        for (int i = 0; i < count; i++)
+                        {
+                            ref var texture = ref textures[i];
 
 
-                                if (texture.ImageView.Handle == 0)
-                                {
-                                    texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
-                                }
+                            texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
+                            texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
 
 
-                                if (texture.Sampler.Handle == 0)
-                                {
-                                    texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
-                                }
+                            if (texture.ImageView.Handle == 0)
+                            {
+                                texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
                             }
                             }
 
 
-                            dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler);
-                        }
-                        else
-                        {
-                            Span<BufferView> bufferTextures = _bufferTextures;
-
-                            for (int i = 0; i < count; i++)
+                            if (texture.Sampler.Handle == 0)
                             {
                             {
-                                bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
+                                texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
                             }
                             }
-
-                            dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer);
                         }
                         }
+
+                        dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler);
                     }
                     }
-                    else if (setIndex == PipelineBase.ImageSetIndex)
+                    else
                     {
                     {
-                        if (((uint)binding % (Constants.MaxImagesPerStage * 2)) < Constants.MaxImagesPerStage || program.HasMinimalLayout)
+                        Span<BufferView> bufferTextures = _bufferTextures;
+
+                        for (int i = 0; i < count; i++)
                         {
                         {
-                            Span<DescriptorImageInfo> images = _images;
+                            bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
+                        }
 
 
-                            for (int i = 0; i < count; i++)
-                            {
-                                images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
-                            }
+                        dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer);
+                    }
+                }
+                else if (setIndex == PipelineBase.ImageSetIndex)
+                {
+                    if (segment.Type != ResourceType.BufferImage)
+                    {
+                        Span<DescriptorImageInfo> images = _images;
 
 
-                            dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage);
-                        }
-                        else
+                        for (int i = 0; i < count; i++)
                         {
                         {
-                            Span<BufferView> bufferImages = _bufferImages;
+                            images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
+                        }
 
 
-                            for (int i = 0; i < count; i++)
-                            {
-                                bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
-                            }
+                        dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage);
+                    }
+                    else
+                    {
+                        Span<BufferView> bufferImages = _bufferImages;
 
 
-                            dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer);
+                        for (int i = 0; i < count; i++)
+                        {
+                            bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
                         }
                         }
+
+                        dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer);
                     }
                     }
                 }
                 }
             }
             }
@@ -568,9 +557,6 @@ namespace Ryujinx.Graphics.Vulkan
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
         private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
         {
         {
-            var dummyBuffer = _dummyBuffer?.GetBuffer();
-            int stagesCount = _program.Bindings[PipelineBase.UniformSetIndex].Length;
-
             if (!_uniformSet[0])
             if (!_uniformSet[0])
             {
             {
                 Span<DescriptorBufferInfo> uniformBuffer = stackalloc DescriptorBufferInfo[1];
                 Span<DescriptorBufferInfo> uniformBuffer = stackalloc DescriptorBufferInfo[1];
@@ -587,41 +573,32 @@ namespace Ryujinx.Graphics.Vulkan
                 UpdateBuffers(cbs, pbp, 0, uniformBuffer, DescriptorType.UniformBuffer);
                 UpdateBuffers(cbs, pbp, 0, uniformBuffer, DescriptorType.UniformBuffer);
             }
             }
 
 
-            for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
+            var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
+            var dummyBuffer = _dummyBuffer?.GetBuffer();
+
+            foreach (ResourceBindingSegment segment in bindingSegments)
             {
             {
-                var stageBindings = _program.Bindings[PipelineBase.UniformSetIndex][stageIndex];
-                int bindingsCount = stageBindings.Length;
-                int count;
+                int binding = segment.Binding;
+                int count = segment.Count;
+
+                bool doUpdate = false;
 
 
-                for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
+                for (int i = 0; i < count; i++)
                 {
                 {
-                    int binding = stageBindings[bindingIndex];
-                    count = 1;
+                    int index = binding + i;
 
 
-                    while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
+                    if (!_uniformSet[index])
                     {
                     {
-                        count++;
-                    }
-
-                    bool doUpdate = false;
-
-                    for (int i = 0; i < count; i++)
-                    {
-                        int index = binding + i;
-
-                        if (!_uniformSet[index])
-                        {
-                            UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
-                            _uniformSet[index] = true;
-                            doUpdate = true;
-                        }
+                        UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
+                        _uniformSet[index] = true;
+                        doUpdate = true;
                     }
                     }
+                }
 
 
-                    if (doUpdate)
-                    {
-                        ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
-                        UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
-                    }
+                if (doUpdate)
+                {
+                    ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
+                    UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
                 }
                 }
             }
             }
         }
         }

+ 15 - 17
src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs

@@ -54,29 +54,29 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
             var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
             var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
             var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
 
 
-            var computeBindings = new ShaderBindings(
-                new[] { 2 },
-                Array.Empty<int>(),
-                new[] { 1 },
-                new[] { 0 });
-
-            var sharpeningBindings = new ShaderBindings(
-                new[] { 2, 3, 4 },
-                Array.Empty<int>(),
-                new[] { 1 },
-                new[] { 0 });
+            var scalingResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+
+            var sharpeningResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
 
 
             _sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
 
             _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            });
+                new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, scalingResourceLayout);
 
 
             _sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            });
+                new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, sharpeningResourceLayout);
         }
         }
 
 
         public void Run(
         public void Run(
@@ -160,10 +160,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             _pipeline.ComputeBarrier();
             _pipeline.ComputeBarrier();
 
 
             // Sharpening pass
             // Sharpening pass
-            _pipeline.SetCommandBuffer(cbs);
             _pipeline.SetProgram(_sharpeningProgram);
             _pipeline.SetProgram(_sharpeningProgram);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
-            _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
             var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
             var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
             _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
             _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
             _pipeline.SetImage(0, destinationTexture);
             _pipeline.SetImage(0, destinationTexture);

+ 6 - 7
src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs

@@ -38,18 +38,17 @@ namespace Ryujinx.Graphics.Vulkan.Effects
 
 
             var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
             var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
 
 
-            var computeBindings = new ShaderBindings(
-                new[] { 2 },
-                Array.Empty<int>(),
-                new[] { 1 },
-                new[] { 0 });
+            var resourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
 
 
             _samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
 
             _shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            });
+                new ShaderSource(shader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, resourceLayout);
         }
         }
 
 
         public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
         public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)

+ 23 - 23
src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs

@@ -77,23 +77,23 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
             var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
             var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
             var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
 
 
-            var edgeBindings = new ShaderBindings(
-                new[] { 2 },
-                Array.Empty<int>(),
-                new[] { 1 },
-                new[] { 0 });
-
-            var blendBindings = new ShaderBindings(
-                new[] { 2 },
-                Array.Empty<int>(),
-                new[] { 1, 3, 4 },
-                new[] { 0 });
-
-            var neighbourBindings = new ShaderBindings(
-                new[] { 2 },
-                Array.Empty<int>(),
-                new[] { 1, 3 },
-                new[] { 0 });
+            var edgeResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+
+            var blendResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+
+            var neighbourResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
 
 
             _samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
 
@@ -117,18 +117,18 @@ namespace Ryujinx.Graphics.Vulkan.Effects
 
 
             _edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(edgeShader, edgeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            }, new[] { specInfo });
+                new ShaderSource(edgeShader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, edgeResourceLayout, new[] { specInfo });
 
 
             _blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(blendShader, blendBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            }, new[] { specInfo });
+                new ShaderSource(blendShader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, blendResourceLayout, new[] { specInfo });
 
 
             _neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             _neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(neighbourShader, neighbourBindings, ShaderStage.Compute, TargetLanguage.Spirv)
-            }, new[] { specInfo });
+                new ShaderSource(neighbourShader, ShaderStage.Compute, TargetLanguage.Spirv)
+            }, neighbourResourceLayout, new[] { specInfo });
         }
         }
 
 
         public void DeletePipelines()
         public void DeletePipelines()

+ 51 - 1
src/Ryujinx.Graphics.Vulkan/EnumConversion.cs

@@ -36,6 +36,56 @@ namespace Ryujinx.Graphics.Vulkan
             };
             };
         }
         }
 
 
+        public static ShaderStageFlags Convert(this ResourceStages stages)
+        {
+            ShaderStageFlags stageFlags = stages.HasFlag(ResourceStages.Compute)
+                ? ShaderStageFlags.ComputeBit
+                : ShaderStageFlags.None;
+
+            if (stages.HasFlag(ResourceStages.Vertex))
+            {
+                stageFlags |= ShaderStageFlags.VertexBit;
+            }
+
+            if (stages.HasFlag(ResourceStages.TessellationControl))
+            {
+                stageFlags |= ShaderStageFlags.TessellationControlBit;
+            }
+
+            if (stages.HasFlag(ResourceStages.TessellationEvaluation))
+            {
+                stageFlags |= ShaderStageFlags.TessellationEvaluationBit;
+            }
+
+            if (stages.HasFlag(ResourceStages.Geometry))
+            {
+                stageFlags |= ShaderStageFlags.GeometryBit;
+            }
+
+            if (stages.HasFlag(ResourceStages.Fragment))
+            {
+                stageFlags |= ShaderStageFlags.FragmentBit;
+            }
+
+            return stageFlags;
+        }
+
+        public static DescriptorType Convert(this ResourceType type)
+        {
+            return type switch
+            {
+                ResourceType.UniformBuffer => DescriptorType.UniformBuffer,
+                ResourceType.StorageBuffer => DescriptorType.StorageBuffer,
+                ResourceType.Texture => DescriptorType.SampledImage,
+                ResourceType.Sampler => DescriptorType.Sampler,
+                ResourceType.TextureAndSampler => DescriptorType.CombinedImageSampler,
+                ResourceType.Image => DescriptorType.StorageImage,
+                ResourceType.BufferTexture => DescriptorType.UniformTexelBuffer,
+                ResourceType.BufferImage => DescriptorType.StorageTexelBuffer,
+                _ => throw new ArgumentException($"Invalid resource type \"{type}\".")
+            };
+        }
+
         public static SamplerAddressMode Convert(this AddressMode mode)
         public static SamplerAddressMode Convert(this AddressMode mode)
         {
         {
             return mode switch
             return mode switch
@@ -48,7 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
                 AddressMode.ClampToBorder => SamplerAddressMode.ClampToBorder,
                 AddressMode.ClampToBorder => SamplerAddressMode.ClampToBorder,
                 AddressMode.MirroredRepeat => SamplerAddressMode.MirroredRepeat,
                 AddressMode.MirroredRepeat => SamplerAddressMode.MirroredRepeat,
                 AddressMode.ClampToEdge => SamplerAddressMode.ClampToEdge,
                 AddressMode.ClampToEdge => SamplerAddressMode.ClampToEdge,
-                _ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge)  // TODO: Should be clamp.
+                _ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
             };
             };
         }
         }
 
 

+ 87 - 111
src/Ryujinx.Graphics.Vulkan/HelperShader.cs

@@ -55,192 +55,168 @@ namespace Ryujinx.Graphics.Vulkan
             _samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
             _samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
             _samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
 
 
-            var blitVertexBindings = new ShaderBindings(
-                new[] { 1 },
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                Array.Empty<int>());
-
-            var blitFragmentBindings = new ShaderBindings(
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                new[] { 0 },
-                Array.Empty<int>());
+            var blitResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 1)
+                .Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build();
 
 
             _programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
             _programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, blitResourceLayout);
 
 
             _programColorBlitMs = gd.CreateProgramWithMinimalLayout(new[]
             _programColorBlitMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, blitResourceLayout);
 
 
             _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
             _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, blitResourceLayout);
 
 
-            var colorClearFragmentBindings = new ShaderBindings(
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                Array.Empty<int>());
+            var colorClearResourceLayout = new ResourceLayoutBuilder().Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 1).Build();
 
 
             _programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
             _programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorClearResourceLayout);
 
 
             _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
             _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorClearResourceLayout);
 
 
             _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
             _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorClearResourceLayout);
 
 
-            var strideChangeBindings = new ShaderBindings(
-                new[] { 0 },
-                new[] { 1, 2 },
-                Array.Empty<int>(),
-                Array.Empty<int>());
+            var strideChangeResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
 
 
             _programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
             _programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, strideChangeBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, strideChangeResourceLayout);
 
 
-            var colorCopyBindings = new ShaderBindings(
-                new[] { 0 },
-                Array.Empty<int>(),
-                new[] { 0 },
-                new[] { 0 });
+            var colorCopyResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
+                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
 
 
             _programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
             _programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorCopyShorteningComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorCopyShorteningComputeShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, colorCopyResourceLayout);
 
 
             _programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[]
             _programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorCopyToNonMsComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorCopyToNonMsComputeShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, colorCopyResourceLayout);
 
 
             _programColorCopyWidening = gd.CreateProgramWithMinimalLayout(new[]
             _programColorCopyWidening = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorCopyWideningComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorCopyWideningComputeShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, colorCopyResourceLayout);
 
 
-            var colorDrawToMsVertexBindings = new ShaderBindings(
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                Array.Empty<int>(),
-                Array.Empty<int>());
-
-            var colorDrawToMsFragmentBindings = new ShaderBindings(
-                new[] { 0 },
-                Array.Empty<int>(),
-                new[] { 0 },
-                Array.Empty<int>());
+            var colorDrawToMsResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build();
 
 
             _programColorDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
             _programColorDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorDrawToMsResourceLayout);
 
 
-            var convertD32S8ToD24S8Bindings = new ShaderBindings(
-                new[] { 0 },
-                new[] { 1, 2 },
-                Array.Empty<int>(),
-                Array.Empty<int>());
+            var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
 
 
             _programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
             _programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ConvertD32S8ToD24S8ShaderSource, convertD32S8ToD24S8Bindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ConvertD32S8ToD24S8ShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, convertD32S8ToD24S8ResourceLayout);
 
 
-            var convertIndexBufferBindings = new ShaderBindings(
-                new[] { 0 },
-                new[] { 1, 2 },
-                Array.Empty<int>(),
-                Array.Empty<int>());
+            var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
 
 
             _programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
             _programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ConvertIndexBufferShaderSource, convertIndexBufferBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ConvertIndexBufferShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, convertIndexBufferResourceLayout);
 
 
-            var convertIndirectDataBindings = new ShaderBindings(
-                new[] { 0 },
-                new[] { 1, 2, 3 },
-                Array.Empty<int>(),
-                Array.Empty<int>());
+            var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
+                .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
 
 
             _programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
             _programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, convertIndirectDataBindings, ShaderStage.Compute, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, ShaderStage.Compute, TargetLanguage.Spirv),
+            }, convertIndirectDataResourceLayout);
 
 
             _programDepthBlit = gd.CreateProgramWithMinimalLayout(new[]
             _programDepthBlit = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.DepthBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.DepthBlitFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, blitResourceLayout);
 
 
             _programDepthBlitMs = gd.CreateProgramWithMinimalLayout(new[]
             _programDepthBlitMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.DepthBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.DepthBlitMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, blitResourceLayout);
 
 
             _programDepthDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
             _programDepthDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.DepthDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.DepthDrawToMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorDrawToMsResourceLayout);
 
 
             _programDepthDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[]
             _programDepthDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[]
             {
             {
-                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                new ShaderSource(ShaderBinaries.DepthDrawToNonMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-            });
+                new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                new ShaderSource(ShaderBinaries.DepthDrawToNonMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+            }, colorDrawToMsResourceLayout);
 
 
             if (gd.Capabilities.SupportsShaderStencilExport)
             if (gd.Capabilities.SupportsShaderStencilExport)
             {
             {
                 _programStencilBlit = gd.CreateProgramWithMinimalLayout(new[]
                 _programStencilBlit = gd.CreateProgramWithMinimalLayout(new[]
                 {
                 {
-                    new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                    new ShaderSource(ShaderBinaries.StencilBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-                });
+                    new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                    new ShaderSource(ShaderBinaries.StencilBlitFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+                }, blitResourceLayout);
 
 
                 _programStencilBlitMs = gd.CreateProgramWithMinimalLayout(new[]
                 _programStencilBlitMs = gd.CreateProgramWithMinimalLayout(new[]
                 {
                 {
-                    new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                    new ShaderSource(ShaderBinaries.StencilBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-                });
+                    new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                    new ShaderSource(ShaderBinaries.StencilBlitMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+                }, blitResourceLayout);
 
 
                 _programStencilDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
                 _programStencilDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
                 {
                 {
-                    new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                    new ShaderSource(ShaderBinaries.StencilDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-                });
+                    new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                    new ShaderSource(ShaderBinaries.StencilDrawToMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+                }, colorDrawToMsResourceLayout);
 
 
                 _programStencilDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[]
                 _programStencilDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[]
                 {
                 {
-                    new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
-                    new ShaderSource(ShaderBinaries.StencilDrawToNonMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
-                });
+                    new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, ShaderStage.Vertex, TargetLanguage.Spirv),
+                    new ShaderSource(ShaderBinaries.StencilDrawToNonMsFragmentShaderSource, ShaderStage.Fragment, TargetLanguage.Spirv),
+                }, colorDrawToMsResourceLayout);
             }
             }
         }
         }
 
 

+ 73 - 24
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs

@@ -1,52 +1,101 @@
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
-using System.Collections.Generic;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
 {
 {
     class PipelineLayoutCache
     class PipelineLayoutCache
     {
     {
-        private readonly PipelineLayoutCacheEntry[] _plce;
-        private readonly List<PipelineLayoutCacheEntry> _plceMinimal;
-
-        public PipelineLayoutCache()
+        private readonly struct PlceKey : IEquatable<PlceKey>
         {
         {
-            _plce = new PipelineLayoutCacheEntry[1 << Constants.MaxShaderStages];
-            _plceMinimal = new List<PipelineLayoutCacheEntry>();
+            public readonly ReadOnlyCollection<ResourceDescriptorCollection> SetDescriptors;
+            public readonly bool UsePushDescriptors;
+
+            public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, bool usePushDescriptors)
+            {
+                SetDescriptors = setDescriptors;
+                UsePushDescriptors = usePushDescriptors;
+            }
+
+            public override int GetHashCode()
+            {
+                HashCode hasher = new HashCode();
+
+                if (SetDescriptors != null)
+                {
+                    foreach (var setDescriptor in SetDescriptors)
+                    {
+                        hasher.Add(setDescriptor);
+                    }
+                }
+
+                hasher.Add(UsePushDescriptors);
+
+                return hasher.ToHashCode();
+            }
+
+            public override bool Equals(object obj)
+            {
+                return obj is PlceKey other && Equals(other);
+            }
+
+            public bool Equals(PlceKey other)
+            {
+                if ((SetDescriptors == null) != (other.SetDescriptors == null))
+                {
+                    return false;
+                }
+
+                if (SetDescriptors != null)
+                {
+                    if (SetDescriptors.Count != other.SetDescriptors.Count)
+                    {
+                        return false;
+                    }
+
+                    for (int index = 0; index < SetDescriptors.Count; index++)
+                    {
+                        if (!SetDescriptors[index].Equals(other.SetDescriptors[index]))
+                        {
+                            return false;
+                        }
+                    }
+                }
+
+                return UsePushDescriptors == other.UsePushDescriptors;
+            }
         }
         }
 
 
-        public PipelineLayoutCacheEntry Create(VulkanRenderer gd, Device device, ShaderSource[] shaders)
+        private readonly ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry> _plces;
+
+        public PipelineLayoutCache()
         {
         {
-            var plce = new PipelineLayoutCacheEntry(gd, device, shaders);
-            _plceMinimal.Add(plce);
-            return plce;
+            _plces = new ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry>();
         }
         }
 
 
-        public PipelineLayoutCacheEntry GetOrCreate(VulkanRenderer gd, Device device, uint stages, bool usePd)
+        public PipelineLayoutCacheEntry GetOrCreate(
+            VulkanRenderer gd,
+            Device device,
+            ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
+            bool usePushDescriptors)
         {
         {
-            if (_plce[stages] == null)
-            {
-                _plce[stages] = new PipelineLayoutCacheEntry(gd, device, stages, usePd);
-            }
+            var key = new PlceKey(setDescriptors, usePushDescriptors);
 
 
-            return _plce[stages];
+            return _plces.GetOrAdd(key, (newKey) => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usePushDescriptors));
         }
         }
 
 
         protected virtual unsafe void Dispose(bool disposing)
         protected virtual unsafe void Dispose(bool disposing)
         {
         {
             if (disposing)
             if (disposing)
             {
             {
-                for (int i = 0; i < _plce.Length; i++)
-                {
-                    _plce[i]?.Dispose();
-                }
-
-                foreach (var plce in _plceMinimal)
+                foreach (var plce in _plces.Values)
                 {
                 {
                     plce.Dispose();
                     plce.Dispose();
                 }
                 }
 
 
-                _plceMinimal.Clear();
+                _plces.Clear();
             }
             }
         }
         }
 
 

+ 12 - 14
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs

@@ -1,6 +1,7 @@
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
 {
 {
@@ -16,7 +17,7 @@ namespace Ryujinx.Graphics.Vulkan
         private readonly int[] _dsCacheCursor;
         private readonly int[] _dsCacheCursor;
         private int _dsLastCbIndex;
         private int _dsLastCbIndex;
 
 
-        private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device)
+        private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
         {
         {
             _gd = gd;
             _gd = gd;
             _device = device;
             _device = device;
@@ -25,27 +26,24 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
             for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
             {
             {
-                _dsCache[i] = new List<Auto<DescriptorSetCollection>>[PipelineBase.DescriptorSetLayouts];
+                _dsCache[i] = new List<Auto<DescriptorSetCollection>>[setsCount];
 
 
-                for (int j = 0; j < PipelineBase.DescriptorSetLayouts; j++)
+                for (int j = 0; j < _dsCache[i].Length; j++)
                 {
                 {
                     _dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
                     _dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
                 }
                 }
             }
             }
 
 
-            _dsCacheCursor = new int[PipelineBase.DescriptorSetLayouts];
+            _dsCacheCursor = new int[setsCount];
         }
         }
 
 
-        public PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, uint stages, bool usePd) : this(gd, device)
-        {
-            DescriptorSetLayouts = PipelineLayoutFactory.Create(gd, device, stages, usePd, out var pipelineLayout);
-            PipelineLayout = pipelineLayout;
-        }
-
-        public PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, ShaderSource[] shaders) : this(gd, device)
+        public PipelineLayoutCacheEntry(
+            VulkanRenderer gd,
+            Device device,
+            ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
+            bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
         {
         {
-            DescriptorSetLayouts = PipelineLayoutFactory.CreateMinimal(gd, device, shaders, out var pipelineLayout);
-            PipelineLayout = pipelineLayout;
+            (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
         }
         }
 
 
         public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
         public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
@@ -58,7 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
             {
             {
                 _dsLastCbIndex = commandBufferIndex;
                 _dsLastCbIndex = commandBufferIndex;
 
 
-                for (int i = 0; i < PipelineBase.DescriptorSetLayouts; i++)
+                for (int i = 0; i < _dsCacheCursor.Length; i++)
                 {
                 {
                     _dsCacheCursor[i] = 0;
                     _dsCacheCursor[i] = 0;
                 }
                 }

+ 44 - 227
src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs

@@ -1,257 +1,74 @@
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
-using System.Collections.Generic;
-using System.Numerics;
+using System.Collections.ObjectModel;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
 {
 {
     static class PipelineLayoutFactory
     static class PipelineLayoutFactory
     {
     {
-        private const ShaderStageFlags SupportBufferStages =
-            ShaderStageFlags.VertexBit |
-            ShaderStageFlags.FragmentBit |
-            ShaderStageFlags.ComputeBit;
-
-        private static ShaderStageFlags ActiveStages(uint stages)
-        {
-            ShaderStageFlags stageFlags = 0;
-
-            while (stages != 0)
-            {
-                int stage = BitOperations.TrailingZeroCount(stages);
-                stages &= ~(1u << stage);
-
-                stageFlags |= stage switch
-                {
-                    1 => ShaderStageFlags.FragmentBit,
-                    2 => ShaderStageFlags.GeometryBit,
-                    3 => ShaderStageFlags.TessellationControlBit,
-                    4 => ShaderStageFlags.TessellationEvaluationBit,
-                    _ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
-                };
-            }
-
-            return stageFlags;
-        }
-
-        public static unsafe DescriptorSetLayout[] Create(VulkanRenderer gd, Device device, uint stages, bool usePd, out PipelineLayout layout)
+        public static unsafe (DescriptorSetLayout[], PipelineLayout) Create(
+            VulkanRenderer gd,
+            Device device,
+            ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
+            bool usePushDescriptors)
         {
         {
-            int stagesCount = BitOperations.PopCount(stages);
-
-            int uCount = Constants.MaxUniformBuffersPerStage * stagesCount + 1;
-            int tCount = Constants.MaxTexturesPerStage * 2 * stagesCount;
-            int iCount = Constants.MaxImagesPerStage * 2 * stagesCount;
+            DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
 
 
-            DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount];
-            DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[stagesCount];
-            DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount];
-            DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount];
+            bool isMoltenVk = gd.IsMoltenVk;
 
 
-            uLayoutBindings[0] = new DescriptorSetLayoutBinding
+            for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
             {
             {
-                Binding = 0,
-                DescriptorType = DescriptorType.UniformBuffer,
-                DescriptorCount = 1,
-                StageFlags = SupportBufferStages
-            };
+                ResourceDescriptorCollection rdc = setDescriptors[setIndex];
 
 
-            int iter = 0;
-            var activeStages = ActiveStages(stages);
-
-            while (stages != 0)
-            {
-                int stage = BitOperations.TrailingZeroCount(stages);
-                stages &= ~(1u << stage);
+                ResourceStages activeStages = ResourceStages.None;
 
 
-                var stageFlags = stage switch
+                if (isMoltenVk)
                 {
                 {
-                    1 => ShaderStageFlags.FragmentBit,
-                    2 => ShaderStageFlags.GeometryBit,
-                    3 => ShaderStageFlags.TessellationControlBit,
-                    4 => ShaderStageFlags.TessellationEvaluationBit,
-                    _ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
-                };
-
-                void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start, int skip)
-                {
-                    int totalPerStage = maxPerStage * skip;
-
-                    for (int i = 0; i < maxPerStage; i++)
+                    for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
                     {
                     {
-                        bindings[start + iter * totalPerStage + i] = new DescriptorSetLayoutBinding
-                        {
-                            Binding = (uint)(start + stage * totalPerStage + i),
-                            DescriptorType = type,
-                            DescriptorCount = 1,
-                            StageFlags = stageFlags
-                        };
+                        activeStages |= rdc.Descriptors[descIndex].Stages;
                     }
                     }
                 }
                 }
 
 
-                void SetStorage(DescriptorSetLayoutBinding* bindings, int maxPerStage, int start = 0)
-                {
-                    // There's a bug on MoltenVK where using the same buffer across different stages
-                    // causes invalid resource errors, allow the binding on all active stages as workaround.
-                    var flags = gd.IsMoltenVk ? activeStages : stageFlags;
-
-                    bindings[start + iter] = new DescriptorSetLayoutBinding
-                    {
-                        Binding = (uint)(start + stage * maxPerStage),
-                        DescriptorType = DescriptorType.StorageBuffer,
-                        DescriptorCount = (uint)maxPerStage,
-                        StageFlags = flags
-                    };
-                }
-
-                Set(uLayoutBindings, Constants.MaxUniformBuffersPerStage, DescriptorType.UniformBuffer, 1, 1);
-                SetStorage(sLayoutBindings, Constants.MaxStorageBuffersPerStage);
-                Set(tLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.CombinedImageSampler, 0, 2);
-                Set(tLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.UniformTexelBuffer, Constants.MaxTexturesPerStage, 2);
-                Set(iLayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageImage, 0, 2);
-                Set(iLayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageTexelBuffer, Constants.MaxImagesPerStage, 2);
-
-                iter++;
-            }
-
-            DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineBase.DescriptorSetLayouts];
-
-            var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = uLayoutBindings,
-                BindingCount = (uint)uCount,
-                Flags = usePd ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : 0
-            };
-
-            var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = sLayoutBindings,
-                BindingCount = (uint)stagesCount
-            };
-
-            var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = tLayoutBindings,
-                BindingCount = (uint)tCount
-            };
-
-            var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = iLayoutBindings,
-                BindingCount = (uint)iCount
-            };
-
-            gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.UniformSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.StorageSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.TextureSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.ImageSetIndex]).ThrowOnError();
+                DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
 
 
-            fixed (DescriptorSetLayout* pLayouts = layouts)
-            {
-                var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
+                for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
                 {
                 {
-                    SType = StructureType.PipelineLayoutCreateInfo,
-                    PSetLayouts = pLayouts,
-                    SetLayoutCount = PipelineBase.DescriptorSetLayouts
-                };
-
-                gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
-            }
+                    ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
 
 
-            return layouts;
-        }
-
-        public static unsafe DescriptorSetLayout[] CreateMinimal(VulkanRenderer gd, Device device, ShaderSource[] shaders, out PipelineLayout layout)
-        {
-            int stagesCount = shaders.Length;
+                    ResourceStages stages = descriptor.Stages;
 
 
-            int uCount = 0;
-            int sCount = 0;
-            int tCount = 0;
-            int iCount = 0;
-
-            foreach (var shader in shaders)
-            {
-                uCount += shader.Bindings.UniformBufferBindings.Count;
-                sCount += shader.Bindings.StorageBufferBindings.Count;
-                tCount += shader.Bindings.TextureBindings.Count;
-                iCount += shader.Bindings.ImageBindings.Count;
-            }
-
-            DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount];
-            DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[sCount];
-            DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount];
-            DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount];
-
-            int uIndex = 0;
-            int sIndex = 0;
-            int tIndex = 0;
-            int iIndex = 0;
+                    if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
+                    {
+                        // There's a bug on MoltenVK where using the same buffer across different stages
+                        // causes invalid resource errors, allow the binding on all active stages as workaround.
+                        stages = activeStages;
+                    }
 
 
-            foreach (var shader in shaders)
-            {
-                var stageFlags = shader.Stage.Convert();
+                    layoutBindings[descIndex] = new DescriptorSetLayoutBinding()
+                    {
+                        Binding = (uint)descriptor.Binding,
+                        DescriptorType = descriptor.Type.Convert(),
+                        DescriptorCount = (uint)descriptor.Count,
+                        StageFlags = stages.Convert()
+                    };
+                }
 
 
-                void Set(DescriptorSetLayoutBinding* bindings, DescriptorType type, ref int start, IEnumerable<int> bds)
+                fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings)
                 {
                 {
-                    foreach (var b in bds)
+                    var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
                     {
                     {
-                        bindings[start++] = new DescriptorSetLayoutBinding
-                        {
-                            Binding = (uint)b,
-                            DescriptorType = type,
-                            DescriptorCount = 1,
-                            StageFlags = stageFlags
-                        };
-                    }
-                }
+                        SType = StructureType.DescriptorSetLayoutCreateInfo,
+                        PBindings = pLayoutBindings,
+                        BindingCount = (uint)layoutBindings.Length,
+                        Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None
+                    };
 
 
-                // TODO: Support buffer textures and images here.
-                // This is only used for the helper shaders on the backend, and we don't use buffer textures on them
-                // so far, so it's not really necessary right now.
-                Set(uLayoutBindings, DescriptorType.UniformBuffer, ref uIndex, shader.Bindings.UniformBufferBindings);
-                Set(sLayoutBindings, DescriptorType.StorageBuffer, ref sIndex, shader.Bindings.StorageBufferBindings);
-                Set(tLayoutBindings, DescriptorType.CombinedImageSampler, ref tIndex, shader.Bindings.TextureBindings);
-                Set(iLayoutBindings, DescriptorType.StorageImage, ref iIndex, shader.Bindings.ImageBindings);
+                    gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
+                }
             }
             }
 
 
-            DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineBase.DescriptorSetLayouts];
-
-            var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = uLayoutBindings,
-                BindingCount = (uint)uCount
-            };
-
-            var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = sLayoutBindings,
-                BindingCount = (uint)sCount
-            };
-
-            var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = tLayoutBindings,
-                BindingCount = (uint)tCount
-            };
-
-            var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
-            {
-                SType = StructureType.DescriptorSetLayoutCreateInfo,
-                PBindings = iLayoutBindings,
-                BindingCount = (uint)iCount
-            };
-
-            gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.UniformSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.StorageSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.TextureSetIndex]).ThrowOnError();
-            gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.ImageSetIndex]).ThrowOnError();
+            PipelineLayout layout;
 
 
             fixed (DescriptorSetLayout* pLayouts = layouts)
             fixed (DescriptorSetLayout* pLayouts = layouts)
             {
             {
@@ -259,13 +76,13 @@ namespace Ryujinx.Graphics.Vulkan
                 {
                 {
                     SType = StructureType.PipelineLayoutCreateInfo,
                     SType = StructureType.PipelineLayoutCreateInfo,
                     PSetLayouts = pLayouts,
                     PSetLayouts = pLayouts,
-                    SetLayoutCount = PipelineBase.DescriptorSetLayouts
+                    SetLayoutCount = (uint)layouts.Length
                 };
                 };
 
 
                 gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
                 gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
             }
             }
 
 
-            return layouts;
+            return (layouts, layout);
         }
         }
     }
     }
 }
 }

+ 22 - 0
src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs

@@ -0,0 +1,22 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    readonly struct ResourceBindingSegment
+    {
+        public readonly int Binding;
+        public readonly int Count;
+        public readonly ResourceType Type;
+        public readonly ResourceStages Stages;
+        public readonly ResourceAccess Access;
+
+        public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, ResourceAccess access)
+        {
+            Binding = binding;
+            Count = count;
+            Type = type;
+            Stages = stages;
+            Access = access;
+        }
+    }
+}

+ 67 - 0
src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs

@@ -0,0 +1,67 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    class ResourceLayoutBuilder
+    {
+        private const int TotalSets = PipelineBase.DescriptorSetLayouts;
+
+        private readonly List<ResourceDescriptor>[] _resourceDescriptors;
+        private readonly List<ResourceUsage>[] _resourceUsages;
+
+        public ResourceLayoutBuilder()
+        {
+            _resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
+            _resourceUsages = new List<ResourceUsage>[TotalSets];
+
+            for (int index = 0; index < TotalSets; index++)
+            {
+                _resourceDescriptors[index] = new();
+                _resourceUsages[index] = new();
+            }
+        }
+
+        public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
+        {
+            int setIndex = type switch
+            {
+                ResourceType.UniformBuffer => PipelineBase.UniformSetIndex,
+                ResourceType.StorageBuffer => PipelineBase.StorageSetIndex,
+                ResourceType.TextureAndSampler or ResourceType.BufferTexture => PipelineBase.TextureSetIndex,
+                ResourceType.Image or ResourceType.BufferImage => PipelineBase.ImageSetIndex,
+                _ => throw new ArgumentException($"Invalid resource type \"{type}\".")
+            };
+
+            ResourceAccess access = IsReadOnlyType(type) ? ResourceAccess.Read : ResourceAccess.ReadWrite;
+
+            _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
+            _resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages, access));
+
+            return this;
+        }
+
+        private static bool IsReadOnlyType(ResourceType type)
+        {
+            return type == ResourceType.UniformBuffer ||
+                   type == ResourceType.Sampler ||
+                   type == ResourceType.TextureAndSampler ||
+                   type == ResourceType.BufferTexture;
+        }
+
+        public ResourceLayout Build()
+        {
+            var descriptors = new ResourceDescriptorCollection[TotalSets];
+            var usages = new ResourceUsageCollection[TotalSets];
+
+            for (int index = 0; index < TotalSets; index++)
+            {
+                descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
+                usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
+            }
+
+            return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
+        }
+    }
+}

+ 0 - 3
src/Ryujinx.Graphics.Vulkan/Shader.cs

@@ -26,8 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
 
 
         public ShaderStageFlags StageFlags => _stage;
         public ShaderStageFlags StageFlags => _stage;
 
 
-        public ShaderBindings Bindings { get; }
-
         public ProgramLinkStatus CompileStatus { private set; get; }
         public ProgramLinkStatus CompileStatus { private set; get; }
 
 
         public readonly Task CompileTask;
         public readonly Task CompileTask;
@@ -36,7 +34,6 @@ namespace Ryujinx.Graphics.Vulkan
         {
         {
             _api = api;
             _api = api;
             _device = device;
             _device = device;
-            Bindings = shaderSource.Bindings;
 
 
             CompileStatus = ProgramLinkStatus.Incomplete;
             CompileStatus = ProgramLinkStatus.Incomplete;
 
 

+ 76 - 30
src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs

@@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -23,7 +24,7 @@ namespace Ryujinx.Graphics.Vulkan
 
 
         public uint Stages { get; }
         public uint Stages { get; }
 
 
-        public int[][][] Bindings { get; }
+        public ResourceBindingSegment[][] BindingSegments { get; }
 
 
         public ProgramLinkStatus LinkStatus { get; private set; }
         public ProgramLinkStatus LinkStatus { get; private set; }
 
 
@@ -54,7 +55,13 @@ namespace Ryujinx.Graphics.Vulkan
         private Task _compileTask;
         private Task _compileTask;
         private bool _firstBackgroundUse;
         private bool _firstBackgroundUse;
 
 
-        public ShaderCollection(VulkanRenderer gd, Device device, ShaderSource[] shaders, SpecDescription[] specDescription = null, bool isMinimal = false)
+        public ShaderCollection(
+            VulkanRenderer gd,
+            Device device,
+            ShaderSource[] shaders,
+            ResourceLayout resourceLayout,
+            SpecDescription[] specDescription = null,
+            bool isMinimal = false)
         {
         {
             _gd = gd;
             _gd = gd;
             _device = device;
             _device = device;
@@ -99,39 +106,16 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             _shaders = internalShaders;
             _shaders = internalShaders;
 
 
-            bool usePd = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
+            bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
 
 
-            _plce = isMinimal
-                ? gd.PipelineLayoutCache.Create(gd, device, shaders)
-                : gd.PipelineLayoutCache.GetOrCreate(gd, device, stages, usePd);
+            _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
 
 
             HasMinimalLayout = isMinimal;
             HasMinimalLayout = isMinimal;
-            UsePushDescriptors = usePd;
+            UsePushDescriptors = usePushDescriptors;
 
 
             Stages = stages;
             Stages = stages;
 
 
-            int[][] GrabAll(Func<ShaderBindings, IReadOnlyCollection<int>> selector)
-            {
-                bool hasAny = false;
-                int[][] bindings = new int[internalShaders.Length][];
-
-                for (int i = 0; i < internalShaders.Length; i++)
-                {
-                    var collection = selector(internalShaders[i].Bindings);
-                    hasAny |= collection.Count != 0;
-                    bindings[i] = collection.ToArray();
-                }
-
-                return hasAny ? bindings : Array.Empty<int[]>();
-            }
-
-            Bindings = new[]
-            {
-                GrabAll(x => x.UniformBufferBindings),
-                GrabAll(x => x.StorageBufferBindings),
-                GrabAll(x => x.TextureBindings),
-                GrabAll(x => x.ImageBindings)
-            };
+            BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
 
 
             _compileTask = Task.CompletedTask;
             _compileTask = Task.CompletedTask;
             _firstBackgroundUse = false;
             _firstBackgroundUse = false;
@@ -141,8 +125,9 @@ namespace Ryujinx.Graphics.Vulkan
             VulkanRenderer gd,
             VulkanRenderer gd,
             Device device,
             Device device,
             ShaderSource[] sources,
             ShaderSource[] sources,
+            ResourceLayout resourceLayout,
             ProgramPipelineState state,
             ProgramPipelineState state,
-            bool fromCache) : this(gd, device, sources)
+            bool fromCache) : this(gd, device, sources, resourceLayout)
         {
         {
             _state = state;
             _state = state;
 
 
@@ -150,6 +135,67 @@ namespace Ryujinx.Graphics.Vulkan
             _firstBackgroundUse = !fromCache;
             _firstBackgroundUse = !fromCache;
         }
         }
 
 
+        private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
+        {
+            ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
+
+            for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
+            {
+                List<ResourceBindingSegment> currentSegments = new List<ResourceBindingSegment>();
+
+                ResourceUsage currentUsage = default;
+                int currentCount = 0;
+
+                for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
+                {
+                    ResourceUsage usage = setUsages[setIndex].Usages[index];
+
+                    // If the resource is not accessed, we don't need to update it.
+                    if (usage.Access == ResourceAccess.None)
+                    {
+                        continue;
+                    }
+
+                    if (currentUsage.Binding + currentCount != usage.Binding ||
+                        currentUsage.Type != usage.Type ||
+                        currentUsage.Stages != usage.Stages ||
+                        currentUsage.Access != usage.Access)
+                    {
+                        if (currentCount != 0)
+                        {
+                            currentSegments.Add(new ResourceBindingSegment(
+                                currentUsage.Binding,
+                                currentCount,
+                                currentUsage.Type,
+                                currentUsage.Stages,
+                                currentUsage.Access));
+                        }
+
+                        currentUsage = usage;
+                        currentCount = 1;
+                    }
+                    else
+                    {
+                        currentCount++;
+                    }
+                }
+
+                if (currentCount != 0)
+                {
+                    currentSegments.Add(new ResourceBindingSegment(
+                        currentUsage.Binding,
+                        currentCount,
+                        currentUsage.Type,
+                        currentUsage.Stages,
+                        currentUsage.Access));
+                }
+
+                segments[setIndex] = currentSegments.ToArray();
+            }
+
+            return segments;
+        }
+
         private async Task BackgroundCompilation()
         private async Task BackgroundCompilation()
         {
         {
             await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
             await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));

+ 6 - 7
src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs

@@ -10,7 +10,6 @@ using Silk.NET.Vulkan.Extensions.EXT;
 using Silk.NET.Vulkan.Extensions.KHR;
 using Silk.NET.Vulkan.Extensions.KHR;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 
 
 namespace Ryujinx.Graphics.Vulkan
 namespace Ryujinx.Graphics.Vulkan
@@ -398,17 +397,17 @@ namespace Ryujinx.Graphics.Vulkan
 
 
             if (info.State.HasValue || isCompute)
             if (info.State.HasValue || isCompute)
             {
             {
-                return new ShaderCollection(this, _device, sources, info.State ?? default, info.FromCache);
+                return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.FromCache);
             }
             }
             else
             else
             {
             {
-                return new ShaderCollection(this, _device, sources);
+                return new ShaderCollection(this, _device, sources, info.ResourceLayout);
             }
             }
         }
         }
 
 
-        internal ShaderCollection CreateProgramWithMinimalLayout(ShaderSource[] sources, SpecDescription[] specDescription = null)
+        internal ShaderCollection CreateProgramWithMinimalLayout(ShaderSource[] sources, ResourceLayout resourceLayout, SpecDescription[] specDescription = null)
         {
         {
-            return new ShaderCollection(this, _device, sources, specDescription: specDescription, isMinimal: true);
+            return new ShaderCollection(this, _device, sources, resourceLayout, specDescription, isMinimal: true);
         }
         }
 
 
         public ISampler CreateSampler(GAL.SamplerCreateInfo info)
         public ISampler CreateSampler(GAL.SamplerCreateInfo info)
@@ -658,7 +657,7 @@ namespace Ryujinx.Graphics.Vulkan
             Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
             Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
         }
         }
 
 
-        public GAL.PrimitiveTopology TopologyRemap(GAL.PrimitiveTopology topology)
+        internal GAL.PrimitiveTopology TopologyRemap(GAL.PrimitiveTopology topology)
         {
         {
             return topology switch
             return topology switch
             {
             {
@@ -669,7 +668,7 @@ namespace Ryujinx.Graphics.Vulkan
             };
             };
         }
         }
 
 
-        public bool TopologyUnsupported(GAL.PrimitiveTopology topology)
+        internal bool TopologyUnsupported(GAL.PrimitiveTopology topology)
         {
         {
             return topology switch
             return topology switch
             {
             {