Metal: Buffers Take 2 (#21)
* Basic BufferManager * Start Scoped Command Buffers * Fences stuff * Remember to cleanup sync manager * Auto, Command Buffer Dependants * Cleanup * Cleanup + Fix Texture->Buffer Copies * Slow buffer upload * Cleanup + Rework TextureBuffer * Don’t get unsafe * Cleanup * Goddamn it * Staging Buffer + Interrupt Action + Flush
This commit is contained in:
parent
d0946213fa
commit
58b3e2e82b
24 changed files with 2380 additions and 205 deletions
146
src/Ryujinx.Graphics.Metal/Auto.cs
Normal file
146
src/Ryujinx.Graphics.Metal/Auto.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public interface IAuto
|
||||
{
|
||||
bool HasCommandBufferDependency(CommandBufferScoped cbs);
|
||||
|
||||
void IncrementReferenceCount();
|
||||
void DecrementReferenceCount(int cbIndex);
|
||||
void DecrementReferenceCount();
|
||||
}
|
||||
|
||||
public interface IAutoPrivate : IAuto
|
||||
{
|
||||
void AddCommandBufferDependencies(CommandBufferScoped cbs);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
|
||||
{
|
||||
private int _referenceCount;
|
||||
private T _value;
|
||||
|
||||
private readonly BitMap _cbOwnership;
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
|
||||
private bool _disposed;
|
||||
private bool _destroyed;
|
||||
|
||||
public Auto(T value)
|
||||
{
|
||||
_referenceCount = 1;
|
||||
_value = value;
|
||||
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public Auto(T value, MultiFenceHolder waitable) : this(value)
|
||||
{
|
||||
_waitable = waitable;
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
|
||||
{
|
||||
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
|
||||
return Get(cbs);
|
||||
}
|
||||
|
||||
public T GetUnsafe()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs)
|
||||
{
|
||||
if (!_destroyed)
|
||||
{
|
||||
AddCommandBufferDependencies(cbs);
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
public bool HasCommandBufferDependency(CommandBufferScoped cbs)
|
||||
{
|
||||
return _cbOwnership.IsSet(cbs.CommandBufferIndex);
|
||||
}
|
||||
|
||||
public bool HasRentedCommandBufferDependency(CommandBufferPool cbp)
|
||||
{
|
||||
return _cbOwnership.AnySet();
|
||||
}
|
||||
|
||||
public void AddCommandBufferDependencies(CommandBufferScoped cbs)
|
||||
{
|
||||
// We don't want to add a reference to this object to the command buffer
|
||||
// more than once, so if we detect that the command buffer already has ownership
|
||||
// of this object, then we can just return without doing anything else.
|
||||
if (_cbOwnership.Set(cbs.CommandBufferIndex))
|
||||
{
|
||||
if (_waitable != null)
|
||||
{
|
||||
cbs.AddWaitable(_waitable);
|
||||
}
|
||||
|
||||
cbs.AddDependant(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryIncrementReferenceCount()
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void IncrementReferenceCount()
|
||||
{
|
||||
if (Interlocked.Increment(ref _referenceCount) == 1)
|
||||
{
|
||||
Interlocked.Decrement(ref _referenceCount);
|
||||
throw new InvalidOperationException("Attempted to increment the reference count of an object that was already destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void DecrementReferenceCount(int cbIndex)
|
||||
{
|
||||
_cbOwnership.Clear(cbIndex);
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
|
||||
public void DecrementReferenceCount()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||
{
|
||||
_value.Dispose();
|
||||
_value = default;
|
||||
_destroyed = true;
|
||||
}
|
||||
|
||||
Debug.Assert(_referenceCount >= 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
DecrementReferenceCount();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/Ryujinx.Graphics.Metal/BitMap.cs
Normal file
157
src/Ryujinx.Graphics.Metal/BitMap.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
readonly struct BitMap
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
private const int IntShift = 6;
|
||||
private const int IntMask = IntSize - 1;
|
||||
|
||||
private readonly long[] _masks;
|
||||
|
||||
public BitMap(int count)
|
||||
{
|
||||
_masks = new long[(count + IntMask) / 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 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] |= -1;
|
||||
}
|
||||
|
||||
_masks[endIndex] |= endMask;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
285
src/Ryujinx.Graphics.Metal/BufferHolder.cs
Normal file
285
src/Ryujinx.Graphics.Metal/BufferHolder.cs
Normal file
|
@ -0,0 +1,285 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class BufferHolder : IDisposable
|
||||
{
|
||||
public int Size { get; }
|
||||
|
||||
private readonly IntPtr _map;
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly Pipeline _pipeline;
|
||||
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
|
||||
private readonly ReaderWriterLockSlim _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
public BufferHolder(MetalRenderer renderer, Pipeline pipeline, MTLBuffer buffer, int size)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
_map = buffer.Contents;
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new(buffer), _waitable);
|
||||
|
||||
_flushLock = new ReaderWriterLockSlim();
|
||||
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(bool isWrite)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(int offset, int size, bool isWrite)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
// TODO: Cache converted buffers
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Cache converted buffers
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Assumes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
if (_flushFence == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
_flushLock.ExitReadLock();
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
var fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
_flushLock.ExitWriteLock();
|
||||
_flushLock.EnterReadLock();
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.EnterReadLock();
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
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.ExitReadLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The buffer is not mapped");
|
||||
}
|
||||
|
||||
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 mapped.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_renderer.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, false);
|
||||
|
||||
if (!needsFlush)
|
||||
{
|
||||
WaitForFences(offset, dataSize);
|
||||
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowCbsWait)
|
||||
{
|
||||
_renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, endRenderPass, this, offset, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rentCbs = cbs == null;
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs = _renderer.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data))
|
||||
{
|
||||
// Need to do a slow upload.
|
||||
BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize);
|
||||
srcHolder.SetDataUnchecked(0, data);
|
||||
|
||||
var srcBuffer = srcHolder.GetBuffer();
|
||||
var dstBuffer = this.GetBuffer(true);
|
||||
|
||||
Copy(_pipeline, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
|
||||
|
||||
srcHolder.Dispose();
|
||||
}
|
||||
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetDataUnchecked(offset, MemoryMarshal.AsBytes(data));
|
||||
}
|
||||
|
||||
public static void Copy(
|
||||
Pipeline pipeline,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableBuffer> src,
|
||||
Auto<DisposableBuffer> dst,
|
||||
int srcOffset,
|
||||
int dstOffset,
|
||||
int size,
|
||||
bool registerSrcUsage = true)
|
||||
{
|
||||
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
|
||||
var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value;
|
||||
|
||||
pipeline.GetOrCreateBlitEncoder().CopyFromBuffer(
|
||||
srcBuffer,
|
||||
(ulong)srcOffset,
|
||||
dstbuffer,
|
||||
(ulong)dstOffset,
|
||||
(ulong)size);
|
||||
}
|
||||
|
||||
public void WaitForFences()
|
||||
{
|
||||
_waitable.WaitForFences();
|
||||
}
|
||||
|
||||
public void WaitForFences(int offset, int size)
|
||||
{
|
||||
_waitable.WaitForFences(offset, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer.Dispose();
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public struct BufferInfo
|
||||
{
|
||||
public IntPtr Handle;
|
||||
public int Offset;
|
||||
public int Index;
|
||||
}
|
||||
}
|
203
src/Ryujinx.Graphics.Metal/BufferManager.cs
Normal file
203
src/Ryujinx.Graphics.Metal/BufferManager.cs
Normal file
|
@ -0,0 +1,203 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public readonly struct ScopedTemporaryBuffer : IDisposable
|
||||
{
|
||||
private readonly BufferManager _bufferManager;
|
||||
private readonly bool _isReserved;
|
||||
|
||||
public readonly BufferRange Range;
|
||||
public readonly BufferHolder Holder;
|
||||
|
||||
public BufferHandle Handle => Range.Handle;
|
||||
public int Offset => Range.Offset;
|
||||
|
||||
public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved)
|
||||
{
|
||||
_bufferManager = bufferManager;
|
||||
|
||||
Range = new BufferRange(handle, offset, size);
|
||||
Holder = holder;
|
||||
|
||||
_isReserved = isReserved;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isReserved)
|
||||
{
|
||||
_bufferManager.Delete(Range.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class BufferManager : IDisposable
|
||||
{
|
||||
private readonly IdList<BufferHolder> _buffers;
|
||||
|
||||
private readonly MTLDevice _device;
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly Pipeline _pipeline;
|
||||
|
||||
public int BufferCount { get; private set; }
|
||||
|
||||
public StagingBuffer StagingBuffer { get; }
|
||||
|
||||
public BufferManager(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
_buffers = new IdList<BufferHolder>();
|
||||
|
||||
StagingBuffer = new StagingBuffer(_renderer, _pipeline, this);
|
||||
}
|
||||
|
||||
public BufferHandle Create(nint pointer, int size)
|
||||
{
|
||||
var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
|
||||
if (buffer == IntPtr.Zero)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}, and pointer 0x{pointer:X}.");
|
||||
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
var holder = new BufferHolder(_renderer, _pipeline, buffer, size);
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(int size)
|
||||
{
|
||||
return CreateWithHandle(size, out _);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(int size, out BufferHolder holder)
|
||||
{
|
||||
holder = Create(size);
|
||||
|
||||
if (holder == null)
|
||||
{
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public ScopedTemporaryBuffer ReserveOrCreate(CommandBufferScoped cbs, int size)
|
||||
{
|
||||
StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a temporary buffer.
|
||||
BufferHandle handle = CreateWithHandle(size, out BufferHolder holder);
|
||||
|
||||
return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHolder Create(int size)
|
||||
{
|
||||
var buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
|
||||
if (buffer != IntPtr.Zero)
|
||||
{
|
||||
return new BufferHolder(_renderer, _pipeline, buffer, size);
|
||||
}
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X}.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, int offset, int size, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(offset, size, isWrite);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(BufferHandle handle, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(isWrite);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetData(offset, size);
|
||||
}
|
||||
|
||||
return new PinnedSpan<byte>();
|
||||
}
|
||||
|
||||
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null, null);
|
||||
}
|
||||
|
||||
public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs, Action endRenderPass)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
holder.SetData(offset, data, cbs, endRenderPass);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(BufferHandle handle)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
holder.Dispose();
|
||||
_buffers.Remove((int)Unsafe.As<BufferHandle, ulong>(ref handle));
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder)
|
||||
{
|
||||
return _buffers.TryGetValue((int)Unsafe.As<BufferHandle, ulong>(ref handle), out holder);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StagingBuffer.Dispose();
|
||||
|
||||
foreach (var buffer in _buffers)
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
src/Ryujinx.Graphics.Metal/BufferUsageBitmap.cs
Normal file
85
src/Ryujinx.Graphics.Metal/BufferUsageBitmap.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
internal class BufferUsageBitmap
|
||||
{
|
||||
private readonly BitMap _bitmap;
|
||||
private readonly int _size;
|
||||
private readonly int _granularity;
|
||||
private readonly int _bits;
|
||||
private readonly int _writeBitOffset;
|
||||
|
||||
private readonly int _intsPerCb;
|
||||
private readonly int _bitsPerCb;
|
||||
|
||||
public BufferUsageBitmap(int size, int granularity)
|
||||
{
|
||||
_size = size;
|
||||
_granularity = 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;
|
||||
|
||||
_bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public void Add(int cbIndex, int offset, int size, bool write)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Some usages can be out of bounds (vertex buffer on amd), so bound if necessary.
|
||||
if (offset + size > _size)
|
||||
{
|
||||
size = _size - offset;
|
||||
}
|
||||
|
||||
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, bool write = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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, bool write)
|
||||
{
|
||||
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
|
||||
{
|
||||
if (OverlapsWith(i, offset, size, write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear(int cbIndex)
|
||||
{
|
||||
_bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1);
|
||||
}
|
||||
}
|
||||
}
|
275
src/Ryujinx.Graphics.Metal/CommandBufferPool.cs
Normal file
275
src/Ryujinx.Graphics.Metal/CommandBufferPool.cs
Normal file
|
@ -0,0 +1,275 @@
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class CommandBufferPool : IDisposable
|
||||
{
|
||||
public const int MaxCommandBuffers = 16;
|
||||
|
||||
private readonly int _totalCommandBuffers;
|
||||
private readonly int _totalCommandBuffersMask;
|
||||
|
||||
private readonly MTLDevice _device;
|
||||
private readonly MTLCommandQueue _queue;
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
private struct ReservedCommandBuffer
|
||||
{
|
||||
public bool InUse;
|
||||
public bool InConsumption;
|
||||
public int SubmissionCount;
|
||||
public MTLCommandBuffer CommandBuffer;
|
||||
public FenceHolder Fence;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
|
||||
public void Initialize(MTLCommandQueue queue)
|
||||
{
|
||||
CommandBuffer = queue.CommandBuffer();
|
||||
|
||||
Dependants = new List<IAuto>();
|
||||
Waitables = new List<MultiFenceHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReservedCommandBuffer[] _commandBuffers;
|
||||
|
||||
private readonly int[] _queuedIndexes;
|
||||
private int _queuedIndexesPtr;
|
||||
private int _queuedCount;
|
||||
private int _inUseCount;
|
||||
|
||||
public CommandBufferPool(MTLDevice device, MTLCommandQueue queue)
|
||||
{
|
||||
_device = device;
|
||||
_queue = queue;
|
||||
|
||||
_totalCommandBuffers = MaxCommandBuffers;
|
||||
_totalCommandBuffersMask = _totalCommandBuffers - 1;
|
||||
|
||||
_commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
|
||||
|
||||
_queuedIndexes = new int[_totalCommandBuffers];
|
||||
_queuedIndexesPtr = 0;
|
||||
_queuedCount = 0;
|
||||
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
_commandBuffers[i].Initialize(_queue);
|
||||
WaitAndDecrementRef(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDependant(int cbIndex, IAuto dependant)
|
||||
{
|
||||
dependant.IncrementReferenceCount();
|
||||
_commandBuffers[cbIndex].Dependants.Add(dependant);
|
||||
}
|
||||
|
||||
public void AddWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InConsumption)
|
||||
{
|
||||
AddWaitable(i, waitable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddInUseWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse)
|
||||
{
|
||||
AddWaitable(i, waitable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
if (waitable.AddFence(cbIndex, entry.Fence))
|
||||
{
|
||||
entry.Waitables.Add(waitable);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFenceOnRentedCommandBuffer(FenceHolder fence)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse && entry.Fence == fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public FenceHolder GetFence(int cbIndex)
|
||||
{
|
||||
return _commandBuffers[cbIndex].Fence;
|
||||
}
|
||||
|
||||
public int GetSubmissionCount(int cbIndex)
|
||||
{
|
||||
return _commandBuffers[cbIndex].SubmissionCount;
|
||||
}
|
||||
|
||||
private int FreeConsumed(bool wait)
|
||||
{
|
||||
int freeEntry = 0;
|
||||
|
||||
while (_queuedCount > 0)
|
||||
{
|
||||
int index = _queuedIndexes[_queuedIndexesPtr];
|
||||
|
||||
ref var entry = ref _commandBuffers[index];
|
||||
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
|
||||
{
|
||||
WaitAndDecrementRef(index);
|
||||
|
||||
wait = false;
|
||||
freeEntry = index;
|
||||
|
||||
_queuedCount--;
|
||||
_queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return freeEntry;
|
||||
}
|
||||
|
||||
public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
|
||||
{
|
||||
Return(cbs);
|
||||
return Rent();
|
||||
}
|
||||
|
||||
public CommandBufferScoped Rent()
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers);
|
||||
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cursor];
|
||||
|
||||
if (!entry.InUse && !entry.InConsumption)
|
||||
{
|
||||
entry.InUse = true;
|
||||
|
||||
_inUseCount++;
|
||||
|
||||
return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
|
||||
}
|
||||
|
||||
cursor = (cursor + 1) & _totalCommandBuffersMask;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
|
||||
}
|
||||
|
||||
public void Return(CommandBufferScoped cbs)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
int cbIndex = cbs.CommandBufferIndex;
|
||||
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
|
||||
Debug.Assert(entry.InUse);
|
||||
Debug.Assert(entry.CommandBuffer.NativePtr == cbs.CommandBuffer.NativePtr);
|
||||
entry.InUse = false;
|
||||
entry.InConsumption = true;
|
||||
entry.SubmissionCount++;
|
||||
_inUseCount--;
|
||||
|
||||
var commandBuffer = entry.CommandBuffer;
|
||||
commandBuffer.Commit();
|
||||
|
||||
// Replace entry with new MTLCommandBuffer
|
||||
entry.Initialize(_queue);
|
||||
|
||||
int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
|
||||
_queuedIndexes[ptr] = cbIndex;
|
||||
_queuedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitAndDecrementRef(int cbIndex, bool refreshFence = true)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
|
||||
if (entry.InConsumption)
|
||||
{
|
||||
entry.Fence.Wait();
|
||||
entry.InConsumption = false;
|
||||
}
|
||||
|
||||
foreach (var dependant in entry.Dependants)
|
||||
{
|
||||
dependant.DecrementReferenceCount(cbIndex);
|
||||
}
|
||||
|
||||
foreach (var waitable in entry.Waitables)
|
||||
{
|
||||
waitable.RemoveFence(cbIndex);
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
entry.Dependants.Clear();
|
||||
entry.Waitables.Clear();
|
||||
entry.Fence?.Dispose();
|
||||
|
||||
if (refreshFence)
|
||||
{
|
||||
entry.Fence = new FenceHolder(entry.CommandBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Fence = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
WaitAndDecrementRef(i, refreshFence: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs
Normal file
41
src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public readonly struct CommandBufferScoped : IDisposable
|
||||
{
|
||||
private readonly CommandBufferPool _pool;
|
||||
public MTLCommandBuffer CommandBuffer { get; }
|
||||
public int CommandBufferIndex { get; }
|
||||
|
||||
public CommandBufferScoped(CommandBufferPool pool, MTLCommandBuffer commandBuffer, int commandBufferIndex)
|
||||
{
|
||||
_pool = pool;
|
||||
CommandBuffer = commandBuffer;
|
||||
CommandBufferIndex = commandBufferIndex;
|
||||
}
|
||||
|
||||
public void AddDependant(IAuto dependant)
|
||||
{
|
||||
_pool.AddDependant(CommandBufferIndex, dependant);
|
||||
}
|
||||
|
||||
public void AddWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||
}
|
||||
|
||||
public FenceHolder GetFence()
|
||||
{
|
||||
return _pool.GetFence(CommandBufferIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool?.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,5 +16,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
public const int MaxVertexLayouts = 31;
|
||||
public const int MaxTextures = 31;
|
||||
public const int MaxSamplers = 16;
|
||||
|
||||
public const int MinResourceAlignment = 16;
|
||||
}
|
||||
}
|
||||
|
|
26
src/Ryujinx.Graphics.Metal/DisposableBuffer.cs
Normal file
26
src/Ryujinx.Graphics.Metal/DisposableBuffer.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public readonly struct DisposableBuffer : IDisposable
|
||||
{
|
||||
public MTLBuffer Value { get; }
|
||||
|
||||
public DisposableBuffer(MTLBuffer buffer)
|
||||
{
|
||||
Value = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Value != IntPtr.Zero)
|
||||
{
|
||||
Value.SetPurgeableState(MTLPurgeableState.Empty);
|
||||
Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using SharpMetal.Metal;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -38,10 +37,10 @@ namespace Ryujinx.Graphics.Metal
|
|||
public TextureBase[] ComputeTextures = new TextureBase[Constants.MaxTextures];
|
||||
public MTLSamplerState[] ComputeSamplers = new MTLSamplerState[Constants.MaxSamplers];
|
||||
|
||||
public List<BufferInfo> UniformBuffers = [];
|
||||
public List<BufferInfo> StorageBuffers = [];
|
||||
public BufferAssignment[] UniformBuffers = [];
|
||||
public BufferAssignment[] StorageBuffers = [];
|
||||
|
||||
public MTLBuffer IndexBuffer = default;
|
||||
public BufferRange IndexBuffer = default;
|
||||
public MTLIndexType IndexType = MTLIndexType.UInt16;
|
||||
public ulong IndexBufferOffset = 0;
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ using Ryujinx.Graphics.Shader;
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
using BufferAssignment = Ryujinx.Graphics.GAL.BufferAssignment;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
struct EncoderStateManager : IDisposable
|
||||
{
|
||||
private readonly Pipeline _pipeline;
|
||||
private readonly BufferManager _bufferManager;
|
||||
|
||||
private readonly RenderPipelineCache _renderPipelineCache;
|
||||
private readonly ComputePipelineCache _computePipelineCache;
|
||||
|
@ -21,7 +22,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
private EncoderState _currentState = new();
|
||||
private readonly Stack<EncoderState> _backStates = [];
|
||||
|
||||
public readonly MTLBuffer IndexBuffer => _currentState.IndexBuffer;
|
||||
public readonly BufferRange IndexBuffer => _currentState.IndexBuffer;
|
||||
public readonly MTLIndexType IndexType => _currentState.IndexType;
|
||||
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
|
||||
public readonly PrimitiveTopology Topology => _currentState.Topology;
|
||||
|
@ -30,11 +31,13 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
// RGBA32F is the biggest format
|
||||
private const int ZeroBufferSize = 4 * 4;
|
||||
private readonly MTLBuffer _zeroBuffer;
|
||||
private readonly BufferHandle _zeroBuffer;
|
||||
|
||||
public unsafe EncoderStateManager(MTLDevice device, Pipeline pipeline)
|
||||
public unsafe EncoderStateManager(MTLDevice device, BufferManager bufferManager, Pipeline pipeline)
|
||||
{
|
||||
_pipeline = pipeline;
|
||||
_bufferManager = bufferManager;
|
||||
|
||||
_renderPipelineCache = new(device);
|
||||
_computePipelineCache = new(device);
|
||||
_depthStencilCache = new(device);
|
||||
|
@ -43,7 +46,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
byte[] zeros = new byte[ZeroBufferSize];
|
||||
fixed (byte* ptr = zeros)
|
||||
{
|
||||
_zeroBuffer = device.NewBuffer((IntPtr)ptr, ZeroBufferSize, MTLResourceOptions.ResourceStorageModeShared);
|
||||
_zeroBuffer = _bufferManager.Create((IntPtr)ptr, ZeroBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,8 +358,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
{
|
||||
_currentState.IndexType = type.Convert();
|
||||
_currentState.IndexBufferOffset = (ulong)buffer.Offset;
|
||||
var handle = buffer.Handle;
|
||||
_currentState.IndexBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
||||
_currentState.IndexBuffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,20 +659,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
// Inlineable
|
||||
public void UpdateUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
_currentState.UniformBuffers = [];
|
||||
|
||||
foreach (BufferAssignment buffer in buffers)
|
||||
{
|
||||
if (buffer.Range.Size != 0)
|
||||
{
|
||||
_currentState.UniformBuffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = buffer.Range.Handle.ToIntPtr(),
|
||||
Offset = buffer.Range.Offset,
|
||||
Index = buffer.Binding
|
||||
});
|
||||
}
|
||||
}
|
||||
_currentState.UniformBuffers = buffers.ToArray();
|
||||
|
||||
// Inline update
|
||||
if (_pipeline.CurrentEncoder != null)
|
||||
|
@ -691,20 +680,13 @@ namespace Ryujinx.Graphics.Metal
|
|||
// Inlineable
|
||||
public void UpdateStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
_currentState.StorageBuffers = [];
|
||||
_currentState.StorageBuffers = buffers.ToArray();
|
||||
|
||||
foreach (BufferAssignment buffer in buffers)
|
||||
{
|
||||
if (buffer.Range.Size != 0)
|
||||
for (int i = 0; i < _currentState.StorageBuffers.Length; i++)
|
||||
{
|
||||
BufferAssignment buffer = _currentState.StorageBuffers[i];
|
||||
// TODO: DONT offset the binding by 15
|
||||
_currentState.StorageBuffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = buffer.Range.Handle.ToIntPtr(),
|
||||
Offset = buffer.Range.Offset,
|
||||
Index = buffer.Binding + 15
|
||||
});
|
||||
}
|
||||
_currentState.StorageBuffers[i] = new BufferAssignment(buffer.Binding + 15, buffer.Range);
|
||||
}
|
||||
|
||||
// Inline update
|
||||
|
@ -956,50 +938,51 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
private void SetVertexBuffers(MTLRenderCommandEncoder renderCommandEncoder, VertexBufferDescriptor[] bufferDescriptors)
|
||||
{
|
||||
var buffers = new List<BufferInfo>();
|
||||
var buffers = new List<BufferAssignment>();
|
||||
|
||||
for (int i = 0; i < bufferDescriptors.Length; i++)
|
||||
{
|
||||
if (bufferDescriptors[i].Buffer.Handle.ToIntPtr() != IntPtr.Zero)
|
||||
{
|
||||
buffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = bufferDescriptors[i].Buffer.Handle.ToIntPtr(),
|
||||
Offset = bufferDescriptors[i].Buffer.Offset,
|
||||
Index = i
|
||||
});
|
||||
}
|
||||
buffers.Add(new BufferAssignment(i, bufferDescriptors[i].Buffer));
|
||||
}
|
||||
|
||||
// Zero buffer
|
||||
buffers.Add(new BufferInfo
|
||||
{
|
||||
Handle = _zeroBuffer.NativePtr,
|
||||
Offset = 0,
|
||||
Index = bufferDescriptors.Length
|
||||
});
|
||||
buffers.Add(new BufferAssignment(
|
||||
bufferDescriptors.Length,
|
||||
new BufferRange(_zeroBuffer, 0, ZeroBufferSize)));
|
||||
|
||||
SetRenderBuffers(renderCommandEncoder, buffers);
|
||||
SetRenderBuffers(renderCommandEncoder, buffers.ToArray());
|
||||
}
|
||||
|
||||
private readonly void SetRenderBuffers(MTLRenderCommandEncoder renderCommandEncoder, List<BufferInfo> buffers, bool fragment = false)
|
||||
private readonly void SetRenderBuffers(MTLRenderCommandEncoder renderCommandEncoder, BufferAssignment[] buffers, bool fragment = false)
|
||||
{
|
||||
foreach (var buffer in buffers)
|
||||
{
|
||||
renderCommandEncoder.SetVertexBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
|
||||
var range = buffer.Range;
|
||||
var autoBuffer = _bufferManager.GetBuffer(range.Handle, range.Offset, range.Size, range.Write);
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
var mtlBuffer = autoBuffer.Get(_pipeline.CurrentCommandBuffer).Value;
|
||||
|
||||
renderCommandEncoder.SetVertexBuffer(mtlBuffer, (ulong)range.Offset, (ulong)buffer.Binding);
|
||||
|
||||
if (fragment)
|
||||
{
|
||||
renderCommandEncoder.SetFragmentBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
|
||||
renderCommandEncoder.SetFragmentBuffer(mtlBuffer, (ulong)range.Offset, (ulong)buffer.Binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void SetComputeBuffers(MTLComputeCommandEncoder computeCommandEncoder, List<BufferInfo> buffers)
|
||||
private readonly void SetComputeBuffers(MTLComputeCommandEncoder computeCommandEncoder, BufferAssignment[] buffers)
|
||||
{
|
||||
foreach (var buffer in buffers)
|
||||
{
|
||||
computeCommandEncoder.SetBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
|
||||
var range = buffer.Range;
|
||||
var mtlBuffer = _bufferManager.GetBuffer(range.Handle, range.Offset, range.Size, range.Write).Get(_pipeline.CurrentCommandBuffer).Value;
|
||||
|
||||
computeCommandEncoder.SetBuffer(mtlBuffer, (ulong)range.Offset, (ulong)buffer.Binding);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
77
src/Ryujinx.Graphics.Metal/FenceHolder.cs
Normal file
77
src/Ryujinx.Graphics.Metal/FenceHolder.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class FenceHolder : IDisposable
|
||||
{
|
||||
private MTLCommandBuffer _fence;
|
||||
private int _referenceCount;
|
||||
private bool _disposed;
|
||||
|
||||
public FenceHolder(MTLCommandBuffer fence)
|
||||
{
|
||||
_fence = fence;
|
||||
_referenceCount = 1;
|
||||
}
|
||||
|
||||
public MTLCommandBuffer GetUnsafe()
|
||||
{
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public bool TryGet(out MTLCommandBuffer fence)
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
fence = default;
|
||||
return false;
|
||||
}
|
||||
} while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
fence = _fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
public MTLCommandBuffer Get()
|
||||
{
|
||||
Interlocked.Increment(ref _referenceCount);
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public void Put()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||
{
|
||||
_fence = default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
_fence.WaitUntilCompleted();
|
||||
}
|
||||
|
||||
public bool IsSignaled()
|
||||
{
|
||||
return _fence.Status == MTLCommandBufferStatus.Completed;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Put();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
static class Handle
|
||||
{
|
||||
public static IntPtr ToIntPtr(this BufferHandle handle)
|
||||
{
|
||||
return Unsafe.As<BufferHandle, IntPtr>(ref handle);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ using System.Runtime.Versioning;
|
|||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class HelperShader : IDisposable
|
||||
public class HelperShader : IDisposable
|
||||
{
|
||||
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
|
||||
private readonly Pipeline _pipeline;
|
||||
|
|
121
src/Ryujinx.Graphics.Metal/IdList.cs
Normal file
121
src/Ryujinx.Graphics.Metal/IdList.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
class IdList<T> where T : class
|
||||
{
|
||||
private readonly List<T> _list;
|
||||
private int _freeMin;
|
||||
|
||||
public IdList()
|
||||
{
|
||||
_list = new List<T>();
|
||||
_freeMin = 0;
|
||||
}
|
||||
|
||||
public int Add(T value)
|
||||
{
|
||||
int id;
|
||||
int count = _list.Count;
|
||||
id = _list.IndexOf(null, _freeMin);
|
||||
|
||||
if ((uint)id < (uint)count)
|
||||
{
|
||||
_list[id] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
id = count;
|
||||
_freeMin = id + 1;
|
||||
|
||||
_list.Add(value);
|
||||
}
|
||||
|
||||
return id + 1;
|
||||
}
|
||||
|
||||
public void Remove(int id)
|
||||
{
|
||||
id--;
|
||||
|
||||
int count = _list.Count;
|
||||
|
||||
if ((uint)id >= (uint)count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (id + 1 == count)
|
||||
{
|
||||
// Trim unused items.
|
||||
int removeIndex = id;
|
||||
|
||||
while (removeIndex > 0 && _list[removeIndex - 1] == null)
|
||||
{
|
||||
removeIndex--;
|
||||
}
|
||||
|
||||
_list.RemoveRange(removeIndex, count - removeIndex);
|
||||
|
||||
if (_freeMin > removeIndex)
|
||||
{
|
||||
_freeMin = removeIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_list[id] = null;
|
||||
|
||||
if (_freeMin > id)
|
||||
{
|
||||
_freeMin = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(int id, out T value)
|
||||
{
|
||||
id--;
|
||||
|
||||
try
|
||||
{
|
||||
if ((uint)id < (uint)_list.Count)
|
||||
{
|
||||
value = _list[id];
|
||||
return value != null;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_list.Clear();
|
||||
_freeMin = 0;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < _list.Count; i++)
|
||||
{
|
||||
if (_list[i] != null)
|
||||
{
|
||||
yield return _list[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,9 @@ using Ryujinx.Common.Configuration;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using SharpMetal.Foundation;
|
||||
using SharpMetal.Metal;
|
||||
using SharpMetal.QuartzCore;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
|
@ -19,12 +17,20 @@ namespace Ryujinx.Graphics.Metal
|
|||
private readonly Func<CAMetalLayer> _getMetalLayer;
|
||||
|
||||
private Pipeline _pipeline;
|
||||
private HelperShader _helperShader;
|
||||
private BufferManager _bufferManager;
|
||||
private Window _window;
|
||||
private CommandBufferPool _commandBufferPool;
|
||||
|
||||
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||
public bool PreferThreading => true;
|
||||
public IPipeline Pipeline => _pipeline;
|
||||
public IWindow Window => _window;
|
||||
public HelperShader HelperShader => _helperShader;
|
||||
public BufferManager BufferManager => _bufferManager;
|
||||
public CommandBufferPool CommandBufferPool => _commandBufferPool;
|
||||
public Action<Action> InterruptAction { get; private set; }
|
||||
public SyncManager SyncManager { get; private set; }
|
||||
|
||||
public MetalRenderer(Func<CAMetalLayer> metalLayer)
|
||||
{
|
||||
|
@ -35,7 +41,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
|
||||
}
|
||||
|
||||
_queue = _device.NewCommandQueue();
|
||||
_queue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers);
|
||||
_getMetalLayer = metalLayer;
|
||||
}
|
||||
|
||||
|
@ -45,8 +51,15 @@ namespace Ryujinx.Graphics.Metal
|
|||
layer.Device = _device;
|
||||
layer.FramebufferOnly = false;
|
||||
|
||||
_commandBufferPool = new CommandBufferPool(_device, _queue);
|
||||
_window = new Window(this, layer);
|
||||
_pipeline = new Pipeline(_device, _queue);
|
||||
_pipeline = new Pipeline(_device, this, _queue);
|
||||
_bufferManager = new BufferManager(_device, this, _pipeline);
|
||||
|
||||
_pipeline.InitEncoderStateManager(_bufferManager);
|
||||
|
||||
_helperShader = new HelperShader(_device, _pipeline);
|
||||
SyncManager = new SyncManager(this);
|
||||
}
|
||||
|
||||
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
|
||||
|
@ -54,11 +67,14 @@ namespace Ryujinx.Graphics.Metal
|
|||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||
{
|
||||
return _bufferManager.CreateWithHandle(size);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(IntPtr pointer, int size)
|
||||
{
|
||||
var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
var bufferPtr = buffer.NativePtr;
|
||||
return Unsafe.As<IntPtr, BufferHandle>(ref bufferPtr);
|
||||
return _bufferManager.Create(pointer, size);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
|
||||
|
@ -71,15 +87,6 @@ namespace Ryujinx.Graphics.Metal
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size, BufferAccess access)
|
||||
{
|
||||
var buffer = _device.NewBuffer((ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
||||
buffer.SetPurgeableState(MTLPurgeableState.NonVolatile);
|
||||
|
||||
var bufferPtr = buffer.NativePtr;
|
||||
return Unsafe.As<IntPtr, BufferHandle>(ref bufferPtr);
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
return new Program(shaders, _device);
|
||||
|
@ -94,10 +101,10 @@ namespace Ryujinx.Graphics.Metal
|
|||
{
|
||||
if (info.Target == Target.TextureBuffer)
|
||||
{
|
||||
return new TextureBuffer(_device, _pipeline, info);
|
||||
return new TextureBuffer(this, info);
|
||||
}
|
||||
|
||||
return new Texture(_device, _pipeline, info);
|
||||
return new Texture(_device, this, _pipeline, info);
|
||||
}
|
||||
|
||||
public ITextureArray CreateTextureArray(int size, bool isBuffer)
|
||||
|
@ -113,19 +120,17 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public void CreateSync(ulong id, bool strict)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
SyncManager.Create(id, strict);
|
||||
}
|
||||
|
||||
public void DeleteBuffer(BufferHandle buffer)
|
||||
{
|
||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
|
||||
mtlBuffer.SetPurgeableState(MTLPurgeableState.Empty);
|
||||
_bufferManager.Delete(buffer);
|
||||
}
|
||||
|
||||
public unsafe PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
|
||||
return new PinnedSpan<byte>(IntPtr.Add(mtlBuffer.Contents, offset).ToPointer(), size);
|
||||
return _bufferManager.GetData(buffer, offset, size);
|
||||
}
|
||||
|
||||
public Capabilities GetCapabilities()
|
||||
|
@ -198,8 +203,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
return 0;
|
||||
return SyncManager.GetCurrent();
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
|
@ -212,18 +216,9 @@ namespace Ryujinx.Graphics.Metal
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public unsafe void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
|
||||
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var blitEncoder = _pipeline.GetOrCreateBlitEncoder();
|
||||
|
||||
using MTLBuffer src = _device.NewBuffer((ulong)data.Length, MTLResourceOptions.ResourceStorageModeManaged);
|
||||
{
|
||||
var span = new Span<byte>(src.Contents.ToPointer(), data.Length);
|
||||
data.CopyTo(span);
|
||||
|
||||
MTLBuffer dst = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
|
||||
blitEncoder.CopyFromBuffer(src, 0, dst, (ulong)offset, (ulong)data.Length);
|
||||
}
|
||||
_bufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate);
|
||||
}
|
||||
|
||||
public void UpdateCounters()
|
||||
|
@ -233,7 +228,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public void PreFrame()
|
||||
{
|
||||
|
||||
SyncManager.Cleanup();
|
||||
}
|
||||
|
||||
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
|
||||
|
@ -251,12 +246,25 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public void WaitSync(ulong id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
SyncManager.Wait(id);
|
||||
}
|
||||
|
||||
public void FlushAllCommands()
|
||||
{
|
||||
_pipeline.FlushCommandsImpl();
|
||||
}
|
||||
|
||||
public void RegisterFlush()
|
||||
{
|
||||
SyncManager.RegisterFlush();
|
||||
|
||||
// Periodically free unused regions of the staging buffer to avoid doing it all at once.
|
||||
_bufferManager.StagingBuffer.FreeCompleted();
|
||||
}
|
||||
|
||||
public void SetInterruptAction(Action<Action> interruptAction)
|
||||
{
|
||||
// Not needed for now
|
||||
InterruptAction = interruptAction;
|
||||
}
|
||||
|
||||
public void Screenshot()
|
||||
|
|
262
src/Ryujinx.Graphics.Metal/MultiFenceHolder.cs
Normal file
262
src/Ryujinx.Graphics.Metal/MultiFenceHolder.cs
Normal file
|
@ -0,0 +1,262 @@
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
/// <summary>
|
||||
/// Holder for multiple host GPU fences.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class MultiFenceHolder
|
||||
{
|
||||
private const int BufferUsageTrackingGranularity = 4096;
|
||||
|
||||
private readonly FenceHolder[] _fences;
|
||||
private readonly BufferUsageBitmap _bufferUsageBitmap;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the multiple fence holder.
|
||||
/// </summary>
|
||||
public MultiFenceHolder()
|
||||
{
|
||||
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the multiple fence holder, with a given buffer size in mind.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of the buffer</param>
|
||||
public MultiFenceHolder(int size)
|
||||
{
|
||||
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <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, false);
|
||||
|
||||
if (write)
|
||||
{
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all buffer usage information for a given command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
|
||||
public void RemoveBufferUses(int cbIndex)
|
||||
{
|
||||
_bufferUsageBitmap?.Clear(cbIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU.
|
||||
/// </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>
|
||||
/// <returns>True if in use, false otherwise</returns>
|
||||
public bool IsBufferRangeInUse(int cbIndex, int offset, int size)
|
||||
{
|
||||
return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU.
|
||||
/// </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, bool write)
|
||||
{
|
||||
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a fence to the holder.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
|
||||
/// <param name="fence">Fence to be added</param>
|
||||
/// <returns>True if the command buffer's previous fence value was null</returns>
|
||||
public bool AddFence(int cbIndex, FenceHolder fence)
|
||||
{
|
||||
ref FenceHolder fenceRef = ref _fences[cbIndex];
|
||||
|
||||
if (fenceRef == null)
|
||||
{
|
||||
fenceRef = fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a fence from the holder.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
|
||||
public void RemoveFence(int cbIndex)
|
||||
{
|
||||
_fences[cbIndex] = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a fence referenced on the given command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
|
||||
/// <returns>True if referenced, false otherwise</returns>
|
||||
public bool HasFence(int cbIndex)
|
||||
{
|
||||
return _fences[cbIndex] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until all the fences on the holder are signaled.
|
||||
/// </summary>
|
||||
public void WaitForFences()
|
||||
{
|
||||
WaitForFencesImpl(0, 0, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset of the buffer range</param>
|
||||
/// <param name="size">Size of the buffer range in bytes</param>
|
||||
public void WaitForFences(int offset, int size)
|
||||
{
|
||||
WaitForFencesImpl(offset, size, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
|
||||
/// </summary>
|
||||
|
||||
// TODO: Add a proper timeout!
|
||||
public bool WaitForFences(bool indefinite)
|
||||
{
|
||||
return WaitForFencesImpl(0, 0, indefinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset of the buffer range</param>
|
||||
/// <param name="size">Size of the buffer range in bytes</param>
|
||||
/// <param name="indefinite">Indicates if this should wait indefinitely</param>
|
||||
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
|
||||
private bool WaitForFencesImpl(int offset, int size, bool indefinite)
|
||||
{
|
||||
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
|
||||
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
|
||||
Span<MTLCommandBuffer> fences = stackalloc MTLCommandBuffer[count];
|
||||
|
||||
int fenceCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (fenceHolders[i].TryGet(out MTLCommandBuffer fence))
|
||||
{
|
||||
fences[fenceCount] = fence;
|
||||
|
||||
if (fenceCount < i)
|
||||
{
|
||||
fenceHolders[fenceCount] = fenceHolders[i];
|
||||
}
|
||||
|
||||
fenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fenceCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool signaled = true;
|
||||
|
||||
if (indefinite)
|
||||
{
|
||||
foreach (var fence in fences)
|
||||
{
|
||||
fence.WaitUntilCompleted();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var fence in fences)
|
||||
{
|
||||
if (fence.Status != MTLCommandBufferStatus.Completed)
|
||||
{
|
||||
signaled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < fenceCount; i++)
|
||||
{
|
||||
fenceHolders[i].Put();
|
||||
}
|
||||
|
||||
return signaled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for.
|
||||
/// </summary>
|
||||
/// <param name="storage">Span to store fences in</param>
|
||||
/// <returns>Number of fences placed in storage</returns>
|
||||
private int GetFences(Span<FenceHolder> storage)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = _fences[i];
|
||||
|
||||
if (fence != null)
|
||||
{
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for use of a given buffer region.
|
||||
/// </summary>
|
||||
/// <param name="storage">Span to store overlapping fences in</param>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>Number of fences for the specified region placed in storage</returns>
|
||||
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = _fences[i];
|
||||
|
||||
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
|
||||
{
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,11 @@ using SharpMetal.Foundation;
|
|||
using SharpMetal.Metal;
|
||||
using SharpMetal.QuartzCore;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
enum EncoderType
|
||||
public enum EncoderType
|
||||
{
|
||||
Blit,
|
||||
Compute,
|
||||
|
@ -19,14 +18,19 @@ namespace Ryujinx.Graphics.Metal
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
class Pipeline : IPipeline, IDisposable
|
||||
public class Pipeline : IPipeline, IDisposable
|
||||
{
|
||||
private readonly MTLDevice _device;
|
||||
private readonly MTLCommandQueue _commandQueue;
|
||||
private readonly HelperShader _helperShader;
|
||||
private readonly MetalRenderer _renderer;
|
||||
|
||||
private MTLCommandBuffer _commandBuffer;
|
||||
public MTLCommandBuffer CommandBuffer => _commandBuffer;
|
||||
private CommandBufferScoped Cbs;
|
||||
private CommandBufferScoped? PreloadCbs;
|
||||
public MTLCommandBuffer CommandBuffer;
|
||||
|
||||
public readonly Action EndRenderPassDelegate;
|
||||
|
||||
public CommandBufferScoped CurrentCommandBuffer => Cbs;
|
||||
|
||||
private MTLCommandEncoder? _currentEncoder;
|
||||
public MTLCommandEncoder? CurrentEncoder => _currentEncoder;
|
||||
|
@ -36,14 +40,20 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
private EncoderStateManager _encoderStateManager;
|
||||
|
||||
public Pipeline(MTLDevice device, MTLCommandQueue commandQueue)
|
||||
public Pipeline(MTLDevice device, MetalRenderer renderer, MTLCommandQueue commandQueue)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_commandQueue = commandQueue;
|
||||
_helperShader = new HelperShader(_device, this);
|
||||
|
||||
_commandBuffer = _commandQueue.CommandBuffer();
|
||||
_encoderStateManager = new EncoderStateManager(_device, this);
|
||||
EndRenderPassDelegate = EndCurrentPass;
|
||||
|
||||
CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer;
|
||||
}
|
||||
|
||||
public void InitEncoderStateManager(BufferManager bufferManager)
|
||||
{
|
||||
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
|
||||
}
|
||||
|
||||
public void SaveState()
|
||||
|
@ -156,7 +166,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
EndCurrentPass();
|
||||
|
||||
var descriptor = new MTLBlitPassDescriptor();
|
||||
var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor);
|
||||
var blitCommandEncoder = Cbs.CommandBuffer.BlitCommandEncoder(descriptor);
|
||||
|
||||
_currentEncoder = blitCommandEncoder;
|
||||
_currentEncoderType = EncoderType.Blit;
|
||||
|
@ -178,21 +188,35 @@ namespace Ryujinx.Graphics.Metal
|
|||
{
|
||||
// TODO: Clean this up
|
||||
var textureInfo = new TextureCreateInfo((int)drawable.Texture.Width, (int)drawable.Texture.Height, (int)drawable.Texture.Depth, (int)drawable.Texture.MipmapLevelCount, (int)drawable.Texture.SampleCount, 0, 0, 0, Format.B8G8R8A8Unorm, 0, Target.Texture2D, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha);
|
||||
var dst = new Texture(_device, this, textureInfo, drawable.Texture, 0, 0);
|
||||
var dst = new Texture(_device, _renderer, this, textureInfo, drawable.Texture, 0, 0);
|
||||
|
||||
_helperShader.BlitColor(src, dst, srcRegion, dstRegion, isLinear);
|
||||
_renderer.HelperShader.BlitColor(src, dst, srcRegion, dstRegion, isLinear);
|
||||
|
||||
EndCurrentPass();
|
||||
|
||||
_commandBuffer.PresentDrawable(drawable);
|
||||
_commandBuffer.Commit();
|
||||
Cbs.CommandBuffer.PresentDrawable(drawable);
|
||||
|
||||
_commandBuffer = _commandQueue.CommandBuffer();
|
||||
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
|
||||
// TODO: Auto flush counting
|
||||
_renderer.SyncManager.GetAndResetWaitTicks();
|
||||
|
||||
// Cleanup
|
||||
dst.Dispose();
|
||||
}
|
||||
|
||||
public void FlushCommandsImpl()
|
||||
{
|
||||
SaveState();
|
||||
|
||||
EndCurrentPass();
|
||||
|
||||
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
_renderer.RegisterFlush();
|
||||
|
||||
RestoreState();
|
||||
}
|
||||
|
||||
public void BlitColor(
|
||||
ITexture src,
|
||||
ITexture dst,
|
||||
|
@ -200,7 +224,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
Extents2D dstRegion,
|
||||
bool linearFilter)
|
||||
{
|
||||
_helperShader.BlitColor(src, dst, srcRegion, dstRegion, linearFilter);
|
||||
_renderer.HelperShader.BlitColor(src, dst, srcRegion, dstRegion, linearFilter);
|
||||
}
|
||||
|
||||
public void Barrier()
|
||||
|
@ -235,9 +259,10 @@ namespace Ryujinx.Graphics.Metal
|
|||
{
|
||||
var blitCommandEncoder = GetOrCreateBlitEncoder();
|
||||
|
||||
var mtlBuffer = _renderer.BufferManager.GetBuffer(destination, offset, size, true).Get(Cbs, offset, size, true).Value;
|
||||
|
||||
// Might need a closer look, range's count, lower, and upper bound
|
||||
// must be a multiple of 4
|
||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref destination));
|
||||
blitCommandEncoder.FillBuffer(mtlBuffer,
|
||||
new NSRange
|
||||
{
|
||||
|
@ -259,7 +284,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
return;
|
||||
}
|
||||
|
||||
_helperShader.ClearColor(index, colors, componentMask, dst.Width, dst.Height);
|
||||
_renderer.HelperShader.ClearColor(index, colors, componentMask, dst.Width, dst.Height);
|
||||
}
|
||||
|
||||
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
|
@ -273,7 +298,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
return;
|
||||
}
|
||||
|
||||
_helperShader.ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask, depthStencil.Width, depthStencil.Height);
|
||||
_renderer.HelperShader.ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask, depthStencil.Width, depthStencil.Height);
|
||||
}
|
||||
|
||||
public void CommandBufferBarrier()
|
||||
|
@ -281,19 +306,12 @@ namespace Ryujinx.Graphics.Metal
|
|||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
||||
}
|
||||
|
||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||
public void CopyBuffer(BufferHandle src, BufferHandle dst, int srcOffset, int dstOffset, int size)
|
||||
{
|
||||
var blitCommandEncoder = GetOrCreateBlitEncoder();
|
||||
var srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false);
|
||||
var dstBuffer = _renderer.BufferManager.GetBuffer(dst, dstOffset, size, true);
|
||||
|
||||
MTLBuffer sourceBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref source));
|
||||
MTLBuffer destinationBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref destination));
|
||||
|
||||
blitCommandEncoder.CopyFromBuffer(
|
||||
sourceBuffer,
|
||||
(ulong)srcOffset,
|
||||
destinationBuffer,
|
||||
(ulong)dstOffset,
|
||||
(ulong)size);
|
||||
BufferHolder.Copy(this, Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size);
|
||||
}
|
||||
|
||||
public void DispatchCompute(int groupsX, int groupsY, int groupsZ, int groupSizeX, int groupSizeY, int groupSizeZ)
|
||||
|
@ -327,11 +345,13 @@ namespace Ryujinx.Graphics.Metal
|
|||
// TODO: Support topology re-indexing to provide support for TriangleFans
|
||||
var primitiveType = _encoderStateManager.Topology.Convert();
|
||||
|
||||
var indexBuffer = _renderer.BufferManager.GetBuffer(_encoderStateManager.IndexBuffer.Handle, false);
|
||||
|
||||
renderCommandEncoder.DrawIndexedPrimitives(
|
||||
primitiveType,
|
||||
(ulong)indexCount,
|
||||
_encoderStateManager.IndexType,
|
||||
_encoderStateManager.IndexBuffer,
|
||||
indexBuffer.Get(Cbs, 0, indexCount * sizeof(int)).Value,
|
||||
_encoderStateManager.IndexBufferOffset,
|
||||
(ulong)instanceCount,
|
||||
firstVertex,
|
||||
|
@ -368,7 +388,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
|
||||
{
|
||||
_helperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
|
||||
_renderer.HelperShader.DrawTexture(texture, sampler, srcRegion, dstRegion);
|
||||
}
|
||||
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
|
|
294
src/Ryujinx.Graphics.Metal/StagingBuffer.cs
Normal file
294
src/Ryujinx.Graphics.Metal/StagingBuffer.cs
Normal file
|
@ -0,0 +1,294 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class StagingBuffer : IDisposable
|
||||
{
|
||||
private const int BufferSize = 32 * 1024 * 1024;
|
||||
|
||||
private int _freeOffset;
|
||||
private int _freeSize;
|
||||
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly Pipeline _pipeline;
|
||||
private readonly BufferHolder _buffer;
|
||||
private readonly int _resourceAlignment;
|
||||
|
||||
public readonly BufferHandle Handle;
|
||||
|
||||
private readonly struct PendingCopy
|
||||
{
|
||||
public FenceHolder Fence { get; }
|
||||
public int Size { get; }
|
||||
|
||||
public PendingCopy(FenceHolder fence, int size)
|
||||
{
|
||||
Fence = fence;
|
||||
Size = size;
|
||||
fence.Get();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<PendingCopy> _pendingCopies;
|
||||
|
||||
public StagingBuffer(MetalRenderer renderer, Pipeline pipeline, BufferManager bufferManager)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
|
||||
Handle = bufferManager.CreateWithHandle(BufferSize, out _buffer);
|
||||
_pendingCopies = new Queue<PendingCopy>();
|
||||
_freeSize = BufferSize;
|
||||
_resourceAlignment = Constants.MinResourceAlignment;
|
||||
}
|
||||
|
||||
public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
bool isRender = cbs != null;
|
||||
CommandBufferScoped scoped = cbs ?? cbp.Rent();
|
||||
|
||||
// Must push all data to the buffer. If it can't fit, split it up.
|
||||
|
||||
endRenderPass?.Invoke();
|
||||
|
||||
while (data.Length > 0)
|
||||
{
|
||||
if (_freeSize < data.Length)
|
||||
{
|
||||
FreeCompleted();
|
||||
}
|
||||
|
||||
while (_freeSize == 0)
|
||||
{
|
||||
if (!WaitFreeCompleted(cbp))
|
||||
{
|
||||
if (isRender)
|
||||
{
|
||||
_renderer.FlushAllCommands();
|
||||
scoped = cbp.Rent();
|
||||
isRender = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
scoped = cbp.ReturnAndRent(scoped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int chunkSize = Math.Min(_freeSize, data.Length);
|
||||
|
||||
PushDataImpl(scoped, dst, dstOffset, data[..chunkSize]);
|
||||
|
||||
dstOffset += chunkSize;
|
||||
data = data[chunkSize..];
|
||||
}
|
||||
|
||||
if (!isRender)
|
||||
{
|
||||
scoped.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var srcBuffer = _buffer.GetBuffer();
|
||||
var dstBuffer = dst.GetBuffer(dstOffset, data.Length, true);
|
||||
|
||||
int offset = _freeOffset;
|
||||
int capacity = BufferSize - offset;
|
||||
if (capacity < data.Length)
|
||||
{
|
||||
_buffer.SetDataUnchecked(offset, data[..capacity]);
|
||||
_buffer.SetDataUnchecked(0, data[capacity..]);
|
||||
|
||||
BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity);
|
||||
BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer.SetDataUnchecked(offset, data);
|
||||
|
||||
BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length);
|
||||
}
|
||||
|
||||
_freeOffset = (offset + data.Length) & (BufferSize - 1);
|
||||
_freeSize -= data.Length;
|
||||
Debug.Assert(_freeSize >= 0);
|
||||
|
||||
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length));
|
||||
}
|
||||
|
||||
public bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length > BufferSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_freeSize < data.Length)
|
||||
{
|
||||
FreeCompleted();
|
||||
|
||||
if (_freeSize < data.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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="size">The minimum size the reserved data requires</param>
|
||||
/// <param name="alignment">The required alignment for the buffer offset</param>
|
||||
/// <returns>The reserved range of the staging buffer</returns>
|
||||
public 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserve a range on the staging buffer for the current command buffer and upload data to it.
|
||||
/// Uses the most permissive byte alignment.
|
||||
/// </summary>
|
||||
/// <param name="cbs">Command buffer to reserve the data on</param>
|
||||
/// <param name="size">The minimum size the reserved data requires</param>
|
||||
/// <returns>The reserved range of the staging buffer</returns>
|
||||
public StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size)
|
||||
{
|
||||
return TryReserveData(cbs, size, _resourceAlignment);
|
||||
}
|
||||
|
||||
private bool WaitFreeCompleted(CommandBufferPool cbp)
|
||||
{
|
||||
if (_pendingCopies.TryPeek(out var pc))
|
||||
{
|
||||
if (!pc.Fence.IsSignaled())
|
||||
{
|
||||
if (cbp.IsFenceOnRentedCommandBuffer(pc.Fence))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pc.Fence.Wait();
|
||||
}
|
||||
|
||||
var dequeued = _pendingCopies.Dequeue();
|
||||
Debug.Assert(dequeued.Fence == pc.Fence);
|
||||
_freeSize += pc.Size;
|
||||
pc.Fence.Put();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FreeCompleted()
|
||||
{
|
||||
FenceHolder signalledFence = null;
|
||||
while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled()))
|
||||
{
|
||||
signalledFence = pc.Fence; // Already checked - don't need to do it again.
|
||||
var dequeued = _pendingCopies.Dequeue();
|
||||
Debug.Assert(dequeued.Fence == pc.Fence);
|
||||
_freeSize += pc.Size;
|
||||
pc.Fence.Put();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderer.BufferManager.Delete(Handle);
|
||||
|
||||
while (_pendingCopies.TryDequeue(out var pc))
|
||||
{
|
||||
pc.Fence.Put();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
214
src/Ryujinx.Graphics.Metal/SyncManager.cs
Normal file
214
src/Ryujinx.Graphics.Metal/SyncManager.cs
Normal file
|
@ -0,0 +1,214 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class SyncManager
|
||||
{
|
||||
private class SyncHandle
|
||||
{
|
||||
public ulong ID;
|
||||
public MultiFenceHolder Waitable;
|
||||
public ulong FlushId;
|
||||
public bool Signalled;
|
||||
|
||||
public bool NeedsFlush(ulong currentFlushId)
|
||||
{
|
||||
return (long)(FlushId - currentFlushId) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong _firstHandle;
|
||||
|
||||
private readonly MetalRenderer _renderer;
|
||||
private readonly List<SyncHandle> _handles;
|
||||
private ulong _flushId;
|
||||
private long _waitTicks;
|
||||
|
||||
public SyncManager(MetalRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_handles = new List<SyncHandle>();
|
||||
}
|
||||
|
||||
public void RegisterFlush()
|
||||
{
|
||||
_flushId++;
|
||||
}
|
||||
|
||||
public void Create(ulong id, bool strict)
|
||||
{
|
||||
ulong flushId = _flushId;
|
||||
MultiFenceHolder waitable = new();
|
||||
if (strict || _renderer.InterruptAction == null)
|
||||
{
|
||||
_renderer.FlushAllCommands();
|
||||
_renderer.CommandBufferPool.AddWaitable(waitable);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't flush commands, instead wait for the current command buffer to finish.
|
||||
// If this sync is waited on before the command buffer is submitted, interrupt the gpu thread and flush it manually.
|
||||
|
||||
_renderer.CommandBufferPool.AddInUseWaitable(waitable);
|
||||
}
|
||||
|
||||
SyncHandle handle = new()
|
||||
{
|
||||
ID = id,
|
||||
Waitable = waitable,
|
||||
FlushId = flushId,
|
||||
};
|
||||
|
||||
lock (_handles)
|
||||
{
|
||||
_handles.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetCurrent()
|
||||
{
|
||||
lock (_handles)
|
||||
{
|
||||
ulong lastHandle = _firstHandle;
|
||||
|
||||
foreach (SyncHandle handle in _handles)
|
||||
{
|
||||
lock (handle)
|
||||
{
|
||||
if (handle.Waitable == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handle.ID > lastHandle)
|
||||
{
|
||||
bool signaled = handle.Signalled || handle.Waitable.WaitForFences(false);
|
||||
if (signaled)
|
||||
{
|
||||
lastHandle = handle.ID;
|
||||
handle.Signalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait(ulong id)
|
||||
{
|
||||
SyncHandle result = null;
|
||||
|
||||
lock (_handles)
|
||||
{
|
||||
if ((long)(_firstHandle - id) > 0)
|
||||
{
|
||||
return; // The handle has already been signalled or deleted.
|
||||
}
|
||||
|
||||
foreach (SyncHandle handle in _handles)
|
||||
{
|
||||
if (handle.ID == id)
|
||||
{
|
||||
result = handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
if (result.Waitable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long beforeTicks = Stopwatch.GetTimestamp();
|
||||
|
||||
if (result.NeedsFlush(_flushId))
|
||||
{
|
||||
_renderer.InterruptAction(() =>
|
||||
{
|
||||
if (result.NeedsFlush(_flushId))
|
||||
{
|
||||
_renderer.FlushAllCommands();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lock (result)
|
||||
{
|
||||
if (result.Waitable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool signaled = result.Signalled || result.Waitable.WaitForFences(false);
|
||||
|
||||
if (!signaled)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
|
||||
}
|
||||
else
|
||||
{
|
||||
_waitTicks += Stopwatch.GetTimestamp() - beforeTicks;
|
||||
result.Signalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// Iterate through handles and remove any that have already been signalled.
|
||||
|
||||
while (true)
|
||||
{
|
||||
SyncHandle first = null;
|
||||
lock (_handles)
|
||||
{
|
||||
first = _handles.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (first == null || first.NeedsFlush(_flushId))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool signaled = first.Waitable.WaitForFences(false);
|
||||
if (signaled)
|
||||
{
|
||||
// Delete the sync object.
|
||||
lock (_handles)
|
||||
{
|
||||
lock (first)
|
||||
{
|
||||
_firstHandle = first.ID + 1;
|
||||
_handles.RemoveAt(0);
|
||||
first.Waitable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This sync handle and any following have not been reached yet.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long GetAndResetWaitTicks()
|
||||
{
|
||||
long result = _waitTicks;
|
||||
_waitTicks = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,15 +3,14 @@ using SharpMetal.Foundation;
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class Texture : TextureBase, ITexture
|
||||
public class Texture : TextureBase, ITexture
|
||||
{
|
||||
public Texture(MTLDevice device, Pipeline pipeline, TextureCreateInfo info) : base(device, pipeline, info)
|
||||
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info) : base(device, renderer, pipeline, info)
|
||||
{
|
||||
var descriptor = new MTLTextureDescriptor
|
||||
{
|
||||
|
@ -38,7 +37,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
_mtlTexture = _device.NewTexture(descriptor);
|
||||
}
|
||||
|
||||
public Texture(MTLDevice device, Pipeline pipeline, TextureCreateInfo info, MTLTexture sourceTexture, int firstLayer, int firstLevel) : base(device, pipeline, info)
|
||||
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info, MTLTexture sourceTexture, int firstLayer, int firstLevel) : base(device, renderer, pipeline, info)
|
||||
{
|
||||
var pixelFormat = FormatTable.GetFormat(Info.Format);
|
||||
var textureType = Info.Target.Convert();
|
||||
|
@ -168,6 +167,9 @@ namespace Ryujinx.Graphics.Metal
|
|||
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||
{
|
||||
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
|
||||
var cbs = _pipeline.CurrentCommandBuffer;
|
||||
|
||||
int outSize = Info.GetMipSize(level);
|
||||
|
||||
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
|
||||
ulong bytesPerImage = 0;
|
||||
|
@ -176,8 +178,8 @@ namespace Ryujinx.Graphics.Metal
|
|||
bytesPerImage = bytesPerRow * (ulong)Info.Height;
|
||||
}
|
||||
|
||||
var handle = range.Handle;
|
||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
||||
var autoBuffer = _renderer.BufferManager.GetBuffer(range.Handle, true);
|
||||
var mtlBuffer = autoBuffer.Get(cbs, range.Offset, outSize).Value;
|
||||
|
||||
blitCommandEncoder.CopyFromTexture(
|
||||
_mtlTexture,
|
||||
|
@ -193,7 +195,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
|
||||
{
|
||||
return new Texture(_device, _pipeline, info, _mtlTexture, firstLayer, firstLevel);
|
||||
return new Texture(_device, _renderer, _pipeline, info, _mtlTexture, firstLayer, firstLevel);
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData()
|
||||
|
@ -215,6 +217,7 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
unsafe
|
||||
{
|
||||
|
||||
var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared);
|
||||
|
||||
blitCommandEncoder.CopyFromTexture(
|
||||
|
|
|
@ -6,13 +6,14 @@ using System.Runtime.Versioning;
|
|||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
abstract class TextureBase : IDisposable
|
||||
public abstract class TextureBase : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
protected readonly TextureCreateInfo _info;
|
||||
protected readonly Pipeline _pipeline;
|
||||
protected readonly MTLDevice _device;
|
||||
protected readonly MetalRenderer _renderer;
|
||||
|
||||
protected MTLTexture _mtlTexture;
|
||||
|
||||
|
@ -21,9 +22,10 @@ namespace Ryujinx.Graphics.Metal
|
|||
public int Height => Info.Height;
|
||||
public int Depth => Info.Depth;
|
||||
|
||||
public TextureBase(MTLDevice device, Pipeline pipeline, TextureCreateInfo info)
|
||||
public TextureBase(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_pipeline = pipeline;
|
||||
_info = info;
|
||||
}
|
||||
|
|
|
@ -2,33 +2,32 @@ using Ryujinx.Graphics.GAL;
|
|||
using SharpMetal.Metal;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Metal
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class TextureBuffer : Texture, ITexture
|
||||
class TextureBuffer : ITexture
|
||||
{
|
||||
private MTLBuffer? _bufferHandle;
|
||||
private readonly MetalRenderer _renderer;
|
||||
|
||||
private BufferHandle _bufferHandle;
|
||||
private int _offset;
|
||||
private int _size;
|
||||
|
||||
public TextureBuffer(MTLDevice device, Pipeline pipeline, TextureCreateInfo info) : base(device, pipeline, info) { }
|
||||
private int _bufferCount;
|
||||
|
||||
public void CreateView()
|
||||
{
|
||||
var descriptor = new MTLTextureDescriptor
|
||||
{
|
||||
PixelFormat = FormatTable.GetFormat(Info.Format),
|
||||
Usage = MTLTextureUsage.ShaderRead | MTLTextureUsage.ShaderWrite,
|
||||
StorageMode = MTLStorageMode.Shared,
|
||||
TextureType = Info.Target.Convert(),
|
||||
Width = (ulong)Info.Width,
|
||||
Height = (ulong)Info.Height
|
||||
};
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
_mtlTexture = _bufferHandle.Value.NewTexture(descriptor, (ulong)_offset, (ulong)_size);
|
||||
public MTLPixelFormat MtlFormat { get; }
|
||||
|
||||
public TextureBuffer(MetalRenderer renderer, TextureCreateInfo info)
|
||||
{
|
||||
_renderer = renderer;
|
||||
Width = info.Width;
|
||||
Height = info.Height;
|
||||
MtlFormat = FormatTable.GetFormat(info.Format);
|
||||
}
|
||||
|
||||
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
|
||||
|
@ -51,10 +50,9 @@ namespace Ryujinx.Graphics.Metal
|
|||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// TODO: Implement this method
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _renderer.GetBufferData(_bufferHandle, _offset, _size);
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
|
@ -67,10 +65,14 @@ namespace Ryujinx.Graphics.Metal
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetData(IMemoryOwner<byte> data)
|
||||
{
|
||||
// TODO
|
||||
//_gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
|
||||
_renderer.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
|
@ -86,25 +88,20 @@ namespace Ryujinx.Graphics.Metal
|
|||
|
||||
public void SetStorage(BufferRange buffer)
|
||||
{
|
||||
if (buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
var handle = buffer.Handle;
|
||||
MTLBuffer bufferHandle = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
||||
if (_bufferHandle == bufferHandle &&
|
||||
if (_bufferHandle == buffer.Handle &&
|
||||
_offset == buffer.Offset &&
|
||||
_size == buffer.Size)
|
||||
_size == buffer.Size &&
|
||||
_bufferCount == _renderer.BufferManager.BufferCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bufferHandle = bufferHandle;
|
||||
_bufferHandle = buffer.Handle;
|
||||
_offset = buffer.Offset;
|
||||
_size = buffer.Size;
|
||||
_bufferCount = _renderer.BufferManager.BufferCount;
|
||||
|
||||
Release();
|
||||
|
||||
CreateView();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue