From 2511bf1e4c43bfe2503b251dc9303b5a02a73dbd Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sun, 30 Jun 2024 17:23:53 +0100 Subject: [PATCH] Preload command speedup, Texture/buffer data flush, blit shader fix (#30) * Move encoder state to be tied to command buffer, so preload and background cbs have their own encoder state * Texture buffer/data flush, blit shader fix --- .../BackgroundResources.cs | 1 + src/Ryujinx.Graphics.Metal/BufferHolder.cs | 12 +- src/Ryujinx.Graphics.Metal/BufferManager.cs | 6 +- .../CommandBufferEncoder.cs | 170 ++++++++++++++++ .../CommandBufferPool.cs | 30 ++- .../CommandBufferScoped.cs | 4 +- .../EncoderStateManager.cs | 24 +-- src/Ryujinx.Graphics.Metal/HelperShader.cs | 8 +- src/Ryujinx.Graphics.Metal/MetalRenderer.cs | 7 +- .../PersistentFlushBuffer.cs | 37 +++- src/Ryujinx.Graphics.Metal/Pipeline.cs | 118 +++-------- src/Ryujinx.Graphics.Metal/StagingBuffer.cs | 14 +- src/Ryujinx.Graphics.Metal/Texture.cs | 187 ++++++++++++------ 13 files changed, 414 insertions(+), 204 deletions(-) create mode 100644 src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs diff --git a/src/Ryujinx.Graphics.Metal/BackgroundResources.cs b/src/Ryujinx.Graphics.Metal/BackgroundResources.cs index f02fd7205..ea49ac6ec 100644 --- a/src/Ryujinx.Graphics.Metal/BackgroundResources.cs +++ b/src/Ryujinx.Graphics.Metal/BackgroundResources.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Metal { MTLCommandQueue queue = _renderer.BackgroundQueue; _pool = new CommandBufferPool(queue); + _pool.Initialize(null); // TODO: Proper encoder factory for background render/compute } return _pool; diff --git a/src/Ryujinx.Graphics.Metal/BufferHolder.cs b/src/Ryujinx.Graphics.Metal/BufferHolder.cs index f07143a43..e0089322f 100644 --- a/src/Ryujinx.Graphics.Metal/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Metal/BufferHolder.cs @@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Metal throw new InvalidOperationException("The buffer is not mapped."); } - public unsafe void SetData(int offset, ReadOnlySpan data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true) + public unsafe void SetData(int offset, ReadOnlySpan data, CommandBufferScoped? cbs = null, bool allowCbsWait = true) { int dataSize = Math.Min(data.Length, Size - offset); if (dataSize == 0) @@ -199,12 +199,11 @@ namespace Ryujinx.Graphics.Metal // 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); + _renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, this, offset, data); } else { @@ -214,7 +213,7 @@ namespace Ryujinx.Graphics.Metal cbs = _renderer.CommandBufferPool.Rent(); } - if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data)) + if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, this, offset, data)) { // Need to do a slow upload. BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize); @@ -223,7 +222,7 @@ namespace Ryujinx.Graphics.Metal var srcBuffer = srcHolder.GetBuffer(); var dstBuffer = this.GetBuffer(true); - Copy(_pipeline, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize); + Copy(cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize); srcHolder.Dispose(); } @@ -255,7 +254,6 @@ namespace Ryujinx.Graphics.Metal } public static void Copy( - Pipeline pipeline, CommandBufferScoped cbs, Auto src, Auto dst, @@ -267,7 +265,7 @@ namespace Ryujinx.Graphics.Metal var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value; var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value; - pipeline.GetOrCreateBlitEncoder().CopyFromBuffer( + cbs.Encoders.EnsureBlitEncoder().CopyFromBuffer( srcBuffer, (ulong)srcOffset, dstbuffer, diff --git a/src/Ryujinx.Graphics.Metal/BufferManager.cs b/src/Ryujinx.Graphics.Metal/BufferManager.cs index 76d6d4fb8..28b6b2e24 100644 --- a/src/Ryujinx.Graphics.Metal/BufferManager.cs +++ b/src/Ryujinx.Graphics.Metal/BufferManager.cs @@ -176,14 +176,14 @@ namespace Ryujinx.Graphics.Metal public void SetData(BufferHandle handle, int offset, ReadOnlySpan data) where T : unmanaged { - SetData(handle, offset, MemoryMarshal.Cast(data), null, null); + SetData(handle, offset, MemoryMarshal.Cast(data), null); } - public void SetData(BufferHandle handle, int offset, ReadOnlySpan data, CommandBufferScoped? cbs, Action endRenderPass) + public void SetData(BufferHandle handle, int offset, ReadOnlySpan data, CommandBufferScoped? cbs) { if (TryGetBuffer(handle, out var holder)) { - holder.SetData(offset, data, cbs, endRenderPass); + holder.SetData(offset, data, cbs); } } diff --git a/src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs b/src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs new file mode 100644 index 000000000..9e7dc73ea --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/CommandBufferEncoder.cs @@ -0,0 +1,170 @@ +using Ryujinx.Graphics.Metal; +using SharpMetal.Metal; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +interface IEncoderFactory +{ + MTLRenderCommandEncoder CreateRenderCommandEncoder(); + MTLComputeCommandEncoder CreateComputeCommandEncoder(); +} + +/// +/// Tracks active encoder object for a command buffer. +/// +[SupportedOSPlatform("macos")] +class CommandBufferEncoder +{ + public EncoderType CurrentEncoderType { get; private set; } = EncoderType.None; + + public MTLBlitCommandEncoder BlitEncoder => new MTLBlitCommandEncoder(CurrentEncoder.Value); + + public MTLComputeCommandEncoder ComputeEncoder => new MTLComputeCommandEncoder(CurrentEncoder.Value); + + public MTLRenderCommandEncoder RenderEncoder => new MTLRenderCommandEncoder(CurrentEncoder.Value); + + internal MTLCommandEncoder? CurrentEncoder { get; private set; } + + private MTLCommandBuffer _commandBuffer; + private IEncoderFactory _encoderFactory; + + public void Initialize(MTLCommandBuffer commandBuffer, IEncoderFactory encoderFactory) + { + _commandBuffer = commandBuffer; + _encoderFactory = encoderFactory; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MTLRenderCommandEncoder EnsureRenderEncoder() + { + if (CurrentEncoderType != EncoderType.Render) + { + return BeginRenderPass(); + } + + return RenderEncoder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MTLBlitCommandEncoder EnsureBlitEncoder() + { + if (CurrentEncoderType != EncoderType.Blit) + { + return BeginBlitPass(); + } + + return BlitEncoder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MTLComputeCommandEncoder EnsureComputeEncoder() + { + if (CurrentEncoderType != EncoderType.Compute) + { + return BeginComputePass(); + } + + return ComputeEncoder; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetRenderEncoder(out MTLRenderCommandEncoder encoder) + { + if (CurrentEncoderType != EncoderType.Render) + { + encoder = default; + return false; + } + + encoder = RenderEncoder; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetBlitEncoder(out MTLBlitCommandEncoder encoder) + { + if (CurrentEncoderType != EncoderType.Blit) + { + encoder = default; + return false; + } + + encoder = BlitEncoder; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetComputeEncoder(out MTLComputeCommandEncoder encoder) + { + if (CurrentEncoderType != EncoderType.Compute) + { + encoder = default; + return false; + } + + encoder = ComputeEncoder; + return true; + } + + public void EndCurrentPass() + { + if (CurrentEncoder != null) + { + switch (CurrentEncoderType) + { + case EncoderType.Blit: + BlitEncoder.EndEncoding(); + CurrentEncoder = null; + break; + case EncoderType.Compute: + ComputeEncoder.EndEncoding(); + CurrentEncoder = null; + break; + case EncoderType.Render: + RenderEncoder.EndEncoding(); + CurrentEncoder = null; + break; + default: + throw new InvalidOperationException(); + } + + CurrentEncoderType = EncoderType.None; + } + } + + private MTLRenderCommandEncoder BeginRenderPass() + { + EndCurrentPass(); + + var renderCommandEncoder = _encoderFactory.CreateRenderCommandEncoder(); + + CurrentEncoder = renderCommandEncoder; + CurrentEncoderType = EncoderType.Render; + + return renderCommandEncoder; + } + + private MTLBlitCommandEncoder BeginBlitPass() + { + EndCurrentPass(); + + var descriptor = new MTLBlitPassDescriptor(); + var blitCommandEncoder = _commandBuffer.BlitCommandEncoder(descriptor); + + CurrentEncoder = blitCommandEncoder; + CurrentEncoderType = EncoderType.Blit; + return blitCommandEncoder; + } + + private MTLComputeCommandEncoder BeginComputePass() + { + EndCurrentPass(); + + var computeCommandEncoder = _encoderFactory.CreateComputeCommandEncoder(); + + CurrentEncoder = computeCommandEncoder; + CurrentEncoderType = EncoderType.Compute; + return computeCommandEncoder; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Metal/CommandBufferPool.cs b/src/Ryujinx.Graphics.Metal/CommandBufferPool.cs index ac8c45b20..9c9e452fb 100644 --- a/src/Ryujinx.Graphics.Metal/CommandBufferPool.cs +++ b/src/Ryujinx.Graphics.Metal/CommandBufferPool.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.Versioning; +using System.Threading; namespace Ryujinx.Graphics.Metal { @@ -14,6 +15,10 @@ namespace Ryujinx.Graphics.Metal 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 @@ -22,22 +27,28 @@ namespace Ryujinx.Graphics.Metal public bool InConsumption; public int SubmissionCount; public MTLCommandBuffer CommandBuffer; + public CommandBufferEncoder Encoders; public FenceHolder Fence; public List Dependants; public List Waitables; - public void Reinitialize(MTLCommandQueue queue) + public void Reinitialize(MTLCommandQueue queue, IEncoderFactory stateManager) { CommandBuffer = queue.CommandBuffer(); + + Encoders.Initialize(CommandBuffer, stateManager); } - public void Initialize(MTLCommandQueue queue) + public void Initialize(MTLCommandQueue queue, IEncoderFactory stateManager) { CommandBuffer = queue.CommandBuffer(); Dependants = new List(); Waitables = new List(); + Encoders = new CommandBufferEncoder(); + + Encoders.Initialize(CommandBuffer, stateManager); } } @@ -51,6 +62,7 @@ namespace Ryujinx.Graphics.Metal public CommandBufferPool(MTLCommandQueue queue) { _queue = queue; + _owner = Thread.CurrentThread; _totalCommandBuffers = MaxCommandBuffers; _totalCommandBuffersMask = _totalCommandBuffers - 1; @@ -60,10 +72,15 @@ namespace Ryujinx.Graphics.Metal _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(_queue); + _commandBuffers[i].Initialize(_queue, _defaultEncoderFactory); WaitAndDecrementRef(i); } } @@ -194,7 +211,7 @@ namespace Ryujinx.Graphics.Metal _inUseCount++; - return new CommandBufferScoped(this, entry.CommandBuffer, cursor); + return new CommandBufferScoped(this, entry.CommandBuffer, entry.Encoders, cursor); } cursor = (cursor + 1) & _totalCommandBuffersMask; @@ -206,6 +223,9 @@ namespace Ryujinx.Graphics.Metal public void Return(CommandBufferScoped cbs) { + // Ensure the encoder is committed. + cbs.Encoders.EndCurrentPass(); + lock (_commandBuffers) { int cbIndex = cbs.CommandBufferIndex; @@ -223,7 +243,7 @@ namespace Ryujinx.Graphics.Metal commandBuffer.Commit(); // Replace entry with new MTLCommandBuffer - entry.Reinitialize(_queue); + entry.Reinitialize(_queue, _defaultEncoderFactory); int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers; _queuedIndexes[ptr] = cbIndex; diff --git a/src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs b/src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs index 43cea6fe9..822f69b46 100644 --- a/src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs +++ b/src/Ryujinx.Graphics.Metal/CommandBufferScoped.cs @@ -9,12 +9,14 @@ namespace Ryujinx.Graphics.Metal { private readonly CommandBufferPool _pool; public MTLCommandBuffer CommandBuffer { get; } + public CommandBufferEncoder Encoders { get; } public int CommandBufferIndex { get; } - public CommandBufferScoped(CommandBufferPool pool, MTLCommandBuffer commandBuffer, int commandBufferIndex) + public CommandBufferScoped(CommandBufferPool pool, MTLCommandBuffer commandBuffer, CommandBufferEncoder encoders, int commandBufferIndex) { _pool = pool; CommandBuffer = commandBuffer; + Encoders = encoders; CommandBufferIndex = commandBufferIndex; } diff --git a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs index db0e8ffa7..7699ed8f6 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs @@ -581,9 +581,8 @@ namespace Ryujinx.Graphics.Metal _currentState.DepthClipMode = clamp ? MTLDepthClipMode.Clamp : MTLDepthClipMode.Clip; // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetDepthClamp(renderCommandEncoder); return; } @@ -600,9 +599,8 @@ namespace Ryujinx.Graphics.Metal _currentState.Clamp = clamp; // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetDepthBias(renderCommandEncoder); return; } @@ -632,9 +630,8 @@ namespace Ryujinx.Graphics.Metal } // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetScissors(renderCommandEncoder); return; } @@ -669,9 +666,8 @@ namespace Ryujinx.Graphics.Metal } // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetViewports(renderCommandEncoder); return; } @@ -688,9 +684,8 @@ namespace Ryujinx.Graphics.Metal UpdatePipelineVertexState(_currentState.VertexBuffers, _currentState.VertexAttribs); // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers); return; } @@ -755,9 +750,8 @@ namespace Ryujinx.Graphics.Metal _currentState.CullBoth = face == Face.FrontAndBack; // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetCullMode(renderCommandEncoder); SetScissors(renderCommandEncoder); return; @@ -778,9 +772,8 @@ namespace Ryujinx.Graphics.Metal _currentState.Winding = frontFace.Convert(); // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetFrontFace(renderCommandEncoder); return; } @@ -795,9 +788,8 @@ namespace Ryujinx.Graphics.Metal _currentState.BackRefValue = backRef; // Inline update - if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null) + if (_pipeline.Encoders.TryGetRenderEncoder(out MTLRenderCommandEncoder renderCommandEncoder)) { - var renderCommandEncoder = new MTLRenderCommandEncoder(_pipeline.CurrentEncoder.Value); SetStencilRefValue(renderCommandEncoder); } diff --git a/src/Ryujinx.Graphics.Metal/HelperShader.cs b/src/Ryujinx.Graphics.Metal/HelperShader.cs index 5525186f6..54ba9889d 100644 --- a/src/Ryujinx.Graphics.Metal/HelperShader.cs +++ b/src/Ryujinx.Graphics.Metal/HelperShader.cs @@ -277,7 +277,7 @@ namespace Ryujinx.Graphics.Metal DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors); // Save current state - EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags); + EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false); // Inherit some state without fully recreating render pipeline. RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index); @@ -312,7 +312,7 @@ namespace Ryujinx.Graphics.Metal _pipeline.Draw(4, 1, 0, 0); // Restore previous state - _pipeline.SwapState(null, clearFlags); + _pipeline.SwapState(null, clearFlags, false); _helperShaderState.Restore(save); } @@ -330,7 +330,7 @@ namespace Ryujinx.Graphics.Metal var helperScissors = _helperShaderState.Scissors; // Save current state - EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags); + EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false); // Inherit some state without fully recreating render pipeline. RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true); @@ -365,7 +365,7 @@ namespace Ryujinx.Graphics.Metal _pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); // Restore previous state - _pipeline.SwapState(null, clearFlags); + _pipeline.SwapState(null, clearFlags, false); _helperShaderState.Restore(save); } diff --git a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs index a0d6faced..1edd91c56 100644 --- a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs +++ b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs @@ -67,7 +67,10 @@ namespace Ryujinx.Graphics.Metal public void BackgroundContextAction(Action action, bool alwaysBackground = false) { - Logger.Warning?.Print(LogClass.Gpu, "Not Implemented!"); + // GetData methods should be thread safe, so we can call this directly. + // Texture copy (scaled) may also happen in here, so that should also be thread safe. + + action(); } public BufferHandle CreateBuffer(int size, BufferAccess access) @@ -221,7 +224,7 @@ namespace Ryujinx.Graphics.Metal public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) { - BufferManager.SetData(buffer, offset, data, _pipeline.Cbs, _pipeline.EndRenderPassDelegate); + BufferManager.SetData(buffer, offset, data, _pipeline.Cbs); } public void UpdateCounters() diff --git a/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs b/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs index 6b51d4af5..a1834f0b7 100644 --- a/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs +++ b/src/Ryujinx.Graphics.Metal/PersistentFlushBuffer.cs @@ -1,3 +1,4 @@ +using Ryujinx.Graphics.GAL; using System; using System.Runtime.Versioning; @@ -44,7 +45,7 @@ namespace Ryujinx.Graphics.Metal if (srcBuffer.TryIncrementReferenceCount()) { - BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false); + BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false); } else { @@ -58,6 +59,40 @@ namespace Ryujinx.Graphics.Metal return flushStorage.GetDataStorage(0, size); } + public Span GetTextureData(CommandBufferPool cbp, Texture view, int size) + { + TextureCreateInfo info = view.Info; + + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var buffer = flushStorage.GetBuffer().Get(cbs).Value; + var image = view.GetHandle(); + + view.CopyFromOrToBuffer(cbs, buffer, image, size, true, 0, 0, info.GetLayers(), info.Levels, singleSlice: false); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + + public Span GetTextureData(CommandBufferPool cbp, Texture view, int size, int layer, int level) + { + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var buffer = flushStorage.GetBuffer().Get(cbs).Value; + var image = view.GetHandle(); + + view.CopyFromOrToBuffer(cbs, buffer, image, size, true, layer, level, 1, 1, singleSlice: true); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + public void Dispose() { _flushStorage.Dispose(); diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index 7f11ecded..3e17dde41 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Metal } [SupportedOSPlatform("macos")] - class Pipeline : IPipeline, IDisposable + class Pipeline : IPipeline, IEncoderFactory, IDisposable { private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB @@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Metal private EncoderStateManager _encoderStateManager; private ulong _byteWeight; - public readonly Action EndRenderPassDelegate; public MTLCommandBuffer CommandBuffer; public IndexBufferPattern QuadsToTrisPattern; @@ -35,8 +34,8 @@ namespace Ryujinx.Graphics.Metal 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 CommandBufferEncoder Encoders => Cbs.Encoders; + internal EncoderType CurrentEncoderType => Encoders.CurrentEncoderType; internal bool RenderPassActive { get; private set; } public Pipeline(MTLDevice device, MetalRenderer renderer) @@ -44,7 +43,7 @@ namespace Ryujinx.Graphics.Metal _device = device; _renderer = renderer; - EndRenderPassDelegate = EndCurrentPass; + renderer.CommandBufferPool.Initialize(this); CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer; } @@ -57,8 +56,13 @@ namespace Ryujinx.Graphics.Metal TriFanToTrisPattern = new IndexBufferPattern(_renderer, 3, 3, 2, [int.MinValue, -1, 0], 1, true); } - public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All) + public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All, bool endRenderPass = true) { + if (endRenderPass && CurrentEncoderType == EncoderType.Render) + { + EndCurrentPass(); + } + return _encoderStateManager.SwapState(state, flags); } @@ -79,15 +83,7 @@ namespace Ryujinx.Graphics.Metal public MTLRenderCommandEncoder GetOrCreateRenderEncoder(bool forDraw = false) { - MTLRenderCommandEncoder renderCommandEncoder; - if (CurrentEncoder == null || CurrentEncoderType != EncoderType.Render) - { - renderCommandEncoder = BeginRenderPass(); - } - else - { - renderCommandEncoder = new MTLRenderCommandEncoder(CurrentEncoder.Value); - } + MTLRenderCommandEncoder renderCommandEncoder = Cbs.Encoders.EnsureRenderEncoder(); if (forDraw) { @@ -99,28 +95,12 @@ namespace Ryujinx.Graphics.Metal public MTLBlitCommandEncoder GetOrCreateBlitEncoder() { - if (CurrentEncoder != null) - { - if (CurrentEncoderType == EncoderType.Blit) - { - return new MTLBlitCommandEncoder(CurrentEncoder.Value); - } - } - - return BeginBlitPass(); + return Cbs.Encoders.EnsureBlitEncoder(); } public MTLComputeCommandEncoder GetOrCreateComputeEncoder(bool forDispatch = false) { - MTLComputeCommandEncoder computeCommandEncoder; - if (CurrentEncoder == null || CurrentEncoderType != EncoderType.Compute) - { - computeCommandEncoder = BeginComputePass(); - } - else - { - computeCommandEncoder = new MTLComputeCommandEncoder(CurrentEncoder.Value); - } + MTLComputeCommandEncoder computeCommandEncoder = Cbs.Encoders.EnsureComputeEncoder(); if (forDispatch) { @@ -132,65 +112,17 @@ namespace Ryujinx.Graphics.Metal public void EndCurrentPass() { - if (CurrentEncoder != null) - { - switch (CurrentEncoderType) - { - case EncoderType.Blit: - new MTLBlitCommandEncoder(CurrentEncoder.Value).EndEncoding(); - CurrentEncoder = null; - break; - case EncoderType.Compute: - new MTLComputeCommandEncoder(CurrentEncoder.Value).EndEncoding(); - CurrentEncoder = null; - break; - case EncoderType.Render: - new MTLRenderCommandEncoder(CurrentEncoder.Value).EndEncoding(); - CurrentEncoder = null; - RenderPassActive = false; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - CurrentEncoderType = EncoderType.None; - } + Cbs.Encoders.EndCurrentPass(); } - private MTLRenderCommandEncoder BeginRenderPass() + public MTLRenderCommandEncoder CreateRenderCommandEncoder() { - EndCurrentPass(); - - var renderCommandEncoder = _encoderStateManager.CreateRenderCommandEncoder(); - - CurrentEncoder = renderCommandEncoder; - CurrentEncoderType = EncoderType.Render; - RenderPassActive = true; - - return renderCommandEncoder; + return _encoderStateManager.CreateRenderCommandEncoder(); } - private MTLBlitCommandEncoder BeginBlitPass() + public MTLComputeCommandEncoder CreateComputeCommandEncoder() { - EndCurrentPass(); - - var descriptor = new MTLBlitPassDescriptor(); - var blitCommandEncoder = Cbs.CommandBuffer.BlitCommandEncoder(descriptor); - - CurrentEncoder = blitCommandEncoder; - CurrentEncoderType = EncoderType.Blit; - return blitCommandEncoder; - } - - private MTLComputeCommandEncoder BeginComputePass() - { - EndCurrentPass(); - - var computeCommandEncoder = _encoderStateManager.CreateComputeCommandEncoder(); - - CurrentEncoder = computeCommandEncoder; - CurrentEncoderType = EncoderType.Compute; - return computeCommandEncoder; + return _encoderStateManager.CreateComputeCommandEncoder(); } public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear) @@ -279,19 +211,15 @@ namespace Ryujinx.Graphics.Metal { case EncoderType.Render: { - var renderCommandEncoder = GetOrCreateRenderEncoder(); - var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets; MTLRenderStages stages = MTLRenderStages.RenderStageVertex | MTLRenderStages.RenderStageFragment; - renderCommandEncoder.MemoryBarrier(scope, stages, stages); + Encoders.RenderEncoder.MemoryBarrier(scope, stages, stages); break; } case EncoderType.Compute: { - var computeCommandEncoder = GetOrCreateComputeEncoder(); - var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;; - computeCommandEncoder.MemoryBarrier(scope); + Encoders.ComputeEncoder.MemoryBarrier(scope); break; } } @@ -353,7 +281,7 @@ namespace Ryujinx.Graphics.Metal var srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false); var dstBuffer = _renderer.BufferManager.GetBuffer(dst, dstOffset, size, true); - BufferHolder.Copy(this, Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size); + BufferHolder.Copy(Cbs, srcBuffer, dstBuffer, srcOffset, dstOffset, size); } public void DispatchCompute(int groupsX, int groupsY, int groupsZ) @@ -709,9 +637,7 @@ namespace Ryujinx.Graphics.Metal { if (CurrentEncoderType == EncoderType.Render) { - var renderCommandEncoder = GetOrCreateRenderEncoder(); - - renderCommandEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment); + Encoders.RenderEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment); } } diff --git a/src/Ryujinx.Graphics.Metal/StagingBuffer.cs b/src/Ryujinx.Graphics.Metal/StagingBuffer.cs index 07450f6b0..d739cdd3f 100644 --- a/src/Ryujinx.Graphics.Metal/StagingBuffer.cs +++ b/src/Ryujinx.Graphics.Metal/StagingBuffer.cs @@ -63,15 +63,13 @@ namespace Ryujinx.Graphics.Metal _resourceAlignment = Constants.MinResourceAlignment; } - public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, BufferHolder dst, int dstOffset, ReadOnlySpan data) { bool isRender = cbs != null; CommandBufferScoped scoped = cbs ?? cbp.Rent(); // Must push all data to the buffer. If it can't fit, split it up. - endRenderPass?.Invoke(); - while (data.Length > 0) { if (_freeSize < data.Length) @@ -122,14 +120,14 @@ namespace Ryujinx.Graphics.Metal _buffer.SetDataUnchecked(offset, data[..capacity]); _buffer.SetDataUnchecked(0, data[capacity..]); - BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity); - BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity); + BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity); + BufferHolder.Copy(cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity); } else { _buffer.SetDataUnchecked(offset, data); - BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length); + BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length); } _freeOffset = (offset + data.Length) & (BufferSize - 1); @@ -139,7 +137,7 @@ namespace Ryujinx.Graphics.Metal _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length)); } - public bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + public bool TryPushData(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan data) { if (data.Length > BufferSize) { @@ -156,8 +154,6 @@ namespace Ryujinx.Graphics.Metal } } - endRenderPass?.Invoke(); - PushDataImpl(cbs, dst, dstOffset, data); return true; diff --git a/src/Ryujinx.Graphics.Metal/Texture.cs b/src/Ryujinx.Graphics.Metal/Texture.cs index 9c4fed5c9..e938a04b8 100644 --- a/src/Ryujinx.Graphics.Metal/Texture.cs +++ b/src/Ryujinx.Graphics.Metal/Texture.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using SharpMetal.Foundation; using SharpMetal.Metal; @@ -94,6 +95,13 @@ namespace Ryujinx.Graphics.Metal public void CopyTo(ITexture destination, int firstLayer, int firstLevel) { + if (!_renderer.CommandBufferPool.OwnedByCurrentThread) + { + Logger.Warning?.PrintMsg(LogClass.Gpu, "Metal doesn't currently support scaled blit on background thread."); + + return; + } + var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder(); if (destination is Texture destinationTexture) @@ -202,98 +210,157 @@ namespace Ryujinx.Graphics.Metal return new Texture(_device, _renderer, _pipeline, info, _mtlTexture, firstLayer, firstLevel); } - public PinnedSpan GetData() + private int GetBufferDataLength(int size) { - var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder(); + // TODO: D32S8 conversion - ulong length = 0; + return size; + } - for (int level = 0; level < Info.Levels; level++) + private ReadOnlySpan GetDataFromBuffer(ReadOnlySpan storage, int size, Span output) + { + // TODO: D32S8 conversion + + return storage; + } + + public void CopyFromOrToBuffer( + CommandBufferScoped cbs, + MTLBuffer buffer, + MTLTexture image, + int size, + bool to, + int dstLayer, + int dstLevel, + int dstLayers, + int dstLevels, + bool singleSlice, + int offset = 0, + int stride = 0) + { + MTLBlitCommandEncoder blitCommandEncoder = cbs.Encoders.EnsureBlitEncoder(); + + bool is3D = Info.Target == Target.Texture3D; + int width = Math.Max(1, Info.Width >> dstLevel); + int height = Math.Max(1, Info.Height >> dstLevel); + int depth = is3D && !singleSlice ? Math.Max(1, Info.Depth >> dstLevel) : 1; + int layers = dstLayers; + int levels = dstLevels; + + for (int oLevel = 0; oLevel < levels; oLevel++) { - length += (ulong)Info.GetMipSize(level); - } + int level = oLevel + dstLevel; + int mipSize = Info.GetMipSize2D(level); - unsafe - { - var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared); + int mipSizeLevel = GetBufferDataLength(is3D && !singleSlice + ? Info.GetMipSize(level) + : mipSize * dstLayers); - int width = Info.Width; - int height = Info.Height; - int depth = Info.Depth; - int levels = Info.GetLevelsClamped(); - int layers = Info.GetLayers(); - bool is3D = Info.Target == Target.Texture3D; + int endOffset = offset + mipSizeLevel; - int offset = 0; - - for (int level = 0; level < levels; level++) + if ((uint)endOffset > (uint)size) { - int mipSize = Info.GetMipSize2D(level); - int endOffset = offset + mipSize; + break; + } - for (int layer = 0; layer < layers; layer++) + for (int oLayer = 0; oLayer < layers; oLayer++) + { + int layer = !is3D ? dstLayer + oLayer : 0; + int z = is3D ? dstLayer + oLayer : 0; + + if (to) { blitCommandEncoder.CopyFromTexture( - _mtlTexture, + image, (ulong)layer, (ulong)level, - new MTLOrigin(), - new MTLSize { width = (ulong)width, height = (ulong)height, depth = is3D ? (ulong)depth : 1 }, - mtlBuffer, + new MTLOrigin { z = (ulong)z }, + new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 }, + buffer, (ulong)offset, (ulong)Info.GetMipStride(level), (ulong)mipSize ); - - offset += mipSize; } - - width = Math.Max(1, width >> 1); - height = Math.Max(1, height >> 1); - - if (is3D) + else { - depth = Math.Max(1, depth >> 1); + blitCommandEncoder.CopyFromBuffer( + buffer, + (ulong)offset, + (ulong)Info.GetMipStride(level), + (ulong)mipSize, + new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 }, + image, + (ulong)(layer + oLayer), + (ulong)level, + new MTLOrigin { z = (ulong)z } + ); } + + offset += mipSize; } - // TODO: wait + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); - return new PinnedSpan(mtlBuffer.Contents.ToPointer(), (int)length, () => mtlBuffer.Dispose()); + if (Info.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } } } + private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer) + { + int size = 0; + + for (int level = 0; level < Info.Levels; level++) + { + size += Info.GetMipSize(level); + } + + size = GetBufferDataLength(size); + + Span result = flushBuffer.GetTextureData(cbp, this, size); + + return GetDataFromBuffer(result, size, result); + } + + private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level) + { + int size = GetBufferDataLength(Info.GetMipSize(level)); + + Span result = flushBuffer.GetTextureData(cbp, this, size, layer, level); + + return GetDataFromBuffer(result, size, result); + } + + public PinnedSpan GetData() + { + BackgroundResource resources = _renderer.BackgroundResources.Get(); + + if (_renderer.CommandBufferPool.OwnedByCurrentThread) + { + _renderer.FlushAllCommands(); + + return PinnedSpan.UnsafeFromSpan(GetData(_renderer.CommandBufferPool, resources.GetFlushBuffer())); + } + + return PinnedSpan.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer())); + } + public PinnedSpan GetData(int layer, int level) { - var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder(); + BackgroundResource resources = _renderer.BackgroundResources.Get(); - ulong bytesPerRow = (ulong)Info.GetMipStride(level); - ulong length = bytesPerRow * (ulong)Info.Height; - ulong bytesPerImage = 0; - if (_mtlTexture.TextureType == MTLTextureType.Type3D) + if (_renderer.CommandBufferPool.OwnedByCurrentThread) { - bytesPerImage = length; + _renderer.FlushAllCommands(); + + return PinnedSpan.UnsafeFromSpan(GetData(_renderer.CommandBufferPool, resources.GetFlushBuffer(), layer, level)); } - unsafe - { - - var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared); - - blitCommandEncoder.CopyFromTexture( - _mtlTexture, - (ulong)layer, - (ulong)level, - new MTLOrigin(), - new MTLSize { width = _mtlTexture.Width, height = _mtlTexture.Height, depth = _mtlTexture.Depth }, - mtlBuffer, - 0, - bytesPerRow, - bytesPerImage - ); - - return new PinnedSpan(mtlBuffer.Contents.ToPointer(), (int)length, () => mtlBuffer.Dispose()); - } + return PinnedSpan.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level)); } public void SetData(IMemoryOwner data)