From 1f91c74a95dbc73ff3900a1ecb8ab6220343eed2 Mon Sep 17 00:00:00 2001 From: SamoZ256 <96914946+SamoZ256@users.noreply.github.com> Date: Mon, 27 May 2024 13:58:03 +0200 Subject: [PATCH] Clone the state & flip viewport vertically (#16) * implement texture get data * reset all state before blit & clone state * format * support blit regions * implement source region for blit * replace bottom with top * account for 0 size * support image flipping * revert presentation fixes & y flip * revert * flip viewport vertically * switch face winding * comment * use SetBytes for texture clear * implement missing compute builtins * change storage and texture buffer alignment * correct compute builtins * don't use nullable for textures and samplers * remove incorrect texture get data implementation * Cleanup IntPtrs --------- Co-authored-by: Isaac Marovitz --- src/Ryujinx.Graphics.Metal/Constants.cs | 2 + src/Ryujinx.Graphics.Metal/EncoderState.cs | 27 ++++++-- .../EncoderStateManager.cs | 62 ++++++++++++------- src/Ryujinx.Graphics.Metal/EnumConversion.cs | 5 +- src/Ryujinx.Graphics.Metal/HelperShader.cs | 48 ++++---------- src/Ryujinx.Graphics.Metal/MetalRenderer.cs | 4 +- src/Ryujinx.Graphics.Metal/Pipeline.cs | 7 ++- .../CodeGen/Msl/Instructions/IoMap.cs | 5 +- 8 files changed, 91 insertions(+), 69 deletions(-) diff --git a/src/Ryujinx.Graphics.Metal/Constants.cs b/src/Ryujinx.Graphics.Metal/Constants.cs index f20598f9c..c9af5deaf 100644 --- a/src/Ryujinx.Graphics.Metal/Constants.cs +++ b/src/Ryujinx.Graphics.Metal/Constants.cs @@ -14,5 +14,7 @@ namespace Ryujinx.Graphics.Metal public const int MaxVertexAttributes = 16; // TODO: Check this value public const int MaxVertexLayouts = 16; + public const int MaxTextures = 31; + public const int MaxSamplers = 31; } } diff --git a/src/Ryujinx.Graphics.Metal/EncoderState.cs b/src/Ryujinx.Graphics.Metal/EncoderState.cs index 919677732..0bd2e9651 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderState.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderState.cs @@ -31,11 +31,11 @@ namespace Ryujinx.Graphics.Metal public MTLFunction? VertexFunction = null; public MTLFunction? FragmentFunction = null; - public Dictionary FragmentTextures = new(); - public Dictionary FragmentSamplers = new(); + public MTLTexture[] FragmentTextures = new MTLTexture[Constants.MaxTextures]; + public MTLSamplerState[] FragmentSamplers = new MTLSamplerState[Constants.MaxSamplers]; - public Dictionary VertexTextures = new(); - public Dictionary VertexSamplers = new(); + public MTLTexture[] VertexTextures = new MTLTexture[Constants.MaxTextures]; + public MTLSamplerState[] VertexSamplers = new MTLSamplerState[Constants.MaxSamplers]; public List UniformBuffers = []; public List StorageBuffers = []; @@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Metal public PrimitiveTopology Topology = PrimitiveTopology.Triangles; public MTLCullMode CullMode = MTLCullMode.None; - public MTLWinding Winding = MTLWinding.Clockwise; + public MTLWinding Winding = MTLWinding.CounterClockwise; public MTLViewport[] Viewports = []; public MTLScissorRect[] Scissors = []; @@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Metal // Changes to attachments take recreation! public Texture DepthStencil = default; public Texture[] RenderTargets = new Texture[Constants.MaxColorAttachments]; - public Dictionary BlendDescriptors = new(); + public BlendDescriptor?[] BlendDescriptors = new BlendDescriptor?[Constants.MaxColorAttachments]; public ColorF BlendColor = new(); public VertexBufferDescriptor[] VertexBuffers = []; @@ -74,5 +74,20 @@ namespace Ryujinx.Graphics.Metal public DirtyFlags Dirty = new(); public EncoderState() { } + + public EncoderState Clone() + { + // 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 = (MTLTexture[])FragmentTextures.Clone(); + clone.FragmentSamplers = (MTLSamplerState[])FragmentSamplers.Clone(); + clone.VertexTextures = (MTLTexture[])VertexTextures.Clone(); + clone.VertexSamplers = (MTLSamplerState[])VertexSamplers.Clone(); + clone.BlendDescriptors = (BlendDescriptor?[])BlendDescriptors.Clone(); + clone.VertexBuffers = (VertexBufferDescriptor[])VertexBuffers.Clone(); + clone.VertexAttribs = (VertexAttribDescriptor[])VertexAttribs.Clone(); + + return clone; + } } } diff --git a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs index a15b23a69..849461802 100644 --- a/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs +++ b/src/Ryujinx.Graphics.Metal/EncoderStateManager.cs @@ -44,9 +44,16 @@ namespace Ryujinx.Graphics.Metal _depthStencilCache.Dispose(); } - public readonly void SaveState() + public void SaveState() { _backStates.Push(_currentState); + _currentState = _currentState.Clone(); + } + + public void SaveAndResetState() + { + _backStates.Push(_currentState); + _currentState = new(); } public void RestoreState() @@ -65,6 +72,9 @@ namespace Ryujinx.Graphics.Metal SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true); SetCullMode(renderCommandEncoder); SetFrontFace(renderCommandEncoder); + + // Mark the other state as dirty + _currentState.Dirty.MarkAll(); } else { @@ -184,8 +194,9 @@ namespace Ryujinx.Graphics.Metal pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha; pipelineAttachment.DestinationRGBBlendFactor = MTLBlendFactor.OneMinusSourceAlpha; - if (_currentState.BlendDescriptors.TryGetValue(i, out BlendDescriptor blendDescriptor)) + 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(); @@ -469,12 +480,13 @@ namespace Ryujinx.Graphics.Metal for (int i = 0; i < viewports.Length; i++) { var viewport = viewports[i]; + // Y coordinate is inverted _currentState.Viewports[i] = new MTLViewport { originX = viewport.Region.X, - originY = viewport.Region.Y, + originY = viewport.Region.Y + viewport.Region.Height, width = viewport.Region.Width, - height = viewport.Region.Height, + height = -viewport.Region.Height, znear = Clamp(viewport.DepthNear), zfar = Clamp(viewport.DepthFar) }; @@ -708,31 +720,39 @@ namespace Ryujinx.Graphics.Metal renderCommandEncoder.SetFrontFacingWinding(_currentState.Winding); } - private static void SetTextureAndSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, Dictionary textures, Dictionary samplers) + private static void SetTextureAndSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, MTLTexture[] textures, MTLSamplerState[] samplers) { - foreach (var texture in textures) + for (int i = 0; i < textures.Length; i++) { - switch (stage) + var texture = textures[i]; + if (texture != IntPtr.Zero) { - case ShaderStage.Vertex: - renderCommandEncoder.SetVertexTexture(texture.Value, texture.Key); - break; - case ShaderStage.Fragment: - renderCommandEncoder.SetFragmentTexture(texture.Value, texture.Key); - break; + switch (stage) + { + case ShaderStage.Vertex: + renderCommandEncoder.SetVertexTexture(texture, (ulong)i); + break; + case ShaderStage.Fragment: + renderCommandEncoder.SetFragmentTexture(texture, (ulong)i); + break; + } } } - foreach (var sampler in samplers) + for (int i = 0; i < samplers.Length; i++) { - switch (stage) + var sampler = samplers[i]; + if (sampler != IntPtr.Zero) { - case ShaderStage.Vertex: - renderCommandEncoder.SetVertexSamplerState(sampler.Value, sampler.Key); - break; - case ShaderStage.Fragment: - renderCommandEncoder.SetFragmentSamplerState(sampler.Value, sampler.Key); - break; + switch (stage) + { + case ShaderStage.Vertex: + renderCommandEncoder.SetVertexSamplerState(sampler, (ulong)i); + break; + case ShaderStage.Fragment: + renderCommandEncoder.SetFragmentSamplerState(sampler, (ulong)i); + break; + } } } } diff --git a/src/Ryujinx.Graphics.Metal/EnumConversion.cs b/src/Ryujinx.Graphics.Metal/EnumConversion.cs index 81a84f668..18662e21a 100644 --- a/src/Ryujinx.Graphics.Metal/EnumConversion.cs +++ b/src/Ryujinx.Graphics.Metal/EnumConversion.cs @@ -93,10 +93,11 @@ namespace Ryujinx.Graphics.Metal public static MTLWinding Convert(this FrontFace frontFace) { + // The viewport is flipped vertically, therefore we need to switch the winding order as well return frontFace switch { - FrontFace.Clockwise => MTLWinding.Clockwise, - FrontFace.CounterClockwise => MTLWinding.CounterClockwise, + FrontFace.Clockwise => MTLWinding.CounterClockwise, + FrontFace.CounterClockwise => MTLWinding.Clockwise, _ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise) }; } diff --git a/src/Ryujinx.Graphics.Metal/HelperShader.cs b/src/Ryujinx.Graphics.Metal/HelperShader.cs index 35c02a90d..c2f7a012f 100644 --- a/src/Ryujinx.Graphics.Metal/HelperShader.cs +++ b/src/Ryujinx.Graphics.Metal/HelperShader.cs @@ -2,11 +2,9 @@ using Ryujinx.Common; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; -using SharpMetal.Foundation; using SharpMetal.Metal; using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Runtime.Versioning; namespace Ryujinx.Graphics.Metal @@ -70,11 +68,9 @@ namespace Ryujinx.Graphics.Metal }); // Save current state - _pipeline.SaveState(); + _pipeline.SaveAndResetState(); _pipeline.SetProgram(_programColorBlit); - _pipeline.SetFaceCulling(false, Face.Front); - _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); // Viewport and scissor needs to be set before render pass begin so as not to bind the old ones _pipeline.SetViewports([]); _pipeline.SetScissors([]); @@ -93,29 +89,21 @@ namespace Ryujinx.Graphics.Metal { const int ClearColorBufferSize = 16; - var buffer = _device.NewBuffer(ClearColorBufferSize, MTLResourceOptions.ResourceStorageModeManaged); - var span = new Span(buffer.Contents.ToPointer(), ClearColorBufferSize); - clearColor.CopyTo(span); - - buffer.DidModifyRange(new NSRange - { - location = 0, - length = ClearColorBufferSize - }); - - var handle = buffer.NativePtr; - var range = new BufferRange(Unsafe.As(ref handle), 0, ClearColorBufferSize); - // Save current state _pipeline.SaveState(); - _pipeline.SetUniformBuffers([new BufferAssignment(0, range)]); - _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.SetFaceCulling(false, Face.Front); _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); // _pipeline.SetRenderTargetColorMasks([componentMask]); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + + fixed (float* ptr = clearColor) + { + _pipeline.GetOrCreateRenderEncoder().SetFragmentBytes((IntPtr)ptr, ClearColorBufferSize, 0); + } + _pipeline.Draw(4, 1, 0, 0); // Restore previous state @@ -123,37 +111,25 @@ namespace Ryujinx.Graphics.Metal } public unsafe void ClearDepthStencil( - ReadOnlySpan depthValue, + float depthValue, bool depthMask, int stencilValue, int stencilMask) { - const int ClearColorBufferSize = 16; + const int ClearDepthBufferSize = 4; - var buffer = _device.NewBuffer(ClearColorBufferSize, MTLResourceOptions.ResourceStorageModeManaged); - var span = new Span(buffer.Contents.ToPointer(), ClearColorBufferSize); - depthValue.CopyTo(span); - - buffer.DidModifyRange(new NSRange - { - location = 0, - length = ClearColorBufferSize - }); - - var handle = buffer.NativePtr; - var range = new BufferRange(Unsafe.As(ref handle), 0, ClearColorBufferSize); + IntPtr ptr = new(&depthValue); // Save current state _pipeline.SaveState(); - _pipeline.SetUniformBuffers([new BufferAssignment(0, range)]); - _pipeline.SetProgram(_programDepthStencilClear); _pipeline.SetFaceCulling(false, Face.Front); _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always)); // _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask)); + _pipeline.GetOrCreateRenderEncoder().SetFragmentBytes(ptr, ClearDepthBufferSize, 0); _pipeline.Draw(4, 1, 0, 0); // Restore previous state diff --git a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs index 2b535c579..55d25c8ae 100644 --- a/src/Ryujinx.Graphics.Metal/MetalRenderer.cs +++ b/src/Ryujinx.Graphics.Metal/MetalRenderer.cs @@ -186,8 +186,8 @@ namespace Ryujinx.Graphics.Metal maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength, maximumSupportedAnisotropy: 0, shaderSubgroupSize: 256, - storageBufferOffsetAlignment: 0, - textureBufferOffsetAlignment: 0, + storageBufferOffsetAlignment: 16, + textureBufferOffsetAlignment: 16, gatherBiasPrecision: 0 ); } diff --git a/src/Ryujinx.Graphics.Metal/Pipeline.cs b/src/Ryujinx.Graphics.Metal/Pipeline.cs index b3be4124d..2734e6930 100644 --- a/src/Ryujinx.Graphics.Metal/Pipeline.cs +++ b/src/Ryujinx.Graphics.Metal/Pipeline.cs @@ -51,6 +51,11 @@ namespace Ryujinx.Graphics.Metal _encoderStateManager.SaveState(); } + public void SaveAndResetState() + { + _encoderStateManager.SaveAndResetState(); + } + public void RestoreState() { _encoderStateManager.RestoreState(); @@ -242,7 +247,7 @@ namespace Ryujinx.Graphics.Metal public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) { - _helperShader.ClearDepthStencil([depthValue], depthMask, stencilValue, stencilMask); + _helperShader.ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask); } public void CommandBufferBarrier() diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/IoMap.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/IoMap.cs index d0c198904..b98db242d 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/IoMap.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/IoMap.cs @@ -19,18 +19,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions { IoVariable.BaseInstance => ("base_instance", AggregateType.S32), IoVariable.BaseVertex => ("base_vertex", AggregateType.S32), + IoVariable.CtaId => ("threadgroup_position_in_grid", AggregateType.Vector3 | AggregateType.U32), IoVariable.ClipDistance => ("clip_distance", AggregateType.Array | AggregateType.FP32), IoVariable.FragmentOutputColor => ($"out.color{location}", AggregateType.Vector4 | AggregateType.FP32), IoVariable.FragmentOutputDepth => ("out.depth", AggregateType.FP32), IoVariable.FrontFacing => ("front_facing", AggregateType.Bool), + IoVariable.GlobalId => ("thread_position_in_grid", AggregateType.Vector3 | AggregateType.U32), IoVariable.InstanceId => ("instance_id", AggregateType.S32), + IoVariable.InvocationId => ("INVOCATION_ID", AggregateType.S32), IoVariable.PointCoord => ("point_coord", AggregateType.Vector2), IoVariable.PointSize => ("out.point_size", AggregateType.FP32), IoVariable.Position => ("out.position", AggregateType.Vector4 | AggregateType.FP32), IoVariable.PrimitiveId => ("primitive_id", AggregateType.S32), IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch), + IoVariable.ThreadId => ("thread_position_in_threadgroup", AggregateType.Vector3 | AggregateType.U32), IoVariable.VertexId => ("vertex_id", AggregateType.S32), - IoVariable.GlobalId => ("global_id", AggregateType.Vector3 | AggregateType.U32), // gl_VertexIndex does not have a direct equivalent in MSL IoVariable.VertexIndex => ("vertex_index", AggregateType.U32), IoVariable.ViewportIndex => ("viewport_array_index", AggregateType.S32),