diff --git a/src/Ryujinx.Graphics.Metal/BufferHolder.cs b/src/Ryujinx.Graphics.Metal/BufferHolder.cs index af1d42744..f07143a43 100644 --- a/src/Ryujinx.Graphics.Metal/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Metal/BufferHolder.cs @@ -190,6 +190,18 @@ namespace Ryujinx.Graphics.Metal } } + if (cbs != null && + _pipeline.RenderPassActive && + !(_buffer.HasCommandBufferDependency(cbs.Value) && + _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize))) + { + // If the buffer hasn't been used on the command buffer yet, try to preload the data. + // This avoids ending and beginning render passes on each buffer data upload. + + cbs = _pipeline.PreloadCbs; + endRenderPass = null; + } + if (allowCbsWait) { _renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, endRenderPass, this, offset, data); @@ -331,6 +343,8 @@ namespace Ryujinx.Graphics.Metal public void Dispose() { + _pipeline.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size); + _buffer.Dispose(); _cachedConvertedBuffers.Dispose(); diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index 3a913986b..67c3da891 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -20,16 +20,21 @@ namespace Ryujinx.Graphics.Metal [SupportedOSPlatform("macos")] class Pipeline : IPipeline, IDisposable { + private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB + private readonly MTLDevice _device; private readonly MetalRenderer _renderer; private EncoderStateManager _encoderStateManager; + private ulong _byteWeight; public readonly Action EndRenderPassDelegate; public MTLCommandBuffer CommandBuffer; + internal CommandBufferScoped? PreloadCbs { get; private set; } internal CommandBufferScoped Cbs { get; private set; } internal MTLCommandEncoder? CurrentEncoder { get; private set; } internal EncoderType CurrentEncoderType { get; private set; } = EncoderType.None; + internal bool RenderPassActive { get; private set; } public Pipeline(MTLDevice device, MetalRenderer renderer) { @@ -133,6 +138,7 @@ namespace Ryujinx.Graphics.Metal case EncoderType.Render: new MTLRenderCommandEncoder(CurrentEncoder.Value).EndEncoding(); CurrentEncoder = null; + RenderPassActive = false; break; default: throw new ArgumentOutOfRangeException(); @@ -150,6 +156,7 @@ namespace Ryujinx.Graphics.Metal CurrentEncoder = renderCommandEncoder; CurrentEncoderType = EncoderType.Render; + RenderPassActive = true; return renderCommandEncoder; } @@ -198,12 +205,44 @@ namespace Ryujinx.Graphics.Metal dst.Dispose(); } + public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight) + { + bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs); + + if (PreloadCbs != null && !usedByCurrentCb) + { + usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value); + } + + if (usedByCurrentCb) + { + // Since we can only free memory after the command buffer that uses a given resource was executed, + // keeping the command buffer might cause a high amount of memory to be in use. + // To prevent that, we force submit command buffers if the memory usage by resources + // in use by the current command buffer is above a given limit, and those resources were disposed. + _byteWeight += byteWeight; + + if (_byteWeight >= MinByteWeightForFlush) + { + FlushCommandsImpl(); + } + } + } + public void FlushCommandsImpl() { SaveState(); EndCurrentPass(); + _byteWeight = 0; + + if (PreloadCbs != null) + { + PreloadCbs.Value.Dispose(); + PreloadCbs = null; + } + CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; _renderer.RegisterFlush();