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
This commit is contained in:
riperiperi 2024-06-30 17:23:53 +01:00 committed by Isaac Marovitz
parent 80f9a5d0da
commit 2511bf1e4c
13 changed files with 414 additions and 204 deletions

View file

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Metal
{ {
MTLCommandQueue queue = _renderer.BackgroundQueue; MTLCommandQueue queue = _renderer.BackgroundQueue;
_pool = new CommandBufferPool(queue); _pool = new CommandBufferPool(queue);
_pool.Initialize(null); // TODO: Proper encoder factory for background render/compute
} }
return _pool; return _pool;

View file

@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Metal
throw new InvalidOperationException("The buffer is not mapped."); throw new InvalidOperationException("The buffer is not mapped.");
} }
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true) public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, bool allowCbsWait = true)
{ {
int dataSize = Math.Min(data.Length, Size - offset); int dataSize = Math.Min(data.Length, Size - offset);
if (dataSize == 0) if (dataSize == 0)
@ -199,12 +199,11 @@ namespace Ryujinx.Graphics.Metal
// This avoids ending and beginning render passes on each buffer data upload. // This avoids ending and beginning render passes on each buffer data upload.
cbs = _pipeline.PreloadCbs; cbs = _pipeline.PreloadCbs;
endRenderPass = null;
} }
if (allowCbsWait) if (allowCbsWait)
{ {
_renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, endRenderPass, this, offset, data); _renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, this, offset, data);
} }
else else
{ {
@ -214,7 +213,7 @@ namespace Ryujinx.Graphics.Metal
cbs = _renderer.CommandBufferPool.Rent(); 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. // Need to do a slow upload.
BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize); BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize);
@ -223,7 +222,7 @@ namespace Ryujinx.Graphics.Metal
var srcBuffer = srcHolder.GetBuffer(); var srcBuffer = srcHolder.GetBuffer();
var dstBuffer = this.GetBuffer(true); var dstBuffer = this.GetBuffer(true);
Copy(_pipeline, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize); Copy(cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
srcHolder.Dispose(); srcHolder.Dispose();
} }
@ -255,7 +254,6 @@ namespace Ryujinx.Graphics.Metal
} }
public static void Copy( public static void Copy(
Pipeline pipeline,
CommandBufferScoped cbs, CommandBufferScoped cbs,
Auto<DisposableBuffer> src, Auto<DisposableBuffer> src,
Auto<DisposableBuffer> dst, Auto<DisposableBuffer> dst,
@ -267,7 +265,7 @@ namespace Ryujinx.Graphics.Metal
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value; var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value; var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value;
pipeline.GetOrCreateBlitEncoder().CopyFromBuffer( cbs.Encoders.EnsureBlitEncoder().CopyFromBuffer(
srcBuffer, srcBuffer,
(ulong)srcOffset, (ulong)srcOffset,
dstbuffer, dstbuffer,

View file

@ -176,14 +176,14 @@ namespace Ryujinx.Graphics.Metal
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
{ {
SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null, null); SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null);
} }
public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs, Action endRenderPass) public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs)
{ {
if (TryGetBuffer(handle, out var holder)) if (TryGetBuffer(handle, out var holder))
{ {
holder.SetData(offset, data, cbs, endRenderPass); holder.SetData(offset, data, cbs);
} }
} }

View file

@ -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();
}
/// <summary>
/// Tracks active encoder object for a command buffer.
/// </summary>
[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;
}
}

View file

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Graphics.Metal namespace Ryujinx.Graphics.Metal
{ {
@ -14,6 +15,10 @@ namespace Ryujinx.Graphics.Metal
private readonly int _totalCommandBuffers; private readonly int _totalCommandBuffers;
private readonly int _totalCommandBuffersMask; private readonly int _totalCommandBuffersMask;
private readonly MTLCommandQueue _queue; private readonly MTLCommandQueue _queue;
private readonly Thread _owner;
private IEncoderFactory _defaultEncoderFactory;
public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
private struct ReservedCommandBuffer private struct ReservedCommandBuffer
@ -22,22 +27,28 @@ namespace Ryujinx.Graphics.Metal
public bool InConsumption; public bool InConsumption;
public int SubmissionCount; public int SubmissionCount;
public MTLCommandBuffer CommandBuffer; public MTLCommandBuffer CommandBuffer;
public CommandBufferEncoder Encoders;
public FenceHolder Fence; public FenceHolder Fence;
public List<IAuto> Dependants; public List<IAuto> Dependants;
public List<MultiFenceHolder> Waitables; public List<MultiFenceHolder> Waitables;
public void Reinitialize(MTLCommandQueue queue) public void Reinitialize(MTLCommandQueue queue, IEncoderFactory stateManager)
{ {
CommandBuffer = queue.CommandBuffer(); CommandBuffer = queue.CommandBuffer();
Encoders.Initialize(CommandBuffer, stateManager);
} }
public void Initialize(MTLCommandQueue queue) public void Initialize(MTLCommandQueue queue, IEncoderFactory stateManager)
{ {
CommandBuffer = queue.CommandBuffer(); CommandBuffer = queue.CommandBuffer();
Dependants = new List<IAuto>(); Dependants = new List<IAuto>();
Waitables = new List<MultiFenceHolder>(); Waitables = new List<MultiFenceHolder>();
Encoders = new CommandBufferEncoder();
Encoders.Initialize(CommandBuffer, stateManager);
} }
} }
@ -51,6 +62,7 @@ namespace Ryujinx.Graphics.Metal
public CommandBufferPool(MTLCommandQueue queue) public CommandBufferPool(MTLCommandQueue queue)
{ {
_queue = queue; _queue = queue;
_owner = Thread.CurrentThread;
_totalCommandBuffers = MaxCommandBuffers; _totalCommandBuffers = MaxCommandBuffers;
_totalCommandBuffersMask = _totalCommandBuffers - 1; _totalCommandBuffersMask = _totalCommandBuffers - 1;
@ -60,10 +72,15 @@ namespace Ryujinx.Graphics.Metal
_queuedIndexes = new int[_totalCommandBuffers]; _queuedIndexes = new int[_totalCommandBuffers];
_queuedIndexesPtr = 0; _queuedIndexesPtr = 0;
_queuedCount = 0; _queuedCount = 0;
}
public void Initialize(IEncoderFactory encoderFactory)
{
_defaultEncoderFactory = encoderFactory;
for (int i = 0; i < _totalCommandBuffers; i++) for (int i = 0; i < _totalCommandBuffers; i++)
{ {
_commandBuffers[i].Initialize(_queue); _commandBuffers[i].Initialize(_queue, _defaultEncoderFactory);
WaitAndDecrementRef(i); WaitAndDecrementRef(i);
} }
} }
@ -194,7 +211,7 @@ namespace Ryujinx.Graphics.Metal
_inUseCount++; _inUseCount++;
return new CommandBufferScoped(this, entry.CommandBuffer, cursor); return new CommandBufferScoped(this, entry.CommandBuffer, entry.Encoders, cursor);
} }
cursor = (cursor + 1) & _totalCommandBuffersMask; cursor = (cursor + 1) & _totalCommandBuffersMask;
@ -206,6 +223,9 @@ namespace Ryujinx.Graphics.Metal
public void Return(CommandBufferScoped cbs) public void Return(CommandBufferScoped cbs)
{ {
// Ensure the encoder is committed.
cbs.Encoders.EndCurrentPass();
lock (_commandBuffers) lock (_commandBuffers)
{ {
int cbIndex = cbs.CommandBufferIndex; int cbIndex = cbs.CommandBufferIndex;
@ -223,7 +243,7 @@ namespace Ryujinx.Graphics.Metal
commandBuffer.Commit(); commandBuffer.Commit();
// Replace entry with new MTLCommandBuffer // Replace entry with new MTLCommandBuffer
entry.Reinitialize(_queue); entry.Reinitialize(_queue, _defaultEncoderFactory);
int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers; int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
_queuedIndexes[ptr] = cbIndex; _queuedIndexes[ptr] = cbIndex;

View file

@ -9,12 +9,14 @@ namespace Ryujinx.Graphics.Metal
{ {
private readonly CommandBufferPool _pool; private readonly CommandBufferPool _pool;
public MTLCommandBuffer CommandBuffer { get; } public MTLCommandBuffer CommandBuffer { get; }
public CommandBufferEncoder Encoders { get; }
public int CommandBufferIndex { 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; _pool = pool;
CommandBuffer = commandBuffer; CommandBuffer = commandBuffer;
Encoders = encoders;
CommandBufferIndex = commandBufferIndex; CommandBufferIndex = commandBufferIndex;
} }

View file

@ -581,9 +581,8 @@ namespace Ryujinx.Graphics.Metal
_currentState.DepthClipMode = clamp ? MTLDepthClipMode.Clamp : MTLDepthClipMode.Clip; _currentState.DepthClipMode = clamp ? MTLDepthClipMode.Clamp : MTLDepthClipMode.Clip;
// Inline update // 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); SetDepthClamp(renderCommandEncoder);
return; return;
} }
@ -600,9 +599,8 @@ namespace Ryujinx.Graphics.Metal
_currentState.Clamp = clamp; _currentState.Clamp = clamp;
// Inline update // 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); SetDepthBias(renderCommandEncoder);
return; return;
} }
@ -632,9 +630,8 @@ namespace Ryujinx.Graphics.Metal
} }
// Inline update // 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); SetScissors(renderCommandEncoder);
return; return;
} }
@ -669,9 +666,8 @@ namespace Ryujinx.Graphics.Metal
} }
// Inline update // 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); SetViewports(renderCommandEncoder);
return; return;
} }
@ -688,9 +684,8 @@ namespace Ryujinx.Graphics.Metal
UpdatePipelineVertexState(_currentState.VertexBuffers, _currentState.VertexAttribs); UpdatePipelineVertexState(_currentState.VertexBuffers, _currentState.VertexAttribs);
// Inline update // 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); SetVertexBuffers(renderCommandEncoder, _currentState.VertexBuffers);
return; return;
} }
@ -755,9 +750,8 @@ namespace Ryujinx.Graphics.Metal
_currentState.CullBoth = face == Face.FrontAndBack; _currentState.CullBoth = face == Face.FrontAndBack;
// Inline update // 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); SetCullMode(renderCommandEncoder);
SetScissors(renderCommandEncoder); SetScissors(renderCommandEncoder);
return; return;
@ -778,9 +772,8 @@ namespace Ryujinx.Graphics.Metal
_currentState.Winding = frontFace.Convert(); _currentState.Winding = frontFace.Convert();
// Inline update // 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); SetFrontFace(renderCommandEncoder);
return; return;
} }
@ -795,9 +788,8 @@ namespace Ryujinx.Graphics.Metal
_currentState.BackRefValue = backRef; _currentState.BackRefValue = backRef;
// Inline update // 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); SetStencilRefValue(renderCommandEncoder);
} }

View file

@ -277,7 +277,7 @@ namespace Ryujinx.Graphics.Metal
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors); DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
// Save current state // Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags); EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline. // Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index); RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index);
@ -312,7 +312,7 @@ namespace Ryujinx.Graphics.Metal
_pipeline.Draw(4, 1, 0, 0); _pipeline.Draw(4, 1, 0, 0);
// Restore previous state // Restore previous state
_pipeline.SwapState(null, clearFlags); _pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save); _helperShaderState.Restore(save);
} }
@ -330,7 +330,7 @@ namespace Ryujinx.Graphics.Metal
var helperScissors = _helperShaderState.Scissors; var helperScissors = _helperShaderState.Scissors;
// Save current state // Save current state
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags); EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags, false);
// Inherit some state without fully recreating render pipeline. // Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true); RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true);
@ -365,7 +365,7 @@ namespace Ryujinx.Graphics.Metal
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); _pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
// Restore previous state // Restore previous state
_pipeline.SwapState(null, clearFlags); _pipeline.SwapState(null, clearFlags, false);
_helperShaderState.Restore(save); _helperShaderState.Restore(save);
} }

View file

@ -67,7 +67,10 @@ namespace Ryujinx.Graphics.Metal
public void BackgroundContextAction(Action action, bool alwaysBackground = false) 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) public BufferHandle CreateBuffer(int size, BufferAccess access)
@ -221,7 +224,7 @@ namespace Ryujinx.Graphics.Metal
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data) public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{ {
BufferManager.SetData(buffer, offset, data, _pipeline.Cbs, _pipeline.EndRenderPassDelegate); BufferManager.SetData(buffer, offset, data, _pipeline.Cbs);
} }
public void UpdateCounters() public void UpdateCounters()

View file

@ -1,3 +1,4 @@
using Ryujinx.Graphics.GAL;
using System; using System;
using System.Runtime.Versioning; using System.Runtime.Versioning;
@ -44,7 +45,7 @@ namespace Ryujinx.Graphics.Metal
if (srcBuffer.TryIncrementReferenceCount()) 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 else
{ {
@ -58,6 +59,40 @@ namespace Ryujinx.Graphics.Metal
return flushStorage.GetDataStorage(0, size); return flushStorage.GetDataStorage(0, size);
} }
public Span<byte> 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<byte> 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() public void Dispose()
{ {
_flushStorage.Dispose(); _flushStorage.Dispose();

View file

@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Metal
} }
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
class Pipeline : IPipeline, IDisposable class Pipeline : IPipeline, IEncoderFactory, IDisposable
{ {
private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB
@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Metal
private EncoderStateManager _encoderStateManager; private EncoderStateManager _encoderStateManager;
private ulong _byteWeight; private ulong _byteWeight;
public readonly Action EndRenderPassDelegate;
public MTLCommandBuffer CommandBuffer; public MTLCommandBuffer CommandBuffer;
public IndexBufferPattern QuadsToTrisPattern; public IndexBufferPattern QuadsToTrisPattern;
@ -35,8 +34,8 @@ namespace Ryujinx.Graphics.Metal
internal CommandBufferScoped? PreloadCbs { get; private set; } internal CommandBufferScoped? PreloadCbs { get; private set; }
internal CommandBufferScoped Cbs { get; private set; } internal CommandBufferScoped Cbs { get; private set; }
internal MTLCommandEncoder? CurrentEncoder { get; private set; } internal CommandBufferEncoder Encoders => Cbs.Encoders;
internal EncoderType CurrentEncoderType { get; private set; } = EncoderType.None; internal EncoderType CurrentEncoderType => Encoders.CurrentEncoderType;
internal bool RenderPassActive { get; private set; } internal bool RenderPassActive { get; private set; }
public Pipeline(MTLDevice device, MetalRenderer renderer) public Pipeline(MTLDevice device, MetalRenderer renderer)
@ -44,7 +43,7 @@ namespace Ryujinx.Graphics.Metal
_device = device; _device = device;
_renderer = renderer; _renderer = renderer;
EndRenderPassDelegate = EndCurrentPass; renderer.CommandBufferPool.Initialize(this);
CommandBuffer = (Cbs = _renderer.CommandBufferPool.Rent()).CommandBuffer; 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); 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); return _encoderStateManager.SwapState(state, flags);
} }
@ -79,15 +83,7 @@ namespace Ryujinx.Graphics.Metal
public MTLRenderCommandEncoder GetOrCreateRenderEncoder(bool forDraw = false) public MTLRenderCommandEncoder GetOrCreateRenderEncoder(bool forDraw = false)
{ {
MTLRenderCommandEncoder renderCommandEncoder; MTLRenderCommandEncoder renderCommandEncoder = Cbs.Encoders.EnsureRenderEncoder();
if (CurrentEncoder == null || CurrentEncoderType != EncoderType.Render)
{
renderCommandEncoder = BeginRenderPass();
}
else
{
renderCommandEncoder = new MTLRenderCommandEncoder(CurrentEncoder.Value);
}
if (forDraw) if (forDraw)
{ {
@ -99,28 +95,12 @@ namespace Ryujinx.Graphics.Metal
public MTLBlitCommandEncoder GetOrCreateBlitEncoder() public MTLBlitCommandEncoder GetOrCreateBlitEncoder()
{ {
if (CurrentEncoder != null) return Cbs.Encoders.EnsureBlitEncoder();
{
if (CurrentEncoderType == EncoderType.Blit)
{
return new MTLBlitCommandEncoder(CurrentEncoder.Value);
}
}
return BeginBlitPass();
} }
public MTLComputeCommandEncoder GetOrCreateComputeEncoder(bool forDispatch = false) public MTLComputeCommandEncoder GetOrCreateComputeEncoder(bool forDispatch = false)
{ {
MTLComputeCommandEncoder computeCommandEncoder; MTLComputeCommandEncoder computeCommandEncoder = Cbs.Encoders.EnsureComputeEncoder();
if (CurrentEncoder == null || CurrentEncoderType != EncoderType.Compute)
{
computeCommandEncoder = BeginComputePass();
}
else
{
computeCommandEncoder = new MTLComputeCommandEncoder(CurrentEncoder.Value);
}
if (forDispatch) if (forDispatch)
{ {
@ -132,65 +112,17 @@ namespace Ryujinx.Graphics.Metal
public void EndCurrentPass() public void EndCurrentPass()
{ {
if (CurrentEncoder != null) Cbs.Encoders.EndCurrentPass();
{
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;
}
} }
private MTLRenderCommandEncoder BeginRenderPass() public MTLRenderCommandEncoder CreateRenderCommandEncoder()
{ {
EndCurrentPass(); return _encoderStateManager.CreateRenderCommandEncoder();
var renderCommandEncoder = _encoderStateManager.CreateRenderCommandEncoder();
CurrentEncoder = renderCommandEncoder;
CurrentEncoderType = EncoderType.Render;
RenderPassActive = true;
return renderCommandEncoder;
} }
private MTLBlitCommandEncoder BeginBlitPass() public MTLComputeCommandEncoder CreateComputeCommandEncoder()
{ {
EndCurrentPass(); return _encoderStateManager.CreateComputeCommandEncoder();
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;
} }
public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear) public void Present(CAMetalDrawable drawable, Texture src, Extents2D srcRegion, Extents2D dstRegion, bool isLinear)
@ -279,19 +211,15 @@ namespace Ryujinx.Graphics.Metal
{ {
case EncoderType.Render: case EncoderType.Render:
{ {
var renderCommandEncoder = GetOrCreateRenderEncoder();
var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets; var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;
MTLRenderStages stages = MTLRenderStages.RenderStageVertex | MTLRenderStages.RenderStageFragment; MTLRenderStages stages = MTLRenderStages.RenderStageVertex | MTLRenderStages.RenderStageFragment;
renderCommandEncoder.MemoryBarrier(scope, stages, stages); Encoders.RenderEncoder.MemoryBarrier(scope, stages, stages);
break; break;
} }
case EncoderType.Compute: case EncoderType.Compute:
{ {
var computeCommandEncoder = GetOrCreateComputeEncoder();
var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;; var scope = MTLBarrierScope.Buffers | MTLBarrierScope.Textures | MTLBarrierScope.RenderTargets;;
computeCommandEncoder.MemoryBarrier(scope); Encoders.ComputeEncoder.MemoryBarrier(scope);
break; break;
} }
} }
@ -353,7 +281,7 @@ namespace Ryujinx.Graphics.Metal
var srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false); var srcBuffer = _renderer.BufferManager.GetBuffer(src, srcOffset, size, false);
var dstBuffer = _renderer.BufferManager.GetBuffer(dst, dstOffset, size, true); 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) public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
@ -709,9 +637,7 @@ namespace Ryujinx.Graphics.Metal
{ {
if (CurrentEncoderType == EncoderType.Render) if (CurrentEncoderType == EncoderType.Render)
{ {
var renderCommandEncoder = GetOrCreateRenderEncoder(); Encoders.RenderEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment);
renderCommandEncoder.MemoryBarrier(MTLBarrierScope.Textures, MTLRenderStages.RenderStageFragment, MTLRenderStages.RenderStageFragment);
} }
} }

View file

@ -63,15 +63,13 @@ namespace Ryujinx.Graphics.Metal
_resourceAlignment = Constants.MinResourceAlignment; _resourceAlignment = Constants.MinResourceAlignment;
} }
public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data) public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
{ {
bool isRender = cbs != null; bool isRender = cbs != null;
CommandBufferScoped scoped = cbs ?? cbp.Rent(); CommandBufferScoped scoped = cbs ?? cbp.Rent();
// Must push all data to the buffer. If it can't fit, split it up. // Must push all data to the buffer. If it can't fit, split it up.
endRenderPass?.Invoke();
while (data.Length > 0) while (data.Length > 0)
{ {
if (_freeSize < data.Length) if (_freeSize < data.Length)
@ -122,14 +120,14 @@ namespace Ryujinx.Graphics.Metal
_buffer.SetDataUnchecked(offset, data[..capacity]); _buffer.SetDataUnchecked(offset, data[..capacity]);
_buffer.SetDataUnchecked(0, data[capacity..]); _buffer.SetDataUnchecked(0, data[capacity..]);
BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity); BufferHolder.Copy(cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity);
BufferHolder.Copy(_pipeline, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity); BufferHolder.Copy(cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity);
} }
else else
{ {
_buffer.SetDataUnchecked(offset, data); _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); _freeOffset = (offset + data.Length) & (BufferSize - 1);
@ -139,7 +137,7 @@ namespace Ryujinx.Graphics.Metal
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length)); _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length));
} }
public bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data) public bool TryPushData(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
{ {
if (data.Length > BufferSize) if (data.Length > BufferSize)
{ {
@ -156,8 +154,6 @@ namespace Ryujinx.Graphics.Metal
} }
} }
endRenderPass?.Invoke();
PushDataImpl(cbs, dst, dstOffset, data); PushDataImpl(cbs, dst, dstOffset, data);
return true; return true;

View file

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using SharpMetal.Foundation; using SharpMetal.Foundation;
using SharpMetal.Metal; using SharpMetal.Metal;
@ -94,6 +95,13 @@ namespace Ryujinx.Graphics.Metal
public void CopyTo(ITexture destination, int firstLayer, int firstLevel) 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(); var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder();
if (destination is Texture destinationTexture) if (destination is Texture destinationTexture)
@ -202,98 +210,157 @@ namespace Ryujinx.Graphics.Metal
return new Texture(_device, _renderer, _pipeline, info, _mtlTexture, firstLayer, firstLevel); return new Texture(_device, _renderer, _pipeline, info, _mtlTexture, firstLayer, firstLevel);
} }
public PinnedSpan<byte> 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<byte> GetDataFromBuffer(ReadOnlySpan<byte> storage, int size, Span<byte> 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 int mipSizeLevel = GetBufferDataLength(is3D && !singleSlice
{ ? Info.GetMipSize(level)
var mtlBuffer = _device.NewBuffer(length, MTLResourceOptions.ResourceStorageModeShared); : mipSize * dstLayers);
int width = Info.Width; int endOffset = offset + mipSizeLevel;
int height = Info.Height;
int depth = Info.Depth;
int levels = Info.GetLevelsClamped();
int layers = Info.GetLayers();
bool is3D = Info.Target == Target.Texture3D;
int offset = 0; if ((uint)endOffset > (uint)size)
for (int level = 0; level < levels; level++)
{ {
int mipSize = Info.GetMipSize2D(level); break;
int endOffset = offset + mipSize; }
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( blitCommandEncoder.CopyFromTexture(
_mtlTexture, image,
(ulong)layer, (ulong)layer,
(ulong)level, (ulong)level,
new MTLOrigin(), new MTLOrigin { z = (ulong)z },
new MTLSize { width = (ulong)width, height = (ulong)height, depth = is3D ? (ulong)depth : 1 }, new MTLSize { width = (ulong)width, height = (ulong)height, depth = 1 },
mtlBuffer, buffer,
(ulong)offset, (ulong)offset,
(ulong)Info.GetMipStride(level), (ulong)Info.GetMipStride(level),
(ulong)mipSize (ulong)mipSize
); );
offset += mipSize;
} }
else
width = Math.Max(1, width >> 1);
height = Math.Max(1, height >> 1);
if (is3D)
{ {
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<byte>(mtlBuffer.Contents.ToPointer(), (int)length, () => mtlBuffer.Dispose()); if (Info.Target == Target.Texture3D)
{
depth = Math.Max(1, depth >> 1);
}
} }
} }
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
{
int size = 0;
for (int level = 0; level < Info.Levels; level++)
{
size += Info.GetMipSize(level);
}
size = GetBufferDataLength(size);
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size);
return GetDataFromBuffer(result, size, result);
}
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
{
int size = GetBufferDataLength(Info.GetMipSize(level));
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
return GetDataFromBuffer(result, size, result);
}
public PinnedSpan<byte> GetData()
{
BackgroundResource resources = _renderer.BackgroundResources.Get();
if (_renderer.CommandBufferPool.OwnedByCurrentThread)
{
_renderer.FlushAllCommands();
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_renderer.CommandBufferPool, resources.GetFlushBuffer()));
}
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer()));
}
public PinnedSpan<byte> GetData(int layer, int level) public PinnedSpan<byte> GetData(int layer, int level)
{ {
var blitCommandEncoder = _pipeline.GetOrCreateBlitEncoder(); BackgroundResource resources = _renderer.BackgroundResources.Get();
ulong bytesPerRow = (ulong)Info.GetMipStride(level); if (_renderer.CommandBufferPool.OwnedByCurrentThread)
ulong length = bytesPerRow * (ulong)Info.Height;
ulong bytesPerImage = 0;
if (_mtlTexture.TextureType == MTLTextureType.Type3D)
{ {
bytesPerImage = length; _renderer.FlushAllCommands();
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_renderer.CommandBufferPool, resources.GetFlushBuffer(), layer, level));
} }
unsafe return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level));
{
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<byte>(mtlBuffer.Contents.ToPointer(), (int)length, () => mtlBuffer.Dispose());
}
} }
public void SetData(IMemoryOwner<byte> data) public void SetData(IMemoryOwner<byte> data)