mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2024-12-27 11:03:04 +00:00
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 MaxVertexLayouts = 31;
|
||||||
public const int MaxTextures = 31;
|
public const int MaxTextures = 31;
|
||||||
public const int MaxSamplers = 16;
|
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 Ryujinx.Graphics.GAL;
|
||||||
using SharpMetal.Metal;
|
using SharpMetal.Metal;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
@ -38,10 +37,10 @@ namespace Ryujinx.Graphics.Metal
|
||||||
public TextureBase[] ComputeTextures = new TextureBase[Constants.MaxTextures];
|
public TextureBase[] ComputeTextures = new TextureBase[Constants.MaxTextures];
|
||||||
public MTLSamplerState[] ComputeSamplers = new MTLSamplerState[Constants.MaxSamplers];
|
public MTLSamplerState[] ComputeSamplers = new MTLSamplerState[Constants.MaxSamplers];
|
||||||
|
|
||||||
public List<BufferInfo> UniformBuffers = [];
|
public BufferAssignment[] UniformBuffers = [];
|
||||||
public List<BufferInfo> StorageBuffers = [];
|
public BufferAssignment[] StorageBuffers = [];
|
||||||
|
|
||||||
public MTLBuffer IndexBuffer = default;
|
public BufferRange IndexBuffer = default;
|
||||||
public MTLIndexType IndexType = MTLIndexType.UInt16;
|
public MTLIndexType IndexType = MTLIndexType.UInt16;
|
||||||
public ulong IndexBufferOffset = 0;
|
public ulong IndexBufferOffset = 0;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ using Ryujinx.Graphics.Shader;
|
||||||
using SharpMetal.Metal;
|
using SharpMetal.Metal;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using BufferAssignment = Ryujinx.Graphics.GAL.BufferAssignment;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
struct EncoderStateManager : IDisposable
|
struct EncoderStateManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Pipeline _pipeline;
|
private readonly Pipeline _pipeline;
|
||||||
|
private readonly BufferManager _bufferManager;
|
||||||
|
|
||||||
private readonly RenderPipelineCache _renderPipelineCache;
|
private readonly RenderPipelineCache _renderPipelineCache;
|
||||||
private readonly ComputePipelineCache _computePipelineCache;
|
private readonly ComputePipelineCache _computePipelineCache;
|
||||||
|
@ -21,7 +22,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
private EncoderState _currentState = new();
|
private EncoderState _currentState = new();
|
||||||
private readonly Stack<EncoderState> _backStates = [];
|
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 MTLIndexType IndexType => _currentState.IndexType;
|
||||||
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
|
public readonly ulong IndexBufferOffset => _currentState.IndexBufferOffset;
|
||||||
public readonly PrimitiveTopology Topology => _currentState.Topology;
|
public readonly PrimitiveTopology Topology => _currentState.Topology;
|
||||||
|
@ -30,11 +31,13 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
// RGBA32F is the biggest format
|
// RGBA32F is the biggest format
|
||||||
private const int ZeroBufferSize = 4 * 4;
|
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;
|
_pipeline = pipeline;
|
||||||
|
_bufferManager = bufferManager;
|
||||||
|
|
||||||
_renderPipelineCache = new(device);
|
_renderPipelineCache = new(device);
|
||||||
_computePipelineCache = new(device);
|
_computePipelineCache = new(device);
|
||||||
_depthStencilCache = new(device);
|
_depthStencilCache = new(device);
|
||||||
|
@ -43,7 +46,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
byte[] zeros = new byte[ZeroBufferSize];
|
byte[] zeros = new byte[ZeroBufferSize];
|
||||||
fixed (byte* ptr = zeros)
|
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.IndexType = type.Convert();
|
||||||
_currentState.IndexBufferOffset = (ulong)buffer.Offset;
|
_currentState.IndexBufferOffset = (ulong)buffer.Offset;
|
||||||
var handle = buffer.Handle;
|
_currentState.IndexBuffer = buffer;
|
||||||
_currentState.IndexBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,20 +659,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
// Inlineable
|
// Inlineable
|
||||||
public void UpdateUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
public void UpdateUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||||
{
|
{
|
||||||
_currentState.UniformBuffers = [];
|
_currentState.UniformBuffers = buffers.ToArray();
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline update
|
// Inline update
|
||||||
if (_pipeline.CurrentEncoder != null)
|
if (_pipeline.CurrentEncoder != null)
|
||||||
|
@ -691,20 +680,13 @@ namespace Ryujinx.Graphics.Metal
|
||||||
// Inlineable
|
// Inlineable
|
||||||
public void UpdateStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
public void UpdateStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||||
{
|
{
|
||||||
_currentState.StorageBuffers = [];
|
_currentState.StorageBuffers = buffers.ToArray();
|
||||||
|
|
||||||
foreach (BufferAssignment buffer in buffers)
|
for (int i = 0; i < _currentState.StorageBuffers.Length; i++)
|
||||||
{
|
{
|
||||||
if (buffer.Range.Size != 0)
|
BufferAssignment buffer = _currentState.StorageBuffers[i];
|
||||||
{
|
// TODO: DONT offset the binding by 15
|
||||||
// TODO: DONT offset the binding by 15
|
_currentState.StorageBuffers[i] = new BufferAssignment(buffer.Binding + 15, buffer.Range);
|
||||||
_currentState.StorageBuffers.Add(new BufferInfo
|
|
||||||
{
|
|
||||||
Handle = buffer.Range.Handle.ToIntPtr(),
|
|
||||||
Offset = buffer.Range.Offset,
|
|
||||||
Index = buffer.Binding + 15
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline update
|
// Inline update
|
||||||
|
@ -956,50 +938,51 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
private void SetVertexBuffers(MTLRenderCommandEncoder renderCommandEncoder, VertexBufferDescriptor[] bufferDescriptors)
|
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++)
|
for (int i = 0; i < bufferDescriptors.Length; i++)
|
||||||
{
|
{
|
||||||
if (bufferDescriptors[i].Buffer.Handle.ToIntPtr() != IntPtr.Zero)
|
buffers.Add(new BufferAssignment(i, bufferDescriptors[i].Buffer));
|
||||||
{
|
|
||||||
buffers.Add(new BufferInfo
|
|
||||||
{
|
|
||||||
Handle = bufferDescriptors[i].Buffer.Handle.ToIntPtr(),
|
|
||||||
Offset = bufferDescriptors[i].Buffer.Offset,
|
|
||||||
Index = i
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero buffer
|
// Zero buffer
|
||||||
buffers.Add(new BufferInfo
|
buffers.Add(new BufferAssignment(
|
||||||
{
|
bufferDescriptors.Length,
|
||||||
Handle = _zeroBuffer.NativePtr,
|
new BufferRange(_zeroBuffer, 0, ZeroBufferSize)));
|
||||||
Offset = 0,
|
|
||||||
Index = bufferDescriptors.Length
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
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 (fragment)
|
if (autoBuffer != null)
|
||||||
{
|
{
|
||||||
renderCommandEncoder.SetFragmentBuffer(new MTLBuffer(buffer.Handle), (ulong)buffer.Offset, (ulong)buffer.Index);
|
var mtlBuffer = autoBuffer.Get(_pipeline.CurrentCommandBuffer).Value;
|
||||||
|
|
||||||
|
renderCommandEncoder.SetVertexBuffer(mtlBuffer, (ulong)range.Offset, (ulong)buffer.Binding);
|
||||||
|
|
||||||
|
if (fragment)
|
||||||
|
{
|
||||||
|
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)
|
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
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
class HelperShader : IDisposable
|
public class HelperShader : IDisposable
|
||||||
{
|
{
|
||||||
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
|
private const string ShadersSourcePath = "/Ryujinx.Graphics.Metal/Shaders";
|
||||||
private readonly Pipeline _pipeline;
|
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.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using SharpMetal.Foundation;
|
|
||||||
using SharpMetal.Metal;
|
using SharpMetal.Metal;
|
||||||
using SharpMetal.QuartzCore;
|
using SharpMetal.QuartzCore;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
|
@ -19,12 +17,20 @@ namespace Ryujinx.Graphics.Metal
|
||||||
private readonly Func<CAMetalLayer> _getMetalLayer;
|
private readonly Func<CAMetalLayer> _getMetalLayer;
|
||||||
|
|
||||||
private Pipeline _pipeline;
|
private Pipeline _pipeline;
|
||||||
|
private HelperShader _helperShader;
|
||||||
|
private BufferManager _bufferManager;
|
||||||
private Window _window;
|
private Window _window;
|
||||||
|
private CommandBufferPool _commandBufferPool;
|
||||||
|
|
||||||
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||||
public bool PreferThreading => true;
|
public bool PreferThreading => true;
|
||||||
public IPipeline Pipeline => _pipeline;
|
public IPipeline Pipeline => _pipeline;
|
||||||
public IWindow Window => _window;
|
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)
|
public MetalRenderer(Func<CAMetalLayer> metalLayer)
|
||||||
{
|
{
|
||||||
|
@ -35,7 +41,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
|
throw new NotSupportedException("Metal backend requires Tier 2 Argument Buffer support.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_queue = _device.NewCommandQueue();
|
_queue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers);
|
||||||
_getMetalLayer = metalLayer;
|
_getMetalLayer = metalLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +51,15 @@ namespace Ryujinx.Graphics.Metal
|
||||||
layer.Device = _device;
|
layer.Device = _device;
|
||||||
layer.FramebufferOnly = false;
|
layer.FramebufferOnly = false;
|
||||||
|
|
||||||
|
_commandBufferPool = new CommandBufferPool(_device, _queue);
|
||||||
_window = new Window(this, layer);
|
_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)
|
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
|
||||||
|
@ -54,11 +67,14 @@ namespace Ryujinx.Graphics.Metal
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
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)
|
public BufferHandle CreateBuffer(IntPtr pointer, int size)
|
||||||
{
|
{
|
||||||
var buffer = _device.NewBuffer(pointer, (ulong)size, MTLResourceOptions.ResourceStorageModeShared);
|
return _bufferManager.Create(pointer, size);
|
||||||
var bufferPtr = buffer.NativePtr;
|
|
||||||
return Unsafe.As<IntPtr, BufferHandle>(ref bufferPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
|
public BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers)
|
||||||
|
@ -71,15 +87,6 @@ namespace Ryujinx.Graphics.Metal
|
||||||
throw new NotImplementedException();
|
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)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
return new Program(shaders, _device);
|
return new Program(shaders, _device);
|
||||||
|
@ -94,10 +101,10 @@ namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
if (info.Target == Target.TextureBuffer)
|
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)
|
public ITextureArray CreateTextureArray(int size, bool isBuffer)
|
||||||
|
@ -113,19 +120,17 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void CreateSync(ulong id, bool strict)
|
public void CreateSync(ulong id, bool strict)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
SyncManager.Create(id, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteBuffer(BufferHandle buffer)
|
public void DeleteBuffer(BufferHandle buffer)
|
||||||
{
|
{
|
||||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref buffer));
|
_bufferManager.Delete(buffer);
|
||||||
mtlBuffer.SetPurgeableState(MTLPurgeableState.Empty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 _bufferManager.GetData(buffer, offset, size);
|
||||||
return new PinnedSpan<byte>(IntPtr.Add(mtlBuffer.Contents, offset).ToPointer(), size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Capabilities GetCapabilities()
|
public Capabilities GetCapabilities()
|
||||||
|
@ -198,8 +203,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public ulong GetCurrentSync()
|
public ulong GetCurrentSync()
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
return SyncManager.GetCurrent();
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HardwareInfo GetHardwareInfo()
|
public HardwareInfo GetHardwareInfo()
|
||||||
|
@ -212,18 +216,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
throw new NotImplementedException();
|
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();
|
_bufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCounters()
|
public void UpdateCounters()
|
||||||
|
@ -233,7 +228,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void PreFrame()
|
public void PreFrame()
|
||||||
{
|
{
|
||||||
|
SyncManager.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, float divisor, bool hostReserved)
|
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)
|
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)
|
public void SetInterruptAction(Action<Action> interruptAction)
|
||||||
{
|
{
|
||||||
// Not needed for now
|
InterruptAction = interruptAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Screenshot()
|
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.Metal;
|
||||||
using SharpMetal.QuartzCore;
|
using SharpMetal.QuartzCore;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
enum EncoderType
|
public enum EncoderType
|
||||||
{
|
{
|
||||||
Blit,
|
Blit,
|
||||||
Compute,
|
Compute,
|
||||||
|
@ -19,14 +18,19 @@ namespace Ryujinx.Graphics.Metal
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
class Pipeline : IPipeline, IDisposable
|
public class Pipeline : IPipeline, IDisposable
|
||||||
{
|
{
|
||||||
private readonly MTLDevice _device;
|
private readonly MTLDevice _device;
|
||||||
private readonly MTLCommandQueue _commandQueue;
|
private readonly MTLCommandQueue _commandQueue;
|
||||||
private readonly HelperShader _helperShader;
|
private readonly MetalRenderer _renderer;
|
||||||
|
|
||||||
private MTLCommandBuffer _commandBuffer;
|
private CommandBufferScoped Cbs;
|
||||||
public MTLCommandBuffer CommandBuffer => _commandBuffer;
|
private CommandBufferScoped? PreloadCbs;
|
||||||
|
public MTLCommandBuffer CommandBuffer;
|
||||||
|
|
||||||
|
public readonly Action EndRenderPassDelegate;
|
||||||
|
|
||||||
|
public CommandBufferScoped CurrentCommandBuffer => Cbs;
|
||||||
|
|
||||||
private MTLCommandEncoder? _currentEncoder;
|
private MTLCommandEncoder? _currentEncoder;
|
||||||
public MTLCommandEncoder? CurrentEncoder => _currentEncoder;
|
public MTLCommandEncoder? CurrentEncoder => _currentEncoder;
|
||||||
|
@ -36,14 +40,20 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
private EncoderStateManager _encoderStateManager;
|
private EncoderStateManager _encoderStateManager;
|
||||||
|
|
||||||
public Pipeline(MTLDevice device, MTLCommandQueue commandQueue)
|
public Pipeline(MTLDevice device, MetalRenderer renderer, MTLCommandQueue commandQueue)
|
||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
|
_renderer = renderer;
|
||||||
_commandQueue = commandQueue;
|
_commandQueue = commandQueue;
|
||||||
_helperShader = new HelperShader(_device, this);
|
|
||||||
|
|
||||||
_commandBuffer = _commandQueue.CommandBuffer();
|
EndRenderPassDelegate = EndCurrentPass;
|
||||||
_encoderStateManager = new EncoderStateManager(_device, this);
|
|
||||||
|
CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitEncoderStateManager(BufferManager bufferManager)
|
||||||
|
{
|
||||||
|
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveState()
|
public void SaveState()
|
||||||
|
@ -156,7 +166,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
EndCurrentPass();
|
EndCurrentPass();
|
||||||
|
|
||||||
var descriptor = new MTLBlitPassDescriptor();
|
var descriptor = new MTLBlitPassDescriptor();
|
||||||
var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor);
|
var blitCommandEncoder = Cbs.CommandBuffer.BlitCommandEncoder(descriptor);
|
||||||
|
|
||||||
_currentEncoder = blitCommandEncoder;
|
_currentEncoder = blitCommandEncoder;
|
||||||
_currentEncoderType = EncoderType.Blit;
|
_currentEncoderType = EncoderType.Blit;
|
||||||
|
@ -178,21 +188,35 @@ namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
// TODO: Clean this up
|
// 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 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();
|
EndCurrentPass();
|
||||||
|
|
||||||
_commandBuffer.PresentDrawable(drawable);
|
Cbs.CommandBuffer.PresentDrawable(drawable);
|
||||||
_commandBuffer.Commit();
|
|
||||||
|
|
||||||
_commandBuffer = _commandQueue.CommandBuffer();
|
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||||
|
|
||||||
|
// TODO: Auto flush counting
|
||||||
|
_renderer.SyncManager.GetAndResetWaitTicks();
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
dst.Dispose();
|
dst.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FlushCommandsImpl()
|
||||||
|
{
|
||||||
|
SaveState();
|
||||||
|
|
||||||
|
EndCurrentPass();
|
||||||
|
|
||||||
|
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||||
|
_renderer.RegisterFlush();
|
||||||
|
|
||||||
|
RestoreState();
|
||||||
|
}
|
||||||
|
|
||||||
public void BlitColor(
|
public void BlitColor(
|
||||||
ITexture src,
|
ITexture src,
|
||||||
ITexture dst,
|
ITexture dst,
|
||||||
|
@ -200,7 +224,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
Extents2D dstRegion,
|
Extents2D dstRegion,
|
||||||
bool linearFilter)
|
bool linearFilter)
|
||||||
{
|
{
|
||||||
_helperShader.BlitColor(src, dst, srcRegion, dstRegion, linearFilter);
|
_renderer.HelperShader.BlitColor(src, dst, srcRegion, dstRegion, linearFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Barrier()
|
public void Barrier()
|
||||||
|
@ -235,9 +259,10 @@ namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
var blitCommandEncoder = GetOrCreateBlitEncoder();
|
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
|
// Might need a closer look, range's count, lower, and upper bound
|
||||||
// must be a multiple of 4
|
// must be a multiple of 4
|
||||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref destination));
|
|
||||||
blitCommandEncoder.FillBuffer(mtlBuffer,
|
blitCommandEncoder.FillBuffer(mtlBuffer,
|
||||||
new NSRange
|
new NSRange
|
||||||
{
|
{
|
||||||
|
@ -259,7 +284,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
return;
|
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)
|
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||||
|
@ -273,7 +298,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
return;
|
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()
|
public void CommandBufferBarrier()
|
||||||
|
@ -281,19 +306,12 @@ namespace Ryujinx.Graphics.Metal
|
||||||
Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!");
|
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));
|
BufferHolder.Copy(this, Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size);
|
||||||
MTLBuffer destinationBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref destination));
|
|
||||||
|
|
||||||
blitCommandEncoder.CopyFromBuffer(
|
|
||||||
sourceBuffer,
|
|
||||||
(ulong)srcOffset,
|
|
||||||
destinationBuffer,
|
|
||||||
(ulong)dstOffset,
|
|
||||||
(ulong)size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DispatchCompute(int groupsX, int groupsY, int groupsZ, int groupSizeX, int groupSizeY, int groupSizeZ)
|
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
|
// TODO: Support topology re-indexing to provide support for TriangleFans
|
||||||
var primitiveType = _encoderStateManager.Topology.Convert();
|
var primitiveType = _encoderStateManager.Topology.Convert();
|
||||||
|
|
||||||
|
var indexBuffer = _renderer.BufferManager.GetBuffer(_encoderStateManager.IndexBuffer.Handle, false);
|
||||||
|
|
||||||
renderCommandEncoder.DrawIndexedPrimitives(
|
renderCommandEncoder.DrawIndexedPrimitives(
|
||||||
primitiveType,
|
primitiveType,
|
||||||
(ulong)indexCount,
|
(ulong)indexCount,
|
||||||
_encoderStateManager.IndexType,
|
_encoderStateManager.IndexType,
|
||||||
_encoderStateManager.IndexBuffer,
|
indexBuffer.Get(Cbs, 0, indexCount * sizeof(int)).Value,
|
||||||
_encoderStateManager.IndexBufferOffset,
|
_encoderStateManager.IndexBufferOffset,
|
||||||
(ulong)instanceCount,
|
(ulong)instanceCount,
|
||||||
firstVertex,
|
firstVertex,
|
||||||
|
@ -368,7 +388,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
|
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)
|
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 SharpMetal.Metal;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[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
|
var descriptor = new MTLTextureDescriptor
|
||||||
{
|
{
|
||||||
|
@ -38,7 +37,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
_mtlTexture = _device.NewTexture(descriptor);
|
_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 pixelFormat = FormatTable.GetFormat(Info.Format);
|
||||||
var textureType = Info.Target.Convert();
|
var textureType = Info.Target.Convert();
|
||||||
|
@ -168,6 +167,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
public void CopyTo(BufferRange range, int layer, int level, int stride)
|
||||||
{
|
{
|
||||||
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
|
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
|
||||||
|
var cbs = _pipeline.CurrentCommandBuffer;
|
||||||
|
|
||||||
|
int outSize = Info.GetMipSize(level);
|
||||||
|
|
||||||
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
|
ulong bytesPerRow = (ulong)Info.GetMipStride(level);
|
||||||
ulong bytesPerImage = 0;
|
ulong bytesPerImage = 0;
|
||||||
|
@ -176,8 +178,8 @@ namespace Ryujinx.Graphics.Metal
|
||||||
bytesPerImage = bytesPerRow * (ulong)Info.Height;
|
bytesPerImage = bytesPerRow * (ulong)Info.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handle = range.Handle;
|
var autoBuffer = _renderer.BufferManager.GetBuffer(range.Handle, true);
|
||||||
MTLBuffer mtlBuffer = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
var mtlBuffer = autoBuffer.Get(cbs, range.Offset, outSize).Value;
|
||||||
|
|
||||||
blitCommandEncoder.CopyFromTexture(
|
blitCommandEncoder.CopyFromTexture(
|
||||||
_mtlTexture,
|
_mtlTexture,
|
||||||
|
@ -193,7 +195,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
|
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()
|
public PinnedSpan<byte> GetData()
|
||||||
|
@ -215,6 +217,7 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|
||||||
var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared);
|
var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared);
|
||||||
|
|
||||||
blitCommandEncoder.CopyFromTexture(
|
blitCommandEncoder.CopyFromTexture(
|
||||||
|
|
|
@ -6,13 +6,14 @@ using System.Runtime.Versioning;
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
abstract class TextureBase : IDisposable
|
public abstract class TextureBase : IDisposable
|
||||||
{
|
{
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
protected readonly TextureCreateInfo _info;
|
protected readonly TextureCreateInfo _info;
|
||||||
protected readonly Pipeline _pipeline;
|
protected readonly Pipeline _pipeline;
|
||||||
protected readonly MTLDevice _device;
|
protected readonly MTLDevice _device;
|
||||||
|
protected readonly MetalRenderer _renderer;
|
||||||
|
|
||||||
protected MTLTexture _mtlTexture;
|
protected MTLTexture _mtlTexture;
|
||||||
|
|
||||||
|
@ -21,9 +22,10 @@ namespace Ryujinx.Graphics.Metal
|
||||||
public int Height => Info.Height;
|
public int Height => Info.Height;
|
||||||
public int Depth => Info.Depth;
|
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;
|
_device = device;
|
||||||
|
_renderer = renderer;
|
||||||
_pipeline = pipeline;
|
_pipeline = pipeline;
|
||||||
_info = info;
|
_info = info;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,33 +2,32 @@ using Ryujinx.Graphics.GAL;
|
||||||
using SharpMetal.Metal;
|
using SharpMetal.Metal;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
class TextureBuffer : Texture, ITexture
|
class TextureBuffer : ITexture
|
||||||
{
|
{
|
||||||
private MTLBuffer? _bufferHandle;
|
private readonly MetalRenderer _renderer;
|
||||||
|
|
||||||
|
private BufferHandle _bufferHandle;
|
||||||
private int _offset;
|
private int _offset;
|
||||||
private int _size;
|
private int _size;
|
||||||
|
|
||||||
public TextureBuffer(MTLDevice device, Pipeline pipeline, TextureCreateInfo info) : base(device, pipeline, info) { }
|
private int _bufferCount;
|
||||||
|
|
||||||
public void CreateView()
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
public MTLPixelFormat MtlFormat { get; }
|
||||||
|
|
||||||
|
public TextureBuffer(MetalRenderer renderer, TextureCreateInfo info)
|
||||||
{
|
{
|
||||||
var descriptor = new MTLTextureDescriptor
|
_renderer = renderer;
|
||||||
{
|
Width = info.Width;
|
||||||
PixelFormat = FormatTable.GetFormat(Info.Format),
|
Height = info.Height;
|
||||||
Usage = MTLTextureUsage.ShaderRead | MTLTextureUsage.ShaderWrite,
|
MtlFormat = FormatTable.GetFormat(info.Format);
|
||||||
StorageMode = MTLStorageMode.Shared,
|
|
||||||
TextureType = Info.Target.Convert(),
|
|
||||||
Width = (ulong)Info.Width,
|
|
||||||
Height = (ulong)Info.Height
|
|
||||||
};
|
|
||||||
|
|
||||||
_mtlTexture = _bufferHandle.Value.NewTexture(descriptor, (ulong)_offset, (ulong)_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
|
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
|
||||||
|
@ -51,10 +50,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this method
|
|
||||||
public PinnedSpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _renderer.GetBufferData(_bufferHandle, _offset, _size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PinnedSpan<byte> GetData(int layer, int level)
|
public PinnedSpan<byte> GetData(int layer, int level)
|
||||||
|
@ -67,10 +65,14 @@ namespace Ryujinx.Graphics.Metal
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Release()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(IMemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
// TODO
|
_renderer.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
|
||||||
//_gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
|
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,25 +88,20 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void SetStorage(BufferRange buffer)
|
public void SetStorage(BufferRange buffer)
|
||||||
{
|
{
|
||||||
if (buffer.Handle != BufferHandle.Null)
|
if (_bufferHandle == buffer.Handle &&
|
||||||
|
_offset == buffer.Offset &&
|
||||||
|
_size == buffer.Size &&
|
||||||
|
_bufferCount == _renderer.BufferManager.BufferCount)
|
||||||
{
|
{
|
||||||
var handle = buffer.Handle;
|
return;
|
||||||
MTLBuffer bufferHandle = new(Unsafe.As<BufferHandle, IntPtr>(ref handle));
|
|
||||||
if (_bufferHandle == bufferHandle &&
|
|
||||||
_offset == buffer.Offset &&
|
|
||||||
_size == buffer.Size)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bufferHandle = bufferHandle;
|
|
||||||
_offset = buffer.Offset;
|
|
||||||
_size = buffer.Size;
|
|
||||||
|
|
||||||
Release();
|
|
||||||
|
|
||||||
CreateView();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_bufferHandle = buffer.Handle;
|
||||||
|
_offset = buffer.Offset;
|
||||||
|
_size = buffer.Size;
|
||||||
|
_bufferCount = _renderer.BufferManager.BufferCount;
|
||||||
|
|
||||||
|
Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue