diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
index 17eeef68a5..4d3b8640f1 100644
--- a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
+++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
         {
             public bool InUse;
             public bool InConsumption;
+            public int SubmissionCount;
             public CommandBuffer CommandBuffer;
             public FenceHolder Fence;
             public SemaphoreHolder Semaphore;
@@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan
             return _commandBuffers[cbIndex].Fence;
         }
 
+        public int GetSubmissionCount(int cbIndex)
+        {
+            return _commandBuffers[cbIndex].SubmissionCount;
+        }
+
         private int FreeConsumed(bool wait)
         {
             int freeEntry = 0;
@@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan
                 Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
                 entry.InUse = false;
                 entry.InConsumption = true;
+                entry.SubmissionCount++;
                 _inUseCount--;
 
                 var commandBuffer = entry.CommandBuffer;
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
index 2f7b604c4e..7594384d6a 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
@@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
 {
     class DescriptorSetManager : IDisposable
     {
-        private const uint DescriptorPoolMultiplier = 16;
+        public const uint MaxSets = 16;
 
         public class DescriptorPoolHolder : IDisposable
         {
@@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan
             public Device Device { get; }
 
             private readonly DescriptorPool _pool;
-            private readonly uint _capacity;
+            private int _freeDescriptors;
             private int _totalSets;
             private int _setsInUse;
             private bool _done;
 
-            public unsafe DescriptorPoolHolder(Vk api, Device device)
+            public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
             {
                 Api = api;
                 Device = device;
 
-                var poolSizes = new[]
+                foreach (var poolSize in poolSizes)
                 {
-                    new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier),
-                    new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier),
-                    new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
-                    new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
-                    new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
-                    new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier),
-                };
-
-                uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
-
-                _capacity = maxSets;
+                    _freeDescriptors += (int)poolSize.DescriptorCount;
+                }
 
                 fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
                 {
                     var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
                     {
                         SType = StructureType.DescriptorPoolCreateInfo,
-                        MaxSets = maxSets,
+                        Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
+                        MaxSets = MaxSets,
                         PoolSizeCount = (uint)poolSizes.Length,
                         PPoolSizes = pPoolsSize,
                     };
@@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan
                 }
             }
 
-            public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts)
+            public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
             {
-                TryAllocateDescriptorSets(layouts, isTry: false, out var dsc);
+                TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
                 return dsc;
             }
 
-            public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc)
+            public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
             {
-                return TryAllocateDescriptorSets(layouts, isTry: true, out dsc);
+                return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
             }
 
-            private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc)
+            private unsafe bool TryAllocateDescriptorSets(
+                ReadOnlySpan<DescriptorSetLayout> layouts,
+                int consumedDescriptors,
+                bool isTry,
+                out DescriptorSetCollection dsc)
             {
                 Debug.Assert(!_done);
 
@@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
                         var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
                         if (isTry && result == Result.ErrorOutOfPoolMemory)
                         {
-                            _totalSets = (int)_capacity;
+                            _totalSets = (int)MaxSets;
                             _done = true;
                             DestroyIfDone();
                             dsc = default;
@@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
                     }
                 }
 
+                _freeDescriptors -= consumedDescriptors;
                 _totalSets += layouts.Length;
                 _setsInUse += layouts.Length;
 
@@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan
                 DestroyIfDone();
             }
 
-            public bool CanFit(int count)
+            public bool CanFit(int setsCount, int descriptorsCount)
             {
-                if (_totalSets + count <= _capacity)
+                // Try to determine if an allocation with the given parameters will succeed.
+                // An allocation may fail if the sets count or descriptors count exceeds the available counts
+                // of the pool.
+                // Not getting that right is not fatal, it will just create a new pool and try again,
+                // but it is less efficient.
+
+                if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
                 {
                     return true;
                 }
@@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan
         }
 
         private readonly Device _device;
-        private DescriptorPoolHolder _currentPool;
+        private readonly DescriptorPoolHolder[] _currentPools;
 
-        public DescriptorSetManager(Device device)
+        public DescriptorSetManager(Device device, int poolCount)
         {
             _device = device;
+            _currentPools = new DescriptorPoolHolder[poolCount];
         }
 
-        public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout)
+        public Auto<DescriptorSetCollection> AllocateDescriptorSet(
+            Vk api,
+            DescriptorSetLayout layout,
+            ReadOnlySpan<DescriptorPoolSize> poolSizes,
+            int poolIndex,
+            int consumedDescriptors,
+            bool updateAfterBind)
         {
             Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
             layouts[0] = layout;
-            return AllocateDescriptorSets(api, layouts);
+            return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
         }
 
-        public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts)
+        public Auto<DescriptorSetCollection> AllocateDescriptorSets(
+            Vk api,
+            ReadOnlySpan<DescriptorSetLayout> layouts,
+            ReadOnlySpan<DescriptorPoolSize> poolSizes,
+            int poolIndex,
+            int consumedDescriptors,
+            bool updateAfterBind)
         {
             // If we fail the first time, just create a new pool and try again.
-            if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
+
+            var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
+            if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
             {
-                dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts);
+                pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
+                dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
             }
 
             return new Auto<DescriptorSetCollection>(dsc);
         }
 
-        private DescriptorPoolHolder GetPool(Vk api, int requiredCount)
+        private DescriptorPoolHolder GetPool(
+            Vk api,
+            ReadOnlySpan<DescriptorPoolSize> poolSizes,
+            int poolIndex,
+            int setsCount,
+            int descriptorsCount,
+            bool updateAfterBind)
         {
-            if (_currentPool == null || !_currentPool.CanFit(requiredCount))
+            ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
+
+            if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
             {
-                _currentPool = new DescriptorPoolHolder(api, _device);
+                currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
             }
 
-            return _currentPool;
+            return currentPool;
         }
 
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
             {
-                _currentPool?.Dispose();
+                for (int index = 0; index < _currentPools.Length; index++)
+                {
+                    _currentPools[index]?.Dispose();
+                    _currentPools[index] = null;
+                }
             }
         }
 
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
index 14e4c02f07..a9a92df1db 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
         private BitMapStruct<Array2<long>> _uniformMirrored;
         private BitMapStruct<Array2<long>> _storageMirrored;
 
+        private bool _updateDescriptorCacheCbIndex;
+
         [Flags]
         private enum DirtyFlags
         {
@@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
         public void SetProgram(ShaderCollection program)
         {
             _program = program;
+            _updateDescriptorCacheCbIndex = true;
             _dirty = DirtyFlags.All;
         }
 
@@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan
 
             var dummyBuffer = _dummyBuffer?.GetBuffer();
 
-            var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs);
+            if (_updateDescriptorCacheCbIndex)
+            {
+                _updateDescriptorCacheCbIndex = false;
+                program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
+            }
+
+            var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
 
             if (!program.HasMinimalLayout)
             {
@@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void SignalCommandBufferChange()
         {
+            _updateDescriptorCacheCbIndex = true;
             _dirty = DirtyFlags.All;
 
             _uniformSet.Clear();
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
index eeb25dc0f6..2840dda0f5 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
@@ -1,5 +1,6 @@
 using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
+using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 
@@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan
 {
     class PipelineLayoutCacheEntry
     {
+        // Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts.
+        // It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
+        private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
+        private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
+        private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
+        private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
+
+        private const int MaxPoolSizesPerSet = 2;
+
         private readonly VulkanRenderer _gd;
         private readonly Device _device;
 
         public DescriptorSetLayout[] DescriptorSetLayouts { get; }
         public PipelineLayout PipelineLayout { get; }
 
+        private readonly int[] _consumedDescriptorsPerSet;
+
         private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
+        private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
         private readonly int[] _dsCacheCursor;
         private int _dsLastCbIndex;
+        private int _dsLastSubmissionCount;
 
         private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
         {
@@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan
             bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
         {
             (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
+
+            _consumedDescriptorsPerSet = new int[setDescriptors.Count];
+
+            for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
+            {
+                int count = 0;
+
+                foreach (var descriptor in setDescriptors[setIndex].Descriptors)
+                {
+                    count += descriptor.Count;
+                }
+
+                _consumedDescriptorsPerSet[setIndex] = count;
+            }
         }
 
-        public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
-            VulkanRenderer gd,
-            int commandBufferIndex,
-            int setIndex,
-            out bool isNew)
+        public void UpdateCommandBufferIndex(int commandBufferIndex)
         {
-            if (_dsLastCbIndex != commandBufferIndex)
+            int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
+
+            if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
             {
                 _dsLastCbIndex = commandBufferIndex;
-
-                for (int i = 0; i < _dsCacheCursor.Length; i++)
-                {
-                    _dsCacheCursor[i] = 0;
-                }
+                _dsLastSubmissionCount = submissionCount;
+                Array.Clear(_dsCacheCursor);
             }
 
-            var list = _dsCache[commandBufferIndex][setIndex];
+            _currentDsCache = _dsCache[commandBufferIndex];
+        }
+
+        public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
+        {
+            var list = _currentDsCache[setIndex];
             int index = _dsCacheCursor[setIndex]++;
             if (index == list.Count)
             {
-                var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]);
+                Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
+                poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
+
+                int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
+
+                var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
+                    _gd.Api,
+                    DescriptorSetLayouts[setIndex],
+                    poolSizes,
+                    setIndex,
+                    consumedDescriptors,
+                    false);
+
                 list.Add(dsc);
                 isNew = true;
                 return dsc;
@@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan
             return list[index];
         }
 
+        private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
+        {
+            int count = 1;
+
+            switch (setIndex)
+            {
+                case PipelineBase.UniformSetIndex:
+                    output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity);
+                    break;
+                case PipelineBase.StorageSetIndex:
+                    output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
+                    break;
+                case PipelineBase.TextureSetIndex:
+                    output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity);
+                    output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity);
+                    count = 2;
+                    break;
+                case PipelineBase.ImageSetIndex:
+                    output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity);
+                    output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
+                    count = 2;
+                    break;
+            }
+
+            return output[..count];
+        }
+
         protected virtual unsafe void Dispose(bool disposing)
         {
             if (disposing)
diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
index 346fd9166c..0cb80ac712 100644
--- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
@@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan
             return true;
         }
 
-        public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
-            VulkanRenderer gd,
-            int commandBufferIndex,
-            int setIndex,
-            out bool isNew)
+        public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
         {
-            return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew);
+            _plce.UpdateCommandBufferIndex(commandBufferIndex);
+        }
+
+        public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
+        {
+            return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
         }
 
         protected virtual void Dispose(bool disposing)
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index ac598c5876..a483dc5997 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
 
-            DescriptorSetManager = new DescriptorSetManager(_device);
+            DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
 
             PipelineLayoutCache = new PipelineLayoutCache();