mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 08:59:04 -06:00 
			
		
		
		
	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
This commit is contained in:
		@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 | 
			
		||||
                    if (!_gd.IsMoltenVk)
 | 
			
		||||
                    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.
 | 
			
		||||
 | 
			
		||||
                                _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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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[]
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user