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