aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Vulkan/BufferHolder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Vulkan/BufferHolder.cs')
-rw-r--r--Ryujinx.Graphics.Vulkan/BufferHolder.cs788
1 files changed, 0 insertions, 788 deletions
diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
deleted file mode 100644
index 21b81bdd..00000000
--- a/Ryujinx.Graphics.Vulkan/BufferHolder.cs
+++ /dev/null
@@ -1,788 +0,0 @@
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.GAL;
-using Silk.NET.Vulkan;
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using VkBuffer = Silk.NET.Vulkan.Buffer;
-using VkFormat = Silk.NET.Vulkan.Format;
-
-namespace Ryujinx.Graphics.Vulkan
-{
- class BufferHolder : IDisposable
- {
- private const int MaxUpdateBufferSize = 0x10000;
-
- private const int SetCountThreshold = 100;
- private const int WriteCountThreshold = 50;
- private const int FlushCountThreshold = 5;
-
- public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
-
- public const AccessFlags DefaultAccessFlags =
- AccessFlags.IndirectCommandReadBit |
- AccessFlags.ShaderReadBit |
- AccessFlags.ShaderWriteBit |
- AccessFlags.TransferReadBit |
- AccessFlags.TransferWriteBit |
- AccessFlags.UniformReadBit;
-
- private readonly VulkanRenderer _gd;
- private readonly Device _device;
- private MemoryAllocation _allocation;
- private Auto<DisposableBuffer> _buffer;
- private Auto<MemoryAllocation> _allocationAuto;
- private ulong _bufferHandle;
-
- private CacheByRange<BufferHolder> _cachedConvertedBuffers;
-
- public int Size { get; }
-
- private IntPtr _map;
-
- private MultiFenceHolder _waitable;
-
- private bool _lastAccessIsWrite;
-
- private BufferAllocationType _baseType;
- private BufferAllocationType _currentType;
- private bool _swapQueued;
-
- public BufferAllocationType DesiredType { get; private set; }
-
- private int _setCount;
- private int _writeCount;
- private int _flushCount;
- private int _flushTemp;
-
- private ReaderWriterLock _flushLock;
- private FenceHolder _flushFence;
- private int _flushWaiting;
-
- private List<Action> _swapActions;
-
- public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
- {
- _gd = gd;
- _device = device;
- _allocation = allocation;
- _allocationAuto = new Auto<MemoryAllocation>(allocation);
- _waitable = new MultiFenceHolder(size);
- _buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
- _bufferHandle = buffer.Handle;
- Size = size;
- _map = allocation.HostPointer;
-
- _baseType = type;
- _currentType = currentType;
- DesiredType = currentType;
-
- _flushLock = new ReaderWriterLock();
- }
-
- public bool TryBackingSwap(ref CommandBufferScoped? cbs)
- {
- if (_swapQueued && DesiredType != _currentType)
- {
- // 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)
- {
- var currentAllocation = _allocationAuto;
- var currentBuffer = _buffer;
- IntPtr currentMap = _map;
-
- (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
-
- if (buffer.Handle != 0)
- {
- _flushLock.AcquireWriterLock(Timeout.Infinite);
-
- ClearFlushFence();
-
- _waitable = new MultiFenceHolder(Size);
-
- _allocation = allocation;
- _allocationAuto = new Auto<MemoryAllocation>(allocation);
- _buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
- _bufferHandle = buffer.Handle;
- _map = allocation.HostPointer;
-
- if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
- {
- // Copy data directly. Readbacks don't have to wait if this is done.
-
- unsafe
- {
- new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
- }
- }
- else
- {
- if (cbs == null)
- {
- cbs = _gd.CommandBufferPool.Rent();
- }
-
- CommandBufferScoped cbsV = cbs.Value;
-
- Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
-
- // Need to wait for the data to reach the new buffer before data can be flushed.
-
- _flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
- _flushFence.Get();
- }
-
- Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
-
- _currentType = resultType;
-
- if (_swapActions != null)
- {
- foreach (var action in _swapActions)
- {
- action();
- }
-
- _swapActions.Clear();
- }
-
- currentBuffer.Dispose();
- currentAllocation.Dispose();
-
- _gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
-
- _flushLock.ReleaseWriterLock();
- }
-
- _swapQueued = false;
-
- return true;
- }
- else
- {
- return false;
- }
- }
- else
- {
- _swapQueued = false;
-
- return true;
- }
- }
-
- private void ConsiderBackingSwap()
- {
- if (_baseType == BufferAllocationType.Auto)
- {
- if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
- {
- if (_flushCount > 0 || _flushTemp-- > 0)
- {
- // Buffers that flush should ideally be mapped in host address space for easy copies.
- // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
- // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
- DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
-
- // It's harder for a buffer that is flushed to revert to another type of mapping.
- if (_flushCount > 0)
- {
- _flushTemp = 1000;
- }
- }
- else if (_writeCount >= WriteCountThreshold)
- {
- // Buffers that are written often should ideally be in the device local heap. (Storage buffers)
- DesiredType = BufferAllocationType.DeviceLocal;
- }
- else if (_setCount > SetCountThreshold)
- {
- // Buffers that have their data set often should ideally be host mapped. (Constant buffers)
- DesiredType = BufferAllocationType.HostMapped;
- }
-
- _flushCount = 0;
- _writeCount = 0;
- _setCount = 0;
- }
-
- if (!_swapQueued && DesiredType != _currentType)
- {
- _swapQueued = true;
-
- _gd.PipelineInternal.AddBackingSwap(this);
- }
- }
- }
-
- public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
- {
- var bufferViewCreateInfo = new BufferViewCreateInfo()
- {
- SType = StructureType.BufferViewCreateInfo,
- Buffer = new VkBuffer(_bufferHandle),
- Format = format,
- Offset = (uint)offset,
- Range = (uint)size
- };
-
- _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
-
- (_swapActions ??= new List<Action>()).Add(invalidateView);
-
- return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
- }
-
- public void InheritMetrics(BufferHolder other)
- {
- _setCount = other._setCount;
- _writeCount = other._writeCount;
- _flushCount = other._flushCount;
- _flushTemp = other._flushTemp;
- }
-
- public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
- {
- // If the last access is write, we always need a barrier to be sure we will read or modify
- // the correct data.
- // If the last access is read, and current one is a write, we need to wait until the
- // read finishes to avoid overwriting data still in use.
- // Otherwise, if the last access is a read and the current one too, we don't need barriers.
- bool needsBarrier = isWrite || _lastAccessIsWrite;
-
- _lastAccessIsWrite = isWrite;
-
- if (needsBarrier)
- {
- MemoryBarrier memoryBarrier = new MemoryBarrier()
- {
- SType = StructureType.MemoryBarrier,
- SrcAccessMask = DefaultAccessFlags,
- DstAccessMask = DefaultAccessFlags
- };
-
- _gd.Api.CmdPipelineBarrier(
- commandBuffer,
- PipelineStageFlags.AllCommandsBit,
- PipelineStageFlags.AllCommandsBit,
- DependencyFlags.DeviceGroupBit,
- 1,
- memoryBarrier,
- 0,
- null,
- 0,
- null);
- }
- }
-
- public Auto<DisposableBuffer> GetBuffer()
- {
- return _buffer;
- }
-
- public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
- {
- if (isWrite)
- {
- _writeCount++;
-
- SignalWrite(0, Size);
- }
- else if (isSSBO)
- {
- // Always consider SSBO access for swapping to device local memory.
-
- _writeCount++;
-
- ConsiderBackingSwap();
- }
-
- return _buffer;
- }
-
- public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false)
- {
- if (isWrite)
- {
- _writeCount++;
-
- SignalWrite(offset, size);
- }
-
- return _buffer;
- }
-
- public void SignalWrite(int offset, int size)
- {
- ConsiderBackingSwap();
-
- if (offset == 0 && size == Size)
- {
- _cachedConvertedBuffers.Clear();
- }
- else
- {
- _cachedConvertedBuffers.ClearRange(offset, size);
- }
- }
-
- public BufferHandle GetHandle()
- {
- var handle = _bufferHandle;
- return Unsafe.As<ulong, BufferHandle>(ref handle);
- }
-
- public unsafe IntPtr Map(int offset, int mappingSize)
- {
- return _map;
- }
-
- private void ClearFlushFence()
- {
- // Asusmes _flushLock is held as writer.
-
- if (_flushFence != null)
- {
- if (_flushWaiting == 0)
- {
- _flushFence.Put();
- }
-
- _flushFence = null;
- }
- }
-
- private void WaitForFlushFence()
- {
- // Assumes the _flushLock is held as reader, returns in same state.
-
- if (_flushFence != null)
- {
- // If storage has changed, make sure the fence has been reached so that the data is in place.
-
- var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
-
- if (_flushFence != null)
- {
- var fence = _flushFence;
- Interlocked.Increment(ref _flushWaiting);
-
- // Don't wait in the lock.
-
- var restoreCookie = _flushLock.ReleaseLock();
-
- fence.Wait();
-
- _flushLock.RestoreLock(ref restoreCookie);
-
- if (Interlocked.Decrement(ref _flushWaiting) == 0)
- {
- fence.Put();
- }
-
- _flushFence = null;
- }
-
- _flushLock.DowngradeFromWriterLock(ref cookie);
- }
- }
-
- public unsafe PinnedSpan<byte> GetData(int offset, int size)
- {
- _flushLock.AcquireReaderLock(Timeout.Infinite);
-
- WaitForFlushFence();
-
- _flushCount++;
-
- Span<byte> result;
-
- if (_map != IntPtr.Zero)
- {
- result = GetDataStorage(offset, size);
-
- // Need to be careful here, the buffer can't be unmapped while the data is being used.
- _buffer.IncrementReferenceCount();
-
- _flushLock.ReleaseReaderLock();
-
- return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
- }
- else
- {
- BackgroundResource resource = _gd.BackgroundResources.Get();
-
- if (_gd.CommandBufferPool.OwnedByCurrentThread)
- {
- _gd.FlushAllCommands();
-
- result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
- }
- else
- {
- result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
- }
-
- _flushLock.ReleaseReaderLock();
-
- // Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
- return PinnedSpan<byte>.UnsafeFromSpan(result);
- }
- }
-
- public unsafe Span<byte> GetDataStorage(int offset, int size)
- {
- int mappingSize = Math.Min(size, Size - offset);
-
- if (_map != IntPtr.Zero)
- {
- return new Span<byte>((void*)(_map + offset), mappingSize);
- }
-
- throw new InvalidOperationException("The buffer is not host mapped.");
- }
-
- public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
- {
- int dataSize = Math.Min(data.Length, Size - offset);
- if (dataSize == 0)
- {
- return;
- }
-
- _setCount++;
-
- if (_map != IntPtr.Zero)
- {
- // If persistently mapped, set the data directly if the buffer is not currently in use.
- 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);
-
- if (!needsFlush)
- {
- WaitForFences(offset, dataSize);
-
- data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
-
- SignalWrite(offset, dataSize);
-
- return;
- }
- }
-
- if (cbs != null &&
- _gd.PipelineInternal.RenderPassActive &&
- !(_buffer.HasCommandBufferDependency(cbs.Value) &&
- _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
- {
- // If the buffer hasn't been used on the command buffer yet, try to preload the data.
- // This avoids ending and beginning render passes on each buffer data upload.
-
- cbs = _gd.PipelineInternal.GetPreloadCommandBuffer();
- endRenderPass = null;
- }
-
- if (cbs == null ||
- !VulkanConfiguration.UseFastBufferUpdates ||
- data.Length > MaxUpdateBufferSize ||
- !TryPushData(cbs.Value, endRenderPass, offset, data))
- {
- _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
- }
- }
-
- public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
- {
- int dataSize = Math.Min(data.Length, Size - offset);
- if (dataSize == 0)
- {
- return;
- }
-
- if (_map != IntPtr.Zero)
- {
- data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
- }
- else
- {
- _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, null, null, this, offset, data);
- }
- }
-
- public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
- {
- if (!TryPushData(cbs, endRenderPass, dstOffset, data))
- {
- throw new ArgumentException($"Invalid offset 0x{dstOffset:X} or data size 0x{data.Length:X}.");
- }
- }
-
- private unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
- {
- if ((dstOffset & 3) != 0 || (data.Length & 3) != 0)
- {
- return false;
- }
-
- endRenderPass?.Invoke();
-
- var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
-
- _writeCount--;
-
- InsertBufferBarrier(
- _gd,
- cbs.CommandBuffer,
- dstBuffer,
- DefaultAccessFlags,
- AccessFlags.TransferWriteBit,
- PipelineStageFlags.AllCommandsBit,
- PipelineStageFlags.TransferBit,
- dstOffset,
- data.Length);
-
- fixed (byte* pData = data)
- {
- for (ulong offset = 0; offset < (ulong)data.Length;)
- {
- ulong size = Math.Min(MaxUpdateBufferSize, (ulong)data.Length - offset);
- _gd.Api.CmdUpdateBuffer(cbs.CommandBuffer, dstBuffer, (ulong)dstOffset + offset, size, pData + offset);
- offset += size;
- }
- }
-
- InsertBufferBarrier(
- _gd,
- cbs.CommandBuffer,
- dstBuffer,
- AccessFlags.TransferWriteBit,
- DefaultAccessFlags,
- PipelineStageFlags.TransferBit,
- PipelineStageFlags.AllCommandsBit,
- dstOffset,
- data.Length);
-
- return true;
- }
-
- public static unsafe void Copy(
- VulkanRenderer gd,
- CommandBufferScoped cbs,
- Auto<DisposableBuffer> src,
- Auto<DisposableBuffer> dst,
- int srcOffset,
- int dstOffset,
- int size)
- {
- var srcBuffer = src.Get(cbs, srcOffset, size).Value;
- var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
-
- InsertBufferBarrier(
- gd,
- cbs.CommandBuffer,
- dstBuffer,
- DefaultAccessFlags,
- AccessFlags.TransferWriteBit,
- PipelineStageFlags.AllCommandsBit,
- PipelineStageFlags.TransferBit,
- dstOffset,
- size);
-
- var region = new BufferCopy((ulong)srcOffset, (ulong)dstOffset, (ulong)size);
-
- gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, 1, &region);
-
- InsertBufferBarrier(
- gd,
- cbs.CommandBuffer,
- dstBuffer,
- AccessFlags.TransferWriteBit,
- DefaultAccessFlags,
- PipelineStageFlags.TransferBit,
- PipelineStageFlags.AllCommandsBit,
- dstOffset,
- size);
- }
-
- public static unsafe void InsertBufferBarrier(
- VulkanRenderer gd,
- CommandBuffer commandBuffer,
- VkBuffer buffer,
- AccessFlags srcAccessMask,
- AccessFlags dstAccessMask,
- PipelineStageFlags srcStageMask,
- PipelineStageFlags dstStageMask,
- int offset,
- int size)
- {
- BufferMemoryBarrier memoryBarrier = new BufferMemoryBarrier()
- {
- SType = StructureType.BufferMemoryBarrier,
- SrcAccessMask = srcAccessMask,
- DstAccessMask = dstAccessMask,
- SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
- DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
- Buffer = buffer,
- Offset = (ulong)offset,
- Size = (ulong)size
- };
-
- gd.Api.CmdPipelineBarrier(
- commandBuffer,
- srcStageMask,
- dstStageMask,
- 0,
- 0,
- null,
- 1,
- memoryBarrier,
- 0,
- null);
- }
-
- public void WaitForFences()
- {
- _waitable.WaitForFences(_gd.Api, _device);
- }
-
- public void WaitForFences(int offset, int size)
- {
- _waitable.WaitForFences(_gd.Api, _device, offset, size);
- }
-
- private bool BoundToRange(int offset, ref int size)
- {
- if (offset >= Size)
- {
- return false;
- }
-
- size = Math.Min(Size - offset, size);
-
- return true;
- }
-
- public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
- {
- if (!BoundToRange(offset, ref size))
- {
- return null;
- }
-
- var key = new I8ToI16CacheKey(_gd);
-
- if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
- {
- holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
-
- _gd.PipelineInternal.EndRenderPass();
- _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
-
- key.SetBuffer(holder.GetBuffer());
-
- _cachedConvertedBuffers.Add(offset, size, key, holder);
- }
-
- return holder.GetBuffer();
- }
-
- public Auto<DisposableBuffer> GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment)
- {
- if (!BoundToRange(offset, ref size))
- {
- return null;
- }
-
- var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment);
-
- if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
- {
- int alignedStride = (stride + (alignment - 1)) & -alignment;
-
- holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
-
- _gd.PipelineInternal.EndRenderPass();
- _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
-
- key.SetBuffer(holder.GetBuffer());
-
- _cachedConvertedBuffers.Add(offset, size, key, holder);
- }
-
- return holder.GetBuffer();
- }
-
- public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
- {
- if (!BoundToRange(offset, ref size))
- {
- return null;
- }
-
- var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
-
- if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
- {
- // The destination index size is always I32.
-
- int indexCount = size / indexSize;
-
- int convertedCount = pattern.GetConvertedCount(indexCount);
-
- holder = _gd.BufferManager.Create(_gd, convertedCount * 4);
-
- _gd.PipelineInternal.EndRenderPass();
- _gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
-
- key.SetBuffer(holder.GetBuffer());
-
- _cachedConvertedBuffers.Add(offset, size, key, holder);
- }
-
- return holder.GetBuffer();
- }
-
- public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
- {
- return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
- }
-
- public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
- {
- _cachedConvertedBuffers.Add(offset, size, key, holder);
- }
-
- public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
- {
- _cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
- }
-
- public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
- {
- _cachedConvertedBuffers.Remove(offset, size, key);
- }
-
- public void Dispose()
- {
- _swapQueued = false;
-
- _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
-
- _buffer.Dispose();
- _allocationAuto.Dispose();
- _cachedConvertedBuffers.Dispose();
-
- _flushLock.AcquireWriterLock(Timeout.Infinite);
-
- ClearFlushFence();
-
- _flushLock.ReleaseWriterLock();
- }
- }
-}