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 Dependants; public List Waitables; public void Use(MTLCommandQueue queue, IEncoderFactory stateManager) { CommandBuffer = queue.CommandBuffer(); Fence = new FenceHolder(CommandBuffer); Encoders.Initialize(CommandBuffer, stateManager); InUse = true; } public void Initialize() { Dependants = new List(); Waitables = new List(); 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 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.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 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(); int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers; _queuedIndexes[ptr] = cbIndex; _queuedCount++; } } private void WaitAndDecrementRef(int cbIndex) { 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(); } public void Dispose() { for (int i = 0; i < _totalCommandBuffers; i++) { WaitAndDecrementRef(i); } } } }