diff --git a/src/Ryujinx.Graphics.Metal/BackgroundResources.cs b/src/Ryujinx.Graphics.Metal/BackgroundResources.cs new file mode 100644 index 000000000..2f846392e --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/BackgroundResources.cs @@ -0,0 +1,110 @@ +using SharpMetal.Metal; +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Graphics.Metal +{ + [SupportedOSPlatform("macos")] + class BackgroundResource : IDisposable + { + private readonly MetalRenderer _renderer; + private readonly Pipeline _pipeline; + + private CommandBufferPool _pool; + private PersistentFlushBuffer _flushBuffer; + + public BackgroundResource(MetalRenderer renderer, Pipeline pipeline) + { + _renderer = renderer; + _pipeline = pipeline; + } + + public CommandBufferPool GetPool() + { + if (_pool == null) + { + MTLCommandQueue queue = _renderer.BackgroundQueue; + _pool = new CommandBufferPool(queue.Device, queue); + } + + return _pool; + } + + public PersistentFlushBuffer GetFlushBuffer() + { + _flushBuffer ??= new PersistentFlushBuffer(_renderer, _pipeline); + + return _flushBuffer; + } + + public void Dispose() + { + _pool?.Dispose(); + _flushBuffer?.Dispose(); + } + } + + [SupportedOSPlatform("macos")] + class BackgroundResources : IDisposable + { + private readonly MetalRenderer _renderer; + private readonly Pipeline _pipeline; + + private readonly Dictionary _resources; + + public BackgroundResources(MetalRenderer renderer, Pipeline pipeline) + { + _renderer = renderer; + _pipeline = pipeline; + + _resources = new Dictionary(); + } + + private void Cleanup() + { + lock (_resources) + { + foreach (KeyValuePair tuple in _resources) + { + if (!tuple.Key.IsAlive) + { + tuple.Value.Dispose(); + _resources.Remove(tuple.Key); + } + } + } + } + + public BackgroundResource Get() + { + Thread thread = Thread.CurrentThread; + + lock (_resources) + { + if (!_resources.TryGetValue(thread, out BackgroundResource resource)) + { + Cleanup(); + + resource = new BackgroundResource(_renderer, _pipeline); + + _resources[thread] = resource; + } + + return resource; + } + } + + public void Dispose() + { + lock (_resources) + { + foreach (var resource in _resources.Values) + { + resource.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs index aac88587d..08d1ca540 100644 --- a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs +++ b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs @@ -17,20 +17,21 @@ namespace Ryujinx.Graphics.Metal private readonly Func _getMetalLayer; private Pipeline _pipeline; - private HelperShader _helperShader; - private BufferManager _bufferManager; private Window _window; - private CommandBufferPool _commandBufferPool; public event EventHandler ScreenCaptured; public bool PreferThreading => true; + public IPipeline Pipeline => _pipeline; public IWindow Window => _window; - public HelperShader HelperShader => _helperShader; - public BufferManager BufferManager => _bufferManager; - public CommandBufferPool CommandBufferPool => _commandBufferPool; - public Action InterruptAction { get; private set; } - public SyncManager SyncManager { get; private set; } + + internal MTLCommandQueue BackgroundQueue { get; private set; } + internal HelperShader HelperShader { get; private set; } + internal BufferManager BufferManager { get; private set; } + internal CommandBufferPool CommandBufferPool { get; private set; } + internal BackgroundResources BackgroundResources { get; private set; } + internal Action InterruptAction { get; private set; } + internal SyncManager SyncManager { get; private set; } public MetalRenderer(Func metalLayer) { @@ -42,6 +43,8 @@ namespace Ryujinx.Graphics.Metal } _queue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers); + BackgroundQueue = _device.NewCommandQueue(CommandBufferPool.MaxCommandBuffers); + _getMetalLayer = metalLayer; } @@ -51,14 +54,15 @@ namespace Ryujinx.Graphics.Metal layer.Device = _device; layer.FramebufferOnly = false; - _commandBufferPool = new CommandBufferPool(_device, _queue); + CommandBufferPool = new CommandBufferPool(_device, _queue); _window = new Window(this, layer); _pipeline = new Pipeline(_device, this, _queue); - _bufferManager = new BufferManager(_device, this, _pipeline); + BufferManager = new BufferManager(_device, this, _pipeline); - _pipeline.InitEncoderStateManager(_bufferManager); + _pipeline.InitEncoderStateManager(BufferManager); - _helperShader = new HelperShader(_device, this, _pipeline); + BackgroundResources = new BackgroundResources(this, _pipeline); + HelperShader = new HelperShader(_device, this, _pipeline); SyncManager = new SyncManager(this); } @@ -69,12 +73,12 @@ namespace Ryujinx.Graphics.Metal public BufferHandle CreateBuffer(int size, BufferAccess access) { - return _bufferManager.CreateWithHandle(size); + return BufferManager.CreateWithHandle(size); } public BufferHandle CreateBuffer(IntPtr pointer, int size) { - return _bufferManager.Create(pointer, size); + return BufferManager.Create(pointer, size); } public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) @@ -125,12 +129,12 @@ namespace Ryujinx.Graphics.Metal public void DeleteBuffer(BufferHandle buffer) { - _bufferManager.Delete(buffer); + BufferManager.Delete(buffer); } public PinnedSpan GetBufferData(BufferHandle buffer, int offset, int size) { - return _bufferManager.GetData(buffer, offset, size); + return BufferManager.GetData(buffer, offset, size); } public Capabilities GetCapabilities() @@ -218,7 +222,7 @@ namespace Ryujinx.Graphics.Metal public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) { - _bufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate); + BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate); } public void UpdateCounters() @@ -259,7 +263,7 @@ namespace Ryujinx.Graphics.Metal SyncManager.RegisterFlush(); // Periodically free unused regions of the staging buffer to avoid doing it all at once. - _bufferManager.StagingBuffer.FreeCompleted(); + BufferManager.StagingBuffer.FreeCompleted(); } public void SetInterruptAction(Action interruptAction) @@ -274,6 +278,7 @@ namespace Ryujinx.Graphics.Metal public void Dispose() { + BackgroundResources.Dispose(); _pipeline.Dispose(); _window.Dispose(); } diff --git a/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs b/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs new file mode 100644 index 000000000..6b51d4af5 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.Metal +{ + [SupportedOSPlatform("macos")] + internal class PersistentFlushBuffer : IDisposable + { + private readonly MetalRenderer _renderer; + private readonly Pipeline _pipeline; + + private BufferHolder _flushStorage; + + public PersistentFlushBuffer(MetalRenderer renderer, Pipeline pipeline) + { + _renderer = renderer; + _pipeline = pipeline; + } + + private BufferHolder ResizeIfNeeded(int size) + { + var flushStorage = _flushStorage; + + if (flushStorage == null || size > _flushStorage.Size) + { + flushStorage?.Dispose(); + + flushStorage = _renderer.BufferManager.Create(size); + _flushStorage = flushStorage; + } + + return flushStorage; + } + + public Span GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size) + { + var flushStorage = ResizeIfNeeded(size); + Auto srcBuffer; + + using (var cbs = cbp.Rent()) + { + srcBuffer = buffer.GetBuffer(); + var dstBuffer = flushStorage.GetBuffer(); + + if (srcBuffer.TryIncrementReferenceCount()) + { + BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false); + } + else + { + // Source buffer is no longer alive, don't copy anything to flush storage. + srcBuffer = null; + } + } + + flushStorage.WaitForFences(); + srcBuffer?.DecrementReferenceCount(); + return flushStorage.GetDataStorage(0, size); + } + + public void Dispose() + { + _flushStorage.Dispose(); + } + } +}