From 1a919e99b29fff4e2158e622cb3dfbee21293b6d Mon Sep 17 00:00:00 2001
From: riperiperi <rhy3756547@hotmail.com>
Date: Thu, 18 Jul 2024 00:21:32 +0100
Subject: Vulkan: Defer guest barriers, and improve image barrier timings
 (#7012)

* More guarantees for buffer correct placement, defer guest requested buffers

* Split RP on indirect barrier rn

* Better handling for feedback loops.

* Qualcomm barriers suck too

* Fix condition

* Remove unused field

* Allow render pass barriers on turnip for now
---
 src/Ryujinx.Graphics.GAL/ResourceLayout.cs         |   4 +-
 .../Shader/ShaderInfoBuilder.cs                    |  24 +-
 src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs        | 260 +++++++++++++++++++--
 .../Effects/FsrScalingFilter.cs                    |   4 +-
 .../Effects/FxaaPostProcessingEffect.cs            |   2 +-
 .../Effects/SmaaPostProcessingEffect.cs            |   6 +-
 src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs   |  17 +-
 src/Ryujinx.Graphics.Vulkan/HelperShader.cs        |  10 +-
 src/Ryujinx.Graphics.Vulkan/PipelineBase.cs        | 101 ++------
 src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs   |  33 ++-
 src/Ryujinx.Graphics.Vulkan/PipelineFull.cs        |   2 +-
 src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs    |  40 +++-
 .../ResourceLayoutBuilder.cs                       |   4 +-
 src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs    |  71 ++++++
 src/Ryujinx.Graphics.Vulkan/TextureCopy.cs         |   2 +-
 src/Ryujinx.Graphics.Vulkan/TextureStorage.cs      |  19 +-
 src/Ryujinx.Graphics.Vulkan/TextureView.cs         |   4 +-
 src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs      |   5 +
 18 files changed, 452 insertions(+), 156 deletions(-)

(limited to 'src')

diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
index 998c046f..b7464ee1 100644
--- a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
+++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
@@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
         public int ArrayLength { get; }
         public ResourceType Type { get; }
         public ResourceStages Stages { get; }
+        public bool Write { get; }
 
-        public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
+        public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write)
         {
             Binding = binding;
             ArrayLength = arrayLength;
             Type = type;
             Stages = stages;
+            Write = write;
         }
 
         public override int GetHashCode()
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
index 42b2cbb5..49823562 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
@@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
             ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
 
             PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
-            PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers);
+            PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
             PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
-            PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
+            PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
         }
 
         /// <summary>
@@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="setIndex">Resource set index where the resources are used</param>
         /// <param name="start">First binding number</param>
         /// <param name="count">Amount of bindings</param>
-        private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
+        /// <param name="write">True if the binding is written from the shader, false otherwise</param>
+        private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
         {
             AddDescriptor(stages, type, setIndex, start, count);
-            AddUsage(stages, type, setIndex, start, count);
+            AddUsage(stages, type, setIndex, start, count, write);
         }
 
         /// <summary>
@@ -216,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="setIndex">Descriptor set number where the resource will be bound</param>
         /// <param name="binding">Binding number where the resource will be bound</param>
         /// <param name="count">Number of resources bound at the binding location</param>
-        private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
+        /// <param name="write">True if the binding is written from the shader, false otherwise</param>
+        private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false)
         {
             for (int index = 0; index < count; index++)
             {
-                _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
+                _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write));
             }
         }
 
@@ -238,7 +240,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     buffer.Binding,
                     1,
                     isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
-                    stages));
+                    stages,
+                    buffer.Flags.HasFlag(BufferUsageFlags.Write)));
             }
         }
 
@@ -254,7 +257,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
             {
                 ResourceType type = GetTextureResourceType(texture, isImage);
 
-                GetUsages(texture.Set).Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
+                GetUsages(texture.Set).Add(new ResourceUsage(
+                    texture.Binding,
+                    texture.ArrayLength,
+                    type,
+                    stages,
+                    texture.Flags.HasFlag(TextureUsageFlags.ImageStore)));
             }
         }
 
diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
index 24642af2..a6a006bb 100644
--- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
@@ -1,6 +1,7 @@
 using Silk.NET.Vulkan;
 using System;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 
 namespace Ryujinx.Graphics.Vulkan
 {
@@ -8,22 +9,64 @@ namespace Ryujinx.Graphics.Vulkan
     {
         private const int MaxBarriersPerCall = 16;
 
+        private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
+        private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
+        private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
+
         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 readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
+        private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
+        private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
         private int _queuedBarrierCount;
 
+        private enum IncoherentBarrierType
+        {
+            None,
+            Texture,
+            All,
+            CommandBuffer
+        }
+
+        private PipelineStageFlags _incoherentBufferWriteStages;
+        private PipelineStageFlags _incoherentTextureWriteStages;
+        private PipelineStageFlags _extraStages;
+        private IncoherentBarrierType _queuedIncoherentBarrier;
+
         public BarrierBatch(VulkanRenderer gd)
         {
             _gd = gd;
         }
 
+        public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
+        {
+            AccessFlags access = BufferAccess;
+            PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
+
+            if (gd.TransformFeedbackApi != null)
+            {
+                access |= AccessFlags.TransformFeedbackWriteBitExt;
+                stages |= PipelineStageFlags.TransformFeedbackBitExt;
+            }
+
+            if (!gd.IsTBDR)
+            {
+                // Desktop GPUs can transform image barriers into memory barriers.
+
+                access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
+                access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
+
+                stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
+                stages |= PipelineStageFlags.ColorAttachmentOutputBit;
+            }
+
+            return (access, stages);
+        }
+
         private readonly record struct StageFlags : IEquatable<StageFlags>
         {
             public readonly PipelineStageFlags Source;
@@ -36,47 +79,130 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
-        private readonly struct BarrierWithStageFlags<T> where T : unmanaged
+        private readonly struct BarrierWithStageFlags<T, T2> where T : unmanaged
         {
             public readonly StageFlags Flags;
             public readonly T Barrier;
+            public readonly T2 Resource;
 
             public BarrierWithStageFlags(StageFlags flags, T barrier)
             {
                 Flags = flags;
                 Barrier = barrier;
+                Resource = default;
             }
 
-            public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
+            public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
             {
                 Flags = new StageFlags(srcStageFlags, dstStageFlags);
                 Barrier = barrier;
+                Resource = resource;
             }
         }
 
-        private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
+        private void QueueBarrier<T, T2>(List<BarrierWithStageFlags<T, T2>> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
         {
-            list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
+            list.Add(new BarrierWithStageFlags<T, T2>(srcStageFlags, dstStageFlags, barrier, resource));
             _queuedBarrierCount++;
         }
 
         public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
         {
-            QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
+            QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
         }
 
         public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
         {
-            QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
+            QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
         }
 
-        public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
+        public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
         {
-            QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
+            QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
         }
 
-        public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass)
         {
+            if (_queuedIncoherentBarrier > IncoherentBarrierType.None)
+            {
+                // We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier)
+                bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None;
+                bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None;
+                bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture;
+
+                if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite))
+                {
+                    AccessFlags access = BaseAccess;
+
+                    PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit;
+
+                    if (hasBufferBarrier && hasBufferWrite)
+                    {
+                        access |= BufferAccess;
+
+                        if (_gd.TransformFeedbackApi != null)
+                        {
+                            access |= AccessFlags.TransformFeedbackWriteBitExt;
+                            stages |= PipelineStageFlags.TransformFeedbackBitExt;
+                        }
+                    }
+
+                    if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer)
+                    {
+                        access |= CommandBufferAccess;
+                        stages |= PipelineStageFlags.DrawIndirectBit;
+                    }
+
+                    MemoryBarrier barrier = new MemoryBarrier()
+                    {
+                        SType = StructureType.MemoryBarrier,
+                        SrcAccessMask = access,
+                        DstAccessMask = access
+                    };
+
+                    QueueBarrier(barrier, stages, stages);
+
+                    _incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None;
+
+                    if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture)
+                    {
+                        if (program != null)
+                        {
+                            _incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages;
+                        }
+                        else
+                        {
+                            _incoherentBufferWriteStages = PipelineStageFlags.None;
+                        }
+                    }
+
+                    _queuedIncoherentBarrier = IncoherentBarrierType.None;
+                }
+            }
+        }
+
+        public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
+        {
+            Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
+        }
+
+        public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
+        {
+            if (program != null)
+            {
+                _incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
+                _incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
+            }
+
+            FlushMemoryBarrier(program, inRenderPass);
+
+            if (!inRenderPass && rpHolder != null)
+            {
+                // Render pass is about to begin. Queue any fences that normally interrupt the pass.
+                rpHolder.InsertForcedFences(cbs);
+            }
+
             while (_queuedBarrierCount > 0)
             {
                 int memoryCount = 0;
@@ -86,20 +212,20 @@ namespace Ryujinx.Graphics.Vulkan
                 bool hasBarrier = false;
                 StageFlags flags = default;
 
-                static void AddBarriers<T>(
+                static void AddBarriers<T, T2>(
                     Span<T> target,
                     ref int queuedBarrierCount,
                     ref bool hasBarrier,
                     ref StageFlags flags,
                     ref int count,
-                    List<BarrierWithStageFlags<T>> list) where T : unmanaged
+                    List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
                 {
                     int firstMatch = -1;
                     int end = list.Count;
 
                     for (int i = 0; i < list.Count; i++)
                     {
-                        BarrierWithStageFlags<T> barrier = list[i];
+                        BarrierWithStageFlags<T, T2> barrier = list[i];
 
                         if (!hasBarrier)
                         {
@@ -162,21 +288,60 @@ namespace Ryujinx.Graphics.Vulkan
                     }
                 }
 
-                if (insideRenderPass)
+                if (inRenderPass && _imageBarriers.Count > 0)
                 {
                     // 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.
+                    // Generally, we want to avoid this from happening in the future, so flag the texture to immediately
+                    // emit a barrier whenever the current render pass is bound again.
+
+                    bool anyIsNonAttachment = false;
+
+                    foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
+                    {
+                        // If the binding is an attachment, don't add it as a forced fence.
+                        bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
+
+                        if (!isAttachment)
+                        {
+                            rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
+                            anyIsNonAttachment = true;
+                        }
+                    }
+
+                    if (_gd.IsTBDR)
+                    {
+                        if (!_gd.IsMoltenVk)
+                        {
+                            if (!anyIsNonAttachment)
+                            {
+                                // This case is a feedback loop. To prevent this from causing an absolute performance disaster,
+                                // remove the barriers entirely.
+                                // If this is not here, there will be a lot of single draw render passes.
+                                // TODO: explicit handling for feedback loops, likely outside this class.
 
-                    if (!_gd.IsMoltenVk)
+                                _queuedBarrierCount -= _imageBarriers.Count;
+                                _imageBarriers.Clear();
+                            }
+                            else
+                            {
+                                // TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available.
+                                // Metal already has hazard tracking so MVK doesn't need this.
+                                endRenderPass();
+                                inRenderPass = false;
+                            }
+                        }
+                    }
+                    else
                     {
+                        // Generic pipeline memory barriers will work for desktop GPUs.
+                        // They do require a few more access flags on the subpass dependency, though.
                         foreach (var barrier in _imageBarriers)
                         {
-                            _memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
+                            _memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
                                 barrier.Flags,
                                 new MemoryBarrier()
                                 {
@@ -190,6 +355,22 @@ namespace Ryujinx.Graphics.Vulkan
                     }
                 }
 
+                if (inRenderPass && _memoryBarriers.Count > 0)
+                {
+                    PipelineStageFlags allFlags = PipelineStageFlags.None;
+
+                    foreach (var barrier in _memoryBarriers)
+                    {
+                        allFlags |= barrier.Flags.Dest;
+                    }
+
+                    if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags))
+                    {
+                        endRenderPass();
+                        inRenderPass = false;
+                    }
+                }
+
                 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);
@@ -198,14 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
                 {
                     PipelineStageFlags srcStageFlags = flags.Source;
 
-                    if (insideRenderPass)
+                    if (inRenderPass)
                     {
                         // Inside a render pass, barrier stages can only be from rasterization.
                         srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
                     }
 
                     _gd.Api.CmdPipelineBarrier(
-                        cb,
+                        cbs.CommandBuffer,
                         srcStageFlags,
                         flags.Dest,
                         0,
@@ -219,6 +400,41 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
+        private void QueueIncoherentBarrier(IncoherentBarrierType type)
+        {
+            if (type > _queuedIncoherentBarrier)
+            {
+                _queuedIncoherentBarrier = type;
+            }
+        }
+
+        public void QueueTextureBarrier()
+        {
+            QueueIncoherentBarrier(IncoherentBarrierType.Texture);
+        }
+
+        public void QueueMemoryBarrier()
+        {
+            QueueIncoherentBarrier(IncoherentBarrierType.All);
+        }
+
+        public void QueueCommandBufferBarrier()
+        {
+            QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer);
+        }
+
+        public void EnableTfbBarriers(bool enable)
+        {
+            if (enable)
+            {
+                _extraStages |= PipelineStageFlags.TransformFeedbackBitExt;
+            }
+            else
+            {
+                _extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt;
+            }
+        }
+
         public void Dispose()
         {
             _memoryBarrierBatch.Dispose();
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
index 5a5ddf8c..c4501ca1 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
@@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var scalingResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             var sharpeningResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
index c1293333..70b3b32a 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var resourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             _samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
index 08e07f25..6d80f4a4 100644
--- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
@@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
             var edgeResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             var blendResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             var neighbourResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             _samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index ea0fd42e..5c5a8f3a 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -286,10 +286,23 @@ namespace Ryujinx.Graphics.Vulkan
 
             _depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
 
-            gd.Barriers.Flush(cbs.CommandBuffer, false, null);
+            gd.Barriers.Flush(cbs, false, null, null);
         }
 
-        public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
+        public void AddStoreOpUsage()
+        {
+            if (_colors != null)
+            {
+                foreach (var color in _colors)
+                {
+                    color.Storage?.AddStoreOpUsage(false);
+                }
+            }
+
+            _depthStencil?.Storage?.AddStoreOpUsage(true);
+        }
+
+        public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
             VulkanRenderer gd,
             Device device,
             CommandBufferScoped cbs)
diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
index 3efb1119..73aa95c7 100644
--- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
             var strideChangeResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
-                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
 
             _programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
             {
@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
             var colorCopyResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
-                .Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
+                .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 
             _programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
             {
@@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
             var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
-                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
 
             _programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
             {
@@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
             var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
-                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
 
             _programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
             {
@@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
             var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
                 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
-                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2)
+                .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
                 .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
 
             _programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 2b2caeae..bda6167d 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         protected FramebufferParams FramebufferParams;
         private Auto<DisposableFramebuffer> _framebuffer;
+        private RenderPassHolder _rpHolder;
         private Auto<DisposableRenderPass> _renderPass;
         private RenderPassHolder _nullRenderPass;
         private int _writtenAttachmentCount;
@@ -85,8 +86,6 @@ namespace Ryujinx.Graphics.Vulkan
         private bool _tfActive;
 
         private readonly PipelineColorBlendAttachmentState[] _storedBlend;
-
-        private ulong _drawCountSinceBarrier;
         public ulong DrawCount { get; private set; }
         public bool RenderPassActive { get; private set; }
 
@@ -135,48 +134,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public unsafe void Barrier()
         {
-            if (_drawCountSinceBarrier != DrawCount)
-            {
-                _drawCountSinceBarrier = DrawCount;
-
-                // Barriers are not supported inside a render pass on Apple GPUs.
-                // As a workaround, end the render pass.
-                if (Gd.Vendor == Vendor.Apple)
-                {
-                    EndRenderPass();
-                }
-            }
-
-            MemoryBarrier memoryBarrier = new()
-            {
-                SType = StructureType.MemoryBarrier,
-                SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
-                DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
-            };
-
-            PipelineStageFlags pipelineStageFlags = PipelineStageFlags.VertexShaderBit | PipelineStageFlags.FragmentShaderBit;
-
-            if (Gd.Capabilities.SupportsGeometryShader)
-            {
-                pipelineStageFlags |= PipelineStageFlags.GeometryShaderBit;
-            }
-
-            if (Gd.Capabilities.SupportsTessellationShader)
-            {
-                pipelineStageFlags |= PipelineStageFlags.TessellationControlShaderBit | PipelineStageFlags.TessellationEvaluationShaderBit;
-            }
-
-            Gd.Api.CmdPipelineBarrier(
-                CommandBuffer,
-                pipelineStageFlags,
-                pipelineStageFlags,
-                0,
-                1,
-                memoryBarrier,
-                0,
-                null,
-                0,
-                null);
+            Gd.Barriers.QueueMemoryBarrier();
         }
 
         public void ComputeBarrier()
@@ -203,6 +161,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void BeginTransformFeedback(PrimitiveTopology topology)
         {
+            Gd.Barriers.EnableTfbBarriers(true);
             _tfEnabled = true;
         }
 
@@ -249,7 +208,7 @@ namespace Ryujinx.Graphics.Vulkan
                 CreateRenderPass();
             }
 
-            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+            Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 
             BeginRenderPass();
 
@@ -287,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
                 CreateRenderPass();
             }
 
-            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+            Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 
             BeginRenderPass();
 
@@ -299,24 +258,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public unsafe void CommandBufferBarrier()
         {
-            MemoryBarrier memoryBarrier = new()
-            {
-                SType = StructureType.MemoryBarrier,
-                SrcAccessMask = BufferHolder.DefaultAccessFlags,
-                DstAccessMask = AccessFlags.IndirectCommandReadBit,
-            };
-
-            Gd.Api.CmdPipelineBarrier(
-                CommandBuffer,
-                PipelineStageFlags.AllCommandsBit,
-                PipelineStageFlags.DrawIndirectBit,
-                0,
-                1,
-                memoryBarrier,
-                0,
-                null,
-                0,
-                null);
+            Gd.Barriers.QueueCommandBufferBarrier();
         }
 
         public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
@@ -722,6 +664,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void EndTransformFeedback()
         {
+            Gd.Barriers.EnableTfbBarriers(false);
             PauseTransformFeedbackInternal();
             _tfEnabled = false;
         }
@@ -1408,24 +1351,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public unsafe void TextureBarrier()
         {
-            MemoryBarrier memoryBarrier = new()
-            {
-                SType = StructureType.MemoryBarrier,
-                SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
-                DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
-            };
-
-            Gd.Api.CmdPipelineBarrier(
-                CommandBuffer,
-                PipelineStageFlags.FragmentShaderBit,
-                PipelineStageFlags.FragmentShaderBit,
-                0,
-                1,
-                memoryBarrier,
-                0,
-                null,
-                0,
-                null);
+            Gd.Barriers.QueueTextureBarrier();
         }
 
         public void TextureBarrierTiled()
@@ -1532,12 +1458,15 @@ namespace Ryujinx.Graphics.Vulkan
                 // Use the null framebuffer.
                 _nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
 
+                _rpHolder = _nullRenderPass;
                 _renderPass = _nullRenderPass.GetRenderPass();
                 _framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
             }
             else
             {
-                (_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
+                (_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
+
+                _renderPass = _rpHolder.GetRenderPass();
             }
         }
 
@@ -1564,7 +1493,7 @@ namespace Ryujinx.Graphics.Vulkan
                 }
             }
 
-            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+            Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 
             _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
         }
@@ -1629,7 +1558,7 @@ namespace Ryujinx.Graphics.Vulkan
                 }
             }
 
-            Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
+            Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 
             _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
 
@@ -1708,6 +1637,8 @@ namespace Ryujinx.Graphics.Vulkan
         {
             if (RenderPassActive)
             {
+                FramebufferParams.AddStoreOpUsage();
+
                 PauseTransformFeedbackInternal();
                 Gd.Api.CmdEndRenderPass(CommandBuffer);
                 SignalRenderPassEnd();
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index 7d124c83..89ce10b0 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
 {
     static class PipelineConverter
     {
-        private const AccessFlags SubpassAccessMask =
-            AccessFlags.MemoryReadBit |
-            AccessFlags.MemoryWriteBit |
-            AccessFlags.ShaderReadBit |
-            AccessFlags.ColorAttachmentWriteBit |
-            AccessFlags.DepthStencilAttachmentWriteBit;
-
         public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
         {
             const int MaxAttachments = Constants.MaxRenderTargets + 1;
@@ -108,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
                 }
             }
 
-            var subpassDependency = CreateSubpassDependency();
+            var subpassDependency = CreateSubpassDependency(gd);
 
             fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
             {
@@ -129,29 +122,33 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
-        public static SubpassDependency CreateSubpassDependency()
+        public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd)
         {
+            var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
+
             return new SubpassDependency(
                 0,
                 0,
-                PipelineStageFlags.AllGraphicsBit,
-                PipelineStageFlags.AllGraphicsBit,
-                SubpassAccessMask,
-                SubpassAccessMask,
+                stages,
+                stages,
+                access,
+                access,
                 0);
         }
 
-        public unsafe static SubpassDependency2 CreateSubpassDependency2()
+        public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
         {
+            var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
+
             return new SubpassDependency2(
                 StructureType.SubpassDependency2,
                 null,
                 0,
                 0,
-                PipelineStageFlags.AllGraphicsBit,
-                PipelineStageFlags.AllGraphicsBit,
-                SubpassAccessMask,
-                SubpassAccessMask,
+                stages,
+                stages,
+                access,
+                access,
                 0);
         }
 
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
index 5808406d..cf65eefb 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan
                 PreloadCbs = null;
             }
 
-            Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
+            Gd.Barriers.Flush(Cbs, false, null, null);
             CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
             Gd.RegisterFlush();
 
diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
index 9edea578..b2dd0dd8 100644
--- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
@@ -1,5 +1,7 @@
 using Silk.NET.Vulkan;
 using System;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace Ryujinx.Graphics.Vulkan
 {
@@ -29,10 +31,13 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
+        private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags);
+
         private readonly TextureView[] _textures;
         private readonly Auto<DisposableRenderPass> _renderPass;
         private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
         private readonly RenderPassCacheKey _key;
+        private readonly List<ForcedFence> _forcedFences;
 
         public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
         {
@@ -105,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
                 }
             }
 
-            var subpassDependency = PipelineConverter.CreateSubpassDependency();
+            var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
 
             fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
             {
@@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
 
             _textures = textures;
             _key = key;
+
+            _forcedFences = new List<ForcedFence>();
         }
 
         public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
@@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
             return _renderPass;
         }
 
+        public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
+        {
+            if (!_forcedFences.Any(fence => fence.Texture == storage))
+            {
+                _forcedFences.Add(new ForcedFence(storage, stageFlags));
+            }
+        }
+
+        public void InsertForcedFences(CommandBufferScoped cbs)
+        {
+            if (_forcedFences.Count > 0)
+            {
+                _forcedFences.RemoveAll((entry) =>
+                {
+                    if (entry.Texture.Disposed)
+                    {
+                        return true;
+                    }
+
+                    entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
+
+                    return false;
+                });
+            }
+        }
+
+        public bool ContainsAttachment(TextureStorage storage)
+        {
+            return _textures.Any(view => view.Storage == storage);
+        }
+
         public void Dispose()
         {
             // Dispose all framebuffers.
diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs
index 76a5ef4f..730a0a2f 100644
--- a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs
@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
-        public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
+        public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
         {
             int setIndex = type switch
             {
@@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
             };
 
             _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
-            _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
+            _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
 
             return this;
         }
diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
index b1547b79..c9aab401 100644
--- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
@@ -27,6 +27,9 @@ namespace Ryujinx.Graphics.Vulkan
 
         public uint Stages { get; }
 
+        public PipelineStageFlags IncoherentBufferWriteStages { get; }
+        public PipelineStageFlags IncoherentTextureWriteStages { get; }
+
         public ResourceBindingSegment[][] ClearSegments { get; }
         public ResourceBindingSegment[][] BindingSegments { get; }
         public DescriptorSetTemplate[] Templates { get; }
@@ -131,6 +134,7 @@ namespace Ryujinx.Graphics.Vulkan
             ClearSegments = BuildClearSegments(sets);
             BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
             Templates = BuildTemplates(usePushDescriptors);
+            (IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
 
             // Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
             UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
@@ -377,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
             return templates;
         }
 
+        private PipelineStageFlags GetPipelineStages(ResourceStages stages)
+        {
+            PipelineStageFlags result = 0;
+
+            if ((stages & ResourceStages.Compute) != 0)
+            {
+                result |= PipelineStageFlags.ComputeShaderBit;
+            }
+
+            if ((stages & ResourceStages.Vertex) != 0)
+            {
+                result |= PipelineStageFlags.VertexShaderBit;
+            }
+
+            if ((stages & ResourceStages.Fragment) != 0)
+            {
+                result |= PipelineStageFlags.FragmentShaderBit;
+            }
+
+            if ((stages & ResourceStages.Geometry) != 0)
+            {
+                result |= PipelineStageFlags.GeometryShaderBit;
+            }
+
+            if ((stages & ResourceStages.TessellationControl) != 0)
+            {
+                result |= PipelineStageFlags.TessellationControlShaderBit;
+            }
+
+            if ((stages & ResourceStages.TessellationEvaluation) != 0)
+            {
+                result |= PipelineStageFlags.TessellationEvaluationShaderBit;
+            }
+
+            return result;
+        }
+
+        private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
+        {
+            PipelineStageFlags buffer = PipelineStageFlags.None;
+            PipelineStageFlags texture = PipelineStageFlags.None;
+
+            foreach (var set in setUsages)
+            {
+                foreach (var range in set.Usages)
+                {
+                    if (range.Write)
+                    {
+                        PipelineStageFlags stages = GetPipelineStages(range.Stages);
+
+                        switch (range.Type)
+                        {
+                            case ResourceType.Image:
+                                texture |= stages;
+                                break;
+                            case ResourceType.StorageBuffer:
+                            case ResourceType.BufferImage:
+                                buffer |= stages;
+                                break;
+                        }
+                    }
+                }
+            }
+
+            return (buffer, texture);
+        }
+
         private async Task BackgroundCompilation()
         {
             await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
index 7c06a5df..fdc0a248 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs
@@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
                 ImageLayout.General,
                 ImageLayout.General);
 
-            var subpassDependency = PipelineConverter.CreateSubpassDependency2();
+            var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
 
             fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
             {
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index 1aaf2fbb..f36db68d 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
 
         public TextureCreateInfo Info => _info;
 
+        public bool Disposed { get; private set; }
+
         private readonly Image _image;
         private readonly Auto<DisposableImage> _imageAuto;
         private readonly Auto<MemoryAllocation> _allocationAuto;
@@ -433,6 +435,17 @@ namespace Ryujinx.Graphics.Vulkan
             return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
         }
 
+        public void AddStoreOpUsage(bool depthStencil)
+        {
+            _lastModificationStage = depthStencil ?
+                PipelineStageFlags.LateFragmentTestsBit :
+                PipelineStageFlags.ColorAttachmentOutputBit;
+
+            _lastModificationAccess = depthStencil ?
+                AccessFlags.DepthStencilAttachmentWriteBit :
+                AccessFlags.ColorAttachmentWriteBit;
+        }
+
         public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
         {
             PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
@@ -458,7 +471,7 @@ namespace Ryujinx.Graphics.Vulkan
                     _info.GetLayers(),
                     _info.Levels);
 
-                _gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
+                _gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
 
                 _lastReadStage = PipelineStageFlags.None;
                 _lastReadAccess = AccessFlags.None;
@@ -491,7 +504,7 @@ namespace Ryujinx.Graphics.Vulkan
                     _info.GetLayers(),
                     _info.Levels);
 
-                _gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
+                _gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
 
                 _lastModificationAccess = AccessFlags.None;
             }
@@ -514,6 +527,8 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void Dispose()
         {
+            Disposed = true;
+
             if (_aliasedStorages != null)
             {
                 foreach (var storage in _aliasedStorages.Values)
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index 52066802..eb612da7 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -993,7 +993,7 @@ namespace Ryujinx.Graphics.Vulkan
             throw new NotImplementedException();
         }
 
-        public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
+        public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
             VulkanRenderer gd,
             Device device,
             CommandBufferScoped cbs,
@@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Vulkan
                 rpHolder = new RenderPassHolder(gd, device, key, fb);
             }
 
-            return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
+            return (rpHolder, rpHolder.GetFramebuffer(gd, cbs, fb));
         }
 
         public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index e46eac95..c9ce678b 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -939,6 +939,11 @@ namespace Ryujinx.Graphics.Vulkan
             ScreenCaptured?.Invoke(this, bitmap);
         }
 
+        public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
+        {
+            return !(IsMoltenVk || IsQualcommProprietary);
+        }
+
         public unsafe void Dispose()
         {
             if (!_initialized)
-- 
cgit v1.2.3-70-g09d2