mirror of
https://github.com/Ryubing/Ryujinx.git
synced 2025-03-11 01:24:25 +00:00
290 lines
8.5 KiB
C#
290 lines
8.5 KiB
C#
using SharpMetal.Metal;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Versioning;
|
|
using System.Threading;
|
|
|
|
namespace Ryujinx.Graphics.Metal
|
|
{
|
|
[SupportedOSPlatform("macos")]
|
|
class CommandBufferPool : IDisposable
|
|
{
|
|
public const int MaxCommandBuffers = 16;
|
|
|
|
private readonly int _totalCommandBuffers;
|
|
private readonly int _totalCommandBuffersMask;
|
|
private readonly MTLCommandQueue _queue;
|
|
private readonly Thread _owner;
|
|
private IEncoderFactory _defaultEncoderFactory;
|
|
|
|
public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
|
|
|
|
[SupportedOSPlatform("macos")]
|
|
private struct ReservedCommandBuffer
|
|
{
|
|
public bool InUse;
|
|
public bool InConsumption;
|
|
public int SubmissionCount;
|
|
public MTLCommandBuffer CommandBuffer;
|
|
public CommandBufferEncoder Encoders;
|
|
public FenceHolder Fence;
|
|
|
|
public List<IAuto> Dependants;
|
|
public List<MultiFenceHolder> Waitables;
|
|
|
|
public void Use(MTLCommandQueue queue, IEncoderFactory stateManager)
|
|
{
|
|
MTLCommandBufferDescriptor descriptor = new();
|
|
#if DEBUG
|
|
descriptor.ErrorOptions = MTLCommandBufferErrorOption.EncoderExecutionStatus;
|
|
#endif
|
|
|
|
CommandBuffer = queue.CommandBuffer(descriptor);
|
|
Fence = new FenceHolder(CommandBuffer);
|
|
|
|
Encoders.Initialize(CommandBuffer, stateManager);
|
|
|
|
InUse = true;
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
Dependants = [];
|
|
Waitables = [];
|
|
Encoders = new CommandBufferEncoder();
|
|
}
|
|
}
|
|
|
|
private readonly ReservedCommandBuffer[] _commandBuffers;
|
|
|
|
private readonly int[] _queuedIndexes;
|
|
private int _queuedIndexesPtr;
|
|
private int _queuedCount;
|
|
private int _inUseCount;
|
|
|
|
public CommandBufferPool(MTLCommandQueue queue, bool isLight = false)
|
|
{
|
|
_queue = queue;
|
|
_owner = Thread.CurrentThread;
|
|
|
|
_totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
|
|
_totalCommandBuffersMask = _totalCommandBuffers - 1;
|
|
|
|
_commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
|
|
|
|
_queuedIndexes = new int[_totalCommandBuffers];
|
|
_queuedIndexesPtr = 0;
|
|
_queuedCount = 0;
|
|
}
|
|
|
|
public void Initialize(IEncoderFactory encoderFactory)
|
|
{
|
|
_defaultEncoderFactory = encoderFactory;
|
|
|
|
for (int i = 0; i < _totalCommandBuffers; i++)
|
|
{
|
|
_commandBuffers[i].Initialize();
|
|
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 ReservedCommandBuffer 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 ReservedCommandBuffer entry = ref _commandBuffers[i];
|
|
|
|
if (entry.InUse)
|
|
{
|
|
AddWaitable(i, waitable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
|
{
|
|
ref ReservedCommandBuffer 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 ReservedCommandBuffer 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 ReservedCommandBuffer 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 ReservedCommandBuffer entry = ref _commandBuffers[cursor];
|
|
|
|
if (!entry.InUse && !entry.InConsumption)
|
|
{
|
|
entry.Use(_queue, _defaultEncoderFactory);
|
|
|
|
_inUseCount++;
|
|
|
|
return new CommandBufferScoped(this, entry.CommandBuffer, entry.Encoders, cursor);
|
|
}
|
|
|
|
cursor = (cursor + 1) & _totalCommandBuffersMask;
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
|
|
}
|
|
|
|
public void Return(CommandBufferScoped cbs)
|
|
{
|
|
// Ensure the encoder is committed.
|
|
cbs.Encoders.EndCurrentPass();
|
|
|
|
lock (_commandBuffers)
|
|
{
|
|
int cbIndex = cbs.CommandBufferIndex;
|
|
|
|
ref ReservedCommandBuffer 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--;
|
|
|
|
MTLCommandBuffer commandBuffer = entry.CommandBuffer;
|
|
commandBuffer.Commit();
|
|
|
|
int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
|
|
_queuedIndexes[ptr] = cbIndex;
|
|
_queuedCount++;
|
|
}
|
|
}
|
|
|
|
private void WaitAndDecrementRef(int cbIndex)
|
|
{
|
|
ref ReservedCommandBuffer entry = ref _commandBuffers[cbIndex];
|
|
|
|
if (entry.InConsumption)
|
|
{
|
|
entry.Fence.Wait();
|
|
entry.InConsumption = false;
|
|
}
|
|
|
|
foreach (IAuto dependant in entry.Dependants)
|
|
{
|
|
dependant.DecrementReferenceCount(cbIndex);
|
|
}
|
|
|
|
foreach (MultiFenceHolder waitable in entry.Waitables)
|
|
{
|
|
waitable.RemoveFence(cbIndex);
|
|
waitable.RemoveBufferUses(cbIndex);
|
|
}
|
|
|
|
entry.Dependants.Clear();
|
|
entry.Waitables.Clear();
|
|
entry.Fence?.Dispose();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
for (int i = 0; i < _totalCommandBuffers; i++)
|
|
{
|
|
WaitAndDecrementRef(i);
|
|
}
|
|
}
|
|
}
|
|
}
|