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 <isaacryu@icloud.com>
This commit is contained in:
SamoZ256 2024-05-27 13:58:03 +02:00 committed by Isaac Marovitz
parent 20b1f6a6ee
commit 1f91c74a95
8 changed files with 91 additions and 69 deletions

View file

@ -14,5 +14,7 @@ namespace Ryujinx.Graphics.Metal
public const int MaxVertexAttributes = 16; public const int MaxVertexAttributes = 16;
// TODO: Check this value // TODO: Check this value
public const int MaxVertexLayouts = 16; public const int MaxVertexLayouts = 16;
public const int MaxTextures = 31;
public const int MaxSamplers = 31;
} }
} }

View file

@ -31,11 +31,11 @@ namespace Ryujinx.Graphics.Metal
public MTLFunction? VertexFunction = null; public MTLFunction? VertexFunction = null;
public MTLFunction? FragmentFunction = null; public MTLFunction? FragmentFunction = null;
public Dictionary<ulong, MTLTexture> FragmentTextures = new(); public MTLTexture[] FragmentTextures = new MTLTexture[Constants.MaxTextures];
public Dictionary<ulong, MTLSamplerState> FragmentSamplers = new(); public MTLSamplerState[] FragmentSamplers = new MTLSamplerState[Constants.MaxSamplers];
public Dictionary<ulong, MTLTexture> VertexTextures = new(); public MTLTexture[] VertexTextures = new MTLTexture[Constants.MaxTextures];
public Dictionary<ulong, MTLSamplerState> VertexSamplers = new(); public MTLSamplerState[] VertexSamplers = new MTLSamplerState[Constants.MaxSamplers];
public List<BufferInfo> UniformBuffers = []; public List<BufferInfo> UniformBuffers = [];
public List<BufferInfo> StorageBuffers = []; public List<BufferInfo> StorageBuffers = [];
@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Metal
public PrimitiveTopology Topology = PrimitiveTopology.Triangles; public PrimitiveTopology Topology = PrimitiveTopology.Triangles;
public MTLCullMode CullMode = MTLCullMode.None; public MTLCullMode CullMode = MTLCullMode.None;
public MTLWinding Winding = MTLWinding.Clockwise; public MTLWinding Winding = MTLWinding.CounterClockwise;
public MTLViewport[] Viewports = []; public MTLViewport[] Viewports = [];
public MTLScissorRect[] Scissors = []; public MTLScissorRect[] Scissors = [];
@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Metal
// Changes to attachments take recreation! // Changes to attachments take recreation!
public Texture DepthStencil = default; public Texture DepthStencil = default;
public Texture[] RenderTargets = new Texture[Constants.MaxColorAttachments]; public Texture[] RenderTargets = new Texture[Constants.MaxColorAttachments];
public Dictionary<int, BlendDescriptor> BlendDescriptors = new(); public BlendDescriptor?[] BlendDescriptors = new BlendDescriptor?[Constants.MaxColorAttachments];
public ColorF BlendColor = new(); public ColorF BlendColor = new();
public VertexBufferDescriptor[] VertexBuffers = []; public VertexBufferDescriptor[] VertexBuffers = [];
@ -74,5 +74,20 @@ namespace Ryujinx.Graphics.Metal
public DirtyFlags Dirty = new(); public DirtyFlags Dirty = new();
public EncoderState() { } 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;
}
} }
} }

View file

@ -44,9 +44,16 @@ namespace Ryujinx.Graphics.Metal
_depthStencilCache.Dispose(); _depthStencilCache.Dispose();
} }
public readonly void SaveState() public void SaveState()
{ {
_backStates.Push(_currentState); _backStates.Push(_currentState);
_currentState = _currentState.Clone();
}
public void SaveAndResetState()
{
_backStates.Push(_currentState);
_currentState = new();
} }
public void RestoreState() public void RestoreState()
@ -65,6 +72,9 @@ namespace Ryujinx.Graphics.Metal
SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true); SetBuffers(renderCommandEncoder, _currentState.StorageBuffers, true);
SetCullMode(renderCommandEncoder); SetCullMode(renderCommandEncoder);
SetFrontFace(renderCommandEncoder); SetFrontFace(renderCommandEncoder);
// Mark the other state as dirty
_currentState.Dirty.MarkAll();
} }
else else
{ {
@ -184,8 +194,9 @@ namespace Ryujinx.Graphics.Metal
pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha; pipelineAttachment.SourceRGBBlendFactor = MTLBlendFactor.SourceAlpha;
pipelineAttachment.DestinationRGBBlendFactor = MTLBlendFactor.OneMinusSourceAlpha; 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.SetBlendingEnabled(blendDescriptor.Enable);
pipelineAttachment.AlphaBlendOperation = blendDescriptor.AlphaOp.Convert(); pipelineAttachment.AlphaBlendOperation = blendDescriptor.AlphaOp.Convert();
pipelineAttachment.RgbBlendOperation = blendDescriptor.ColorOp.Convert(); pipelineAttachment.RgbBlendOperation = blendDescriptor.ColorOp.Convert();
@ -469,12 +480,13 @@ namespace Ryujinx.Graphics.Metal
for (int i = 0; i < viewports.Length; i++) for (int i = 0; i < viewports.Length; i++)
{ {
var viewport = viewports[i]; var viewport = viewports[i];
// Y coordinate is inverted
_currentState.Viewports[i] = new MTLViewport _currentState.Viewports[i] = new MTLViewport
{ {
originX = viewport.Region.X, originX = viewport.Region.X,
originY = viewport.Region.Y, originY = viewport.Region.Y + viewport.Region.Height,
width = viewport.Region.Width, width = viewport.Region.Width,
height = viewport.Region.Height, height = -viewport.Region.Height,
znear = Clamp(viewport.DepthNear), znear = Clamp(viewport.DepthNear),
zfar = Clamp(viewport.DepthFar) zfar = Clamp(viewport.DepthFar)
}; };
@ -708,33 +720,41 @@ namespace Ryujinx.Graphics.Metal
renderCommandEncoder.SetFrontFacingWinding(_currentState.Winding); renderCommandEncoder.SetFrontFacingWinding(_currentState.Winding);
} }
private static void SetTextureAndSampler(MTLRenderCommandEncoder renderCommandEncoder, ShaderStage stage, Dictionary<ulong, MTLTexture> textures, Dictionary<ulong, MTLSamplerState> 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++)
{
var texture = textures[i];
if (texture != IntPtr.Zero)
{ {
switch (stage) switch (stage)
{ {
case ShaderStage.Vertex: case ShaderStage.Vertex:
renderCommandEncoder.SetVertexTexture(texture.Value, texture.Key); renderCommandEncoder.SetVertexTexture(texture, (ulong)i);
break; break;
case ShaderStage.Fragment: case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentTexture(texture.Value, texture.Key); renderCommandEncoder.SetFragmentTexture(texture, (ulong)i);
break; break;
} }
} }
}
foreach (var sampler in samplers) for (int i = 0; i < samplers.Length; i++)
{
var sampler = samplers[i];
if (sampler != IntPtr.Zero)
{ {
switch (stage) switch (stage)
{ {
case ShaderStage.Vertex: case ShaderStage.Vertex:
renderCommandEncoder.SetVertexSamplerState(sampler.Value, sampler.Key); renderCommandEncoder.SetVertexSamplerState(sampler, (ulong)i);
break; break;
case ShaderStage.Fragment: case ShaderStage.Fragment:
renderCommandEncoder.SetFragmentSamplerState(sampler.Value, sampler.Key); renderCommandEncoder.SetFragmentSamplerState(sampler, (ulong)i);
break; break;
} }
} }
} }
} }
}
} }

View file

@ -93,10 +93,11 @@ namespace Ryujinx.Graphics.Metal
public static MTLWinding Convert(this FrontFace frontFace) 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 return frontFace switch
{ {
FrontFace.Clockwise => MTLWinding.Clockwise, FrontFace.Clockwise => MTLWinding.CounterClockwise,
FrontFace.CounterClockwise => MTLWinding.CounterClockwise, FrontFace.CounterClockwise => MTLWinding.Clockwise,
_ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise) _ => LogInvalidAndReturn(frontFace, nameof(FrontFace), MTLWinding.Clockwise)
}; };
} }

View file

@ -2,11 +2,9 @@ using Ryujinx.Common;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using SharpMetal.Foundation;
using SharpMetal.Metal; using SharpMetal.Metal;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
namespace Ryujinx.Graphics.Metal namespace Ryujinx.Graphics.Metal
@ -70,11 +68,9 @@ namespace Ryujinx.Graphics.Metal
}); });
// Save current state // Save current state
_pipeline.SaveState(); _pipeline.SaveAndResetState();
_pipeline.SetProgram(_programColorBlit); _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 // Viewport and scissor needs to be set before render pass begin so as not to bind the old ones
_pipeline.SetViewports([]); _pipeline.SetViewports([]);
_pipeline.SetScissors([]); _pipeline.SetScissors([]);
@ -93,29 +89,21 @@ namespace Ryujinx.Graphics.Metal
{ {
const int ClearColorBufferSize = 16; const int ClearColorBufferSize = 16;
var buffer = _device.NewBuffer(ClearColorBufferSize, MTLResourceOptions.ResourceStorageModeManaged);
var span = new Span<float>(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<IntPtr, BufferHandle>(ref handle), 0, ClearColorBufferSize);
// Save current state // Save current state
_pipeline.SaveState(); _pipeline.SaveState();
_pipeline.SetUniformBuffers([new BufferAssignment(0, range)]);
_pipeline.SetProgram(_programsColorClear[index]); _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.SetFaceCulling(false, Face.Front);
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
// _pipeline.SetRenderTargetColorMasks([componentMask]); // _pipeline.SetRenderTargetColorMasks([componentMask]);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
fixed (float* ptr = clearColor)
{
_pipeline.GetOrCreateRenderEncoder().SetFragmentBytes((IntPtr)ptr, ClearColorBufferSize, 0);
}
_pipeline.Draw(4, 1, 0, 0); _pipeline.Draw(4, 1, 0, 0);
// Restore previous state // Restore previous state
@ -123,37 +111,25 @@ namespace Ryujinx.Graphics.Metal
} }
public unsafe void ClearDepthStencil( public unsafe void ClearDepthStencil(
ReadOnlySpan<float> depthValue, float depthValue,
bool depthMask, bool depthMask,
int stencilValue, int stencilValue,
int stencilMask) int stencilMask)
{ {
const int ClearColorBufferSize = 16; const int ClearDepthBufferSize = 4;
var buffer = _device.NewBuffer(ClearColorBufferSize, MTLResourceOptions.ResourceStorageModeManaged); IntPtr ptr = new(&depthValue);
var span = new Span<float>(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<IntPtr, BufferHandle>(ref handle), 0, ClearColorBufferSize);
// Save current state // Save current state
_pipeline.SaveState(); _pipeline.SaveState();
_pipeline.SetUniformBuffers([new BufferAssignment(0, range)]);
_pipeline.SetProgram(_programDepthStencilClear); _pipeline.SetProgram(_programDepthStencilClear);
_pipeline.SetFaceCulling(false, Face.Front); _pipeline.SetFaceCulling(false, Face.Front);
_pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always));
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always)); _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always));
// _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask)); // _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xFF, stencilMask));
_pipeline.GetOrCreateRenderEncoder().SetFragmentBytes(ptr, ClearDepthBufferSize, 0);
_pipeline.Draw(4, 1, 0, 0); _pipeline.Draw(4, 1, 0, 0);
// Restore previous state // Restore previous state

View file

@ -186,8 +186,8 @@ namespace Ryujinx.Graphics.Metal
maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength, maximumComputeSharedMemorySize: (int)_device.MaxThreadgroupMemoryLength,
maximumSupportedAnisotropy: 0, maximumSupportedAnisotropy: 0,
shaderSubgroupSize: 256, shaderSubgroupSize: 256,
storageBufferOffsetAlignment: 0, storageBufferOffsetAlignment: 16,
textureBufferOffsetAlignment: 0, textureBufferOffsetAlignment: 16,
gatherBiasPrecision: 0 gatherBiasPrecision: 0
); );
} }

View file

@ -51,6 +51,11 @@ namespace Ryujinx.Graphics.Metal
_encoderStateManager.SaveState(); _encoderStateManager.SaveState();
} }
public void SaveAndResetState()
{
_encoderStateManager.SaveAndResetState();
}
public void RestoreState() public void RestoreState()
{ {
_encoderStateManager.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) 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() public void CommandBufferBarrier()

View file

@ -19,18 +19,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
{ {
IoVariable.BaseInstance => ("base_instance", AggregateType.S32), IoVariable.BaseInstance => ("base_instance", AggregateType.S32),
IoVariable.BaseVertex => ("base_vertex", 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.ClipDistance => ("clip_distance", AggregateType.Array | AggregateType.FP32),
IoVariable.FragmentOutputColor => ($"out.color{location}", AggregateType.Vector4 | AggregateType.FP32), IoVariable.FragmentOutputColor => ($"out.color{location}", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputDepth => ("out.depth", AggregateType.FP32), IoVariable.FragmentOutputDepth => ("out.depth", AggregateType.FP32),
IoVariable.FrontFacing => ("front_facing", AggregateType.Bool), IoVariable.FrontFacing => ("front_facing", AggregateType.Bool),
IoVariable.GlobalId => ("thread_position_in_grid", AggregateType.Vector3 | AggregateType.U32),
IoVariable.InstanceId => ("instance_id", AggregateType.S32), IoVariable.InstanceId => ("instance_id", AggregateType.S32),
IoVariable.InvocationId => ("INVOCATION_ID", AggregateType.S32),
IoVariable.PointCoord => ("point_coord", AggregateType.Vector2), IoVariable.PointCoord => ("point_coord", AggregateType.Vector2),
IoVariable.PointSize => ("out.point_size", AggregateType.FP32), IoVariable.PointSize => ("out.point_size", AggregateType.FP32),
IoVariable.Position => ("out.position", AggregateType.Vector4 | AggregateType.FP32), IoVariable.Position => ("out.position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => ("primitive_id", AggregateType.S32), IoVariable.PrimitiveId => ("primitive_id", AggregateType.S32),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch), 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.VertexId => ("vertex_id", AggregateType.S32),
IoVariable.GlobalId => ("global_id", AggregateType.Vector3 | AggregateType.U32),
// gl_VertexIndex does not have a direct equivalent in MSL // gl_VertexIndex does not have a direct equivalent in MSL
IoVariable.VertexIndex => ("vertex_index", AggregateType.U32), IoVariable.VertexIndex => ("vertex_index", AggregateType.U32),
IoVariable.ViewportIndex => ("viewport_array_index", AggregateType.S32), IoVariable.ViewportIndex => ("viewport_array_index", AggregateType.S32),