diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
index f5978cefa..3ba084aa5 100644
--- a/src/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
 
         void SetIndexBuffer(BufferRange buffer, IndexType type);
 
-        void SetImage(int binding, ITexture texture, Format imageFormat);
+        void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
 
         void SetLineParameters(float width, bool smooth);
 
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
index b4e966ca8..243480a81 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
@@ -1,17 +1,20 @@
 using Ryujinx.Graphics.GAL.Multithreading.Model;
 using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
 
 namespace Ryujinx.Graphics.GAL.Multithreading.Commands
 {
     struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
     {
         public readonly CommandType CommandType => CommandType.SetImage;
+        private ShaderStage _stage;
         private int _binding;
         private TableRef<ITexture> _texture;
         private Format _imageFormat;
 
-        public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
+        public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
         {
+            _stage = stage;
             _binding = binding;
             _texture = texture;
             _imageFormat = imageFormat;
@@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
 
         public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
         {
-            renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
+            renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
         }
     }
 }
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index f40d9896c..ad50bddf4 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
             _renderer.QueueCommand();
         }
 
-        public void SetImage(int binding, ITexture texture, Format imageFormat)
+        public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
         {
-            _renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
+            _renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
             _renderer.QueueCommand();
         }
 
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 963bd947d..ef5d0deaa 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                         state.Texture = hostTextureRebind;
                         state.ImageFormat = format;
 
-                        _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
+                        _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
                     }
 
                     continue;
@@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                         state.ImageFormat = format;
 
-                        _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
+                        _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
                     }
 
                     state.CachedTexture = texture;
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index c65602b5b..1f02b9d7f 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
                     if (binding.IsImage)
                     {
-                        _context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
+                        _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
                     }
                     else
                     {
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 923c85d7e..e863c696b 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -935,7 +935,7 @@ namespace Ryujinx.Graphics.OpenGL
             SetFrontFace(_frontFace = frontFace.Convert());
         }
 
-        public void SetImage(int binding, ITexture texture, Format imageFormat)
+        public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
         {
             if ((uint)binding < SavedImages)
             {
diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
new file mode 100644
index 000000000..3b44c98c1
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
@@ -0,0 +1,225 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    internal class BarrierBatch : IDisposable
+    {
+        private const int MaxBarriersPerCall = 16;
+
+        private readonly VulkanRenderer _gd;
+
+        private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
+        private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
+        private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
+
+        private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
+        private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
+        private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
+        private int _queuedBarrierCount;
+
+        public BarrierBatch(VulkanRenderer gd)
+        {
+            _gd = gd;
+        }
+
+        private readonly record struct StageFlags : IEquatable<StageFlags>
+        {
+            public readonly PipelineStageFlags Source;
+            public readonly PipelineStageFlags Dest;
+
+            public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
+            {
+                Source = source;
+                Dest = dest;
+            }
+        }
+
+        private readonly struct BarrierWithStageFlags<T> where T : unmanaged
+        {
+            public readonly StageFlags Flags;
+            public readonly T Barrier;
+
+            public BarrierWithStageFlags(StageFlags flags, T barrier)
+            {
+                Flags = flags;
+                Barrier = barrier;
+            }
+
+            public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
+            {
+                Flags = new StageFlags(srcStageFlags, dstStageFlags);
+                Barrier = barrier;
+            }
+        }
+
+        private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
+        {
+            list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
+            _queuedBarrierCount++;
+        }
+
+        public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
+        {
+            QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
+        }
+
+        public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
+        {
+            QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
+        }
+
+        public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
+        {
+            QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
+        }
+
+        public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
+        {
+            while (_queuedBarrierCount > 0)
+            {
+                int memoryCount = 0;
+                int bufferCount = 0;
+                int imageCount = 0;
+
+                bool hasBarrier = false;
+                StageFlags flags = default;
+
+                static void AddBarriers<T>(
+                    Span<T> target,
+                    ref int queuedBarrierCount,
+                    ref bool hasBarrier,
+                    ref StageFlags flags,
+                    ref int count,
+                    List<BarrierWithStageFlags<T>> list) where T : unmanaged
+                {
+                    int firstMatch = -1;
+
+                    for (int i = 0; i < list.Count; i++)
+                    {
+                        BarrierWithStageFlags<T> barrier = list[i];
+
+                        if (!hasBarrier)
+                        {
+                            flags = barrier.Flags;
+                            hasBarrier = true;
+
+                            target[count++] = barrier.Barrier;
+                            queuedBarrierCount--;
+                            firstMatch = i;
+
+                            if (count >= target.Length)
+                            {
+                                break;
+                            }
+                        }
+                        else
+                        {
+                            if (flags.Equals(barrier.Flags))
+                            {
+                                target[count++] = barrier.Barrier;
+                                queuedBarrierCount--;
+
+                                if (firstMatch == -1)
+                                {
+                                    firstMatch = i;
+                                }
+
+                                if (count >= target.Length)
+                                {
+                                    break;
+                                }
+                            }
+                            else
+                            {
+                                // Delete consumed barriers from the first match to the current non-match.
+                                if (firstMatch != -1)
+                                {
+                                    int deleteCount = i - firstMatch;
+                                    list.RemoveRange(firstMatch, deleteCount);
+                                    i -= deleteCount;
+
+                                    firstMatch = -1;
+                                }
+                            }
+                        }
+                    }
+
+                    if (firstMatch == 0)
+                    {
+                        list.Clear();
+                    }
+                    else if (firstMatch != -1)
+                    {
+                        int deleteCount = list.Count - firstMatch;
+
+                        list.RemoveRange(firstMatch, deleteCount);
+                    }
+                }
+
+                if (insideRenderPass)
+                {
+                    // Image barriers queued in the batch are meant to be globally scoped,
+                    // but inside a render pass they're scoped to just the range of the render pass.
+
+                    // On MoltenVK, we just break the rules and always use image barrier.
+                    // On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
+                    // TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
+                    //       notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
+
+                    if (!_gd.IsMoltenVk)
+                    {
+                        foreach (var barrier in _imageBarriers)
+                        {
+                            _memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
+                                barrier.Flags,
+                                new MemoryBarrier()
+                                {
+                                    SType = StructureType.MemoryBarrier,
+                                    SrcAccessMask = barrier.Barrier.SrcAccessMask,
+                                    DstAccessMask = barrier.Barrier.DstAccessMask
+                                }));
+                        }
+
+                        _imageBarriers.Clear();
+                    }
+                }
+
+                AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
+                AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
+                AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
+
+                if (hasBarrier)
+                {
+                    PipelineStageFlags srcStageFlags = flags.Source;
+
+                    if (insideRenderPass)
+                    {
+                        // Inside a render pass, barrier stages can only be from rasterization.
+                        srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
+                    }
+
+                    _gd.Api.CmdPipelineBarrier(
+                        cb,
+                        srcStageFlags,
+                        flags.Dest,
+                        0,
+                        (uint)memoryCount,
+                        _memoryBarrierBatch.Pointer,
+                        (uint)bufferCount,
+                        _bufferBarrierBatch.Pointer,
+                        (uint)imageCount,
+                        _imageBarrierBatch.Pointer);
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            _memoryBarrierBatch.Dispose();
+            _bufferBarrierBatch.Dispose();
+            _imageBarrierBatch.Dispose();
+        }
+    }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
index d40b201da..946e3bc14 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -35,6 +35,36 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
+        private record struct TextureRef
+        {
+            public ShaderStage Stage;
+            public TextureStorage Storage;
+            public Auto<DisposableImageView> View;
+            public Auto<DisposableSampler> Sampler;
+
+            public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
+            {
+                Stage = stage;
+                Storage = storage;
+                View = view;
+                Sampler = sampler;
+            }
+        }
+
+        private record struct ImageRef
+        {
+            public ShaderStage Stage;
+            public TextureStorage Storage;
+            public Auto<DisposableImageView> View;
+
+            public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
+            {
+                Stage = stage;
+                Storage = storage;
+                View = view;
+            }
+        }
+
         private readonly VulkanRenderer _gd;
         private readonly Device _device;
         private readonly PipelineBase _pipeline;
@@ -42,9 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
 
         private readonly BufferRef[] _uniformBufferRefs;
         private readonly BufferRef[] _storageBufferRefs;
-        private readonly Auto<DisposableImageView>[] _textureRefs;
-        private readonly Auto<DisposableSampler>[] _samplerRefs;
-        private readonly Auto<DisposableImageView>[] _imageRefs;
+        private readonly TextureRef[] _textureRefs;
+        private readonly ImageRef[] _imageRefs;
         private readonly TextureBuffer[] _bufferTextureRefs;
         private readonly TextureBuffer[] _bufferImageRefs;
         private readonly Format[] _bufferImageFormats;
@@ -95,9 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
 
             _uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
             _storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
-            _textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
-            _samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
-            _imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
+            _textureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
+            _imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
             _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
             _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
             _bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
@@ -229,6 +257,33 @@ namespace Ryujinx.Graphics.Vulkan
             });
         }
 
+        public void InsertBindingBarriers(CommandBufferScoped cbs)
+        {
+            foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
+            {
+                if (segment.Type == ResourceType.TextureAndSampler)
+                {
+                    for (int i = 0; i < segment.Count; i++)
+                    {
+                        ref var texture = ref _textureRefs[segment.Binding + i];
+                        texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
+                    }
+                }
+            }
+
+            foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex])
+            {
+                if (segment.Type == ResourceType.Image)
+                {
+                    for (int i = 0; i < segment.Count; i++)
+                    {
+                        ref var image = ref _imageRefs[segment.Binding + i];
+                        image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
+                    }
+                }
+            }
+        }
+
         public void AdvancePdSequence()
         {
             if (++_pdSequence == 0)
@@ -258,7 +313,12 @@ namespace Ryujinx.Graphics.Vulkan
             _dirty = DirtyFlags.All;
         }
 
-        public void SetImage(int binding, ITexture image, Format imageFormat)
+        public void SetImage(
+            CommandBufferScoped cbs,
+            ShaderStage stage,
+            int binding,
+            ITexture image,
+            Format imageFormat)
         {
             if (image is TextureBuffer imageBuffer)
             {
@@ -267,11 +327,13 @@ namespace Ryujinx.Graphics.Vulkan
             }
             else if (image is TextureView view)
             {
-                _imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
+                view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
+
+                _imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
             }
             else
             {
-                _imageRefs[binding] = null;
+                _imageRefs[binding] = default;
                 _bufferImageRefs[binding] = null;
                 _bufferImageFormats[binding] = default;
             }
@@ -281,7 +343,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void SetImage(int binding, Auto<DisposableImageView> image)
         {
-            _imageRefs[binding] = image;
+            _imageRefs[binding] = new(ShaderStage.Compute, null, image);
 
             SignalDirty(DirtyFlags.Image);
         }
@@ -366,15 +428,13 @@ namespace Ryujinx.Graphics.Vulkan
             }
             else if (texture is TextureView view)
             {
-                view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
+                view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
 
-                _textureRefs[binding] = view.GetImageView();
-                _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
+                _textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
             }
             else
             {
-                _textureRefs[binding] = null;
-                _samplerRefs[binding] = null;
+                _textureRefs[binding] = default;
                 _bufferTextureRefs[binding] = null;
             }
 
@@ -390,10 +450,9 @@ namespace Ryujinx.Graphics.Vulkan
         {
             if (texture is TextureView view)
             {
-                view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
+                view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
 
-                _textureRefs[binding] = view.GetIdentityImageView();
-                _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
+                _textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
 
                 SignalDirty(DirtyFlags.Texture);
             }
@@ -608,9 +667,10 @@ namespace Ryujinx.Graphics.Vulkan
                         for (int i = 0; i < count; i++)
                         {
                             ref var texture = ref textures[i];
+                            ref var refs = ref _textureRefs[binding + i];
 
-                            texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
-                            texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
+                            texture.ImageView = refs.View?.Get(cbs).Value ?? default;
+                            texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
 
                             if (texture.ImageView.Handle == 0)
                             {
@@ -645,7 +705,7 @@ namespace Ryujinx.Graphics.Vulkan
 
                         for (int i = 0; i < count; i++)
                         {
-                            images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
+                            images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
                         }
 
                         tu.Push<DescriptorImageInfo>(images[..count]);
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
index 5c0fc468b..5a5ddf8c8 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
 
             _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
-            _pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
+            _pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
             _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
             _pipeline.ComputeBarrier();
 
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
index a7dd8eee8..c12933335 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
             var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
 
-            _pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
+            _pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
             _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
 
             _pipeline.ComputeBarrier();
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
index be392fe0e..259be9d64 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
@@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
 
             buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
             _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
-            _pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
+            _pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
             _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
             _pipeline.ComputeBarrier();
 
@@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
-            _pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
+            _pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
             _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
             _pipeline.ComputeBarrier();
 
@@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             _pipeline.Specialize(_specConstants);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
             _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
-            _pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
+            _pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
             _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
             _pipeline.ComputeBarrier();
 
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index af22f2656..8079e5ff9 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -243,41 +243,6 @@ namespace Ryujinx.Graphics.Vulkan
             return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
         }
 
-        public void UpdateModifications()
-        {
-            if (_colors != null)
-            {
-                for (int index = 0; index < _colors.Length; index++)
-                {
-                    _colors[index].Storage.SetModification(
-                        AccessFlags.ColorAttachmentWriteBit,
-                        PipelineStageFlags.ColorAttachmentOutputBit);
-                }
-            }
-
-            _depthStencil?.Storage.SetModification(
-                AccessFlags.DepthStencilAttachmentWriteBit,
-                PipelineStageFlags.LateFragmentTestsBit);
-        }
-
-        public void InsertClearBarrier(CommandBufferScoped cbs, int index)
-        {
-            _colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
-               cbs,
-               AccessFlags.ColorAttachmentWriteBit,
-               PipelineStageFlags.ColorAttachmentOutputBit,
-               insideRenderPass: true);
-        }
-
-        public void InsertClearBarrierDS(CommandBufferScoped cbs)
-        {
-            _depthStencil?.Storage?.InsertReadToWriteBarrier(
-                cbs,
-                AccessFlags.DepthStencilAttachmentWriteBit,
-                PipelineStageFlags.LateFragmentTestsBit,
-                insideRenderPass: true);
-        }
-
         public TextureView[] GetAttachmentViews()
         {
             var result = new TextureView[_attachments.Length];
@@ -297,23 +262,20 @@ namespace Ryujinx.Graphics.Vulkan
             return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
         }
 
-        public void InsertLoadOpBarriers(CommandBufferScoped cbs)
+        public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs)
         {
             if (_colors != null)
             {
                 foreach (var color in _colors)
                 {
                     // If Clear or DontCare were used, this would need to be write bit.
-                    color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit);
-                    color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
+                    color.Storage?.QueueLoadOpBarrier(cbs, false);
                 }
             }
 
-            if (_depthStencil != null)
-            {
-                _depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
-                _depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
-            }
+            _depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
+
+            gd.Barriers.Flush(cbs.CommandBuffer, false, null);
         }
 
         public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
index c0ded5b3b..3efb1119f 100644
--- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -1039,7 +1039,7 @@ namespace Ryujinx.Graphics.Vulkan
                     var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
 
                     _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
-                    _pipeline.SetImage(0, dstView, dstFormat);
+                    _pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat);
 
                     int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
                     int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32;
@@ -1168,7 +1168,7 @@ namespace Ryujinx.Graphics.Vulkan
                     var dstView = Create2DLayerView(dst, dstLayer + z, 0);
 
                     _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
-                    _pipeline.SetImage(0, dstView, format);
+                    _pipeline.SetImage(ShaderStage.Compute, 0, dstView, format);
 
                     _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
 
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 3b3f59259..2bcab5143 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
         private PipelineState _newState;
         private bool _graphicsStateDirty;
         private bool _computeStateDirty;
+        private bool _bindingBarriersDirty;
         private PrimitiveTopology _topology;
 
         private ulong _currentPipelineHandle;
@@ -248,14 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
                 CreateRenderPass();
             }
 
+            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+
             BeginRenderPass();
 
             var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
             var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
             var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
 
-            FramebufferParams.InsertClearBarrier(Cbs, index);
-
             Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
         }
 
@@ -286,13 +287,13 @@ namespace Ryujinx.Graphics.Vulkan
                 CreateRenderPass();
             }
 
+            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+
             BeginRenderPass();
 
             var attachment = new ClearAttachment(flags, 0, clearValue);
             var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
 
-            FramebufferParams.InsertClearBarrierDS(Cbs);
-
             Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
         }
 
@@ -887,9 +888,9 @@ namespace Ryujinx.Graphics.Vulkan
             SignalStateChange();
         }
 
-        public void SetImage(int binding, ITexture image, Format imageFormat)
+        public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat)
         {
-            _descriptorSetUpdater.SetImage(binding, image, imageFormat);
+            _descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat);
         }
 
         public void SetImage(int binding, Auto<DisposableImageView> image)
@@ -977,6 +978,7 @@ namespace Ryujinx.Graphics.Vulkan
             _program = internalProgram;
 
             _descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0);
+            _bindingBarriersDirty = true;
 
             _newState.PipelineLayout = internalProgram.PipelineLayout;
             _newState.StagesCount = (uint)stages.Length;
@@ -1066,7 +1068,6 @@ namespace Ryujinx.Graphics.Vulkan
         private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
         {
             CreateFramebuffer(colors, depthStencil, filterWriteMasked);
-            FramebufferParams?.UpdateModifications();
             CreateRenderPass();
             SignalStateChange();
             SignalAttachmentChange();
@@ -1520,8 +1521,18 @@ namespace Ryujinx.Graphics.Vulkan
                 CreatePipeline(PipelineBindPoint.Compute);
                 _computeStateDirty = false;
                 Pbp = PipelineBindPoint.Compute;
+
+                if (_bindingBarriersDirty)
+                {
+                    // Stale barriers may have been activated by switching program. Emit any that are relevant.
+                    _descriptorSetUpdater.InsertBindingBarriers(Cbs);
+
+                    _bindingBarriersDirty = false;
+                }
             }
 
+            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+
             _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
         }
 
@@ -1575,8 +1586,18 @@ namespace Ryujinx.Graphics.Vulkan
 
                 _graphicsStateDirty = false;
                 Pbp = PipelineBindPoint.Graphics;
+
+                if (_bindingBarriersDirty)
+                {
+                    // Stale barriers may have been activated by switching program. Emit any that are relevant.
+                    _descriptorSetUpdater.InsertBindingBarriers(Cbs);
+
+                    _bindingBarriersDirty = false;
+                }
             }
 
+            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+
             _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
 
             return true;
@@ -1630,6 +1651,8 @@ namespace Ryujinx.Graphics.Vulkan
         {
             if (!RenderPassActive)
             {
+                FramebufferParams.InsertLoadOpBarriers(Gd, Cbs);
+
                 var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
                 var clearValue = new ClearValue();
 
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
index 6c4419cd2..4987548cd 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -269,6 +269,7 @@ namespace Ryujinx.Graphics.Vulkan
                 PreloadCbs = null;
             }
 
+            Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
             CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
             Gd.RegisterFlush();
 
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index bba659215..230dbd4e8 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -433,99 +433,65 @@ namespace Ryujinx.Graphics.Vulkan
             return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
         }
 
-        public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
+        public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
         {
-            _lastModificationAccess = accessFlags;
-            _lastModificationStage = stage;
-        }
+            PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
+            PipelineStageFlags dstStageFlags = depthStencil ?
+                PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit :
+                PipelineStageFlags.ColorAttachmentOutputBit;
 
-        public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass)
-        {
-            var lastReadStage = _lastReadStage;
+            AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess;
+            AccessFlags dstAccessFlags = depthStencil ?
+                AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit :
+                AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit;
 
-            if (insideRenderPass)
+            if (srcAccessFlags != AccessFlags.None)
             {
-                // We can't have barrier from compute inside a render pass,
-                // as it is invalid to specify compute in the subpass dependency stage mask.
+                ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
+                ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
+                    _imageAuto.Get(cbs).Value,
+                    srcAccessFlags,
+                    dstAccessFlags,
+                    aspectFlags,
+                    0,
+                    0,
+                    _info.GetLayers(),
+                    _info.Levels);
 
-                lastReadStage &= ~PipelineStageFlags.ComputeShaderBit;
-            }
+                _gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
 
-            if (lastReadStage != PipelineStageFlags.None)
-            {
-                // This would result in a validation error, but is
-                // required on MoltenVK as the generic barrier results in
-                // severe texture flickering in some scenarios.
-                if (_gd.IsMoltenVk)
-                {
-                    ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
-                    TextureView.InsertImageBarrier(
-                        _gd.Api,
-                        cbs.CommandBuffer,
-                        _imageAuto.Get(cbs).Value,
-                        _lastReadAccess,
-                        dstAccessFlags,
-                        _lastReadStage,
-                        dstStageFlags,
-                        aspectFlags,
-                        0,
-                        0,
-                        _info.GetLayers(),
-                        _info.Levels);
-                }
-                else
-                {
-                    TextureView.InsertMemoryBarrier(
-                        _gd.Api,
-                        cbs.CommandBuffer,
-                        _lastReadAccess,
-                        dstAccessFlags,
-                        lastReadStage,
-                        dstStageFlags);
-                }
-
-                _lastReadAccess = AccessFlags.None;
                 _lastReadStage = PipelineStageFlags.None;
+                _lastReadAccess = AccessFlags.None;
             }
+
+            _lastModificationStage = depthStencil ?
+                PipelineStageFlags.LateFragmentTestsBit :
+                PipelineStageFlags.ColorAttachmentOutputBit;
+
+            _lastModificationAccess = depthStencil ?
+                AccessFlags.DepthStencilAttachmentWriteBit :
+                AccessFlags.ColorAttachmentWriteBit;
         }
 
-        public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
+        public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
         {
             _lastReadAccess |= dstAccessFlags;
             _lastReadStage |= dstStageFlags;
 
             if (_lastModificationAccess != AccessFlags.None)
             {
-                // This would result in a validation error, but is
-                // required on MoltenVK as the generic barrier results in
-                // severe texture flickering in some scenarios.
-                if (_gd.IsMoltenVk)
-                {
-                    ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
-                    TextureView.InsertImageBarrier(
-                        _gd.Api,
-                        cbs.CommandBuffer,
-                        _imageAuto.Get(cbs).Value,
-                        _lastModificationAccess,
-                        dstAccessFlags,
-                        _lastModificationStage,
-                        dstStageFlags,
-                        aspectFlags,
-                        0,
-                        0,
-                        _info.GetLayers(),
-                        _info.Levels);
-                }
-                else
-                {
-                    TextureView.InsertMemoryBarrier(
-                        _gd.Api,
-                        cbs.CommandBuffer,
-                        _lastModificationAccess,
-                        dstAccessFlags,
-                        _lastModificationStage,
-                        dstStageFlags);
-                }
+                ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
+                ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
+                    _imageAuto.Get(cbs).Value,
+                    _lastModificationAccess,
+                    dstAccessFlags,
+                    aspectFlags,
+                    0,
+                    0,
+                    _info.GetLayers(),
+                    _info.Levels);
+
+                _gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
 
                 _lastModificationAccess = AccessFlags.None;
             }
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index ef511565c..31d139652 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -497,6 +497,30 @@ namespace Ryujinx.Graphics.Vulkan
                 null);
         }
 
+        public static ImageMemoryBarrier GetImageBarrier(
+            Image image,
+            AccessFlags srcAccessMask,
+            AccessFlags dstAccessMask,
+            ImageAspectFlags aspectFlags,
+            int firstLayer,
+            int firstLevel,
+            int layers,
+            int levels)
+        {
+            return new()
+            {
+                SType = StructureType.ImageMemoryBarrier,
+                SrcAccessMask = srcAccessMask,
+                DstAccessMask = dstAccessMask,
+                SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+                DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+                Image = image,
+                OldLayout = ImageLayout.General,
+                NewLayout = ImageLayout.General,
+                SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
+            };
+        }
+
         public static unsafe void InsertImageBarrier(
             Vk api,
             CommandBuffer commandBuffer,
@@ -511,18 +535,15 @@ namespace Ryujinx.Graphics.Vulkan
             int layers,
             int levels)
         {
-            ImageMemoryBarrier memoryBarrier = new()
-            {
-                SType = StructureType.ImageMemoryBarrier,
-                SrcAccessMask = srcAccessMask,
-                DstAccessMask = dstAccessMask,
-                SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
-                DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
-                Image = image,
-                OldLayout = ImageLayout.General,
-                NewLayout = ImageLayout.General,
-                SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
-            };
+            ImageMemoryBarrier memoryBarrier = GetImageBarrier(
+                image,
+                srcAccessMask,
+                dstAccessMask,
+                aspectFlags,
+                firstLayer,
+                firstLevel,
+                layers,
+                levels);
 
             api.CmdPipelineBarrier(
                 commandBuffer,
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 6aa46b79a..434545fe0 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -68,6 +68,8 @@ namespace Ryujinx.Graphics.Vulkan
         internal HelperShader HelperShader { get; private set; }
         internal PipelineFull PipelineInternal => _pipeline;
 
+        internal BarrierBatch Barriers { get; private set; }
+
         public IPipeline Pipeline => _pipeline;
 
         public IWindow Window => _window;
@@ -381,6 +383,8 @@ namespace Ryujinx.Graphics.Vulkan
 
             HelperShader = new HelperShader(this, _device);
 
+            Barriers = new BarrierBatch(this);
+
             _counters = new Counters(this, _device, _pipeline);
         }
 
@@ -914,6 +918,7 @@ namespace Ryujinx.Graphics.Vulkan
             BufferManager.Dispose();
             DescriptorSetManager.Dispose();
             PipelineLayoutCache.Dispose();
+            Barriers.Dispose();
 
             MemoryAllocator.Dispose();