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
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue