diff --git a/src/Ryujinx.Graphics.Metal/Constants.cs b/src/Ryujinx.Graphics.Metal/Constants.cs index 21776ee58..f20598f9c 100644 --- a/src/Ryujinx.Graphics.Metal/Constants.cs +++ b/src/Ryujinx.Graphics.Metal/Constants.cs @@ -9,5 +9,10 @@ namespace Ryujinx.Graphics.Metal public const int MaxTexturesPerStage = 64; public const int MaxCommandBuffersPerQueue = 16; public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages; + public const int MaxColorAttachments = 8; + // TODO: Check this value + public const int MaxVertexAttributes = 16; + // TODO: Check this value + public const int MaxVertexLayouts = 16; } } diff --git a/src/Ryujinx.Graphics.Metal/EncoderState.cs b/src/Ryujinx.Graphics.Metal/EncoderState.cs index adbc568a9..a787d1424 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderState.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderState.cs @@ -27,8 +27,6 @@ namespace Ryujinx.Graphics.Metal [SupportedOSPlatform("macos")] public struct EncoderState { - public const int MaxColorAttachments = 8; - public MTLFunction? VertexFunction = null; public MTLFunction? FragmentFunction = null; @@ -64,7 +62,7 @@ namespace Ryujinx.Graphics.Metal // Changes to attachments take recreation! public MTLTexture DepthStencil = default; - public MTLTexture[] RenderTargets = new MTLTexture[MaxColorAttachments]; + public MTLTexture[] RenderTargets = new MTLTexture[Constants.MaxColorAttachments]; public Dictionary BlendDescriptors = new(); public ColorF BlendColor = new(); diff --git a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs index 6e8314117..15a96cc45 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs @@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.Metal private readonly MTLDevice _device; private readonly Pipeline _pipeline; + private readonly RenderPipelineCache RenderPipelineCache; + private EncoderState _currentState = new(); private EncoderState _backState = new(); @@ -28,6 +30,7 @@ namespace Ryujinx.Graphics.Metal { _device = device; _pipeline = pipeline; + RenderPipelineCache = new(device); } public void SwapStates() @@ -45,7 +48,7 @@ namespace Ryujinx.Graphics.Metal // Initialise Pass & State var renderPassDescriptor = new MTLRenderPassDescriptor(); - for (int i = 0; i < EncoderState.MaxColorAttachments; i++) + for (int i = 0; i < Constants.MaxColorAttachments; i++) { if (_currentState.RenderTargets[i] != IntPtr.Zero) { @@ -131,7 +134,7 @@ namespace Ryujinx.Graphics.Metal private void SetPipelineState(MTLRenderCommandEncoder renderCommandEncoder) { var renderPipelineDescriptor = new MTLRenderPipelineDescriptor(); - for (int i = 0; i < EncoderState.MaxColorAttachments; i++) + for (int i = 0; i < Constants.MaxColorAttachments; i++) { if (_currentState.RenderTargets[i] != IntPtr.Zero) { @@ -198,12 +201,7 @@ namespace Ryujinx.Graphics.Metal renderPipelineDescriptor.FragmentFunction = _currentState.FragmentFunction.Value; } - var error = new NSError(IntPtr.Zero); - var pipelineState = _device.NewRenderPipelineState(renderPipelineDescriptor, ref error); - if (error != IntPtr.Zero) - { - Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to create Render Pipeline State: {StringHelper.String(error.LocalizedDescription)}"); - } + var pipelineState = RenderPipelineCache.GetOrCreate(renderPipelineDescriptor); renderCommandEncoder.SetRenderPipelineState(pipelineState); @@ -249,7 +247,7 @@ namespace Ryujinx.Graphics.Metal public void UpdateRenderTargets(ITexture[] colors, ITexture depthStencil) { - _currentState.RenderTargets = new MTLTexture[EncoderState.MaxColorAttachments]; + _currentState.RenderTargets = new MTLTexture[Constants.MaxColorAttachments]; for (int i = 0; i < colors.Length; i++) { diff --git a/src/Ryujinx.Graphics.Metal/StateCache.cs b/src/Ryujinx.Graphics.Metal/StateCache.cs new file mode 100644 index 000000000..ce60c414b --- /dev/null +++ b/src/Ryujinx.Graphics.Metal/StateCache.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using SharpMetal.Foundation; +using SharpMetal.Metal; +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.Metal +{ + [SupportedOSPlatform("macos")] + public abstract class StateCache + { + private Dictionary Cache = new(); + + protected abstract HashT GetHash(DescriptorT descriptor); + + protected abstract T CreateValue(DescriptorT descriptor); + + public T GetOrCreate(DescriptorT descriptor) + { + var hash = GetHash(descriptor); + if (Cache.TryGetValue(hash, out T value)) + { + return value; + } + else + { + var newValue = CreateValue(descriptor); + Cache.Add(hash, newValue); + + return newValue; + } + } + } + + [SupportedOSPlatform("macos")] + public 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; + } + [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 int Offset; + public int BufferIndex; + } + [System.Runtime.CompilerServices.InlineArray(Constants.MaxVertexAttributes)] + public struct AttributeHashArray + { + public AttributeHash data; + } + public AttributeHashArray Attributes; + public struct LayoutHash + { + public MTLVertexFormat Format; + public int Stride; + public int StepFunction; + public int StepRate; + } + [System.Runtime.CompilerServices.InlineArray(Constants.MaxVertexLayouts)] + public struct LayoutHashArray + { + public LayoutHash data; + } + public LayoutHashArray Layouts; + } + public VertexDescriptorHash VertexDescriptor; + } + + [SupportedOSPlatform("macos")] + public class RenderPipelineCache : StateCache + { + private readonly MTLDevice _device; + + public RenderPipelineCache(MTLDevice device) { + _device = device; + } + + protected override RenderPipelineHash GetHash(MTLRenderPipelineDescriptor descriptor) { + var hash = new RenderPipelineHash(); + + // Functions + hash.VertexFunction = descriptor.VertexFunction; + hash.FragmentFunction = descriptor.FragmentFunction; + + // 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 + }; + } + + // Depth stencil attachment + hash.DepthStencilAttachment = new RenderPipelineHash.DepthStencilAttachmentHash + { + DepthPixelFormat = descriptor.DepthAttachmentPixelFormat, + StencilPixelFormat = descriptor.StencilAttachmentPixelFormat + }; + + // 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 = (int)attribute.Offset, + BufferIndex = (int)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 = (int)layout.Stride, + StepFunction = (int)layout.StepFunction, + StepRate = (int)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; + } + } +}