0
0
Fork 0
mirror of https://github.com/GreemDev/Ryujinx.git synced 2024-12-22 23:55:47 +00:00

State and cache optimization (#27)

* WIP pipeline/depth state cache rework

* Fix some issues

* Fix some more default values

* Reduce allocations for state changes

* fix helpershader stuff

* explanation comment

* fix depth bias
This commit is contained in:
riperiperi 2024-06-28 21:14:53 +01:00 committed by Isaac Marovitz
parent 9d26aa8d06
commit e02df72323
14 changed files with 1142 additions and 565 deletions

View file

@ -1,36 +0,0 @@
using Ryujinx.Common.Logging;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
class ComputePipelineCache : StateCache<MTLComputePipelineState, MTLFunction, MTLFunction>
{
private readonly MTLDevice _device;
public ComputePipelineCache(MTLDevice device)
{
_device = device;
}
protected override MTLFunction GetHash(MTLFunction function)
{
return function;
}
protected override MTLComputePipelineState CreateValue(MTLFunction function)
{
var error = new NSError(IntPtr.Zero);
var pipelineState = _device.NewComputePipelineState(function, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Compute Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
return pipelineState;
}
}
}

View file

@ -1,28 +1,11 @@
using Ryujinx.Graphics.Metal.State;
using SharpMetal.Metal;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct DepthStencilHash
{
public struct StencilHash
{
public MTLStencilOperation StencilFailureOperation;
public MTLStencilOperation DepthFailureOperation;
public MTLStencilOperation DepthStencilPassOperation;
public MTLCompareFunction StencilCompareFunction;
public uint ReadMask;
public uint WriteMask;
}
public StencilHash FrontFace;
public StencilHash BackFace;
public MTLCompareFunction DepthCompareFunction;
public bool DepthWriteEnabled;
}
[SupportedOSPlatform("macos")]
class DepthStencilCache : StateCache<MTLDepthStencilState, MTLDepthStencilDescriptor, DepthStencilHash>
class DepthStencilCache : StateCache<MTLDepthStencilState, DepthStencilUid, DepthStencilUid>
{
private readonly MTLDevice _device;
@ -31,41 +14,55 @@ namespace Ryujinx.Graphics.Metal
_device = device;
}
protected override DepthStencilHash GetHash(MTLDepthStencilDescriptor descriptor)
protected override DepthStencilUid GetHash(DepthStencilUid descriptor)
{
var hash = new DepthStencilHash
return descriptor;
}
protected override MTLDepthStencilState CreateValue(DepthStencilUid descriptor)
{
// Create descriptors
ref StencilUid frontUid = ref descriptor.FrontFace;
using var frontFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = frontUid.StencilFailureOperation,
DepthFailureOperation = frontUid.DepthFailureOperation,
DepthStencilPassOperation = frontUid.DepthStencilPassOperation,
StencilCompareFunction = frontUid.StencilCompareFunction,
ReadMask = frontUid.ReadMask,
WriteMask = frontUid.WriteMask
};
ref StencilUid backUid = ref descriptor.BackFace;
using var backFaceStencil = new MTLStencilDescriptor
{
StencilFailureOperation = backUid.StencilFailureOperation,
DepthFailureOperation = backUid.DepthFailureOperation,
DepthStencilPassOperation = backUid.DepthStencilPassOperation,
StencilCompareFunction = backUid.StencilCompareFunction,
ReadMask = backUid.ReadMask,
WriteMask = backUid.WriteMask
};
var mtlDescriptor = new MTLDepthStencilDescriptor
{
// Front face
FrontFace = new DepthStencilHash.StencilHash
{
StencilFailureOperation = descriptor.FrontFaceStencil.StencilFailureOperation,
DepthFailureOperation = descriptor.FrontFaceStencil.DepthFailureOperation,
DepthStencilPassOperation = descriptor.FrontFaceStencil.DepthStencilPassOperation,
StencilCompareFunction = descriptor.FrontFaceStencil.StencilCompareFunction,
ReadMask = descriptor.FrontFaceStencil.ReadMask,
WriteMask = descriptor.FrontFaceStencil.WriteMask
},
// Back face
BackFace = new DepthStencilHash.StencilHash
{
StencilFailureOperation = descriptor.BackFaceStencil.StencilFailureOperation,
DepthFailureOperation = descriptor.BackFaceStencil.DepthFailureOperation,
DepthStencilPassOperation = descriptor.BackFaceStencil.DepthStencilPassOperation,
StencilCompareFunction = descriptor.BackFaceStencil.StencilCompareFunction,
ReadMask = descriptor.BackFaceStencil.ReadMask,
WriteMask = descriptor.BackFaceStencil.WriteMask
},
// Depth
DepthCompareFunction = descriptor.DepthCompareFunction,
DepthWriteEnabled = descriptor.DepthWriteEnabled
};
return hash;
}
if (descriptor.StencilTestEnabled)
{
mtlDescriptor.BackFaceStencil = backFaceStencil;
mtlDescriptor.FrontFaceStencil = frontFaceStencil;
}
protected override MTLDepthStencilState CreateValue(MTLDepthStencilDescriptor descriptor)
{
return _device.NewDepthStencilState(descriptor);
using (mtlDescriptor)
{
return _device.NewDepthStencilState(mtlDescriptor);
}
}
}
}

View file

@ -1,4 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.State;
using SharpMetal.Metal;
using System;
using System.Linq;
@ -48,12 +50,29 @@ namespace Ryujinx.Graphics.Metal
}
}
[SupportedOSPlatform("macos")]
struct EncoderState
struct PredrawState
{
public MTLFunction? VertexFunction = null;
public MTLFunction? FragmentFunction = null;
public MTLFunction? ComputeFunction = null;
public MTLCullMode CullMode;
public DepthStencilUid DepthStencilUid;
public PrimitiveTopology Topology;
public MTLViewport[] Viewports;
}
struct RenderTargetCopy
{
public MTLScissorRect[] Scissors;
public Texture DepthStencil;
public Texture[] RenderTargets;
}
[SupportedOSPlatform("macos")]
class EncoderState
{
public Program RenderProgram = null;
public Program ComputeProgram = null;
public PipelineState Pipeline;
public DepthStencilUid DepthStencilUid;
public TextureBase[] FragmentTextures = new TextureBase[Constants.MaxTexturesPerStage];
public MTLSamplerState[] FragmentSamplers = new MTLSamplerState[Constants.MaxTexturesPerStage];
@ -71,21 +90,14 @@ namespace Ryujinx.Graphics.Metal
public MTLIndexType IndexType = MTLIndexType.UInt16;
public ulong IndexBufferOffset = 0;
public MTLDepthStencilState? DepthStencilState = null;
public MTLDepthClipMode DepthClipMode = MTLDepthClipMode.Clip;
public MTLCompareFunction DepthCompareFunction = MTLCompareFunction.Always;
public bool DepthWriteEnabled = false;
public float DepthBias;
public float SlopeScale;
public float Clamp;
public MTLStencilDescriptor BackFaceStencil = new();
public MTLStencilDescriptor FrontFaceStencil = new();
public int BackRefValue = 0;
public int FrontRefValue = 0;
public bool StencilTestEnabled = false;
public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
public MTLCullMode CullMode = MTLCullMode.None;
@ -102,8 +114,7 @@ namespace Ryujinx.Graphics.Metal
public ITexture[] PreMaskRenderTargets;
public bool FramebufferUsingColorWriteMask;
public MTLColorWriteMask[] RenderTargetMasks = Enumerable.Repeat(MTLColorWriteMask.All, Constants.MaxColorAttachments).ToArray();
public BlendDescriptor?[] BlendDescriptors = new BlendDescriptor?[Constants.MaxColorAttachments];
public Array8<ColorBlendStateUid> StoredBlend;
public ColorF BlendColor = new();
public VertexBufferDescriptor[] VertexBuffers = [];
@ -115,25 +126,52 @@ namespace Ryujinx.Graphics.Metal
// Only to be used for present
public bool ClearLoadAction = false;
public EncoderState() { }
public readonly EncoderState Clone()
public EncoderState()
{
// Certain state (like viewport and scissor) doesn't need to be cloned, as it is always reacreated when assigned to
EncoderState clone = this;
clone.FragmentTextures = (TextureBase[])FragmentTextures.Clone();
clone.FragmentSamplers = (MTLSamplerState[])FragmentSamplers.Clone();
clone.VertexTextures = (TextureBase[])VertexTextures.Clone();
clone.VertexSamplers = (MTLSamplerState[])VertexSamplers.Clone();
clone.ComputeTextures = (TextureBase[])ComputeTextures.Clone();
clone.ComputeSamplers = (MTLSamplerState[])ComputeSamplers.Clone();
clone.BlendDescriptors = (BlendDescriptor?[])BlendDescriptors.Clone();
clone.VertexBuffers = (VertexBufferDescriptor[])VertexBuffers.Clone();
clone.VertexAttribs = (VertexAttribDescriptor[])VertexAttribs.Clone();
clone.UniformBuffers = (BufferRef[])UniformBuffers.Clone();
clone.StorageBuffers = (BufferRef[])StorageBuffers.Clone();
Pipeline.Initialize();
DepthStencilUid.DepthCompareFunction = MTLCompareFunction.Always;
}
return clone;
public RenderTargetCopy InheritForClear(EncoderState other, bool depth, int singleIndex = -1)
{
// Inherit render target related information without causing a render encoder split.
var oldState = new RenderTargetCopy
{
Scissors = other.Scissors,
RenderTargets = other.RenderTargets,
DepthStencil = other.DepthStencil
};
Scissors = other.Scissors;
RenderTargets = other.RenderTargets;
DepthStencil = other.DepthStencil;
Pipeline.ColorBlendAttachmentStateCount = other.Pipeline.ColorBlendAttachmentStateCount;
Pipeline.Internal.ColorBlendState = other.Pipeline.Internal.ColorBlendState;
Pipeline.DepthStencilFormat = other.Pipeline.DepthStencilFormat;
ref var blendStates = ref Pipeline.Internal.ColorBlendState;
// Mask out irrelevant attachments.
for (int i = 0; i < blendStates.Length; i++)
{
if (depth || (singleIndex != -1 && singleIndex != i))
{
blendStates[i].WriteMask = MTLColorWriteMask.None;
}
}
return oldState;
}
public void Restore(RenderTargetCopy copy)
{
Scissors = copy.Scissors;
RenderTargets = copy.RenderTargets;
DepthStencil = copy.DepthStencil;
Pipeline.Internal.ResetColorState();
}
}
}

View file

@ -1,9 +1,10 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.State;
using Ryujinx.Graphics.Shader;
using SharpMetal.Metal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BufferAssignment = Ryujinx.Graphics.GAL.BufferAssignment;
@ -17,12 +18,10 @@ namespace Ryujinx.Graphics.Metal
private readonly Pipeline _pipeline;
private readonly BufferManager _bufferManager;
private readonly RenderPipelineCache _renderPipelineCache;
private readonly ComputePipelineCache _computePipelineCache;
private readonly DepthStencilCache _depthStencilCache;
private EncoderState _currentState = new();
private readonly Stack<EncoderState> _backStates = [];
private readonly EncoderState _mainState = new();
private EncoderState _currentState;
public readonly Auto<DisposableBuffer> IndexBuffer => _currentState.IndexBuffer;
public readonly MTLIndexType IndexType => _currentState.IndexType;
@ -41,9 +40,8 @@ namespace Ryujinx.Graphics.Metal
_pipeline = pipeline;
_bufferManager = bufferManager;
_renderPipelineCache = new(device);
_computePipelineCache = new(device);
_depthStencilCache = new(device);
_currentState = _mainState;
// Zero buffer
byte[] zeros = new byte[ZeroBufferSize];
@ -56,39 +54,38 @@ namespace Ryujinx.Graphics.Metal
public void Dispose()
{
// State
_currentState.FrontFaceStencil.Dispose();
_currentState.BackFaceStencil.Dispose();
_renderPipelineCache.Dispose();
_computePipelineCache.Dispose();
_depthStencilCache.Dispose();
}
public void SaveState()
public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All)
{
_backStates.Push(_currentState);
_currentState = _currentState.Clone();
_currentState = state ?? _mainState;
_currentState.Dirty |= flags;
return _mainState;
}
public void SaveAndResetState()
public PredrawState SavePredrawState()
{
_backStates.Push(_currentState);
_currentState = new();
return new PredrawState
{
CullMode = _currentState.CullMode,
DepthStencilUid = _currentState.DepthStencilUid,
Topology = _currentState.Topology,
Viewports = _currentState.Viewports.ToArray(),
};
}
public void RestoreState()
public void RestorePredrawState(PredrawState state)
{
if (_backStates.Count > 0)
{
_currentState = _backStates.Pop();
_currentState.CullMode = state.CullMode;
_currentState.DepthStencilUid = state.DepthStencilUid;
_currentState.Topology = state.Topology;
_currentState.Viewports = state.Viewports;
// Mark the other state as dirty
_currentState.Dirty |= DirtyFlags.All;
}
else
{
Logger.Error?.Print(LogClass.Gpu, "No state to restore");
}
_currentState.Dirty |= DirtyFlags.CullMode | DirtyFlags.DepthStencil | DirtyFlags.Viewports;
}
public void SetClearLoadAction(bool clear)
@ -267,106 +264,25 @@ namespace Ryujinx.Graphics.Metal
private void SetRenderPipelineState(MTLRenderCommandEncoder renderCommandEncoder)
{
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
MTLRenderPipelineState pipelineState = _currentState.Pipeline.CreateRenderPipeline(_device, _currentState.RenderProgram);
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
if (_currentState.RenderTargets[i] != null)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
pipelineAttachment.PixelFormat = _currentState.RenderTargets[i].GetHandle().PixelFormat;
pipelineAttachment.SourceAlphaBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationAlphaBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationRGBBlendFactor = MTLBlendFactor.OneMinusSourceAlpha;
pipelineAttachment.WriteMask = _currentState.RenderTargetMasks[i];
renderCommandEncoder.SetRenderPipelineState(pipelineState);
if (_currentState.BlendDescriptors[i] != null)
{
var blendDescriptor = _currentState.BlendDescriptors[i].Value;
pipelineAttachment.SetBlendingEnabled(blendDescriptor.Enable);
pipelineAttachment.AlphaBlendOperation = blendDescriptor.AlphaOp.Convert();
pipelineAttachment.RgbBlendOperation = blendDescriptor.ColorOp.Convert();
pipelineAttachment.SourceAlphaBlendFactor = blendDescriptor.AlphaSrcFactor.Convert();
pipelineAttachment.DestinationAlphaBlendFactor = blendDescriptor.AlphaDstFactor.Convert();
pipelineAttachment.SourceRGBBlendFactor = blendDescriptor.ColorSrcFactor.Convert();
pipelineAttachment.DestinationRGBBlendFactor = blendDescriptor.ColorDstFactor.Convert();
}
}
}
if (_currentState.DepthStencil != null)
{
switch (_currentState.DepthStencil.GetHandle().PixelFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = _currentState.DepthStencil.GetHandle().PixelFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {_currentState.DepthStencil.GetHandle().PixelFormat}!");
break;
}
}
var vertexDescriptor = BuildVertexDescriptor(_currentState.VertexBuffers, _currentState.VertexAttribs);
renderPipelineDescriptor.VertexDescriptor = vertexDescriptor;
try
{
if (_currentState.VertexFunction != null)
{
renderPipelineDescriptor.VertexFunction = _currentState.VertexFunction.Value;
}
else
{
return;
}
if (_currentState.FragmentFunction != null)
{
renderPipelineDescriptor.FragmentFunction = _currentState.FragmentFunction.Value;
}
var pipelineState = _renderPipelineCache.GetOrCreate(renderPipelineDescriptor);
renderCommandEncoder.SetRenderPipelineState(pipelineState);
renderCommandEncoder.SetBlendColor(
_currentState.BlendColor.Red,
_currentState.BlendColor.Green,
_currentState.BlendColor.Blue,
_currentState.BlendColor.Alpha);
}
finally
{
// Cleanup
renderPipelineDescriptor.Dispose();
vertexDescriptor.Dispose();
}
renderCommandEncoder.SetBlendColor(
_currentState.BlendColor.Red,
_currentState.BlendColor.Green,
_currentState.BlendColor.Blue,
_currentState.BlendColor.Alpha);
}
private void SetComputePipelineState(MTLComputeCommandEncoder computeCommandEncoder)
{
if (_currentState.ComputeFunction == null)
if (_currentState.ComputeProgram == null)
{
return;
}
var pipelineState = _computePipelineCache.GetOrCreate(_currentState.ComputeFunction.Value);
var pipelineState = PipelineState.CreateComputePipeline(_device, _currentState.ComputeProgram);
computeCommandEncoder.SetComputePipelineState(pipelineState);
}
@ -414,14 +330,13 @@ namespace Ryujinx.Graphics.Metal
if (prg.VertexFunction != IntPtr.Zero)
{
_currentState.VertexFunction = prg.VertexFunction;
_currentState.FragmentFunction = prg.FragmentFunction;
_currentState.RenderProgram = prg;
_currentState.Dirty |= DirtyFlags.RenderPipeline;
}
if (prg.ComputeFunction != IntPtr.Zero)
else if (prg.ComputeFunction != IntPtr.Zero)
{
_currentState.ComputeFunction = prg.ComputeFunction;
_currentState.ComputeProgram = prg;
_currentState.Dirty |= DirtyFlags.ComputePipeline;
}
@ -435,7 +350,7 @@ namespace Ryujinx.Graphics.Metal
public void UpdateRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
{
_currentState.RenderTargetMasks = new MTLColorWriteMask[Constants.MaxColorAttachments];
ref var blendState = ref _currentState.Pipeline.Internal.ColorBlendState;
for (int i = 0; i < componentMask.Length; i++)
{
@ -451,7 +366,25 @@ namespace Ryujinx.Graphics.Metal
mask |= blue ? MTLColorWriteMask.Blue : 0;
mask |= alpha ? MTLColorWriteMask.Alpha : 0;
_currentState.RenderTargetMasks[i] = mask;
ref ColorBlendStateUid mtlBlend = ref blendState[i];
// When color write mask is 0, remove all blend state to help the pipeline cache.
// Restore it when the mask becomes non-zero.
if (mtlBlend.WriteMask != mask)
{
if (mask == 0)
{
_currentState.StoredBlend[i] = mtlBlend;
mtlBlend = new ColorBlendStateUid();
}
else if (mtlBlend.WriteMask == 0)
{
mtlBlend = _currentState.StoredBlend[i];
}
}
blendState[i].WriteMask = mask;
}
if (_currentState.FramebufferUsingColorWriteMask)
@ -478,6 +411,11 @@ namespace Ryujinx.Graphics.Metal
// Look for textures that are masked out.
ref PipelineState pipeline = ref _currentState.Pipeline;
ref var blendState = ref pipeline.Internal.ColorBlendState;
pipeline.ColorBlendAttachmentStateCount = (uint)colors.Length;
for (int i = 0; i < colors.Length; i++)
{
if (colors[i] == null)
@ -485,7 +423,7 @@ namespace Ryujinx.Graphics.Metal
continue;
}
ref var mtlMask = ref _currentState.RenderTargetMasks[i];
var mtlMask = blendState[i].WriteMask;
for (int j = 0; j < i; j++)
{
@ -495,7 +433,7 @@ namespace Ryujinx.Graphics.Metal
{
// Prefer the binding with no write mask.
ref var mtlMask2 = ref _currentState.RenderTargetMasks[j];
var mtlMask2 = blendState[j].WriteMask;
if (mtlMask == 0)
{
@ -517,18 +455,23 @@ namespace Ryujinx.Graphics.Metal
{
if (colors[i] is not Texture tex)
{
blendState[i].PixelFormat = MTLPixelFormat.Invalid;
continue;
}
blendState[i].PixelFormat = tex.GetHandle().PixelFormat; // TODO: cache this
_currentState.RenderTargets[i] = tex;
}
if (depthStencil is Texture depthTexture)
{
pipeline.DepthStencilFormat = depthTexture.GetHandle().PixelFormat; // TODO: cache this
_currentState.DepthStencil = depthTexture;
}
else if (depthStencil == null)
{
pipeline.DepthStencilFormat = MTLPixelFormat.Invalid;
_currentState.DepthStencil = null;
}
@ -555,13 +498,32 @@ namespace Ryujinx.Graphics.Metal
{
_currentState.VertexAttribs = vertexAttribs.ToArray();
// Update the buffers on the pipeline
UpdatePipelineVertexState(_currentState.VertexBuffers, _currentState.VertexAttribs);
// Mark dirty
_currentState.Dirty |= DirtyFlags.RenderPipeline;
}
public void UpdateBlendDescriptors(int index, BlendDescriptor blend)
{
_currentState.BlendDescriptors[index] = blend;
ref var blendState = ref _currentState.Pipeline.Internal.ColorBlendState[index];
blendState.Enable = blend.Enable;
blendState.AlphaBlendOperation = blend.AlphaOp.Convert();
blendState.RgbBlendOperation = blend.ColorOp.Convert();
blendState.SourceAlphaBlendFactor = blend.AlphaSrcFactor.Convert();
blendState.DestinationAlphaBlendFactor = blend.AlphaDstFactor.Convert();
blendState.SourceRGBBlendFactor = blend.ColorSrcFactor.Convert();
blendState.DestinationRGBBlendFactor = blend.ColorDstFactor.Convert();
if (blendState.WriteMask == 0)
{
_currentState.StoredBlend[index] = blendState;
blendState = new ColorBlendStateUid();
}
_currentState.BlendColor = blend.BlendConstant;
// Mark dirty
@ -571,7 +533,9 @@ namespace Ryujinx.Graphics.Metal
// Inlineable
public void UpdateStencilState(StencilTestDescriptor stencilTest)
{
_currentState.FrontFaceStencil = new MTLStencilDescriptor
ref DepthStencilUid uid = ref _currentState.DepthStencilUid;
uid.FrontFace = new StencilUid
{
StencilFailureOperation = stencilTest.FrontSFail.Convert(),
DepthFailureOperation = stencilTest.FrontDpFail.Convert(),
@ -581,7 +545,7 @@ namespace Ryujinx.Graphics.Metal
WriteMask = (uint)stencilTest.FrontMask
};
_currentState.BackFaceStencil = new MTLStencilDescriptor
uid.BackFace = new StencilUid
{
StencilFailureOperation = stencilTest.BackSFail.Convert(),
DepthFailureOperation = stencilTest.BackDpFail.Convert(),
@ -591,55 +555,23 @@ namespace Ryujinx.Graphics.Metal
WriteMask = (uint)stencilTest.BackMask
};
_currentState.StencilTestEnabled = stencilTest.TestEnable;
var descriptor = new MTLDepthStencilDescriptor
{
DepthCompareFunction = _currentState.DepthCompareFunction,
DepthWriteEnabled = _currentState.DepthWriteEnabled
};
if (_currentState.StencilTestEnabled)
{
descriptor.BackFaceStencil = _currentState.BackFaceStencil;
descriptor.FrontFaceStencil = _currentState.FrontFaceStencil;
}
_currentState.DepthStencilState = _device.NewDepthStencilState(descriptor);
uid.StencilTestEnabled = stencilTest.TestEnable;
UpdateStencilRefValue(stencilTest.FrontFuncRef, stencilTest.BackFuncRef);
// Mark dirty
_currentState.Dirty |= DirtyFlags.DepthStencil;
// Cleanup
descriptor.Dispose();
}
public void UpdateDepthState(DepthTestDescriptor depthTest)
{
_currentState.DepthCompareFunction = depthTest.TestEnable ? depthTest.Func.Convert() : MTLCompareFunction.Always;
_currentState.DepthWriteEnabled = depthTest.TestEnable && depthTest.WriteEnable;
ref DepthStencilUid uid = ref _currentState.DepthStencilUid;
var descriptor = new MTLDepthStencilDescriptor
{
DepthCompareFunction = _currentState.DepthCompareFunction,
DepthWriteEnabled = _currentState.DepthWriteEnabled
};
if (_currentState.StencilTestEnabled)
{
descriptor.BackFaceStencil = _currentState.BackFaceStencil;
descriptor.FrontFaceStencil = _currentState.FrontFaceStencil;
}
_currentState.DepthStencilState = _device.NewDepthStencilState(descriptor);
uid.DepthCompareFunction = depthTest.TestEnable ? depthTest.Func.Convert() : MTLCompareFunction.Always;
uid.DepthWriteEnabled = depthTest.TestEnable && depthTest.WriteEnable;
// Mark dirty
_currentState.Dirty |= DirtyFlags.DepthStencil;
// Cleanup
descriptor.Dispose();
}
// Inlineable
@ -751,6 +683,9 @@ namespace Ryujinx.Graphics.Metal
{
_currentState.VertexBuffers = vertexBuffers.ToArray();
// Update the buffers on the pipeline
UpdatePipelineVertexState(_currentState.VertexBuffers, _currentState.VertexAttribs);
// Inline update
if (_pipeline.CurrentEncoderType == EncoderType.Render && _pipeline.CurrentEncoder != null)
{
@ -925,10 +860,9 @@ namespace Ryujinx.Graphics.Metal
private readonly void SetDepthStencilState(MTLRenderCommandEncoder renderCommandEncoder)
{
if (_currentState.DepthStencilState != null)
{
renderCommandEncoder.SetDepthStencilState(_currentState.DepthStencilState.Value);
}
MTLDepthStencilState state = _depthStencilCache.GetOrCreate(_currentState.DepthStencilUid);
renderCommandEncoder.SetDepthStencilState(state);
}
private readonly void SetDepthClamp(MTLRenderCommandEncoder renderCommandEncoder)
@ -973,16 +907,17 @@ namespace Ryujinx.Graphics.Metal
}
}
private readonly MTLVertexDescriptor BuildVertexDescriptor(VertexBufferDescriptor[] bufferDescriptors, VertexAttribDescriptor[] attribDescriptors)
private void UpdatePipelineVertexState(VertexBufferDescriptor[] bufferDescriptors, VertexAttribDescriptor[] attribDescriptors)
{
var vertexDescriptor = new MTLVertexDescriptor();
ref PipelineState pipeline = ref _currentState.Pipeline;
uint indexMask = 0;
for (int i = 0; i < attribDescriptors.Length; i++)
{
ref var attrib = ref pipeline.Internal.VertexAttributes[i];
if (attribDescriptors[i].IsZero)
{
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = attribDescriptors[i].Format.Convert();
indexMask |= 1u << (int)Constants.ZeroBufferIndex;
attrib.BufferIndex = Constants.ZeroBufferIndex;
@ -990,7 +925,6 @@ namespace Ryujinx.Graphics.Metal
}
else
{
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = attribDescriptors[i].Format.Convert();
indexMask |= 1u << attribDescriptors[i].BufferIndex;
attrib.BufferIndex = (ulong)attribDescriptors[i].BufferIndex;
@ -1000,11 +934,11 @@ namespace Ryujinx.Graphics.Metal
for (int i = 0; i < bufferDescriptors.Length; i++)
{
var layout = vertexDescriptor.Layouts.Object((ulong)i);
ref var layout = ref pipeline.Internal.VertexBindings[i];
if ((indexMask & (1u << i)) != 0)
{
layout.Stride = (ulong)bufferDescriptors[i].Stride;
layout.Stride = (uint)bufferDescriptors[i].Stride;
if (layout.Stride == 0)
{
@ -1017,7 +951,7 @@ namespace Ryujinx.Graphics.Metal
if (bufferDescriptors[i].Divisor > 0)
{
layout.StepFunction = MTLVertexStepFunction.PerInstance;
layout.StepRate = (ulong)bufferDescriptors[i].Divisor;
layout.StepRate = (uint)bufferDescriptors[i].Divisor;
}
else
{
@ -1028,20 +962,21 @@ namespace Ryujinx.Graphics.Metal
}
else
{
layout.Stride = 0;
layout = new();
}
}
// Zero buffer
if ((indexMask & (1u << (int)Constants.ZeroBufferIndex)) != 0)
{
var layout = vertexDescriptor.Layouts.Object(Constants.ZeroBufferIndex);
ref var layout = ref pipeline.Internal.VertexBindings[(int)Constants.ZeroBufferIndex];
layout.Stride = 1;
layout.StepFunction = MTLVertexStepFunction.Constant;
layout.StepRate = 0;
}
return vertexDescriptor;
pipeline.VertexAttributeDescriptionsCount = (uint)attribDescriptors.Length;
pipeline.VertexBindingDescriptionsCount = Constants.ZeroBufferIndex + 1; // TODO: move this out?
}
private void SetVertexBuffers(MTLRenderCommandEncoder renderCommandEncoder, VertexBufferDescriptor[] bufferDescriptors)

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Metal
{
interface IRefEquatable<T>
{
bool Equals(ref T other);
}
class HashTableSlim<TKey, TValue> where TKey : IRefEquatable<TKey>
{
private const int TotalBuckets = 16; // Must be power of 2
private const int TotalBucketsMask = TotalBuckets - 1;
private struct Entry
{
public int Hash;
public TKey Key;
public TValue Value;
}
private struct Bucket
{
public int Length;
public Entry[] Entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<Entry> AsSpan()
{
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
}
}
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
public IEnumerable<TKey> Keys
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Key;
}
}
}
}
public IEnumerable<TValue> Values
{
get
{
foreach (Bucket bucket in _hashTable)
{
for (int i = 0; i < bucket.Length; i++)
{
yield return bucket.Entries[i].Value;
}
}
}
}
public void Add(ref TKey key, TValue value)
{
var entry = new Entry
{
Hash = key.GetHashCode(),
Key = key,
Value = value,
};
int hashCode = key.GetHashCode();
int bucketIndex = hashCode & TotalBucketsMask;
ref var bucket = ref _hashTable[bucketIndex];
if (bucket.Entries != null)
{
int index = bucket.Length;
if (index >= bucket.Entries.Length)
{
Array.Resize(ref bucket.Entries, index + 1);
}
bucket.Entries[index] = entry;
}
else
{
bucket.Entries = new[]
{
entry,
};
}
bucket.Length++;
}
public bool Remove(ref TKey key)
{
int hashCode = key.GetHashCode();
ref var bucket = ref _hashTable[hashCode & TotalBucketsMask];
var entries = bucket.AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
entries[(i + 1)..].CopyTo(entries[i..]);
bucket.Length--;
return true;
}
}
return false;
}
public bool TryGetValue(ref TKey key, out TValue value)
{
int hashCode = key.GetHashCode();
var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
}
}

View file

@ -25,6 +25,8 @@ namespace Ryujinx.Graphics.Metal
private readonly IProgram _programDepthStencilClear;
private readonly IProgram _programStrideChange;
private readonly EncoderState _helperShaderState = new();
public HelperShader(MTLDevice device, MetalRenderer renderer, Pipeline pipeline)
{
_device = device;
@ -80,8 +82,7 @@ namespace Ryujinx.Graphics.Metal
bool linearFilter,
bool clear = false)
{
// Save current state
_pipeline.SaveAndResetState();
_pipeline.SwapState(_helperShaderState);
const int RegionBufferSize = 16;
@ -141,8 +142,14 @@ namespace Ryujinx.Graphics.Metal
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0);
// Cleanup
if (clear)
{
_pipeline.SetClearLoadAction(false);
}
// Restore previous state
_pipeline.RestoreState();
_pipeline.SwapState(null);
}
public unsafe void DrawTexture(
@ -152,7 +159,11 @@ namespace Ryujinx.Graphics.Metal
Extents2DF dstRegion)
{
// Save current state
_pipeline.SaveState();
var state = _pipeline.SavePredrawState();
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetStencilTest(new StencilTestDescriptor());
_pipeline.SetDepthTest(new DepthTestDescriptor());
const int RegionBufferSize = 16;
@ -204,7 +215,7 @@ namespace Ryujinx.Graphics.Metal
_renderer.BufferManager.Delete(bufferHandle);
// Restore previous state
_pipeline.RestoreState();
_pipeline.RestorePredrawState(state);
}
public void ConvertI8ToI16(CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
@ -229,7 +240,7 @@ namespace Ryujinx.Graphics.Metal
const int ParamsBufferSize = 16;
// Save current state
_pipeline.SaveAndResetState();
_pipeline.SwapState(_helperShaderState);
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
@ -252,7 +263,7 @@ namespace Ryujinx.Graphics.Metal
_pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1, 64, 1, 1);
// Restore previous state
_pipeline.RestoreState();
_pipeline.SwapState(null);
}
public unsafe void ClearColor(
@ -262,8 +273,14 @@ namespace Ryujinx.Graphics.Metal
int dstWidth,
int dstHeight)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
// Save current state
_pipeline.SaveState();
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, false, index);
const int ClearColorBufferSize = 16;
@ -286,7 +303,7 @@ namespace Ryujinx.Graphics.Metal
1f);
_pipeline.SetProgram(_programsColorClear[index]);
_pipeline.SetBlendState(index, new BlendDescriptor(false, new ColorF(0f, 0f, 0f, 1f), BlendOp.Add, BlendFactor.One, BlendFactor.Zero, BlendOp.Add, BlendFactor.One, BlendFactor.Zero));
_pipeline.SetBlendState(index, new BlendDescriptor());
_pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetRenderTargetColorMasks([componentMask]);
@ -295,7 +312,9 @@ namespace Ryujinx.Graphics.Metal
_pipeline.Draw(4, 1, 0, 0);
// Restore previous state
_pipeline.RestoreState();
_pipeline.SwapState(null, clearFlags);
_helperShaderState.Restore(save);
}
public unsafe void ClearDepthStencil(
@ -306,8 +325,15 @@ namespace Ryujinx.Graphics.Metal
int dstWidth,
int dstHeight)
{
// Keep original scissor
DirtyFlags clearFlags = DirtyFlags.All & (~DirtyFlags.Scissors);
var helperScissors = _helperShaderState.Scissors;
// Save current state
_pipeline.SaveState();
EncoderState originalState = _pipeline.SwapState(_helperShaderState, clearFlags);
// Inherit some state without fully recreating render pipeline.
RenderTargetCopy save = _helperShaderState.InheritForClear(originalState, true);
const int ClearDepthBufferSize = 16;
@ -334,8 +360,14 @@ namespace Ryujinx.Graphics.Metal
_pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask));
_pipeline.Draw(4, 1, 0, 0);
// Cleanup
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetStencilTest(CreateStencilTestDescriptor(false));
// Restore previous state
_pipeline.RestoreState();
_pipeline.SwapState(null, clearFlags);
_helperShaderState.Restore(save);
}
private static StencilTestDescriptor CreateStencilTestDescriptor(

View file

@ -57,19 +57,19 @@ namespace Ryujinx.Graphics.Metal
TriFanToTrisPattern = new IndexBufferPattern(_renderer, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
}
public void SaveState()
public EncoderState SwapState(EncoderState state, DirtyFlags flags = DirtyFlags.All)
{
_encoderStateManager.SaveState();
return _encoderStateManager.SwapState(state, flags);
}
public void SaveAndResetState()
public PredrawState SavePredrawState()
{
_encoderStateManager.SaveAndResetState();
return _encoderStateManager.SavePredrawState();
}
public void RestoreState()
public void RestorePredrawState(PredrawState state)
{
_encoderStateManager.RestoreState();
_encoderStateManager.RestorePredrawState(state);
}
public void SetClearLoadAction(bool clear)
@ -240,8 +240,6 @@ namespace Ryujinx.Graphics.Metal
public void FlushCommandsImpl()
{
SaveState();
EndCurrentPass();
_byteWeight = 0;
@ -254,8 +252,6 @@ namespace Ryujinx.Graphics.Metal
CommandBuffer = (Cbs = _renderer.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
_renderer.RegisterFlush();
RestoreState();
}
public void BlitColor(
@ -511,7 +507,14 @@ namespace Ryujinx.Graphics.Metal
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
{
_encoderStateManager.UpdateDepthBias(units, factor, clamp);
if (enables == 0)
{
_encoderStateManager.UpdateDepthBias(0, 0, 0);
}
else
{
_encoderStateManager.UpdateDepthBias(units, factor, clamp);
}
}
public void SetDepthClamp(bool clamp)

View file

@ -16,6 +16,10 @@ namespace Ryujinx.Graphics.Metal
public MTLFunction FragmentFunction;
public MTLFunction ComputeFunction;
private HashTableSlim<PipelineUid, MTLRenderPipelineState> _graphicsPipelineCache;
private MTLComputePipelineState? _computePipelineCache;
private bool _firstBackgroundUse;
public Program(ShaderSource[] shaders, MTLDevice device)
{
for (int index = 0; index < shaders.Length; index++)
@ -62,8 +66,64 @@ namespace Ryujinx.Graphics.Metal
return ""u8.ToArray();
}
public void AddGraphicsPipeline(ref PipelineUid key, MTLRenderPipelineState pipeline)
{
(_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
}
public void AddComputePipeline(MTLComputePipelineState pipeline)
{
_computePipelineCache = pipeline;
}
public bool TryGetGraphicsPipeline(ref PipelineUid key, out MTLRenderPipelineState pipeline)
{
if (_graphicsPipelineCache == null)
{
pipeline = default;
return false;
}
if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
{
if (_firstBackgroundUse)
{
Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
_firstBackgroundUse = false;
}
return false;
}
_firstBackgroundUse = false;
return true;
}
public bool TryGetComputePipeline(out MTLComputePipelineState pipeline)
{
if (_computePipelineCache.HasValue)
{
pipeline = _computePipelineCache.Value;
return true;
}
pipeline = default;
return false;
}
public void Dispose()
{
if (_graphicsPipelineCache != null)
{
foreach (MTLRenderPipelineState pipeline in _graphicsPipelineCache.Values)
{
pipeline.Dispose();
}
}
_computePipelineCache?.Dispose();
VertexFunction.Dispose();
FragmentFunction.Dispose();
ComputeFunction.Dispose();

View file

@ -1,248 +0,0 @@
using Ryujinx.Common.Logging;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct RenderPipelineHash
{
public MTLFunction VertexFunction;
public MTLFunction FragmentFunction;
public struct ColorAttachmentHash
{
public MTLPixelFormat PixelFormat;
public bool BlendingEnabled;
public MTLBlendOperation RgbBlendOperation;
public MTLBlendOperation AlphaBlendOperation;
public MTLBlendFactor SourceRGBBlendFactor;
public MTLBlendFactor DestinationRGBBlendFactor;
public MTLBlendFactor SourceAlphaBlendFactor;
public MTLBlendFactor DestinationAlphaBlendFactor;
public MTLColorWriteMask WriteMask;
}
[System.Runtime.CompilerServices.InlineArray(Constants.MaxColorAttachments)]
public struct ColorAttachmentHashArray
{
public ColorAttachmentHash data;
}
public ColorAttachmentHashArray ColorAttachments;
public struct DepthStencilAttachmentHash
{
public MTLPixelFormat DepthPixelFormat;
public MTLPixelFormat StencilPixelFormat;
}
public DepthStencilAttachmentHash DepthStencilAttachment;
public struct VertexDescriptorHash
{
public struct AttributeHash
{
public MTLVertexFormat Format;
public ulong Offset;
public ulong BufferIndex;
}
[System.Runtime.CompilerServices.InlineArray(Constants.MaxVertexAttributes)]
public struct AttributeHashArray
{
public AttributeHash data;
}
public AttributeHashArray Attributes;
public struct LayoutHash
{
public ulong Stride;
public MTLVertexStepFunction StepFunction;
public ulong StepRate;
}
[System.Runtime.CompilerServices.InlineArray(Constants.MaxVertexLayouts)]
public struct LayoutHashArray
{
public LayoutHash data;
}
public LayoutHashArray Layouts;
}
public VertexDescriptorHash VertexDescriptor;
public override bool Equals(object obj)
{
if (obj is not RenderPipelineHash other)
{
return false;
}
if (VertexFunction != other.VertexFunction)
{
return false;
}
if (FragmentFunction != other.FragmentFunction)
{
return false;
}
if (DepthStencilAttachment.DepthPixelFormat != other.DepthStencilAttachment.DepthPixelFormat)
{
return false;
}
if (DepthStencilAttachment.StencilPixelFormat != other.DepthStencilAttachment.StencilPixelFormat)
{
return false;
}
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
if (ColorAttachments[i].PixelFormat != other.ColorAttachments[i].PixelFormat)
{
return false;
}
if (ColorAttachments[i].BlendingEnabled != other.ColorAttachments[i].BlendingEnabled)
{
return false;
}
if (ColorAttachments[i].RgbBlendOperation != other.ColorAttachments[i].RgbBlendOperation)
{
return false;
}
if (ColorAttachments[i].AlphaBlendOperation != other.ColorAttachments[i].AlphaBlendOperation)
{
return false;
}
if (ColorAttachments[i].SourceRGBBlendFactor != other.ColorAttachments[i].SourceRGBBlendFactor)
{
return false;
}
if (ColorAttachments[i].DestinationRGBBlendFactor != other.ColorAttachments[i].DestinationRGBBlendFactor)
{
return false;
}
if (ColorAttachments[i].SourceAlphaBlendFactor != other.ColorAttachments[i].SourceAlphaBlendFactor)
{
return false;
}
if (ColorAttachments[i].DestinationAlphaBlendFactor != other.ColorAttachments[i].DestinationAlphaBlendFactor)
{
return false;
}
if (ColorAttachments[i].WriteMask != other.ColorAttachments[i].WriteMask)
{
return false;
}
}
for (int i = 0; i < Constants.MaxVertexAttributes; i++)
{
if (VertexDescriptor.Attributes[i].Format != other.VertexDescriptor.Attributes[i].Format)
{
return false;
}
if (VertexDescriptor.Attributes[i].Offset != other.VertexDescriptor.Attributes[i].Offset)
{
return false;
}
if (VertexDescriptor.Attributes[i].BufferIndex != other.VertexDescriptor.Attributes[i].BufferIndex)
{
return false;
}
}
for (int i = 0; i < Constants.MaxVertexLayouts; i++)
{
if (VertexDescriptor.Layouts[i].Stride != other.VertexDescriptor.Layouts[i].Stride)
{
return false;
}
if (VertexDescriptor.Layouts[i].StepFunction != other.VertexDescriptor.Layouts[i].StepFunction)
{
return false;
}
if (VertexDescriptor.Layouts[i].StepRate != other.VertexDescriptor.Layouts[i].StepRate)
{
return false;
}
}
return true;
}
}
[SupportedOSPlatform("macos")]
class RenderPipelineCache : StateCache<MTLRenderPipelineState, MTLRenderPipelineDescriptor, RenderPipelineHash>
{
private readonly MTLDevice _device;
public RenderPipelineCache(MTLDevice device)
{
_device = device;
}
protected override RenderPipelineHash GetHash(MTLRenderPipelineDescriptor descriptor)
{
var hash = new RenderPipelineHash
{
// Functions
VertexFunction = descriptor.VertexFunction,
FragmentFunction = descriptor.FragmentFunction,
DepthStencilAttachment = new RenderPipelineHash.DepthStencilAttachmentHash
{
DepthPixelFormat = descriptor.DepthAttachmentPixelFormat,
StencilPixelFormat = descriptor.StencilAttachmentPixelFormat
},
};
// Color Attachments
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var attachment = descriptor.ColorAttachments.Object((ulong)i);
hash.ColorAttachments[i] = new RenderPipelineHash.ColorAttachmentHash
{
PixelFormat = attachment.PixelFormat,
BlendingEnabled = attachment.BlendingEnabled,
RgbBlendOperation = attachment.RgbBlendOperation,
AlphaBlendOperation = attachment.AlphaBlendOperation,
SourceRGBBlendFactor = attachment.SourceRGBBlendFactor,
DestinationRGBBlendFactor = attachment.DestinationRGBBlendFactor,
SourceAlphaBlendFactor = attachment.SourceAlphaBlendFactor,
DestinationAlphaBlendFactor = attachment.DestinationAlphaBlendFactor,
WriteMask = attachment.WriteMask
};
}
// Vertex descriptor
hash.VertexDescriptor = new RenderPipelineHash.VertexDescriptorHash();
// Attributes
for (int i = 0; i < Constants.MaxVertexAttributes; i++)
{
var attribute = descriptor.VertexDescriptor.Attributes.Object((ulong)i);
hash.VertexDescriptor.Attributes[i] = new RenderPipelineHash.VertexDescriptorHash.AttributeHash
{
Format = attribute.Format,
Offset = attribute.Offset,
BufferIndex = attribute.BufferIndex
};
}
// Layouts
for (int i = 0; i < Constants.MaxVertexLayouts; i++)
{
var layout = descriptor.VertexDescriptor.Layouts.Object((ulong)i);
hash.VertexDescriptor.Layouts[i] = new RenderPipelineHash.VertexDescriptorHash.LayoutHash
{
Stride = layout.Stride,
StepFunction = layout.StepFunction,
StepRate = layout.StepRate
};
}
return hash;
}
protected override MTLRenderPipelineState CreateValue(MTLRenderPipelineDescriptor descriptor)
{
var error = new NSError(IntPtr.Zero);
var pipelineState = _device.NewRenderPipelineState(descriptor, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
return pipelineState;
}
}
}

View file

@ -0,0 +1,110 @@
using SharpMetal.Metal;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Metal.State
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct StencilUid
{
public uint ReadMask;
public uint WriteMask;
public ushort Operations;
public MTLStencilOperation StencilFailureOperation
{
readonly get => (MTLStencilOperation)((Operations >> 0) & 0xF);
set => Operations = (ushort)((Operations & 0xFFF0) | ((int)value << 0));
}
public MTLStencilOperation DepthFailureOperation
{
readonly get => (MTLStencilOperation)((Operations >> 4) & 0xF);
set => Operations = (ushort)((Operations & 0xFF0F) | ((int)value << 4));
}
public MTLStencilOperation DepthStencilPassOperation
{
readonly get => (MTLStencilOperation)((Operations >> 8) & 0xF);
set => Operations = (ushort)((Operations & 0xF0FF) | ((int)value << 8));
}
public MTLCompareFunction StencilCompareFunction
{
readonly get => (MTLCompareFunction)((Operations >> 12) & 0xF);
set => Operations = (ushort)((Operations & 0x0FFF) | ((int)value << 12));
}
}
[StructLayout(LayoutKind.Explicit, Size = 24)]
internal struct DepthStencilUid : IEquatable<DepthStencilUid>
{
[FieldOffset(0)]
public StencilUid FrontFace;
[FieldOffset(10)]
public ushort DepthState;
[FieldOffset(12)]
public StencilUid BackFace;
[FieldOffset(22)]
private readonly ushort _padding;
// Quick access aliases
#pragma warning disable IDE0044 // Add readonly modifier
[FieldOffset(0)]
private ulong _id0;
[FieldOffset(8)]
private ulong _id1;
[FieldOffset(0)]
private Vector128<byte> _id01;
[FieldOffset(16)]
private ulong _id2;
#pragma warning restore IDE0044 // Add readonly modifier
public MTLCompareFunction DepthCompareFunction
{
readonly get => (MTLCompareFunction)((DepthState >> 0) & 0xF);
set => DepthState = (ushort)((DepthState & 0xFFF0) | ((int)value << 0));
}
public bool StencilTestEnabled
{
readonly get => ((DepthState >> 4) & 0x1) != 0;
set => DepthState = (ushort)((DepthState & 0xFFEF) | ((value ? 1 : 0) << 4));
}
public bool DepthWriteEnabled
{
readonly get => ((DepthState >> 15) & 0x1) != 0;
set => DepthState = (ushort)((DepthState & 0x7FFF) | ((value ? 1 : 0) << 15));
}
public readonly override bool Equals(object obj)
{
return obj is DepthStencilUid other && EqualsRef(ref other);
}
public readonly bool EqualsRef(ref DepthStencilUid other)
{
return _id01.Equals(other._id01) && _id2 == other._id2;
}
public readonly bool Equals(DepthStencilUid other)
{
return EqualsRef(ref other);
}
public readonly override int GetHashCode()
{
ulong hash64 = _id0 * 23 ^
_id1 * 23 ^
_id2 * 23;
return (int)hash64 ^ ((int)(hash64 >> 32) * 17);
}
}
}

View file

@ -0,0 +1,338 @@
using Ryujinx.Common.Logging;
using SharpMetal.Foundation;
using SharpMetal.Metal;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
[SupportedOSPlatform("macos")]
struct PipelineState
{
public PipelineUid Internal;
public uint StagesCount
{
readonly get => (byte)((Internal.Id0 >> 0) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint VertexAttributeDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 8) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public uint VertexBindingDescriptionsCount
{
readonly get => (byte)((Internal.Id0 >> 16) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFFF00FFFF) | ((ulong)value << 16);
}
public uint ColorBlendAttachmentStateCount
{
readonly get => (byte)((Internal.Id0 >> 24) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00FFFFFF) | ((ulong)value << 24);
}
/*
* Can be an input to a pipeline, but not sure what the situation for that is.
public PrimitiveTopology Topology
{
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
*/
// Reserved for when API is available.
public int LogicOp
{
readonly get => (int)((Internal.Id0 >> 32) & 0xF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFF0FFFFFFFF) | ((ulong)value << 32);
}
//?
public bool PrimitiveRestartEnable
{
readonly get => ((Internal.Id0 >> 36) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFEFFFFFFFFF) | ((value ? 1UL : 0UL) << 36);
}
public bool RasterizerDiscardEnable
{
readonly get => ((Internal.Id0 >> 37) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFDFFFFFFFFF) | ((value ? 1UL : 0UL) << 37);
}
// Reserved for when API is available.
public bool LogicOpEnable
{
readonly get => ((Internal.Id0 >> 38) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFBFFFFFFFFF) | ((value ? 1UL : 0UL) << 38);
}
public bool AlphaToCoverageEnable
{
readonly get => ((Internal.Id0 >> 40) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFEFFFFFFFFFF) | ((value ? 1UL : 0UL) << 40);
}
public bool AlphaToOneEnable
{
readonly get => ((Internal.Id0 >> 41) & 0x1) != 0UL;
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFDFFFFFFFFFF) | ((value ? 1UL : 0UL) << 41);
}
public MTLPixelFormat DepthStencilFormat
{
readonly get => (MTLPixelFormat)(Internal.Id0 >> 48);
set => Internal.Id0 = (Internal.Id0 & 0x0000FFFFFFFFFFFF) | ((ulong)value << 48);
}
// Not sure how to appropriately use this, but it does need to be passed for tess.
public uint PatchControlPoints
{
readonly get => (uint)((Internal.Id1 >> 0) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint SamplesCount
{
readonly get => (uint)((Internal.Id1 >> 32) & 0xFFFFFFFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)value << 32);
}
// Advanced blend not supported
private struct RenderPipelineDescriptorResult : IDisposable
{
public MTLRenderPipelineDescriptor Pipeline;
private MTLVertexDescriptor _vertex;
public RenderPipelineDescriptorResult(MTLRenderPipelineDescriptor pipeline, MTLVertexDescriptor vertex)
{
Pipeline = pipeline;
_vertex = vertex;
}
public void Dispose()
{
Pipeline.Dispose();
_vertex.Dispose();
}
}
private readonly void BuildColorAttachment(MTLRenderPipelineColorAttachmentDescriptor descriptor, ColorBlendStateUid blendState)
{
descriptor.PixelFormat = blendState.PixelFormat;
descriptor.SetBlendingEnabled(blendState.Enable);
descriptor.AlphaBlendOperation = blendState.AlphaBlendOperation;
descriptor.RgbBlendOperation = blendState.RgbBlendOperation;
descriptor.SourceAlphaBlendFactor = blendState.SourceAlphaBlendFactor;
descriptor.DestinationAlphaBlendFactor = blendState.DestinationAlphaBlendFactor;
descriptor.SourceRGBBlendFactor = blendState.SourceRGBBlendFactor;
descriptor.DestinationRGBBlendFactor = blendState.DestinationRGBBlendFactor;
descriptor.WriteMask = blendState.WriteMask;
}
private readonly MTLVertexDescriptor BuildVertexDescriptor()
{
var vertexDescriptor = new MTLVertexDescriptor();
for (int i = 0; i < VertexAttributeDescriptionsCount; i++)
{
VertexInputAttributeUid uid = Internal.VertexAttributes[i];
var attrib = vertexDescriptor.Attributes.Object((ulong)i);
attrib.Format = uid.Format;
attrib.Offset = uid.Offset;
attrib.BufferIndex = uid.BufferIndex;
}
for (int i = 0; i < VertexBindingDescriptionsCount; i++)
{
VertexInputLayoutUid uid = Internal.VertexBindings[i];
var layout = vertexDescriptor.Layouts.Object((ulong)i);
layout.StepFunction = uid.StepFunction;
layout.StepRate = uid.StepRate;
layout.Stride = uid.Stride;
}
return vertexDescriptor;
}
private RenderPipelineDescriptorResult CreateRenderDescriptor(Program program)
{
var renderPipelineDescriptor = new MTLRenderPipelineDescriptor();
for (int i = 0; i < Constants.MaxColorAttachments; i++)
{
var blendState = Internal.ColorBlendState[i];
if (blendState.PixelFormat != MTLPixelFormat.Invalid)
{
var pipelineAttachment = renderPipelineDescriptor.ColorAttachments.Object((ulong)i);
BuildColorAttachment(pipelineAttachment, blendState);
}
}
MTLPixelFormat dsFormat = DepthStencilFormat;
if (dsFormat != MTLPixelFormat.Invalid)
{
switch (dsFormat)
{
// Depth Only Attachment
case MTLPixelFormat.Depth16Unorm:
case MTLPixelFormat.Depth32Float:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
break;
// Stencil Only Attachment
case MTLPixelFormat.Stencil8:
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
// Combined Attachment
case MTLPixelFormat.Depth24UnormStencil8:
case MTLPixelFormat.Depth32FloatStencil8:
renderPipelineDescriptor.DepthAttachmentPixelFormat = dsFormat;
renderPipelineDescriptor.StencilAttachmentPixelFormat = dsFormat;
break;
default:
Logger.Error?.PrintMsg(LogClass.Gpu, $"Unsupported Depth/Stencil Format: {dsFormat}!");
break;
}
}
/* TODO: enable when sharpmetal fixes the bindings
renderPipelineDescriptor.AlphaToCoverageEnabled = AlphaToCoverageEnable;
renderPipelineDescriptor.AlphaToOneEnabled = AlphaToOneEnable;
renderPipelineDescriptor.RasterizationEnabled = !RasterizerDiscardEnable;
*/
renderPipelineDescriptor.SampleCount = Math.Max(1, SamplesCount);
var vertexDescriptor = BuildVertexDescriptor();
renderPipelineDescriptor.VertexDescriptor = vertexDescriptor;
renderPipelineDescriptor.VertexFunction = program.VertexFunction;
if (program.FragmentFunction.NativePtr != 0)
{
renderPipelineDescriptor.FragmentFunction = program.FragmentFunction;
}
return new RenderPipelineDescriptorResult(renderPipelineDescriptor, vertexDescriptor);
}
public MTLRenderPipelineState CreateRenderPipeline(MTLDevice device, Program program)
{
if (program.TryGetGraphicsPipeline(ref Internal, out var pipelineState))
{
return pipelineState;
}
using RenderPipelineDescriptorResult descriptors = CreateRenderDescriptor(program);
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewRenderPipelineState(descriptors.Pipeline, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddGraphicsPipeline(ref Internal, pipelineState);
return pipelineState;
}
public static MTLComputePipelineState CreateComputePipeline(MTLDevice device, Program program)
{
if (program.TryGetComputePipeline(out var pipelineState))
{
return pipelineState;
}
var error = new NSError(IntPtr.Zero);
pipelineState = device.NewComputePipelineState(program.ComputeFunction, ref error);
if (error != IntPtr.Zero)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Compute Pipeline State: {StringHelper.String(error.LocalizedDescription)}");
}
program.AddComputePipeline(pipelineState);
return pipelineState;
}
public void Initialize()
{
SamplesCount = 1;
Internal.ResetColorState();
}
/*
* TODO, this is from vulkan.
private void UpdateVertexAttributeDescriptions(VulkanRenderer gd)
{
// Vertex attributes exceeding the stride are invalid.
// In metal, they cause glitches with the vertex shader fetching incorrect values.
// To work around this, we reduce the format to something that doesn't exceed the stride if possible.
// The assumption is that the exceeding components are not actually accessed on the shader.
for (int index = 0; index < VertexAttributeDescriptionsCount; index++)
{
var attribute = Internal.VertexAttributeDescriptions[index];
int vbIndex = GetVertexBufferIndex(attribute.Binding);
if (vbIndex >= 0)
{
ref var vb = ref Internal.VertexBindingDescriptions[vbIndex];
Format format = attribute.Format;
while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride)
{
Format newFormat = FormatTable.DropLastComponent(format);
if (newFormat == format)
{
// That case means we failed to find a format that fits within the stride,
// so just restore the original format and give up.
format = attribute.Format;
break;
}
format = newFormat;
}
if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format))
{
attribute.Format = format;
}
}
_vertexAttributeDescriptions2[index] = attribute;
}
}
private int GetVertexBufferIndex(uint binding)
{
for (int index = 0; index < VertexBindingDescriptionsCount; index++)
{
if (Internal.VertexBindingDescriptions[index].Binding == binding)
{
return index;
}
}
return -1;
}
*/
}
}

View file

@ -0,0 +1,200 @@
using Ryujinx.Common.Memory;
using SharpMetal.Metal;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal
{
struct VertexInputAttributeUid
{
public ulong Id0;
public ulong Offset
{
readonly get => (uint)((Id0 >> 0) & 0xFFFFFFFF);
set => Id0 = (Id0 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public MTLVertexFormat Format
{
readonly get => (MTLVertexFormat)((Id0 >> 32) & 0xFFFF);
set => Id0 = (Id0 & 0xFFFF0000FFFFFFFF) | ((ulong)value << 32);
}
public ulong BufferIndex
{
readonly get => ((Id0 >> 48) & 0xFFFF);
set => Id0 = (Id0 & 0x0000FFFFFFFFFFFF) | ((ulong)value << 48);
}
}
struct VertexInputLayoutUid
{
public ulong Id0;
public uint Stride
{
readonly get => (uint)((Id0 >> 0) & 0xFFFFFFFF);
set => Id0 = (Id0 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint StepRate
{
readonly get => (uint)((Id0 >> 32) & 0x1FFFFFFF);
set => Id0 = (Id0 & 0xE0000000FFFFFFFF) | ((ulong)value << 32);
}
public MTLVertexStepFunction StepFunction
{
readonly get => (MTLVertexStepFunction)((Id0 >> 61) & 0x7);
set => Id0 = (Id0 & 0x1FFFFFFFFFFFFFFF) | ((ulong)value << 61);
}
}
struct ColorBlendStateUid
{
public ulong Id0;
public MTLPixelFormat PixelFormat
{
readonly get => (MTLPixelFormat)((Id0 >> 0) & 0xFFFF);
set => Id0 = (Id0 & 0xFFFFFFFFFFFF0000) | ((ulong)value << 0);
}
public MTLBlendFactor SourceRGBBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 16) & 0xFF);
set => Id0 = (Id0 & 0xFFFFFFFFFF00FFFF) | ((ulong)value << 16);
}
public MTLBlendFactor DestinationRGBBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 24) & 0xFF);
set => Id0 = (Id0 & 0xFFFFFFFF00FFFFFF) | ((ulong)value << 24);
}
public MTLBlendOperation RgbBlendOperation
{
readonly get => (MTLBlendOperation)((Id0 >> 32) & 0xF);
set => Id0 = (Id0 & 0xFFFFFFF0FFFFFFFF) | ((ulong)value << 32);
}
public MTLBlendOperation AlphaBlendOperation
{
readonly get => (MTLBlendOperation)((Id0 >> 36) & 0xF);
set => Id0 = (Id0 & 0xFFFFFF0FFFFFFFFF) | ((ulong)value << 36);
}
public MTLBlendFactor SourceAlphaBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 40) & 0xFF);
set => Id0 = (Id0 & 0xFFFF00FFFFFFFFFF) | ((ulong)value << 40);
}
public MTLBlendFactor DestinationAlphaBlendFactor
{
readonly get => (MTLBlendFactor)((Id0 >> 48) & 0xFF);
set => Id0 = (Id0 & 0xFF00FFFFFFFFFFFF) | ((ulong)value << 48);
}
public MTLColorWriteMask WriteMask
{
readonly get => (MTLColorWriteMask)((Id0 >> 56) & 0xF);
set => Id0 = (Id0 & 0xF0FFFFFFFFFFFFFF) | ((ulong)value << 56);
}
public bool Enable
{
readonly get => ((Id0 >> 63) & 0x1) != 0UL;
set => Id0 = (Id0 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
}
}
[SupportedOSPlatform("macos")]
struct PipelineUid : IRefEquatable<PipelineUid>
{
public ulong Id0;
public ulong Id1;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 8) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 16) & 0xFF);
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id0 >> 24) & 0xFF);
public Array32<VertexInputAttributeUid> VertexAttributes;
public Array33<VertexInputLayoutUid> VertexBindings;
public Array8<ColorBlendStateUid> ColorBlendState;
public uint AttachmentIntegerFormatMask;
public bool LogicOpsAllowed;
public void ResetColorState()
{
ColorBlendState = new();
for (int i = 0; i < ColorBlendState.Length; i++)
{
ColorBlendState[i].WriteMask = MTLColorWriteMask.All;
}
}
public readonly override bool Equals(object obj)
{
return obj is PipelineUid other && Equals(other);
}
public bool Equals(ref PipelineUid other)
{
if (!Unsafe.As<ulong, Vector128<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id0)))
{
return false;
}
if (!SequenceEqual<VertexInputAttributeUid>(VertexAttributes.AsSpan(), other.VertexAttributes.AsSpan(), VertexAttributeDescriptionsCount))
{
return false;
}
if (!SequenceEqual<VertexInputLayoutUid>(VertexBindings.AsSpan(), other.VertexBindings.AsSpan(), VertexBindingDescriptionsCount))
{
return false;
}
if (!SequenceEqual<ColorBlendStateUid>(ColorBlendState.AsSpan(), other.ColorBlendState.AsSpan(), ColorBlendAttachmentStateCount))
{
return false;
}
return true;
}
private static bool SequenceEqual<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, uint count) where T : unmanaged
{
return MemoryMarshal.Cast<T, byte>(x[..(int)count]).SequenceEqual(MemoryMarshal.Cast<T, byte>(y[..(int)count]));
}
public override int GetHashCode()
{
ulong hash64 = Id0 * 23 ^
Id1 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{
hash64 ^= VertexAttributes[i].Id0 * 23;
}
for (int i = 0; i < (int)VertexBindingDescriptionsCount; i++)
{
hash64 ^= VertexBindings[i].Id0 * 23;
}
for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++)
{
hash64 ^= ColorBlendState[i].Id0 * 23;
}
return (int)hash64 ^ ((int)(hash64 >> 32) * 17);
}
}
}

View file

@ -12,9 +12,11 @@ namespace Ryujinx.Graphics.Metal
{
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info) : base(device, renderer, pipeline, info)
{
MTLPixelFormat pixelFormat = FormatTable.GetFormat(Info.Format);
var descriptor = new MTLTextureDescriptor
{
PixelFormat = FormatTable.GetFormat(Info.Format),
PixelFormat = pixelFormat,
Usage = MTLTextureUsage.Unknown,
SampleCount = (ulong)Info.Samples,
TextureType = Info.Target.Convert(),
@ -35,6 +37,7 @@ namespace Ryujinx.Graphics.Metal
descriptor.Swizzle = GetSwizzle(info, descriptor.PixelFormat);
_mtlTexture = _device.NewTexture(descriptor);
MtlFormat = pixelFormat;
}
public Texture(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info, MTLTexture sourceTexture, int firstLayer, int firstLevel) : base(device, renderer, pipeline, info)
@ -51,6 +54,7 @@ namespace Ryujinx.Graphics.Metal
var swizzle = GetSwizzle(info, pixelFormat);
_mtlTexture = sourceTexture.NewTextureView(pixelFormat, textureType, levels, slices, swizzle);
MtlFormat = pixelFormat;
}
private MTLTextureSwizzleChannels GetSwizzle(TextureCreateInfo info, MTLPixelFormat pixelFormat)

View file

@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Metal
public int Width => Info.Width;
public int Height => Info.Height;
public int Depth => Info.Depth;
public MTLPixelFormat MtlFormat { get; protected set; }
public TextureBase(MTLDevice device, MetalRenderer renderer, Pipeline pipeline, TextureCreateInfo info)
{