using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
namespace Ryujinx.Graphics.Vulkan
{
///
/// Holder for multiple host GPU fences.
///
class MultiFenceHolder
{
private const int BufferUsageTrackingGranularity = 4096;
private readonly FenceHolder[] _fences;
private readonly BufferUsageBitmap _bufferUsageBitmap;
///
/// Creates a new instance of the multiple fence holder.
///
public MultiFenceHolder()
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
}
///
/// Creates a new instance of the multiple fence holder, with a given buffer size in mind.
///
/// Size of the buffer
public MultiFenceHolder(int size)
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
///
/// Adds read/write buffer usage information to the uses list.
///
/// Index of the command buffer where the buffer is used
/// Offset of the buffer being used
/// Size of the buffer region being used, in bytes
/// Whether the access is a write or not
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
if (write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
}
}
///
/// Removes all buffer usage information for a given command buffer.
///
/// Index of the command buffer where the buffer is used
public void RemoveBufferUses(int cbIndex)
{
_bufferUsageBitmap?.Clear(cbIndex);
}
///
/// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU.
///
/// Index of the command buffer where the buffer is used
/// Offset of the buffer being used
/// Size of the buffer region being used, in bytes
/// True if in use, false otherwise
public bool IsBufferRangeInUse(int cbIndex, int offset, int size)
{
return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size);
}
///
/// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU.
///
/// Offset of the buffer being used
/// Size of the buffer region being used, in bytes
/// True if only write usages should count
/// True if in use, false otherwise
public bool IsBufferRangeInUse(int offset, int size, bool write)
{
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
}
///
/// Adds a fence to the holder.
///
/// Command buffer index of the command buffer that owns the fence
/// Fence to be added
/// True if the command buffer's previous fence value was null
public bool AddFence(int cbIndex, FenceHolder fence)
{
ref FenceHolder fenceRef = ref _fences[cbIndex];
if (fenceRef == null)
{
fenceRef = fence;
return true;
}
return false;
}
///
/// Removes a fence from the holder.
///
/// Command buffer index of the command buffer that owns the fence
public void RemoveFence(int cbIndex)
{
_fences[cbIndex] = null;
}
///
/// Determines if a fence referenced on the given command buffer.
///
/// Index of the command buffer to check if it's used
/// True if referenced, false otherwise
public bool HasFence(int cbIndex)
{
return _fences[cbIndex] != null;
}
///
/// Wait until all the fences on the holder are signaled.
///
/// Vulkan API instance
/// GPU device that the fences belongs to
public void WaitForFences(Vk api, Device device)
{
WaitForFencesImpl(api, device, 0, 0, false, 0UL);
}
///
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
///
/// Vulkan API instance
/// GPU device that the fences belongs to
/// Start offset of the buffer range
/// Size of the buffer range in bytes
public void WaitForFences(Vk api, Device device, int offset, int size)
{
WaitForFencesImpl(api, device, offset, size, false, 0UL);
}
///
/// Wait until all the fences on the holder are signaled, or the timeout expires.
///
/// Vulkan API instance
/// GPU device that the fences belongs to
/// Timeout in nanoseconds
/// True if all fences were signaled, false otherwise
public bool WaitForFences(Vk api, Device device, ulong timeout)
{
return WaitForFencesImpl(api, device, 0, 0, true, timeout);
}
///
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
///
/// Vulkan API instance
/// GPU device that the fences belongs to
/// Start offset of the buffer range
/// Size of the buffer range in bytes
/// Indicates if should be used
/// Timeout in nanoseconds
/// True if all fences were signaled before the timeout expired, false otherwise
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{
using SpanOwner fenceHoldersOwner = SpanOwner.Rent(CommandBufferPool.MaxCommandBuffers);
Span fenceHolders = fenceHoldersOwner.Span;
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span fences = stackalloc Fence[count];
int fenceCount = 0;
for (int i = 0; i < fences.Length; i++)
{
if (fenceHolders[i].TryGet(out Fence fence))
{
fences[fenceCount] = fence;
if (fenceCount < i)
{
fenceHolders[fenceCount] = fenceHolders[i];
}
fenceCount++;
}
}
if (fenceCount == 0)
{
return true;
}
bool signaled = true;
try
{
if (hasTimeout)
{
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
}
else
{
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
}
}
finally
{
for (int i = 0; i < fenceCount; i++)
{
fenceHolders[i].PutLock();
}
}
return signaled;
}
///
/// Gets fences to wait for.
///
/// Span to store fences in
/// Number of fences placed in storage
private int GetFences(Span storage)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
var fence = _fences[i];
if (fence != null)
{
storage[count++] = fence;
}
}
return count;
}
///
/// Gets fences to wait for use of a given buffer region.
///
/// Span to store overlapping fences in
/// Offset of the range
/// Size of the range in bytes
/// Number of fences for the specified region placed in storage
private int GetOverlappingFences(Span storage, int offset, int size)
{
int count = 0;
for (int i = 0; i < _fences.Length; i++)
{
var fence = _fences[i];
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
{
storage[count++] = fence;
}
}
return count;
}
}
}