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 Dependants; public List Waitables; public void Initialize(MTLCommandQueue queue) { CommandBuffer = queue.CommandBuffer(); Dependants = new List(); Waitables = new List(); } } 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); } } } }