Vulkan: Buffer Mirrors for MacOS performance (#4899)
* Initial implementation of buffer mirrors Generally slower right now, goal is to reduce render passes in games that do inline updates Fix support buffer mirrors Reintroduce vertex buffer mirror Add storage buffer support Optimisation part 1 More optimisation Avoid useless data copies. Remove unused cbIndex stuff Properly set write flag for storage buffers. Fix minor issues Not sure why this was here. Fix BufferRangeList Fix some big issues Align storage buffers rather than getting full buffer as a range Improves mirrorability of read-only storage buffers Increase staging buffer size, as it now contains mirrors Fix some issues with buffers not updating Fix buffer SetDataUnchecked offset for one of the paths when using mirrors Fix buffer mirrors interaction with buffer textures Fix mirror rebinding Move GetBuffer calls on indirect draws before BeginRenderPass to avoid draws without render pass Fix mirrors rebase Fix rebase 2023 * Fix crash when using stale vertex buffer Similar to `Get` with a size that's too large, just treat it as a clamp. * Explicitly set support buffer as mirrorable * Address feedback * Remove unused fragment of MVK workaround * Replace logging for staging buffer OOM * Address format issues * Address more format issues * Mini cleanup * Address more things * Rename BufferRangeList * Support bounding range for ClearMirrors and UploadPendingData * Add maximum size for vertex buffer mirrors * Enable index buffer mirrors Enabled on all platforms for the IbStreamer. * Feedback * Remove mystery BufferCache change Probably macos related? * Fix mirrors not creating when staging buffer is empty. * Change log level to debug
This commit is contained in:
parent
550fd4a733
commit
492a046335
@ -4,5 +4,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
Default,
|
||||
FlushPersistent,
|
||||
Stream
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,14 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
public bool Write { get; }
|
||||
|
||||
public BufferRange(BufferHandle handle, int offset, int size)
|
||||
public BufferRange(BufferHandle handle, int offset, int size, bool write = false)
|
||||
{
|
||||
Handle = handle;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
Write = write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
|
||||
internal BufferRange MapBufferRange(BufferRange range)
|
||||
{
|
||||
return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size);
|
||||
return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size, range.Write);
|
||||
}
|
||||
|
||||
internal Span<BufferRange> MapBufferRanges(Span<BufferRange> ranges)
|
||||
@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
result = BufferHandle.Null;
|
||||
}
|
||||
|
||||
range = new BufferRange(result, range.Offset, range.Size);
|
||||
range = new BufferRange(result, range.Offset, range.Size, range.Write);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
result = BufferHandle.Null;
|
||||
}
|
||||
|
||||
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
|
||||
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size, range.Write));
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
result = BufferHandle.Null;
|
||||
}
|
||||
|
||||
range = new BufferRange(result, range.Offset, range.Size);
|
||||
range = new BufferRange(result, range.Offset, range.Size, range.Write);
|
||||
|
||||
ranges[i] = new VertexBufferDescriptor(range, ranges[i].Stride, ranges[i].Divisor);
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (_inlineIndexBuffer == BufferHandle.Null)
|
||||
{
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size);
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream);
|
||||
_inlineIndexBufferSize = size;
|
||||
}
|
||||
else if (_inlineIndexBufferSize < size)
|
||||
@ -179,7 +179,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
BufferHandle oldBuffer = _inlineIndexBuffer;
|
||||
int oldSize = _inlineIndexBufferSize;
|
||||
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size);
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream);
|
||||
_inlineIndexBufferSize = size;
|
||||
|
||||
renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize);
|
||||
|
@ -140,18 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a sub-range from the buffer, from a start address till the end of the buffer.
|
||||
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to bind and use sub-ranges of the buffer on the host API.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
|
||||
/// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
|
||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||
/// <returns>The buffer sub-range</returns>
|
||||
public BufferRange GetRange(ulong address)
|
||||
public BufferRange GetRangeAligned(ulong address, ulong size, bool write)
|
||||
{
|
||||
ulong end = ((address + size + MemoryManager.PageMask) & ~MemoryManager.PageMask) - Address;
|
||||
ulong offset = address - Address;
|
||||
|
||||
return new BufferRange(Handle, (int)offset, (int)(Size - offset));
|
||||
return new BufferRange(Handle, (int)offset, (int)(end - offset), write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -162,12 +165,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
|
||||
/// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
|
||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||
/// <returns>The buffer sub-range</returns>
|
||||
public BufferRange GetRange(ulong address, ulong size)
|
||||
public BufferRange GetRange(ulong address, ulong size, bool write)
|
||||
{
|
||||
int offset = (int)(address - Address);
|
||||
|
||||
return new BufferRange(Handle, offset, (int)size);
|
||||
return new BufferRange(Handle, offset, (int)size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -372,15 +372,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer sub-range starting at a given memory address.
|
||||
/// Gets a buffer sub-range from a start address til a page boundary after the given size.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the memory range</param>
|
||||
/// <param name="size">Size in bytes of the memory range</param>
|
||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||
/// <returns>The buffer sub-range starting at the given memory address</returns>
|
||||
public BufferRange GetBufferRangeTillEnd(ulong address, ulong size, bool write = false)
|
||||
public BufferRange GetBufferRangeAligned(ulong address, ulong size, bool write = false)
|
||||
{
|
||||
return GetBuffer(address, size, write).GetRange(address);
|
||||
return GetBuffer(address, size, write).GetRangeAligned(address, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -392,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>The buffer sub-range for the given range</returns>
|
||||
public BufferRange GetBufferRange(ulong address, ulong size, bool write = false)
|
||||
{
|
||||
return GetBuffer(address, size, write).GetRange(address, size);
|
||||
return GetBuffer(address, size, write).GetRange(address, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -614,7 +614,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (_tfInfoBuffer == BufferHandle.Null)
|
||||
{
|
||||
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize);
|
||||
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize, BufferAccess.Stream);
|
||||
}
|
||||
|
||||
buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize));
|
||||
@ -727,7 +727,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
var range = isStorage
|
||||
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
@ -764,7 +764,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
var range = isStorage
|
||||
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
|
@ -228,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (_handle == BufferHandle.Null)
|
||||
{
|
||||
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize);
|
||||
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize, BufferAccess.Stream);
|
||||
_renderer.Pipeline.ClearBuffer(_handle, 0, SupportBuffer.RequiredSize, 0);
|
||||
|
||||
var range = new BufferRange(_handle, 0, SupportBuffer.RequiredSize);
|
||||
|
@ -18,6 +18,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
void AddCommandBufferDependencies(CommandBufferScoped cbs);
|
||||
}
|
||||
|
||||
interface IMirrorable<T> where T : IDisposable
|
||||
{
|
||||
Auto<T> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored);
|
||||
void ClearMirrors(CommandBufferScoped cbs, int offset, int size);
|
||||
}
|
||||
|
||||
class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
|
||||
{
|
||||
private int _referenceCount;
|
||||
@ -26,6 +32,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly BitMap _cbOwnership;
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
private readonly IAutoPrivate[] _referencedObjs;
|
||||
private readonly IMirrorable<T> _mirrorable;
|
||||
|
||||
private bool _disposed;
|
||||
private bool _destroyed;
|
||||
@ -37,6 +44,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public Auto(T value, IMirrorable<T> mirrorable, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value, waitable, referencedObjs)
|
||||
{
|
||||
_mirrorable = mirrorable;
|
||||
}
|
||||
|
||||
public Auto(T value, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value)
|
||||
{
|
||||
_waitable = waitable;
|
||||
@ -48,9 +60,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs, int offset, int size)
|
||||
public T GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size);
|
||||
var mirror = _mirrorable.GetMirrorable(cbs, ref offset, size, out mirrored);
|
||||
mirror._waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, false);
|
||||
return mirror.Get(cbs);
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
|
||||
{
|
||||
_mirrorable?.ClearMirrors(cbs, offset, size);
|
||||
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
|
||||
return Get(cbs);
|
||||
}
|
||||
|
||||
|
263
src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs
Normal file
263
src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs
Normal file
@ -0,0 +1,263 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
interface IBitMapListener
|
||||
{
|
||||
void BitMapSignal(int index, int count);
|
||||
}
|
||||
|
||||
struct BitMapStruct<T> where T : IArray<long>
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
private const int IntShift = 6;
|
||||
private const int IntMask = IntSize - 1;
|
||||
|
||||
private T _masks;
|
||||
|
||||
public BitMapStruct()
|
||||
{
|
||||
_masks = default;
|
||||
}
|
||||
|
||||
public bool BecomesUnsetFrom(in BitMapStruct<T> from, ref BitMapStruct<T> into)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
int masks = _masks.Length;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long fromMask = from._masks[i];
|
||||
long unsetMask = (~fromMask) & (fromMask ^ _masks[i]);
|
||||
into._masks[i] = unsetMask;
|
||||
|
||||
result |= unsetMask != 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SetAndSignalUnset<T2>(in BitMapStruct<T> from, ref T2 listener) where T2 : struct, IBitMapListener
|
||||
{
|
||||
BitMapStruct<T> result = new();
|
||||
|
||||
if (BecomesUnsetFrom(from, ref result))
|
||||
{
|
||||
// Iterate the set bits in the result, and signal them.
|
||||
|
||||
int offset = 0;
|
||||
int masks = _masks.Length;
|
||||
ref T resultMasks = ref result._masks;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long value = resultMasks[i];
|
||||
while (value != 0)
|
||||
{
|
||||
int bit = BitOperations.TrailingZeroCount((ulong)value);
|
||||
|
||||
listener.BitMapSignal(offset + bit, 1);
|
||||
|
||||
value &= ~(1L << bit);
|
||||
}
|
||||
|
||||
offset += IntSize;
|
||||
}
|
||||
}
|
||||
|
||||
_masks = from._masks;
|
||||
}
|
||||
|
||||
public void SignalSet(Action<int, int> action)
|
||||
{
|
||||
// Iterate the set bits in the result, and signal them.
|
||||
|
||||
int offset = 0;
|
||||
int masks = _masks.Length;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long value = _masks[i];
|
||||
while (value != 0)
|
||||
{
|
||||
int bit = BitOperations.TrailingZeroCount((ulong)value);
|
||||
|
||||
action(offset + bit, 1);
|
||||
|
||||
value &= ~(1L << bit);
|
||||
}
|
||||
|
||||
offset += IntSize;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AnySet()
|
||||
{
|
||||
for (int i = 0; i < _masks.Length; i++)
|
||||
{
|
||||
if (_masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSet(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
return (_masks[wordIndex] & wordMask) != 0;
|
||||
}
|
||||
|
||||
public bool IsSet(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
return IsSet(start);
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
return (_masks[startIndex] & startMask & endMask) != 0;
|
||||
}
|
||||
|
||||
if ((_masks[startIndex] & startMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
if (_masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((_masks[endIndex] & endMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Set(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
if ((_masks[wordIndex] & wordMask) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_masks[wordIndex] |= wordMask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Set(int bit, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Set(bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear(bit);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRange(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
Set(start);
|
||||
return;
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
_masks[startIndex] |= startMask & endMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
_masks[startIndex] |= startMask;
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
_masks[i] |= -1L;
|
||||
}
|
||||
|
||||
_masks[endIndex] |= endMask;
|
||||
}
|
||||
}
|
||||
|
||||
public BitMapStruct<T> Union(BitMapStruct<T> other)
|
||||
{
|
||||
var result = new BitMapStruct<T>();
|
||||
|
||||
ref var masks = ref _masks;
|
||||
ref var otherMasks = ref other._masks;
|
||||
ref var newMasks = ref result._masks;
|
||||
|
||||
for (int i = 0; i < masks.Length; i++)
|
||||
{
|
||||
newMasks[i] = masks[i] | otherMasks[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Clear(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
_masks[wordIndex] &= ~wordMask;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < _masks.Length; i++)
|
||||
{
|
||||
_masks[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearInt(int start, int end)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
_masks[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class BufferHolder : IDisposable
|
||||
class BufferHolder : IDisposable, IMirrorable<DisposableBuffer>, IMirrorable<DisposableBufferView>
|
||||
{
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
@ -64,6 +64,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private List<Action> _swapActions;
|
||||
|
||||
private byte[] _pendingData;
|
||||
private BufferMirrorRangeList _pendingDataRanges;
|
||||
private Dictionary<ulong, StagingBufferReserved> _mirrors;
|
||||
private bool _useMirrors;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
|
||||
{
|
||||
_gd = gd;
|
||||
@ -71,7 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_allocation = allocation;
|
||||
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = allocation.HostPointer;
|
||||
@ -81,6 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DesiredType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLock();
|
||||
_useMirrors = gd.IsTBDR;
|
||||
}
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto<MemoryAllocation> allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
|
||||
@ -91,7 +97,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_allocationAuto = allocation;
|
||||
_allocationImported = true;
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = _allocation.HostPointer + offset;
|
||||
@ -110,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Only swap if the buffer is not used in any queued command buffer.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||
|
||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
|
||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld && (_pendingData == null || cbs != null))
|
||||
{
|
||||
var currentAllocation = _allocationAuto;
|
||||
var currentBuffer = _buffer;
|
||||
@ -120,6 +126,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
if (cbs != null)
|
||||
{
|
||||
ClearMirrors(cbs.Value, 0, Size);
|
||||
}
|
||||
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
@ -128,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_allocation = allocation;
|
||||
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), this, _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
@ -257,7 +268,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
(_swapActions ??= new List<Action>()).Add(invalidateView);
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
|
||||
}
|
||||
|
||||
public void InheritMetrics(BufferHolder other)
|
||||
@ -302,6 +313,82 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ToMirrorKey(int offset, int size)
|
||||
{
|
||||
return ((ulong)offset << 32) | (uint)size;
|
||||
}
|
||||
|
||||
private static (int offset, int size) FromMirrorKey(ulong key)
|
||||
{
|
||||
return ((int)(key >> 32), (int)key);
|
||||
}
|
||||
|
||||
private unsafe bool TryGetMirror(CommandBufferScoped cbs, ref int offset, int size, out Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
size = Math.Min(size, Size - offset);
|
||||
|
||||
// Does this binding need to be mirrored?
|
||||
|
||||
if (!_pendingDataRanges.OverlapsWith(offset, size))
|
||||
{
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = ToMirrorKey(offset, size);
|
||||
|
||||
if (_mirrors.TryGetValue(key, out StagingBufferReserved reserved))
|
||||
{
|
||||
buffer = reserved.Buffer.GetBuffer();
|
||||
offset = reserved.Offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is this mirror allowed to exist? Can't be used for write in any in-flight write.
|
||||
if (_waitable.IsBufferRangeInUse(offset, size, true))
|
||||
{
|
||||
// Some of the data is not mirrorable, so upload the whole range.
|
||||
ClearMirrors(cbs, offset, size);
|
||||
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build data for the new mirror.
|
||||
|
||||
var baseData = new Span<byte>((void*)(_map + offset), size);
|
||||
var modData = _pendingData.AsSpan(offset, size);
|
||||
|
||||
StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size, (int)_gd.Capabilities.MinResourceAlignment);
|
||||
|
||||
if (newMirror != null)
|
||||
{
|
||||
var mirror = newMirror.Value;
|
||||
_pendingDataRanges.FillData(baseData, modData, offset, new Span<byte>((void*)(mirror.Buffer._map + mirror.Offset), size));
|
||||
|
||||
if (_mirrors.Count == 0)
|
||||
{
|
||||
_gd.PipelineInternal.RegisterActiveMirror(this);
|
||||
}
|
||||
|
||||
_mirrors.Add(key, mirror);
|
||||
|
||||
buffer = mirror.Buffer.GetBuffer();
|
||||
offset = mirror.Offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Data could not be placed on the mirror, likely out of space. Force the data to flush.
|
||||
ClearMirrors(cbs, offset, size);
|
||||
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _buffer;
|
||||
@ -339,6 +426,86 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
if (_pendingData != null && TryGetMirror(cbs, ref offset, size, out Auto<DisposableBuffer> result))
|
||||
{
|
||||
mirrored = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
mirrored = false;
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
Auto<DisposableBufferView> IMirrorable<DisposableBufferView>.GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
// Cannot mirror buffer views right now.
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ClearMirrors()
|
||||
{
|
||||
// Clear mirrors without forcing a flush. This happens when the command buffer is switched,
|
||||
// as all reserved areas on the staging buffer are released.
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_mirrors.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
public void ClearMirrors(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
// Clear mirrors in the given range, and submit overlapping pending data.
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
bool hadMirrors = _mirrors.Count > 0 && RemoveOverlappingMirrors(offset, size);
|
||||
|
||||
if (_pendingDataRanges.Count() != 0)
|
||||
{
|
||||
UploadPendingData(cbs, offset, size);
|
||||
}
|
||||
|
||||
if (hadMirrors)
|
||||
{
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void UseMirrors()
|
||||
{
|
||||
_useMirrors = true;
|
||||
}
|
||||
|
||||
private void UploadPendingData(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
var ranges = _pendingDataRanges.FindOverlaps(offset, size);
|
||||
|
||||
if (ranges != null)
|
||||
{
|
||||
_pendingDataRanges.Remove(offset, size);
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
int rangeOffset = Math.Max(offset, range.Offset);
|
||||
int rangeSize = Math.Min(offset + size, range.End) - rangeOffset;
|
||||
|
||||
if (_gd.PipelineInternal.CurrentCommandBuffer.CommandBuffer.Handle == cbs.CommandBuffer.Handle)
|
||||
{
|
||||
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPass, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
ConsiderBackingSwap();
|
||||
@ -472,7 +639,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
throw new InvalidOperationException("The buffer is not host mapped.");
|
||||
}
|
||||
|
||||
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
|
||||
public bool RemoveOverlappingMirrors(int offset, int size)
|
||||
{
|
||||
List<ulong> toRemove = null;
|
||||
foreach (var key in _mirrors.Keys)
|
||||
{
|
||||
(int keyOffset, int keySize) = FromMirrorKey(key);
|
||||
if (!(offset + size <= keyOffset || offset >= keyOffset + keySize))
|
||||
{
|
||||
toRemove ??= new List<ulong>();
|
||||
|
||||
toRemove.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_mirrors.Remove(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true)
|
||||
{
|
||||
int dataSize = Math.Min(data.Length, Size - offset);
|
||||
if (dataSize == 0)
|
||||
@ -481,6 +675,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
_setCount++;
|
||||
bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _currentType <= BufferAllocationType.HostMapped;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
@ -488,7 +683,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||
|
||||
// If the buffer is rented, take a little more time and check if the use overlaps this handle.
|
||||
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize);
|
||||
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false);
|
||||
|
||||
if (!needsFlush)
|
||||
{
|
||||
@ -496,12 +691,48 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
bool removed = _pendingDataRanges.Remove(offset, dataSize);
|
||||
if (RemoveOverlappingMirrors(offset, dataSize) || removed)
|
||||
{
|
||||
// If any mirrors were removed, rebind the buffer range.
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the buffer does not have an in-flight write (including an inline update), then upload data to a pendingCopy.
|
||||
if (allowMirror && !_waitable.IsBufferRangeInUse(offset, dataSize, true))
|
||||
{
|
||||
if (_pendingData == null)
|
||||
{
|
||||
_pendingData = new byte[Size];
|
||||
_mirrors = new Dictionary<ulong, StagingBufferReserved>();
|
||||
}
|
||||
|
||||
data[..dataSize].CopyTo(_pendingData.AsSpan(offset, dataSize));
|
||||
_pendingDataRanges.Add(offset, dataSize);
|
||||
|
||||
// Remove any overlapping mirrors.
|
||||
RemoveOverlappingMirrors(offset, dataSize);
|
||||
|
||||
// Tell the graphics device to rebind any constant buffer that overlaps the newly modified range, as it should access a mirror.
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingDataRanges.Remove(offset, dataSize);
|
||||
}
|
||||
|
||||
if (cbs != null &&
|
||||
_gd.PipelineInternal.RenderPassActive &&
|
||||
!(_buffer.HasCommandBufferDependency(cbs.Value) &&
|
||||
@ -519,7 +750,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
data.Length > MaxUpdateBufferSize ||
|
||||
!TryPushData(cbs.Value, endRenderPass, offset, data))
|
||||
{
|
||||
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
|
||||
if (allowCbsWait)
|
||||
{
|
||||
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rentCbs = cbs == null;
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs = _gd.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
if (!_gd.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data))
|
||||
{
|
||||
// Need to do a slow upload.
|
||||
BufferHolder srcHolder = _gd.BufferManager.Create(_gd, dataSize, baseType: BufferAllocationType.HostMapped);
|
||||
srcHolder.SetDataUnchecked(0, data);
|
||||
|
||||
var srcBuffer = srcHolder.GetBuffer();
|
||||
var dstBuffer = this.GetBuffer(cbs.Value.CommandBuffer, true);
|
||||
|
||||
Copy(_gd, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
|
||||
|
||||
srcHolder.Dispose();
|
||||
}
|
||||
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,7 +819,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
endRenderPass?.Invoke();
|
||||
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value;
|
||||
|
||||
_writeCount--;
|
||||
|
||||
@ -608,7 +869,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool registerSrcUsage = true)
|
||||
{
|
||||
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
|
||||
var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
|
||||
var dstBuffer = dst.Get(cbs, dstOffset, size, true).Value;
|
||||
|
||||
InsertBufferBarrier(
|
||||
gd,
|
||||
|
@ -100,9 +100,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
BufferHandle storageHint = default)
|
||||
BufferHandle storageHint = default,
|
||||
bool forceMirrors = false)
|
||||
{
|
||||
return CreateWithHandle(gd, size, out _, baseType, storageHint);
|
||||
return CreateWithHandle(gd, size, out _, baseType, storageHint, forceMirrors);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(
|
||||
@ -110,7 +111,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int size,
|
||||
out BufferHolder holder,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
BufferHandle storageHint = default)
|
||||
BufferHandle storageHint = default,
|
||||
bool forceMirrors = false)
|
||||
{
|
||||
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
||||
if (holder == null)
|
||||
@ -118,6 +120,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
if (forceMirrors)
|
||||
{
|
||||
holder.UseMirrors();
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
305
src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs
Normal file
305
src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure tracking pending upload ranges for buffers.
|
||||
/// Where a range is present, pending data exists that can either be used to build mirrors
|
||||
/// or upload directly to the buffer.
|
||||
/// </summary>
|
||||
struct BufferMirrorRangeList
|
||||
{
|
||||
internal readonly struct Range
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
||||
public int End => Offset + Size;
|
||||
|
||||
public Range(int offset, int size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int offset, int size)
|
||||
{
|
||||
return Offset < offset + size && offset < Offset + Size;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Range> _ranges;
|
||||
|
||||
public readonly IEnumerable<Range> All()
|
||||
{
|
||||
return _ranges;
|
||||
}
|
||||
|
||||
public readonly bool Remove(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
bool removedAny = false;
|
||||
if (list != null)
|
||||
{
|
||||
int overlapIndex = BinarySearch(list, offset, size);
|
||||
|
||||
if (overlapIndex >= 0)
|
||||
{
|
||||
// Overlaps with a range. Search back to find the first one it doesn't overlap with.
|
||||
|
||||
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
overlapIndex--;
|
||||
}
|
||||
|
||||
int endOffset = offset + size;
|
||||
int startIndex = overlapIndex;
|
||||
|
||||
var currentOverlap = list[overlapIndex];
|
||||
|
||||
// Orphan the start of the overlap.
|
||||
if (currentOverlap.Offset < offset)
|
||||
{
|
||||
list[overlapIndex] = new Range(currentOverlap.Offset, offset - currentOverlap.Offset);
|
||||
currentOverlap = new Range(offset, currentOverlap.End - offset);
|
||||
list.Insert(++overlapIndex, currentOverlap);
|
||||
startIndex++;
|
||||
|
||||
removedAny = true;
|
||||
}
|
||||
|
||||
// Remove any middle overlaps.
|
||||
while (currentOverlap.Offset < endOffset)
|
||||
{
|
||||
if (currentOverlap.End > endOffset)
|
||||
{
|
||||
// Update the end overlap instead of removing it, if it spans beyond the removed range.
|
||||
list[overlapIndex] = new Range(endOffset, currentOverlap.End - endOffset);
|
||||
|
||||
removedAny = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (++overlapIndex >= list.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentOverlap = list[overlapIndex];
|
||||
}
|
||||
|
||||
int count = overlapIndex - startIndex;
|
||||
|
||||
list.RemoveRange(startIndex, count);
|
||||
|
||||
removedAny |= count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return removedAny;
|
||||
}
|
||||
|
||||
public void Add(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list != null)
|
||||
{
|
||||
int overlapIndex = BinarySearch(list, offset, size);
|
||||
if (overlapIndex >= 0)
|
||||
{
|
||||
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
overlapIndex--;
|
||||
}
|
||||
|
||||
int endOffset = offset + size;
|
||||
int startIndex = overlapIndex;
|
||||
|
||||
while (overlapIndex < list.Count && list[overlapIndex].OverlapsWith(offset, size))
|
||||
{
|
||||
var currentOverlap = list[overlapIndex];
|
||||
var currentOverlapEndOffset = currentOverlap.Offset + currentOverlap.Size;
|
||||
|
||||
if (offset > currentOverlap.Offset)
|
||||
{
|
||||
offset = currentOverlap.Offset;
|
||||
}
|
||||
|
||||
if (endOffset < currentOverlapEndOffset)
|
||||
{
|
||||
endOffset = currentOverlapEndOffset;
|
||||
}
|
||||
|
||||
overlapIndex++;
|
||||
size = endOffset - offset;
|
||||
}
|
||||
|
||||
int count = overlapIndex - startIndex;
|
||||
|
||||
list.RemoveRange(startIndex, count);
|
||||
|
||||
overlapIndex = startIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlapIndex = ~overlapIndex;
|
||||
}
|
||||
|
||||
list.Insert(overlapIndex, new Range(offset, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
_ranges = new List<Range>
|
||||
{
|
||||
new Range(offset, size)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool OverlapsWith(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return BinarySearch(list, offset, size) >= 0;
|
||||
}
|
||||
|
||||
public readonly List<Range> FindOverlaps(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Range> result = null;
|
||||
|
||||
int index = BinarySearch(list, offset, size);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && list[index - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
(result ??= new List<Range>()).Add(list[index++]);
|
||||
}
|
||||
while (index < list.Count && list[index].OverlapsWith(offset, size));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int BinarySearch(List<Range> list, int offset, int size)
|
||||
{
|
||||
int left = 0;
|
||||
int right = list.Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
var item = list[middle];
|
||||
|
||||
if (item.OverlapsWith(offset, size))
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (offset < item.Offset)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
public readonly void FillData(Span<byte> baseData, Span<byte> modData, int offset, Span<byte> result)
|
||||
{
|
||||
int size = baseData.Length;
|
||||
int endOffset = offset + size;
|
||||
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
baseData.CopyTo(result);
|
||||
}
|
||||
|
||||
int srcOffset = offset;
|
||||
int dstOffset = 0;
|
||||
bool activeRange = false;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var range = list[i];
|
||||
|
||||
int rangeEnd = range.Offset + range.Size;
|
||||
|
||||
if (activeRange)
|
||||
{
|
||||
if (range.Offset >= endOffset)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rangeEnd <= offset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
activeRange = true;
|
||||
}
|
||||
|
||||
int baseSize = range.Offset - srcOffset;
|
||||
|
||||
if (baseSize > 0)
|
||||
{
|
||||
baseData.Slice(dstOffset, baseSize).CopyTo(result.Slice(dstOffset, baseSize));
|
||||
srcOffset += baseSize;
|
||||
dstOffset += baseSize;
|
||||
}
|
||||
|
||||
int modSize = Math.Min(rangeEnd - srcOffset, endOffset - srcOffset);
|
||||
if (modSize != 0)
|
||||
{
|
||||
modData.Slice(dstOffset, modSize).CopyTo(result.Slice(dstOffset, modSize));
|
||||
srcOffset += modSize;
|
||||
dstOffset += modSize;
|
||||
}
|
||||
}
|
||||
|
||||
int baseSizeEnd = endOffset - srcOffset;
|
||||
|
||||
if (baseSizeEnd > 0)
|
||||
{
|
||||
baseData.Slice(dstOffset, baseSizeEnd).CopyTo(result.Slice(dstOffset, baseSizeEnd));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly int Count()
|
||||
{
|
||||
return _ranges?.Count ?? 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_ranges = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (_buffer != null)
|
||||
{
|
||||
var buffer = _buffer.Get(cbs, _offset, _size).Value;
|
||||
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
|
||||
|
||||
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
|
||||
}
|
||||
@ -40,6 +40,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_buffer?.DecrementReferenceCount();
|
||||
|
@ -6,6 +6,7 @@
|
||||
private readonly int _size;
|
||||
private readonly int _granularity;
|
||||
private readonly int _bits;
|
||||
private readonly int _writeBitOffset;
|
||||
|
||||
private readonly int _intsPerCb;
|
||||
private readonly int _bitsPerCb;
|
||||
@ -14,7 +15,11 @@
|
||||
{
|
||||
_size = size;
|
||||
_granularity = granularity;
|
||||
_bits = (size + (granularity - 1)) / granularity;
|
||||
|
||||
// There are two sets of bits - one for read tracking, and the other for write.
|
||||
int bits = (size + (granularity - 1)) / granularity;
|
||||
_writeBitOffset = bits;
|
||||
_bits = bits << 1;
|
||||
|
||||
_intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize;
|
||||
_bitsPerCb = _intsPerCb * BitMap.IntSize;
|
||||
@ -22,7 +27,7 @@
|
||||
_bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public void Add(int cbIndex, int offset, int size)
|
||||
public void Add(int cbIndex, int offset, int size, bool write)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
@ -35,32 +40,32 @@
|
||||
size = _size - offset;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb;
|
||||
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
||||
_bitmap.SetRange(start, end);
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int cbIndex, int offset, int size)
|
||||
public bool OverlapsWith(int cbIndex, int offset, int size, bool write = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb;
|
||||
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
||||
return _bitmap.IsSet(start, end);
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int offset, int size)
|
||||
public bool OverlapsWith(int offset, int size, bool write)
|
||||
{
|
||||
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
|
||||
{
|
||||
if (OverlapsWith(i, offset, size))
|
||||
if (OverlapsWith(i, offset, size, write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Buffer = Silk.NET.Vulkan.Buffer;
|
||||
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
@ -12,13 +12,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class DescriptorSetUpdater
|
||||
{
|
||||
private const ulong StorageBufferMaxMirrorable = 0x2000;
|
||||
private record struct BufferRef
|
||||
{
|
||||
public Auto<DisposableBuffer> Buffer;
|
||||
public int Offset;
|
||||
public bool Write;
|
||||
|
||||
public BufferRef(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = 0;
|
||||
Write = true;
|
||||
}
|
||||
|
||||
public BufferRef(Auto<DisposableBuffer> buffer, ref BufferRange range)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = range.Offset;
|
||||
Write = range.Write;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly PipelineBase _pipeline;
|
||||
|
||||
private ShaderCollection _program;
|
||||
|
||||
private readonly Auto<DisposableBuffer>[] _uniformBufferRefs;
|
||||
private readonly Auto<DisposableBuffer>[] _storageBufferRefs;
|
||||
private readonly BufferRef[] _uniformBufferRefs;
|
||||
private readonly BufferRef[] _storageBufferRefs;
|
||||
private readonly Auto<DisposableImageView>[] _textureRefs;
|
||||
private readonly Auto<DisposableSampler>[] _samplerRefs;
|
||||
private readonly Auto<DisposableImageView>[] _imageRefs;
|
||||
@ -33,8 +54,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly BufferView[] _bufferTextures;
|
||||
private readonly BufferView[] _bufferImages;
|
||||
|
||||
private readonly bool[] _uniformSet;
|
||||
private readonly bool[] _storageSet;
|
||||
private BitMapStruct<Array2<long>> _uniformSet;
|
||||
private BitMapStruct<Array2<long>> _storageSet;
|
||||
private BitMapStruct<Array2<long>> _uniformMirrored;
|
||||
private BitMapStruct<Array2<long>> _storageMirrored;
|
||||
|
||||
[Flags]
|
||||
private enum DirtyFlags
|
||||
@ -61,8 +84,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and
|
||||
// regular textures/images interleaved on the same descriptor set.
|
||||
|
||||
_uniformBufferRefs = new Auto<DisposableBuffer>[Constants.MaxUniformBufferBindings];
|
||||
_storageBufferRefs = new Auto<DisposableBuffer>[Constants.MaxStorageBufferBindings];
|
||||
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
|
||||
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
|
||||
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
|
||||
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
|
||||
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
|
||||
@ -85,9 +108,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_textures.AsSpan().Fill(initialImageInfo);
|
||||
_images.AsSpan().Fill(initialImageInfo);
|
||||
|
||||
_uniformSet = new bool[Constants.MaxUniformBufferBindings];
|
||||
_storageSet = new bool[Constants.MaxStorageBufferBindings];
|
||||
|
||||
if (gd.Capabilities.SupportsNullDescriptors)
|
||||
{
|
||||
// If null descriptors are supported, we can pass null as the handle.
|
||||
@ -138,6 +158,63 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_dummyTexture.SetData(dummyTextureData);
|
||||
}
|
||||
|
||||
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
|
||||
{
|
||||
return offset < bindingOffset + (int)info.Range && (offset + size) > bindingOffset;
|
||||
}
|
||||
|
||||
internal void Rebind(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
if (_program == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check stage bindings
|
||||
|
||||
_uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) =>
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref BufferRef bufferRef = ref _uniformBufferRefs[binding];
|
||||
if (bufferRef.Buffer == buffer)
|
||||
{
|
||||
ref DescriptorBufferInfo info = ref _uniformBuffers[binding];
|
||||
int bindingOffset = bufferRef.Offset;
|
||||
|
||||
if (BindingOverlaps(ref info, bindingOffset, offset, size))
|
||||
{
|
||||
_uniformSet.Clear(binding);
|
||||
SignalDirty(DirtyFlags.Uniform);
|
||||
}
|
||||
}
|
||||
|
||||
binding++;
|
||||
}
|
||||
});
|
||||
|
||||
_storageMirrored.Union(_storageSet).SignalSet((int binding, int count) =>
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref BufferRef bufferRef = ref _storageBufferRefs[binding];
|
||||
if (bufferRef.Buffer == buffer)
|
||||
{
|
||||
ref DescriptorBufferInfo info = ref _storageBuffers[binding];
|
||||
int bindingOffset = bufferRef.Offset;
|
||||
|
||||
if (BindingOverlaps(ref info, bindingOffset, offset, size))
|
||||
{
|
||||
_storageSet.Clear(binding);
|
||||
SignalDirty(DirtyFlags.Storage);
|
||||
}
|
||||
}
|
||||
|
||||
binding++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void SetProgram(ShaderCollection program)
|
||||
{
|
||||
_program = program;
|
||||
@ -180,22 +257,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var buffer = assignment.Range;
|
||||
int index = assignment.Binding;
|
||||
|
||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true);
|
||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
||||
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
||||
? null
|
||||
: _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, buffer.Write, isSSBO: true);
|
||||
|
||||
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
|
||||
|
||||
DescriptorBufferInfo info = new()
|
||||
{
|
||||
Offset = (ulong)buffer.Offset,
|
||||
Range = (ulong)buffer.Size,
|
||||
};
|
||||
|
||||
var newRef = new BufferRef(vkBuffer, ref buffer);
|
||||
|
||||
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
|
||||
|
||||
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
|
||||
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
|
||||
{
|
||||
_storageSet[index] = false;
|
||||
_storageSet.Clear(index);
|
||||
|
||||
currentInfo = info;
|
||||
currentVkBuffer = vkBuffer;
|
||||
currentBufferRef = newRef;
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,21 +292,24 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var vkBuffer = buffers[i];
|
||||
int index = first + i;
|
||||
|
||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
||||
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
|
||||
|
||||
DescriptorBufferInfo info = new()
|
||||
{
|
||||
Offset = 0,
|
||||
Range = Vk.WholeSize,
|
||||
};
|
||||
|
||||
BufferRef newRef = new(vkBuffer);
|
||||
|
||||
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
|
||||
|
||||
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
|
||||
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
|
||||
{
|
||||
_storageSet[index] = false;
|
||||
_storageSet.Clear(index);
|
||||
|
||||
currentInfo = info;
|
||||
currentVkBuffer = vkBuffer;
|
||||
currentBufferRef = newRef;
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,22 +374,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var buffer = assignment.Range;
|
||||
int index = assignment.Binding;
|
||||
|
||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
|
||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];
|
||||
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
||||
? null
|
||||
: _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
|
||||
|
||||
ref BufferRef currentBufferRef = ref _uniformBufferRefs[index];
|
||||
|
||||
DescriptorBufferInfo info = new()
|
||||
{
|
||||
Offset = (ulong)buffer.Offset,
|
||||
Range = (ulong)buffer.Size,
|
||||
};
|
||||
|
||||
BufferRef newRef = new(vkBuffer, ref buffer);
|
||||
|
||||
ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index];
|
||||
|
||||
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
|
||||
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
|
||||
{
|
||||
_uniformSet[index] = false;
|
||||
_uniformSet.Clear(index);
|
||||
|
||||
currentInfo = info;
|
||||
currentVkBuffer = vkBuffer;
|
||||
currentBufferRef = newRef;
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,13 +445,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void UpdateBuffer(
|
||||
private static bool UpdateBuffer(
|
||||
CommandBufferScoped cbs,
|
||||
ref DescriptorBufferInfo info,
|
||||
Auto<DisposableBuffer> buffer,
|
||||
Auto<DisposableBuffer> dummyBuffer)
|
||||
ref BufferRef buffer,
|
||||
Auto<DisposableBuffer> dummyBuffer,
|
||||
bool mirrorable)
|
||||
{
|
||||
info.Buffer = buffer?.Get(cbs, (int)info.Offset, (int)info.Range).Value ?? default;
|
||||
int offset = buffer.Offset;
|
||||
bool mirrored = false;
|
||||
|
||||
if (mirrorable)
|
||||
{
|
||||
info.Buffer = buffer.Buffer?.GetMirrorable(cbs, ref offset, (int)info.Range, out mirrored).Value ?? default;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Buffer = buffer.Buffer?.Get(cbs, offset, (int)info.Range, buffer.Write).Value ?? default;
|
||||
}
|
||||
|
||||
info.Offset = (ulong)offset;
|
||||
|
||||
// The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE.
|
||||
if (info.Buffer.Handle == 0)
|
||||
@ -368,6 +473,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
info.Offset = 0;
|
||||
info.Range = Vk.WholeSize;
|
||||
}
|
||||
|
||||
return mirrored;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -404,11 +511,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_uniformSet[index])
|
||||
if (_uniformSet.Set(index))
|
||||
{
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
ref BufferRef buffer = ref _uniformBufferRefs[index];
|
||||
|
||||
_uniformSet[index] = true;
|
||||
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
|
||||
_uniformMirrored.Set(index, mirrored);
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,11 +530,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_storageSet[index])
|
||||
{
|
||||
UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
|
||||
ref BufferRef buffer = ref _storageBufferRefs[index];
|
||||
|
||||
_storageSet[index] = true;
|
||||
if (_storageSet.Set(index))
|
||||
{
|
||||
ref var info = ref _storageBuffers[index];
|
||||
|
||||
bool mirrored = UpdateBuffer(cbs,
|
||||
ref info,
|
||||
ref _storageBufferRefs[index],
|
||||
dummyBuffer,
|
||||
!buffer.Write && info.Range <= StorageBufferMaxMirrorable);
|
||||
|
||||
_storageMirrored.Set(index, mirrored);
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,7 +581,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
|
||||
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
|
||||
}
|
||||
|
||||
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
|
||||
@ -489,7 +606,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
|
||||
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default;
|
||||
}
|
||||
|
||||
dsc.UpdateBufferImages(0, binding, bufferImages[..count], DescriptorType.StorageTexelBuffer);
|
||||
@ -546,10 +663,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_uniformSet[index])
|
||||
if (_uniformSet.Set(index))
|
||||
{
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
_uniformSet[index] = true;
|
||||
ref BufferRef buffer = ref _uniformBufferRefs[index];
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
doUpdate = true;
|
||||
}
|
||||
}
|
||||
@ -582,17 +699,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_dirty = DirtyFlags.All;
|
||||
|
||||
Array.Clear(_uniformSet);
|
||||
Array.Clear(_storageSet);
|
||||
_uniformSet.Clear();
|
||||
_storageSet.Clear();
|
||||
}
|
||||
|
||||
private static void SwapBuffer(Auto<DisposableBuffer>[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
for (int i = 0; i < list.Length; i++)
|
||||
{
|
||||
if (list[i] == from)
|
||||
if (list[i].Buffer == from)
|
||||
{
|
||||
list[i] = to;
|
||||
list[i].Buffer = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -427,6 +427,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return access switch
|
||||
{
|
||||
BufferAccess.FlushPersistent => BufferAllocationType.HostMapped,
|
||||
BufferAccess.Stream => BufferAllocationType.HostMapped,
|
||||
_ => BufferAllocationType.Auto,
|
||||
};
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||
public readonly uint VertexBufferAlignment;
|
||||
public readonly uint SubTexelPrecisionBits;
|
||||
public readonly ulong MinResourceAlignment;
|
||||
|
||||
public HardwareCapabilities(
|
||||
bool supportsIndexTypeUint8,
|
||||
@ -89,7 +90,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SampleCountFlags supportedSampleCounts,
|
||||
PortabilitySubsetFlags portabilitySubset,
|
||||
uint vertexBufferAlignment,
|
||||
uint subTexelPrecisionBits)
|
||||
uint subTexelPrecisionBits,
|
||||
ulong minResourceAlignment)
|
||||
{
|
||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||
@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PortabilitySubset = portabilitySubset;
|
||||
VertexBufferAlignment = vertexBufferAlignment;
|
||||
SubTexelPrecisionBits = subTexelPrecisionBits;
|
||||
MinResourceAlignment = minResourceAlignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal struct IndexBufferState
|
||||
{
|
||||
private const int IndexBufferMaxMirrorable = 0x20000;
|
||||
|
||||
public static IndexBufferState Null => new(BufferHandle.Null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
@ -37,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Auto<DisposableBuffer> autoBuffer;
|
||||
int offset, size;
|
||||
IndexType type = _type;
|
||||
bool mirrorable = false;
|
||||
|
||||
if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8)
|
||||
{
|
||||
@ -56,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
autoBuffer = null;
|
||||
}
|
||||
|
||||
mirrorable = _size < IndexBufferMaxMirrorable;
|
||||
|
||||
offset = _offset;
|
||||
size = _size;
|
||||
}
|
||||
@ -64,7 +69,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, offset, size).Value, (ulong)offset, type);
|
||||
DisposableBuffer buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, size, out _) : autoBuffer.Get(cbs, offset, size);
|
||||
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, buffer.Value, (ulong)offset, type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,5 +162,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds buffer usage information to the uses list.
|
||||
/// Adds read/write buffer usage information to the uses list.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
|
||||
/// <param name="offset">Offset of the buffer being used</param>
|
||||
/// <param name="size">Size of the buffer region being used, in bytes</param>
|
||||
public void AddBufferUse(int cbIndex, int offset, int size)
|
||||
/// <param name="write">Whether the access is a write or not</param>
|
||||
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
|
||||
{
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size);
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
|
||||
|
||||
if (write)
|
||||
{
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -68,10 +74,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the buffer being used</param>
|
||||
/// <param name="size">Size of the buffer region being used, in bytes</param>
|
||||
/// <param name="write">True if only write usages should count</param>
|
||||
/// <returns>True if in use, false otherwise</returns>
|
||||
public bool IsBufferRangeInUse(int offset, int size)
|
||||
public bool IsBufferRangeInUse(int offset, int size, bool write)
|
||||
{
|
||||
return _bufferUsageBitmap.OverlapsWith(offset, size);
|
||||
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -193,7 +193,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
EndRenderPass();
|
||||
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size).Value;
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size, true).Value;
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
Gd,
|
||||
@ -469,6 +469,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
UpdateIndexBufferPattern();
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
BeginRenderPass();
|
||||
@ -498,10 +502,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
|
||||
@ -515,15 +515,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
UpdateIndexBufferPattern();
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
BeginRenderPass();
|
||||
DrawCount++;
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
|
||||
if (_indexBufferPattern != null)
|
||||
{
|
||||
// Convert the index buffer into a supported topology.
|
||||
@ -570,10 +574,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
ResumeTransformFeedbackInternal();
|
||||
|
||||
if (Gd.Capabilities.SupportsIndirectParameters)
|
||||
@ -609,15 +609,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// TODO: Support quads and other unsupported topologies.
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
|
||||
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
BeginRenderPass();
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
Gd.Api.CmdDrawIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
|
||||
}
|
||||
|
||||
@ -634,6 +634,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size, false).Value;
|
||||
|
||||
// TODO: Support quads and other unsupported topologies.
|
||||
|
||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||
@ -641,14 +649,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
|
||||
Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
|
||||
CommandBuffer,
|
||||
buffer,
|
||||
@ -709,6 +709,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return CommandBuffer.Handle == cb.Handle;
|
||||
}
|
||||
|
||||
internal void Rebind(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
_descriptorSetUpdater.Rebind(buffer, offset, size);
|
||||
|
||||
if (_indexBuffer.Overlaps(buffer, offset, size))
|
||||
{
|
||||
_indexBuffer.BindIndexBuffer(Gd, Cbs);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _vertexBuffers.Length; i++)
|
||||
{
|
||||
if (_vertexBuffers[i].Overlaps(buffer, offset, size))
|
||||
{
|
||||
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
_vertexBufferUpdater.Commit(Cbs);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private CounterQueueEvent _activeConditionalRender;
|
||||
|
||||
private readonly List<BufferedQuery> _pendingQueryCopies;
|
||||
private readonly List<BufferHolder> _activeBufferMirrors;
|
||||
|
||||
private ulong _byteWeight;
|
||||
|
||||
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_activeQueries = new List<(QueryPool, bool)>();
|
||||
_pendingQueryCopies = new();
|
||||
_backingSwaps = new();
|
||||
_activeBufferMirrors = new();
|
||||
|
||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||
}
|
||||
@ -233,6 +235,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Gd.RegisterFlush();
|
||||
|
||||
// Restore per-command buffer state.
|
||||
foreach (BufferHolder buffer in _activeBufferMirrors)
|
||||
{
|
||||
buffer.ClearMirrors();
|
||||
}
|
||||
|
||||
_activeBufferMirrors.Clear();
|
||||
|
||||
foreach ((var queryPool, var isOcclusion) in _activeQueries)
|
||||
{
|
||||
@ -249,6 +257,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Restore();
|
||||
}
|
||||
|
||||
public void RegisterActiveMirror(BufferHolder buffer)
|
||||
{
|
||||
_activeBufferMirrors.Add(buffer);
|
||||
}
|
||||
|
||||
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool)
|
||||
{
|
||||
if (needsReset)
|
||||
|
@ -183,7 +183,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
||||
|
||||
public void PoolCopy(CommandBufferScoped cbs)
|
||||
{
|
||||
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value;
|
||||
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long), true).Value;
|
||||
|
||||
QueryResultFlags flags = QueryResultFlags.ResultWaitBit;
|
||||
|
||||
|
@ -1,12 +1,28 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly struct StagingBufferReserved
|
||||
{
|
||||
public readonly BufferHolder Buffer;
|
||||
public readonly int Offset;
|
||||
public readonly int Size;
|
||||
|
||||
public StagingBufferReserved(BufferHolder buffer, int offset, int size)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
class StagingBuffer : IDisposable
|
||||
{
|
||||
private const int BufferSize = 16 * 1024 * 1024;
|
||||
private const int BufferSize = 32 * 1024 * 1024;
|
||||
|
||||
private int _freeOffset;
|
||||
private int _freeSize;
|
||||
@ -130,13 +146,83 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
endRenderPass();
|
||||
endRenderPass?.Invoke();
|
||||
|
||||
PushDataImpl(cbs, dst, dstOffset, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private StagingBufferReserved ReserveDataImpl(CommandBufferScoped cbs, int size, int alignment)
|
||||
{
|
||||
// Assumes the caller has already determined that there is enough space.
|
||||
int offset = BitUtils.AlignUp(_freeOffset, alignment);
|
||||
int padding = offset - _freeOffset;
|
||||
|
||||
int capacity = Math.Min(_freeSize, BufferSize - offset);
|
||||
int reservedLength = size + padding;
|
||||
if (capacity < size)
|
||||
{
|
||||
offset = 0; // Place at start.
|
||||
reservedLength += capacity;
|
||||
}
|
||||
|
||||
_freeOffset = (_freeOffset + reservedLength) & (BufferSize - 1);
|
||||
_freeSize -= reservedLength;
|
||||
Debug.Assert(_freeSize >= 0);
|
||||
|
||||
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), reservedLength));
|
||||
|
||||
return new StagingBufferReserved(_buffer, offset, size);
|
||||
}
|
||||
|
||||
private int GetContiguousFreeSize(int alignment)
|
||||
{
|
||||
int alignedFreeOffset = BitUtils.AlignUp(_freeOffset, alignment);
|
||||
int padding = alignedFreeOffset - _freeOffset;
|
||||
|
||||
// Free regions:
|
||||
// - Aligned free offset to end (minimum free size - padding)
|
||||
// - 0 to _freeOffset + freeSize wrapped (only if free area contains 0)
|
||||
|
||||
int endOffset = (_freeOffset + _freeSize) & (BufferSize - 1);
|
||||
|
||||
return Math.Max(
|
||||
Math.Min(_freeSize - padding, BufferSize - alignedFreeOffset),
|
||||
endOffset <= _freeOffset ? Math.Min(_freeSize, endOffset) : 0
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserve a range on the staging buffer for the current command buffer and upload data to it.
|
||||
/// </summary>
|
||||
/// <param name="cbs">Command buffer to reserve the data on</param>
|
||||
/// <param name="data">The data to upload</param>
|
||||
/// <param name="alignment">The required alignment for the buffer offset</param>
|
||||
/// <returns>The reserved range of the staging buffer</returns>
|
||||
public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size, int alignment)
|
||||
{
|
||||
if (size > BufferSize)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Temporary reserved data cannot be fragmented.
|
||||
|
||||
if (GetContiguousFreeSize(alignment) < size)
|
||||
{
|
||||
FreeCompleted();
|
||||
|
||||
if (GetContiguousFreeSize(alignment) < size)
|
||||
{
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Staging buffer out of space to reserve data of size {size}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ReserveDataImpl(cbs, size, alignment);
|
||||
}
|
||||
|
||||
private bool WaitFreeCompleted(CommandBufferPool cbp)
|
||||
{
|
||||
if (_pendingCopies.TryPeek(out var pc))
|
||||
|
@ -127,24 +127,24 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ReleaseImpl();
|
||||
}
|
||||
|
||||
public BufferView GetBufferView(CommandBufferScoped cbs)
|
||||
public BufferView GetBufferView(CommandBufferScoped cbs, bool write)
|
||||
{
|
||||
_bufferView ??= _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size, ReleaseImpl);
|
||||
|
||||
return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
|
||||
return _bufferView?.Get(cbs, _offset, _size, write).Value ?? default;
|
||||
}
|
||||
|
||||
public BufferView GetBufferView(CommandBufferScoped cbs, Format format)
|
||||
public BufferView GetBufferView(CommandBufferScoped cbs, Format format, bool write)
|
||||
{
|
||||
var vkFormat = FormatTable.GetFormat(format);
|
||||
if (vkFormat == VkFormat)
|
||||
{
|
||||
return GetBufferView(cbs);
|
||||
return GetBufferView(cbs, write);
|
||||
}
|
||||
|
||||
if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var bufferView))
|
||||
{
|
||||
return bufferView.Get(cbs, _offset, _size).Value;
|
||||
return bufferView.Get(cbs, _offset, _size, write).Value;
|
||||
}
|
||||
|
||||
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size, ReleaseImpl);
|
||||
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(_selfManagedViews ??= new Dictionary<Format, Auto<DisposableBufferView>>()).Add(format, bufferView);
|
||||
}
|
||||
|
||||
return bufferView?.Get(cbs, _offset, _size).Value ?? default;
|
||||
return bufferView?.Get(cbs, _offset, _size, write).Value ?? default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal struct VertexBufferState
|
||||
{
|
||||
private const int VertexBufferMaxMirrorable = 0x20000;
|
||||
|
||||
public static VertexBufferState Null => new(null, 0, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
@ -88,9 +90,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
|
||||
int offset = _offset;
|
||||
bool mirrorable = _size <= VertexBufferMaxMirrorable;
|
||||
var buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, _size, out _).Value : autoBuffer.Get(cbs, offset, _size).Value;
|
||||
|
||||
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)_offset, (ulong)_size, (ulong)_stride);
|
||||
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)offset, (ulong)_size, (ulong)_stride);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +103,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
|
||||
}
|
||||
|
||||
public readonly bool Matches(Auto<DisposableBuffer> buffer, int descriptorIndex, int offset, int size, int stride = 0)
|
||||
{
|
||||
return _buffer == buffer && DescriptorIndex == descriptorIndex && _offset == offset && _size == size && _stride == stride;
|
||||
|
@ -293,6 +293,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
ref var properties = ref properties2.Properties;
|
||||
|
||||
ulong minResourceAlignment = Math.Max(
|
||||
Math.Max(
|
||||
properties.Limits.MinStorageBufferOffsetAlignment,
|
||||
properties.Limits.MinUniformBufferOffsetAlignment),
|
||||
properties.Limits.MinTexelBufferOffsetAlignment
|
||||
);
|
||||
|
||||
SampleCountFlags supportedSampleCounts =
|
||||
properties.Limits.FramebufferColorSampleCounts &
|
||||
properties.Limits.FramebufferDepthSampleCounts &
|
||||
@ -334,7 +341,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportedSampleCounts,
|
||||
portabilityFlags,
|
||||
vertexBufferAlignment,
|
||||
properties.Limits.SubTexelPrecisionBits);
|
||||
properties.Limits.SubTexelPrecisionBits,
|
||||
minResourceAlignment);
|
||||
|
||||
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice);
|
||||
|
||||
@ -397,7 +405,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||
{
|
||||
return BufferManager.CreateWithHandle(this, size, access.Convert());
|
||||
return BufferManager.CreateWithHandle(this, size, access.Convert(), default, access == BufferAccess.Stream);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
|
Loading…
Reference in New Issue
Block a user