diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
index cd509471e9..2ac738fdfb 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
@@ -202,57 +202,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
                 _channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
             }
 
-            _channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
-            _channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
+            _channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
 
-            int maxTextureBinding = -1;
-            int maxImageBinding = -1;
-
-            TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
-
-            for (int index = 0; index < info.Textures.Count; index++)
-            {
-                var descriptor = info.Textures[index];
-
-                Target target = ShaderTexture.GetTarget(descriptor.Type);
-
-                textureBindings[index] = new TextureBindingInfo(
-                    target,
-                    descriptor.Binding,
-                    descriptor.CbufSlot,
-                    descriptor.HandleIndex,
-                    descriptor.Flags);
-
-                if (descriptor.Binding > maxTextureBinding)
-                {
-                    maxTextureBinding = descriptor.Binding;
-                }
-            }
-
-            TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
-
-            for (int index = 0; index < info.Images.Count; index++)
-            {
-                var descriptor = info.Images[index];
-
-                Target target = ShaderTexture.GetTarget(descriptor.Type);
-                Format format = ShaderTexture.GetFormat(descriptor.Format);
-
-                imageBindings[index] = new TextureBindingInfo(
-                    target,
-                    format,
-                    descriptor.Binding,
-                    descriptor.CbufSlot,
-                    descriptor.HandleIndex,
-                    descriptor.Flags);
-
-                if (descriptor.Binding > maxImageBinding)
-                {
-                    maxImageBinding = descriptor.Binding;
-                }
-            }
-
-            _channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
+            _channel.TextureManager.SetComputeBindings(cs.Bindings);
 
             // Should never return false for mismatching spec state, since the shader was fetched above.
             _channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 8da5ea5ef1..fe7e0d09f0 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -1257,88 +1257,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 UpdateUserClipState();
             }
 
+            UpdateShaderBindings(gs.Bindings);
+
             for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
             {
-                UpdateStageBindings(stageIndex, gs.Shaders[stageIndex + 1]?.Info);
+                _currentProgramInfo[stageIndex] = gs.Shaders[stageIndex + 1]?.Info;
             }
 
             _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
         }
 
         /// <summary>
-        /// Updates bindings consumed by the shader stage on the texture and buffer managers.
+        /// Updates bindings consumed by the shader on the texture and buffer managers.
         /// </summary>
-        /// <param name="stage">Shader stage to have the bindings updated</param>
-        /// <param name="info">Shader stage bindings info</param>
-        private void UpdateStageBindings(int stage, ShaderProgramInfo info)
+        /// <param name="bindings">Bindings for the active shader</param>
+        private void UpdateShaderBindings(CachedShaderBindings bindings)
         {
-            _currentProgramInfo[stage] = info;
-
-            if (info == null)
-            {
-                _channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
-                _channel.TextureManager.RentGraphicsImageBindings(stage, 0);
-                _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
-                _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
-                return;
-            }
-
-            int maxTextureBinding = -1;
-            int maxImageBinding = -1;
-
-            Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
-
-            if (info.UsesRtLayer)
-            {
-                _vtgWritesRtLayer = true;
-            }
-
-            for (int index = 0; index < info.Textures.Count; index++)
-            {
-                var descriptor = info.Textures[index];
-
-                Target target = ShaderTexture.GetTarget(descriptor.Type);
-
-                textureBindings[index] = new TextureBindingInfo(
-                    target,
-                    descriptor.Binding,
-                    descriptor.CbufSlot,
-                    descriptor.HandleIndex,
-                    descriptor.Flags);
-
-                if (descriptor.Binding > maxTextureBinding)
-                {
-                    maxTextureBinding = descriptor.Binding;
-                }
-            }
-
-            TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
-
-            for (int index = 0; index < info.Images.Count; index++)
-            {
-                var descriptor = info.Images[index];
-
-                Target target = ShaderTexture.GetTarget(descriptor.Type);
-                Format format = ShaderTexture.GetFormat(descriptor.Format);
-
-                imageBindings[index] = new TextureBindingInfo(
-                    target,
-                    format,
-                    descriptor.Binding,
-                    descriptor.CbufSlot,
-                    descriptor.HandleIndex,
-                    descriptor.Flags);
-
-                if (descriptor.Binding > maxImageBinding)
-                {
-                    maxImageBinding = descriptor.Binding;
-                }
-            }
-
-            _channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
-
-            _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
-            _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
+            _channel.TextureManager.SetGraphicsBindings(bindings);
+            _channel.BufferManager.SetGraphicsBufferBindings(bindings);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 892d9f6a78..0787ce3d50 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -37,8 +37,8 @@ namespace Ryujinx.Graphics.Gpu.Image
         private TexturePool _cachedTexturePool;
         private SamplerPool _cachedSamplerPool;
 
-        private readonly TextureBindingInfo[][] _textureBindings;
-        private readonly TextureBindingInfo[][] _imageBindings;
+        private TextureBindingInfo[][] _textureBindings;
+        private TextureBindingInfo[][] _imageBindings;
 
         private struct TextureState
         {
@@ -56,9 +56,6 @@ namespace Ryujinx.Graphics.Gpu.Image
         private TextureState[] _textureState;
         private TextureState[] _imageState;
 
-        private int[] _textureBindingsCount;
-        private int[] _imageBindingsCount;
-
         private int _texturePoolSequence;
         private int _samplerPoolSequence;
 
@@ -101,9 +98,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             _textureState = new TextureState[InitialTextureStateSize];
             _imageState   = new TextureState[InitialImageStateSize];
 
-            _textureBindingsCount = new int[stages];
-            _imageBindingsCount = new int[stages];
-
             for (int stage = 0; stage < stages; stage++)
             {
                 _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
@@ -112,39 +106,15 @@ namespace Ryujinx.Graphics.Gpu.Image
         }
 
         /// <summary>
-        /// Rents the texture bindings array for a given stage, so that they can be modified.
+        /// Sets the texture and image bindings.
         /// </summary>
-        /// <param name="stage">Shader stage number, or 0 for compute shaders</param>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The texture bindings array</returns>
-        public TextureBindingInfo[] RentTextureBindings(int stage, int count)
+        /// <param name="bindings">Bindings for the active shader</param>
+        public void SetBindings(CachedShaderBindings bindings)
         {
-            if (count > _textureBindings[stage].Length)
-            {
-                Array.Resize(ref _textureBindings[stage], count);
-            }
+            _textureBindings = bindings.TextureBindings;
+            _imageBindings = bindings.ImageBindings;
 
-            _textureBindingsCount[stage] = count;
-
-            return _textureBindings[stage];
-        }
-
-        /// <summary>
-        /// Rents the image bindings array for a given stage, so that they can be modified.
-        /// </summary>
-        /// <param name="stage">Shader stage number, or 0 for compute shaders</param>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The image bindings array</returns>
-        public TextureBindingInfo[] RentImageBindings(int stage, int count)
-        {
-            if (count > _imageBindings[stage].Length)
-            {
-                Array.Resize(ref _imageBindings[stage], count);
-            }
-
-            _imageBindingsCount[stage] = count;
-
-            return _imageBindings[stage];
+            SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
         }
 
         /// <summary>
@@ -257,7 +227,7 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                         case ShaderStage.Vertex:
                             int fragmentIndex = (int)ShaderStage.Fragment - 1;
-                            index += _textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex];
+                            index += _textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length;
 
                             result = texture.ScaleFactor;
                             break;
@@ -284,7 +254,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// </summary>
         private bool VertexRequiresScale()
         {
-            for (int i = 0; i < _textureBindingsCount[0]; i++)
+            for (int i = 0; i < _textureBindings[0].Length; i++)
             {
                 if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
                 {
@@ -292,7 +262,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 }
             }
 
-            for (int i = 0; i < _imageBindingsCount[0]; i++)
+            for (int i = 0; i < _imageBindings[0].Length; i++)
             {
                 if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
                 {
@@ -309,10 +279,10 @@ namespace Ryujinx.Graphics.Gpu.Image
         private void CommitRenderScale()
         {
             // Stage 0 total: Compute or Vertex.
-            int total = _textureBindingsCount[0] + _imageBindingsCount[0];
+            int total = _textureBindings[0].Length + _imageBindings[0].Length;
 
             int fragmentIndex = (int)ShaderStage.Fragment - 1;
-            int fragmentTotal = _isCompute ? 0 : (_textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex]);
+            int fragmentTotal = _isCompute ? 0 : (_textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length);
 
             if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
             {
@@ -481,7 +451,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             bool poolModified,
             ShaderSpecializationState specState)
         {
-            int textureCount = _textureBindingsCount[stageIndex];
+            int textureCount = _textureBindings[stageIndex].Length;
             if (textureCount == 0)
             {
                 return true;
@@ -609,7 +579,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
         private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
         {
-            int imageCount = _imageBindingsCount[stageIndex];
+            int imageCount = _imageBindings[stageIndex].Length;
             if (imageCount == 0)
             {
                 return true;
@@ -622,7 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             }
 
             // Scales for images appear after the texture ones.
-            int baseScaleIndex = _textureBindingsCount[stageIndex];
+            int baseScaleIndex = _textureBindings[stageIndex].Length;
 
             int cachedTextureBufferIndex = -1;
             int cachedSamplerBufferIndex = -1;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index fe0175a6a6..083de64c55 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -57,45 +57,21 @@ namespace Ryujinx.Graphics.Gpu.Image
         }
 
         /// <summary>
-        /// Rents the texture bindings array of the compute pipeline.
+        /// Sets the texture and image bindings for the compute pipeline.
         /// </summary>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The texture bindings array</returns>
-        public TextureBindingInfo[] RentComputeTextureBindings(int count)
+        /// <param name="bindings">Bindings for the active shader</param>
+        public void SetComputeBindings(CachedShaderBindings bindings)
         {
-            return _cpBindingsManager.RentTextureBindings(0, count);
+            _cpBindingsManager.SetBindings(bindings);
         }
 
         /// <summary>
-        /// Rents the texture bindings array for a given stage on the graphics pipeline.
+        /// Sets the texture and image bindings for the graphics pipeline.
         /// </summary>
-        /// <param name="stage">The index of the shader stage to bind the textures</param>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The texture bindings array</returns>
-        public TextureBindingInfo[] RentGraphicsTextureBindings(int stage, int count)
+        /// <param name="bindings">Bindings for the active shader</param>
+        public void SetGraphicsBindings(CachedShaderBindings bindings)
         {
-            return _gpBindingsManager.RentTextureBindings(stage, count);
-        }
-
-        /// <summary>
-        /// Rents the image bindings array of the compute pipeline.
-        /// </summary>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The image bindings array</returns>
-        public TextureBindingInfo[] RentComputeImageBindings(int count)
-        {
-            return _cpBindingsManager.RentImageBindings(0, count);
-        }
-
-        /// <summary>
-        /// Rents the image bindings array for a given stage on the graphics pipeline.
-        /// </summary>
-        /// <param name="stage">The index of the shader stage to bind the images</param>
-        /// <param name="count">The number of bindings needed</param>
-        /// <returns>The image bindings array</returns>
-        public TextureBindingInfo[] RentGraphicsImageBindings(int stage, int count)
-        {
-            return _gpBindingsManager.RentImageBindings(stage, count);
+            _gpBindingsManager.SetBindings(bindings);
         }
 
         /// <summary>
@@ -107,16 +83,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             _cpBindingsManager.SetTextureBufferIndex(index);
         }
 
-        /// <summary>
-        /// Sets the max binding indexes on the compute pipeline.
-        /// </summary>
-        /// <param name="maxTextureBinding">The maximum texture binding</param>
-        /// <param name="maxImageBinding">The maximum image binding</param>
-        public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding)
-        {
-            _cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
-        }
-
         /// <summary>
         /// Sets the texture constant buffer index on the graphics pipeline.
         /// </summary>
@@ -126,16 +92,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             _gpBindingsManager.SetTextureBufferIndex(index);
         }
 
-        /// <summary>
-        /// Sets the max binding indexes on the graphics pipeline.
-        /// </summary>
-        /// <param name="maxTextureBinding">The maximum texture binding</param>
-        /// <param name="maxImageBinding">The maximum image binding</param>
-        public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding)
-        {
-            _gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
-        }
-
         /// <summary>
         /// Sets the current sampler pool on the compute pipeline.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index f0831e1582..1728cdb582 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -1,10 +1,10 @@
 using Ryujinx.Common;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Shader;
 using Ryujinx.Graphics.Shader;
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using System.Runtime.CompilerServices;
 
 namespace Ryujinx.Graphics.Gpu.Memory
@@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
             /// <summary>
             /// Shader buffer binding information.
             /// </summary>
-            public BufferDescriptor[] Bindings { get; }
+            public BufferDescriptor[] Bindings { get; private set; }
 
             /// <summary>
             /// Buffer regions.
@@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
             /// Sets shader buffer binding information.
             /// </summary>
             /// <param name="descriptors">Buffer binding information</param>
-            public void SetBindings(ReadOnlyCollection<BufferDescriptor> descriptors)
+            public void SetBindings(BufferDescriptor[] descriptors)
             {
                 if (descriptors == null)
                 {
@@ -86,8 +86,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
                     return;
                 }
 
-                descriptors.CopyTo(Bindings, 0);
-                Count = descriptors.Count;
+                if ((Count = descriptors.Length) != 0)
+                {
+                    Bindings = descriptors;
+                }
             }
         }
 
@@ -320,41 +322,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// <summary>
         /// Sets the binding points for the storage buffers bound on the compute pipeline.
         /// </summary>
-        /// <param name="descriptors">Buffer descriptors with the binding point values</param>
-        public void SetComputeStorageBufferBindings(ReadOnlyCollection<BufferDescriptor> descriptors)
+        /// <param name="bindings">Bindings for the active shader</param>
+        public void SetComputeBufferBindings(CachedShaderBindings bindings)
         {
-            _cpStorageBuffers.SetBindings(descriptors);
+            _cpStorageBuffers.SetBindings(bindings.StorageBufferBindings[0]);
+            _cpUniformBuffers.SetBindings(bindings.ConstantBufferBindings[0]);
         }
 
         /// <summary>
         /// Sets the binding points for the storage buffers bound on the graphics pipeline.
         /// </summary>
-        /// <param name="stage">Index of the shader stage</param>
-        /// <param name="descriptors">Buffer descriptors with the binding point values</param>
-        public void SetGraphicsStorageBufferBindings(int stage, ReadOnlyCollection<BufferDescriptor> descriptors)
+        /// <param name="bindings">Bindings for the active shader</param>
+        public void SetGraphicsBufferBindings(CachedShaderBindings bindings)
         {
-            _gpStorageBuffers[stage].SetBindings(descriptors);
+            for (int i = 0; i < Constants.ShaderStages; i++)
+            {
+                _gpStorageBuffers[i].SetBindings(bindings.StorageBufferBindings[i]);
+                _gpUniformBuffers[i].SetBindings(bindings.ConstantBufferBindings[i]);
+            }
+
             _gpStorageBuffersDirty = true;
-        }
-
-        /// <summary>
-        /// Sets the binding points for the uniform buffers bound on the compute pipeline.
-        /// </summary>
-        /// <param name="descriptors">Buffer descriptors with the binding point values</param>
-        public void SetComputeUniformBufferBindings(ReadOnlyCollection<BufferDescriptor> descriptors)
-        {
-            _cpUniformBuffers.SetBindings(descriptors);
-        }
-
-        /// <summary>
-        /// Sets the enabled uniform buffers mask on the graphics pipeline.
-        /// Each bit set on the mask indicates that the respective buffer index is enabled.
-        /// </summary>
-        /// <param name="stage">Index of the shader stage</param>
-        /// <param name="descriptors">Buffer descriptors with the binding point values</param>
-        public void SetGraphicsUniformBufferBindings(int stage, ReadOnlyCollection<BufferDescriptor> descriptors)
-        {
-            _gpUniformBuffers[stage].SetBindings(descriptors);
             _gpUniformBuffersDirty = true;
         }
 
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
new file mode 100644
index 0000000000..1734f08a22
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
@@ -0,0 +1,103 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+    /// <summary>
+    /// A collection of shader bindings ready for insertion into the buffer and texture managers.
+    /// </summary>
+    internal class CachedShaderBindings
+    {
+        public TextureBindingInfo[][] TextureBindings { get; }
+        public TextureBindingInfo[][] ImageBindings { get; }
+        public BufferDescriptor[][] ConstantBufferBindings { get; }
+        public BufferDescriptor[][] StorageBufferBindings { get; }
+
+        public int MaxTextureBinding { get; }
+        public int MaxImageBinding { get; }
+
+        /// <summary>
+        /// Create a new cached shader bindings collection.
+        /// </summary>
+        /// <param name="isCompute">Whether the shader is for compute</param>
+        /// <param name="stages">The stages used by the shader</param>
+        public CachedShaderBindings(bool isCompute, CachedShaderStage[] stages)
+        {
+            int stageCount = isCompute ? 1 : Constants.ShaderStages;
+
+            TextureBindings = new TextureBindingInfo[stageCount][];
+            ImageBindings = new TextureBindingInfo[stageCount][];
+            ConstantBufferBindings = new BufferDescriptor[stageCount][];
+            StorageBufferBindings = new BufferDescriptor[stageCount][];
+
+            int maxTextureBinding = -1;
+            int maxImageBinding = -1;
+            int offset = isCompute ? 0 : 1;
+
+            for (int i = 0; i < stageCount; i++)
+            {
+                CachedShaderStage stage = stages[i + offset];
+
+                if (stage == null)
+                {
+                    TextureBindings[i] = Array.Empty<TextureBindingInfo>();
+                    ImageBindings[i] = Array.Empty<TextureBindingInfo>();
+                    ConstantBufferBindings[i] = Array.Empty<BufferDescriptor>();
+                    StorageBufferBindings[i] = Array.Empty<BufferDescriptor>();
+
+                    continue;
+                }
+
+                TextureBindings[i] = stage.Info.Textures.Select(descriptor =>
+                {
+                    Target target = ShaderTexture.GetTarget(descriptor.Type);
+
+                    var result = new TextureBindingInfo(
+                        target,
+                        descriptor.Binding,
+                        descriptor.CbufSlot,
+                        descriptor.HandleIndex,
+                        descriptor.Flags);
+
+                    if (descriptor.Binding > maxTextureBinding)
+                    {
+                        maxTextureBinding = descriptor.Binding;
+                    }
+
+                    return result;
+                }).ToArray();
+
+                ImageBindings[i] = stage.Info.Images.Select(descriptor =>
+                {
+                    Target target = ShaderTexture.GetTarget(descriptor.Type);
+                    Format format = ShaderTexture.GetFormat(descriptor.Format);
+
+                    var result = new TextureBindingInfo(
+                        target,
+                        format,
+                        descriptor.Binding,
+                        descriptor.CbufSlot,
+                        descriptor.HandleIndex,
+                        descriptor.Flags);
+
+                    if (descriptor.Binding > maxImageBinding)
+                    {
+                        maxImageBinding = descriptor.Binding;
+                    }
+
+                    return result;
+                }).ToArray();
+
+                ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray();
+                StorageBufferBindings[i] = stage.Info.SBuffers.ToArray();
+            }
+
+            MaxTextureBinding = maxTextureBinding;
+            MaxImageBinding = maxImageBinding;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
index 69fcb27804..ff9c39a197 100644
--- a/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
@@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public CachedShaderStage[] Shaders { get; }
 
+        /// <summary>
+        /// Cached shader bindings, ready for placing into the bindings manager.
+        /// </summary>
+        public CachedShaderBindings Bindings { get; }
+
         /// <summary>
         /// Creates a new instance of the shader bundle.
         /// </summary>
@@ -37,6 +42,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             Shaders = shaders;
 
             SpecializationState.Prepare(shaders);
+            Bindings = new CachedShaderBindings(shaders.Length == 1, shaders);
         }
 
         /// <summary>