| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- using Silk.NET.Vulkan;
- using System;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- namespace Ryujinx.Graphics.Vulkan
- {
- internal class BarrierBatch : IDisposable
- {
- 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, 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 bool _feedbackLoopActive;
- private PipelineStageFlags _incoherentBufferWriteStages;
- private PipelineStageFlags _incoherentTextureWriteStages;
- private PipelineStageFlags _extraStages;
- private IncoherentBarrierType _queuedIncoherentBarrier;
- private bool _queuedFeedbackLoopBarrier;
- 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;
- }
- return (access, stages);
- }
- private readonly record struct StageFlags : IEquatable<StageFlags>
- {
- public readonly PipelineStageFlags Source;
- public readonly PipelineStageFlags Dest;
- public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
- {
- Source = source;
- Dest = dest;
- }
- }
- private readonly struct BarrierWithStageFlags<T, 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, T2 resource)
- {
- Flags = new StageFlags(srcStageFlags, dstStageFlags);
- Barrier = barrier;
- Resource = resource;
- }
- }
- 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, T2>(srcStageFlags, dstStageFlags, barrier, resource));
- _queuedBarrierCount++;
- }
- public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
- {
- QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
- }
- public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
- {
- QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
- }
- public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
- {
- QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
- }
- [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;
- _queuedFeedbackLoopBarrier = false;
- }
- else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
- {
- // Feedback loop barrier.
- MemoryBarrier barrier = new MemoryBarrier()
- {
- SType = StructureType.MemoryBarrier,
- SrcAccessMask = AccessFlags.ShaderWriteBit,
- DstAccessMask = AccessFlags.ShaderReadBit
- };
- QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
- _queuedFeedbackLoopBarrier = false;
- }
- _feedbackLoopActive = false;
- }
- }
- public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
- {
- Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
- }
- public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
- {
- if (program != null)
- {
- _incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
- _incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
- }
- _feedbackLoopActive |= feedbackLoopActive;
- 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;
- int bufferCount = 0;
- int imageCount = 0;
- bool hasBarrier = false;
- StageFlags flags = default;
- static void AddBarriers<T, T2>(
- Span<T> target,
- ref int queuedBarrierCount,
- ref bool hasBarrier,
- ref StageFlags flags,
- ref int count,
- 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, T2> barrier = list[i];
- if (!hasBarrier)
- {
- flags = barrier.Flags;
- hasBarrier = true;
- target[count++] = barrier.Barrier;
- queuedBarrierCount--;
- firstMatch = i;
- if (count >= target.Length)
- {
- end = i + 1;
- break;
- }
- }
- else
- {
- if (flags.Equals(barrier.Flags))
- {
- target[count++] = barrier.Barrier;
- queuedBarrierCount--;
- if (firstMatch == -1)
- {
- firstMatch = i;
- }
- if (count >= target.Length)
- {
- end = i + 1;
- 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;
- end = list.Count;
- }
- }
- }
- }
- if (firstMatch == 0 && end == list.Count)
- {
- list.Clear();
- }
- else if (firstMatch != -1)
- {
- int deleteCount = end - firstMatch;
- list.RemoveRange(firstMatch, deleteCount);
- }
- }
- 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.
- // Generally, we want to avoid this from happening in the future, so flag the texture to immediately
- // emit a barrier whenever the current render pass is bound again.
- bool anyIsNonAttachment = false;
- foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
- {
- // If the binding is an attachment, don't add it as a forced fence.
- bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
- if (!isAttachment)
- {
- rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
- anyIsNonAttachment = true;
- }
- }
- if (_gd.IsTBDR)
- {
- if (!_gd.IsMoltenVk)
- {
- if (!anyIsNonAttachment)
- {
- // This case is a feedback loop. To prevent this from causing an absolute performance disaster,
- // remove the barriers entirely.
- // If this is not here, there will be a lot of single draw render passes.
- // TODO: explicit handling for feedback loops, likely outside this class.
- _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, int>(
- barrier.Flags,
- new MemoryBarrier()
- {
- SType = StructureType.MemoryBarrier,
- SrcAccessMask = barrier.Barrier.SrcAccessMask,
- DstAccessMask = barrier.Barrier.DstAccessMask
- }));
- }
- _imageBarriers.Clear();
- }
- }
- 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);
- if (hasBarrier)
- {
- PipelineStageFlags srcStageFlags = flags.Source;
- if (inRenderPass)
- {
- // Inside a render pass, barrier stages can only be from rasterization.
- srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
- }
- _gd.Api.CmdPipelineBarrier(
- cbs.CommandBuffer,
- srcStageFlags,
- flags.Dest,
- 0,
- (uint)memoryCount,
- _memoryBarrierBatch.Pointer,
- (uint)bufferCount,
- _bufferBarrierBatch.Pointer,
- (uint)imageCount,
- _imageBarrierBatch.Pointer);
- }
- }
- }
- private void QueueIncoherentBarrier(IncoherentBarrierType type)
- {
- if (type > _queuedIncoherentBarrier)
- {
- _queuedIncoherentBarrier = type;
- }
- _queuedFeedbackLoopBarrier = true;
- }
- 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();
- _bufferBarrierBatch.Dispose();
- _imageBarrierBatch.Dispose();
- }
- }
- }
|