diff --git a/src/Ryujinx.Graphics.Metal/ComputePipelineCache.cs b/src/Ryujinx.Graphics.Metal/ComputePipelineCache.cs deleted file mode 100644 index a76f4c33c..000000000 --- a/src/Ryujinx.Graphics.Metal/ComputePipelineCache.cs +++ /dev/null @@ -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 - { - 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; - } - } -} diff --git a/src/Ryujinx.Graphics.Metal/DepthStencilCache.cs b/src/Ryujinx.Graphics.Metal/DepthStencilCache.cs index be47653c0..bb6e4c180 100644 --- a/src/Ryujinx.Graphics.Metal/DepthStencilCache.cs +++ b/src/Ryujinx.Graphics.Metal/DepthStencilCache.cs @@ -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 + class DepthStencilCache : StateCache { 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); + } } } } diff --git a/src/Ryujinx.Graphics.Metal/EncoderState.cs b/src/Ryujinx.Graphics.Metal/EncoderState.cs index 6863282a8..2f732681b 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderState.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderState.cs @@ -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 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(); } } } diff --git a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs index 79189d0b9..62c965697 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs @@ -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 _backStates = []; + private readonly EncoderState _mainState = new(); + private EncoderState _currentState; public readonly Auto 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 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) diff --git a/src/Ryujinx.Graphics.Metal/HashTableSlim.cs b/src/Ryujinx.Graphics.Metal/HashTableSlim.cs new file mode 100644 index 000000000..a27a53d47 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/HashTableSlim.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Metal +{ + interface IRefEquatable + { + bool Equals(ref T other); + } + + class HashTableSlim where TKey : IRefEquatable + { + 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 AsSpan() + { + return Entries == null ? Span.Empty : Entries.AsSpan(0, Length); + } + } + + private readonly Bucket[] _hashTable = new Bucket[TotalBuckets]; + + public IEnumerable Keys + { + get + { + foreach (Bucket bucket in _hashTable) + { + for (int i = 0; i < bucket.Length; i++) + { + yield return bucket.Entries[i].Key; + } + } + } + } + + public IEnumerable 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; + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/HelperShader.cs b/src/Ryujinx.Graphics.Metal/HelperShader.cs index 1387a5a35..ec944b0f8 100644 --- a/src/Ryujinx.Graphics.Metal/HelperShader.cs +++ b/src/Ryujinx.Graphics.Metal/HelperShader.cs @@ -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 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( diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index 39361f710..6363eb5d8 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -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) diff --git a/src/Ryujinx.Graphics.Metal/Program.cs b/src/Ryujinx.Graphics.Metal/Program.cs index 89f0bd5dd..40cb6df77 100644 --- a/src/Ryujinx.Graphics.Metal/Program.cs +++ b/src/Ryujinx.Graphics.Metal/Program.cs @@ -16,6 +16,10 @@ namespace Ryujinx.Graphics.Metal public MTLFunction FragmentFunction; public MTLFunction ComputeFunction; + private HashTableSlim _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(); diff --git a/src/Ryujinx.Graphics.Metal/RenderPipelineCache.cs b/src/Ryujinx.Graphics.Metal/RenderPipelineCache.cs deleted file mode 100644 index b8e6005c4..000000000 --- a/src/Ryujinx.Graphics.Metal/RenderPipelineCache.cs +++ /dev/null @@ -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 - { - 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; - } - } -} diff --git a/src/Ryujinx.Graphics.Metal/State/DepthStencilUid.cs b/src/Ryujinx.Graphics.Metal/State/DepthStencilUid.cs new file mode 100644 index 000000000..63b1d8ef4 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/State/DepthStencilUid.cs @@ -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 + { + [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 _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); + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/State/PipelineState.cs b/src/Ryujinx.Graphics.Metal/State/PipelineState.cs new file mode 100644 index 000000000..c6e548c95 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/State/PipelineState.cs @@ -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; + } + */ + } +} diff --git a/src/Ryujinx.Graphics.Metal/State/PipelineUid.cs b/src/Ryujinx.Graphics.Metal/State/PipelineUid.cs new file mode 100644 index 000000000..4e2784b42 --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/State/PipelineUid.cs @@ -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 + { + 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 VertexAttributes; + public Array33 VertexBindings; + public Array8 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>(ref Id0).Equals(Unsafe.As>(ref other.Id0))) + { + return false; + } + + if (!SequenceEqual(VertexAttributes.AsSpan(), other.VertexAttributes.AsSpan(), VertexAttributeDescriptionsCount)) + { + return false; + } + + if (!SequenceEqual(VertexBindings.AsSpan(), other.VertexBindings.AsSpan(), VertexBindingDescriptionsCount)) + { + return false; + } + + if (!SequenceEqual(ColorBlendState.AsSpan(), other.ColorBlendState.AsSpan(), ColorBlendAttachmentStateCount)) + { + return false; + } + + return true; + } + + private static bool SequenceEqual(ReadOnlySpan x, ReadOnlySpan y, uint count) where T : unmanaged + { + return MemoryMarshal.Cast(x[..(int)count]).SequenceEqual(MemoryMarshal.Cast(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); + } + } +} diff --git a/src/Ryujinx.Graphics.Metal/Texture.cs b/src/Ryujinx.Graphics.Metal/Texture.cs index 57e446ce6..668ddd8be 100644 --- a/src/Ryujinx.Graphics.Metal/Texture.cs +++ b/src/Ryujinx.Graphics.Metal/Texture.cs @@ -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) diff --git a/src/Ryujinx.Graphics.Metal/TextureBase.cs b/src/Ryujinx.Graphics.Metal/TextureBase.cs index 51f5ec8d2..96daf8d3b 100644 --- a/src/Ryujinx.Graphics.Metal/TextureBase.cs +++ b/src/Ryujinx.Graphics.Metal/TextureBase.cs @@ -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) {