diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index 7a7d83cc..bfc432b1 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -55,11 +55,15 @@ namespace Ryujinx.Graphics.GAL void SetImage(int binding, ITexture texture, Format imageFormat); + void SetLineParameters(float width, bool smooth); + void SetLogicOpState(bool enable, LogicalOp op); - void SetLineParameters(float width, bool smooth); + void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel); void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin); + void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode); + void SetPrimitiveRestart(bool enable, int index); void SetPrimitiveTopology(PrimitiveTopology topology); diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 82a75ea7..47ceeb7d 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -181,8 +181,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading SetLineParametersCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.SetLogicOpState] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => SetLogicOpStateCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetPatchParameters] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetPatchParametersCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.SetPointParameters] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => SetPointParametersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetPolygonMode] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetPolygonModeCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.SetPrimitiveRestart] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => SetPrimitiveRestartCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.SetPrimitiveTopology] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 0761a7f0..ac73a3fe 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -72,7 +72,9 @@ SetIndexBuffer, SetLineParameters, SetLogicOpState, + SetPatchParameters, SetPointParameters, + SetPolygonMode, SetPrimitiveRestart, SetPrimitiveTopology, SetProgram, diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs new file mode 100644 index 00000000..7847e8d0 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPatchParametersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetPatchParameters; + private int _vertices; + private Array4 _defaultOuterLevel; + private Array2 _defaultInnerLevel; + + public void Set(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _vertices = vertices; + defaultOuterLevel.CopyTo(_defaultOuterLevel.ToSpan()); + defaultInnerLevel.CopyTo(_defaultInnerLevel.ToSpan()); + } + + public static void Run(ref SetPatchParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPatchParameters(command._vertices, command._defaultOuterLevel.ToSpan(), command._defaultInnerLevel.ToSpan()); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs new file mode 100644 index 00000000..6de78f04 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPolygonModeCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetPolygonMode; + private PolygonMode _frontMode; + private PolygonMode _backMode; + + public void Set(PolygonMode frontMode, PolygonMode backMode) + { + _frontMode = frontMode; + _backMode = backMode; + } + + public static void Run(ref SetPolygonModeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPolygonMode(command._frontMode, command._backMode); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index 0f523481..3c39a77f 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -179,12 +179,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _renderer.New().Set(vertices, defaultOuterLevel, defaultInnerLevel); + _renderer.QueueCommand(); + } + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) { _renderer.New().Set(size, isProgramPointSize, enablePointSprite, origin); _renderer.QueueCommand(); } + public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode) + { + _renderer.New().Set(frontMode, backMode); + _renderer.QueueCommand(); + } + public void SetPrimitiveRestart(bool enable, int index) { _renderer.New().Set(enable, index); diff --git a/Ryujinx.Graphics.GAL/PolygonMode.cs b/Ryujinx.Graphics.GAL/PolygonMode.cs new file mode 100644 index 00000000..d6110c1b --- /dev/null +++ b/Ryujinx.Graphics.GAL/PolygonMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum PolygonMode + { + Point = 0x1b00, + Line = 0x1b01, + Fill = 0x1b02 + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs index 8469f1ae..00015c40 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -129,7 +129,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute _state.State.SetTexHeaderPoolCMaximumIndex, _state.State.SetBindlessTextureConstantBufferSlotSelect, false, - PrimitiveTopology.Points); + PrimitiveTopology.Points, + default); ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader( _channel, diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index f9d16803..4a5633c9 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -72,6 +72,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed nameof(ThreedClassState.VertexBufferState), nameof(ThreedClassState.VertexBufferEndAddress)), + new StateUpdateCallbackEntry(UpdateTessellationState, + nameof(ThreedClassState.TessOuterLevel), + nameof(ThreedClassState.TessInnerLevel), + nameof(ThreedClassState.PatchVertices)), + new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)), new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)), @@ -100,6 +105,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed nameof(ThreedClassState.ViewportExtents), nameof(ThreedClassState.YControl)), + new StateUpdateCallbackEntry(UpdatePolygonMode, + nameof(ThreedClassState.PolygonModeFront), + nameof(ThreedClassState.PolygonModeBack)), + new StateUpdateCallbackEntry(UpdateDepthBiasState, nameof(ThreedClassState.DepthBiasState), nameof(ThreedClassState.DepthBiasFactor), @@ -259,6 +268,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } } + /// + /// Updates tessellation state based on the guest GPU state. + /// + private void UpdateTessellationState() + { + _context.Renderer.Pipeline.SetPatchParameters( + _state.State.PatchVertices, + _state.State.TessOuterLevel.ToSpan(), + _state.State.TessInnerLevel.ToSpan()); + } + /// /// Updates transform feedback buffer state based on the guest GPU state. /// @@ -544,6 +564,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _context.Renderer.Pipeline.SetViewports(0, viewports); } + /// + /// Updates polygon mode state based on current GPU state. + /// + private void UpdatePolygonMode() + { + _context.Renderer.Pipeline.SetPolygonMode(_state.State.PolygonModeFront, _state.State.PolygonModeBack); + } + /// /// Updates host depth bias (also called polygon offset) state based on current GPU state. /// @@ -949,7 +977,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _state.State.TexturePoolState.MaximumId, (int)_state.State.TextureBufferIndex, _state.State.EarlyZForce, - _drawState.Topology); + _drawState.Topology, + _state.State.TessMode); ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses); diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs index a6392e3d..58bc0957 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; using System; namespace Ryujinx.Graphics.Gpu.Engine.Threed @@ -19,6 +20,43 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed Fragment } + /// + /// Tessellation mode. + /// + struct TessMode + { +#pragma warning disable CS0649 + public uint Packed; +#pragma warning restore CS0649 + + /// + /// Unpacks the tessellation abstract patch type. + /// + /// Abtract patch type + public TessPatchType UnpackPatchType() + { + return (TessPatchType)(Packed & 3); + } + + /// + /// Unpacks the spacing between tessellated vertices of the patch. + /// + /// Spacing between tessellated vertices + public TessSpacing UnpackSpacing() + { + return (TessSpacing)((Packed >> 4) & 3); + } + + /// + /// Unpacks the primitive winding order. + /// + /// True if clockwise, false if counter-clockwise + public bool UnpackCw() + { + return (Packed & (1 << 8)) != 0; + } + } + /// /// Transform feedback buffer state. /// @@ -661,7 +699,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed public Boolean32 EarlyZForce; public fixed uint Reserved214[45]; public uint SyncpointAction; - public fixed uint Reserved2CC[44]; + public fixed uint Reserved2CC[21]; + public TessMode TessMode; + public Array4 TessOuterLevel; + public Array2 TessInnerLevel; + public fixed uint Reserved33C[16]; public Boolean32 RasterizeEnable; public Array4 TfBufferState; public fixed uint Reserved400[192]; @@ -679,9 +721,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed public float ClearDepthValue; public fixed uint ReservedD94[3]; public uint ClearStencilValue; - public fixed uint ReservedDA4[7]; + public fixed uint ReservedDA4[2]; + public PolygonMode PolygonModeFront; + public PolygonMode PolygonModeBack; + public Boolean32 PolygonSmoothEnable; + public fixed uint ReservedDB8[2]; public DepthBiasState DepthBiasState; - public fixed uint ReservedDCC[5]; + public int PatchVertices; + public fixed uint ReservedDD0[4]; public uint TextureBarrier; public fixed uint ReservedDE4[7]; public Array16 ScissorState; diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs index 33da42db..09107346 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs @@ -349,6 +349,26 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache return flags; } + /// + /// Packs the tessellation parameters from the gpu accessor. + /// + /// The gpu accessor + /// The packed tessellation parameters + private static byte GetTessellationModePacked(IGpuAccessor gpuAccessor) + { + byte value; + + value = (byte)((int)gpuAccessor.QueryTessPatchType() & 3); + value |= (byte)(((int)gpuAccessor.QueryTessSpacing() & 3) << 2); + + if (gpuAccessor.QueryTessCw()) + { + value |= 0x10; + } + + return value; + } + /// /// Create a new instance of from an gpu accessor. /// @@ -364,6 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(), ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(), PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(), + TessellationModePacked = GetTessellationModePacked(gpuAccessor), StateFlags = GetGpuStateFlags(gpuAccessor) }; } diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs index 610b2da1..2e044750 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs @@ -49,10 +49,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition /// public InputTopology PrimitiveTopology; + /// + /// Tessellation parameters (packed to fit on a byte). + /// + public byte TessellationModePacked; + /// /// Unused/reserved. /// - public ushort Reserved2; + public byte Reserved2; /// /// GPU boolean state that can influence shader compilation. diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs index 3a52b2fe..21d08823 100644 --- a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs @@ -134,6 +134,33 @@ namespace Ryujinx.Graphics.Gpu.Shader return _header.PrimitiveTopology; } + /// + /// Queries the tessellation evaluation shader primitive winding order. + /// + /// True if the primitive winding order is clockwise, false if counter-clockwise + public bool QueryTessCw() + { + return (_header.TessellationModePacked & 0x10) != 0; + } + + /// + /// Queries the tessellation evaluation shader abstract patch type. + /// + /// Abstract patch type + public TessPatchType QueryTessPatchType() + { + return (TessPatchType)(_header.TessellationModePacked & 3); + } + + /// + /// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch. + /// + /// Spacing between tessellated vertices of the patch + public TessSpacing QueryTessSpacing() + { + return (TessSpacing)((_header.TessellationModePacked >> 2) & 3); + } + /// /// Gets the texture descriptor for a given texture on the pool. /// diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 50e24b97..64604a99 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -168,10 +168,31 @@ namespace Ryujinx.Graphics.Gpu.Shader PrimitiveTopology.TriangleFan => InputTopology.Triangles, PrimitiveTopology.TrianglesAdjacency or PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency, - _ => InputTopology.Points, + PrimitiveTopology.Patches => _state.TessellationMode.UnpackPatchType() == TessPatchType.Isolines + ? InputTopology.Lines + : InputTopology.Triangles, + _ => InputTopology.Points }; } + /// + /// Queries the tessellation evaluation shader primitive winding order. + /// + /// True if the primitive winding order is clockwise, false if counter-clockwise + public bool QueryTessCw() => _state.TessellationMode.UnpackCw(); + + /// + /// Queries the tessellation evaluation shader abstract patch type. + /// + /// Abstract patch type + public TessPatchType QueryTessPatchType() => _state.TessellationMode.UnpackPatchType(); + + /// + /// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch. + /// + /// Spacing between tessellated vertices of the patch + public TessSpacing QueryTessSpacing() => _state.TessellationMode.UnpackSpacing(); + /// /// Gets the texture descriptor for a given texture on the pool. /// diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs index 8d817113..ebbf3b69 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; namespace Ryujinx.Graphics.Gpu.Shader { @@ -32,6 +33,11 @@ namespace Ryujinx.Graphics.Gpu.Shader /// public PrimitiveTopology Topology { get; } + /// + /// Tessellation mode. + /// + public TessMode TessellationMode { get; } + /// /// Creates a new instance of the GPU accessor state. /// @@ -40,18 +46,21 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Constant buffer slot where the texture handles are located /// Early Z force enable /// Primitive topology + /// Tessellation mode public GpuAccessorState( ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex, bool earlyZForce, - PrimitiveTopology topology) + PrimitiveTopology topology, + TessMode tessellationMode) { TexturePoolGpuVa = texturePoolGpuVa; TexturePoolMaximumId = texturePoolMaximumId; TextureBufferIndex = textureBufferIndex; EarlyZForce = earlyZForce; Topology = topology; + TessellationMode = tessellationMode; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index e69e7dcb..f2180820 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Version of the codegen (to be changed when codegen or guest format change). /// - private const ulong ShaderCodeGenVersion = 2702; + private const ulong ShaderCodeGenVersion = 2534; // Progress reporting helpers private volatile int _shaderCount; diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs index cc3db003..ccdbcfec 100644 --- a/Ryujinx.Graphics.OpenGL/EnumConversion.cs +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -290,6 +290,23 @@ namespace Ryujinx.Graphics.OpenGL return TextureMinFilter.Nearest; } + public static OpenTK.Graphics.OpenGL.PolygonMode Convert(this GAL.PolygonMode mode) + { + switch (mode) + { + case GAL.PolygonMode.Point: + return OpenTK.Graphics.OpenGL.PolygonMode.Point; + case GAL.PolygonMode.Line: + return OpenTK.Graphics.OpenGL.PolygonMode.Line; + case GAL.PolygonMode.Fill: + return OpenTK.Graphics.OpenGL.PolygonMode.Fill; + } + + Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.PolygonMode)} enum value: {mode}."); + + return OpenTK.Graphics.OpenGL.PolygonMode.Fill; + } + public static PrimitiveType Convert(this PrimitiveTopology topology) { switch (topology) diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index dd07afcf..d0a509b4 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -830,6 +830,21 @@ namespace Ryujinx.Graphics.OpenGL GL.LineWidth(width); } + public unsafe void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + GL.PatchParameter(PatchParameterInt.PatchVertices, vertices); + + fixed (float* pOuterLevel = defaultOuterLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultOuterLevel, pOuterLevel); + } + + fixed (float* pInnerLevel = defaultInnerLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultInnerLevel, pInnerLevel); + } + } + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) { // GL_POINT_SPRITE was deprecated in core profile 3.2+ and causes GL_INVALID_ENUM when set. @@ -861,6 +876,19 @@ namespace Ryujinx.Graphics.OpenGL GL.PointSize(Math.Max(float.Epsilon, size)); } + public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode) + { + if (frontMode == backMode) + { + GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert()); + } + else + { + GL.PolygonMode(MaterialFace.Front, frontMode.Convert()); + GL.PolygonMode(MaterialFace.Back, backMode.Convert()); + } + } + public void SetPrimitiveRestart(bool enable, int index) { if (!enable) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index e29ff486..3e2e51b0 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -136,6 +136,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine(); } + else if (context.Config.Stage == ShaderStage.TessellationControl) + { + int threadsPerInputPrimitive = context.Config.ThreadsPerInputPrimitive; + + context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;"); + context.AppendLine(); + } + else if (context.Config.Stage == ShaderStage.TessellationEvaluation) + { + string patchType = context.Config.GpuAccessor.QueryTessPatchType().ToGlsl(); + string spacing = context.Config.GpuAccessor.QueryTessSpacing().ToGlsl(); + string windingOrder = context.Config.GpuAccessor.QueryTessCw() ? "cw" : "ccw"; + + context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;"); + context.AppendLine(); + } if (context.Config.UsedInputAttributes != 0 || context.Config.GpPassthrough) { @@ -150,6 +166,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine(); } + + if (context.Config.UsedInputAttributesPerPatch != 0) + { + DeclareInputAttributesPerPatch(context, context.Config.UsedInputAttributesPerPatch); + + context.AppendLine(); + } + + if (context.Config.UsedOutputAttributesPerPatch != 0) + { + DeclareUsedOutputAttributesPerPatch(context, context.Config.UsedOutputAttributesPerPatch); + + context.AppendLine(); + } } else { @@ -424,17 +454,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl while (usedAttributes != 0) { int index = BitOperations.TrailingZeroCount(usedAttributes); - DeclareInputAttribute(context, info, index); - usedAttributes &= ~(1 << index); } } } + private static void DeclareInputAttributesPerPatch(CodeGenContext context, int usedAttributes) + { + while (usedAttributes != 0) + { + int index = BitOperations.TrailingZeroCount(usedAttributes); + DeclareInputAttributePerPatch(context, index); + usedAttributes &= ~(1 << index); + } + } + private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr) { - string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty; + string suffix = OperandManager.IsArrayAttribute(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty; string iq = string.Empty; if (context.Config.Stage == ShaderStage.Fragment) @@ -465,6 +503,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } } + private static void DeclareInputAttributePerPatch(CodeGenContext context, int attr) + { + string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}"; + + context.AppendLine($"patch in vec4 {name};"); + } + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) { if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing)) @@ -477,9 +522,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl while (usedAttributes != 0) { int index = BitOperations.TrailingZeroCount(usedAttributes); - DeclareOutputAttribute(context, index); - usedAttributes &= ~(1 << index); } } @@ -487,7 +530,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl private static void DeclareOutputAttribute(CodeGenContext context, int attr) { - string name = $"{DefaultNames.OAttributePrefix}{attr}"; + string suffix = OperandManager.IsArrayAttribute(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty; + string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}"; if ((context.Config.Options.Flags & TranslationFlags.Feedback) != 0) { @@ -504,6 +548,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } } + private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, int usedAttributes) + { + while (usedAttributes != 0) + { + int index = BitOperations.TrailingZeroCount(usedAttributes); + DeclareOutputAttributePerPatch(context, index); + usedAttributes &= ~(1 << index); + } + } + + private static void DeclareOutputAttributePerPatch(CodeGenContext context, int attr) + { + string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}"; + + context.AppendLine($"patch out vec4 {name};"); + } + private static void DeclareSupportUniformBlock(CodeGenContext context, bool isFragment, int scaleElements) { if (!isFragment && scaleElements == 0) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs index eaf1050c..47350408 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public const string SamplerNamePrefix = "tex"; public const string ImageNamePrefix = "img"; + public const string PerPatchAttributePrefix = "patch_attr_"; public const string IAttributePrefix = "in_attr"; public const string OAttributePrefix = "out_attr"; diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs index 2d6ede0a..077737c8 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -126,9 +126,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl string dest; - if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute) + if (assignment.Destination is AstOperand operand && operand.Type.IsAttribute()) { - dest = OperandManager.GetOutAttributeName(operand.Value, context.Config); + bool perPatch = operand.Type == OperandType.AttributePerPatch; + dest = OperandManager.GetOutAttributeName(operand.Value, context.Config, perPatch); } else { diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index edaacd3e..5e46bb46 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -200,7 +200,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (src2 is AstOperand operand && operand.Type == OperandType.Constant) { - return OperandManager.GetAttributeName(baseAttr.Value + (operand.Value << 2), context.Config, isOutAttr: false, indexExpr); + int attrOffset = baseAttr.Value + (operand.Value << 2); + return OperandManager.GetAttributeName(attrOffset, context.Config, perPatch: false, isOutAttr: false, indexExpr); } else { @@ -326,7 +327,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (src2 is AstOperand operand && operand.Type == OperandType.Constant) { - attrName = OperandManager.GetAttributeName(baseAttr.Value + (operand.Value << 2), context.Config, isOutAttr: true); + int attrOffset = baseAttr.Value + (operand.Value << 2); + attrName = OperandManager.GetAttributeName(attrOffset, context.Config, perPatch: false, isOutAttr: true); } else { diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index d35525f9..9680df27 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -29,27 +29,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl private static Dictionary _builtInAttributes = new Dictionary() { - { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) }, - { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, - { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, - { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) }, - { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) }, - { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) }, - { AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", VariableType.F32) }, - { AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", VariableType.F32) }, - { AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", VariableType.F32) }, - { AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", VariableType.F32) }, - { AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", VariableType.F32) }, - { AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", VariableType.F32) }, - { AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", VariableType.F32) }, - { AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", VariableType.F32) }, - { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) }, - { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, - { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, - { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, - { AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", VariableType.S32) }, - { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, - { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, + { AttributeConsts.TessLevelOuter0, new BuiltInAttribute("gl_TessLevelOuter[0]", VariableType.F32) }, + { AttributeConsts.TessLevelOuter1, new BuiltInAttribute("gl_TessLevelOuter[1]", VariableType.F32) }, + { AttributeConsts.TessLevelOuter2, new BuiltInAttribute("gl_TessLevelOuter[2]", VariableType.F32) }, + { AttributeConsts.TessLevelOuter3, new BuiltInAttribute("gl_TessLevelOuter[3]", VariableType.F32) }, + { AttributeConsts.TessLevelInner0, new BuiltInAttribute("gl_TessLevelInner[0]", VariableType.F32) }, + { AttributeConsts.TessLevelInner1, new BuiltInAttribute("gl_TessLevelInner[1]", VariableType.F32) }, + { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) }, + { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, + { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, + { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) }, + { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) }, + { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) }, + { AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", VariableType.F32) }, + { AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", VariableType.F32) }, + { AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", VariableType.F32) }, + { AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", VariableType.F32) }, + { AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", VariableType.F32) }, + { AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", VariableType.F32) }, + { AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", VariableType.F32) }, + { AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", VariableType.F32) }, + { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) }, + { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, + { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, + { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, + { AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", VariableType.S32) }, + { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, + { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, // Special. { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }, @@ -61,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) }, { AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) }, { AttributeConsts.LaneId, new BuiltInAttribute(null, VariableType.U32) }, + { AttributeConsts.InvocationId, new BuiltInAttribute("gl_InvocationID", VariableType.S32) }, + { AttributeConsts.PrimitiveId, new BuiltInAttribute("gl_PrimitiveID", VariableType.S32) }, + { AttributeConsts.PatchVerticesIn, new BuiltInAttribute("gl_PatchVerticesIn", VariableType.S32) }, { AttributeConsts.EqMask, new BuiltInAttribute(null, VariableType.U32) }, { AttributeConsts.GeMask, new BuiltInAttribute(null, VariableType.U32) }, { AttributeConsts.GtMask, new BuiltInAttribute(null, VariableType.U32) }, @@ -99,19 +108,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return operand.Type switch { OperandType.Argument => GetArgumentName(operand.Value), - OperandType.Attribute => GetAttributeName(operand.Value, config), + OperandType.Attribute => GetAttributeName(operand.Value, config, perPatch: false), + OperandType.AttributePerPatch => GetAttributeName(operand.Value, config, perPatch: true), OperandType.Constant => NumberFormatter.FormatInt(operand.Value), - OperandType.ConstantBuffer => GetConstantBufferName( - operand.CbufSlot, - operand.CbufOffset, - config.Stage, - config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing)), + OperandType.ConstantBuffer => GetConstantBufferName(operand, config), OperandType.LocalVariable => _locals[operand], OperandType.Undefined => DefaultNames.UndefinedName, _ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".") }; } + private static string GetConstantBufferName(AstOperand operand, ShaderConfig config) + { + return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing)); + } + public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable) { return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}"; @@ -142,14 +153,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement); } - public static string GetOutAttributeName(int value, ShaderConfig config) + public static string GetOutAttributeName(int value, ShaderConfig config, bool perPatch) { - return GetAttributeName(value, config, isOutAttr: true); + return GetAttributeName(value, config, perPatch, isOutAttr: true); } - public static string GetAttributeName(int value, ShaderConfig config, bool isOutAttr = false, string indexExpr = "0") + public static string GetAttributeName(int value, ShaderConfig config, bool perPatch, bool isOutAttr = false, string indexExpr = "0") { - value &= ~3; + if ((value & AttributeConsts.LoadOutputMask) != 0) + { + isOutAttr = true; + } + + value &= AttributeConsts.Mask & ~3; char swzMask = GetSwizzleMask((value >> 2) & 3); if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd) @@ -160,7 +176,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix; - if (config.UsedFeatures.HasFlag(isOutAttr ? FeatureFlags.OaIndexing : FeatureFlags.IaIndexing)) + bool indexable = config.UsedFeatures.HasFlag(isOutAttr ? FeatureFlags.OaIndexing : FeatureFlags.IaIndexing); + + if (!indexable && perPatch) + { + prefix = DefaultNames.PerPatchAttributePrefix; + } + + if (indexable) { string name = prefix; @@ -175,9 +198,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { string name = $"{prefix}{(value >> 4)}_{swzMask}"; - if (config.Stage == ShaderStage.Geometry && !isOutAttr) + if (!perPatch && IsArrayAttribute(config.Stage, isOutAttr)) { - name += $"[{indexExpr}]"; + name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]"; } return name; @@ -186,9 +209,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { string name = $"{prefix}{(value >> 4)}"; - if (config.Stage == ShaderStage.Geometry && !isOutAttr) + if (!perPatch && IsArrayAttribute(config.Stage, isOutAttr)) { - name += $"[{indexExpr}]"; + name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]"; } return name + '.' + swzMask; @@ -250,9 +273,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl string name = builtInAttr.Name; - if (config.Stage == ShaderStage.Geometry && (value & AttributeConsts.SpecialMask) == 0 && !isOutAttr) + if (!perPatch && IsArrayAttribute(config.Stage, isOutAttr) && IsArrayBuiltIn(value)) { - name = $"gl_in[{indexExpr}].{name}"; + name = isOutAttr ? $"gl_out[gl_InvocationID].{name}" : $"gl_in[{indexExpr}].{name}"; } return name; @@ -278,6 +301,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return $"{name}[{attrExpr} >> 2][{attrExpr} & 3]"; } + public static bool IsArrayAttribute(ShaderStage stage, bool isOutAttr) + { + if (isOutAttr) + { + return stage == ShaderStage.TessellationControl; + } + else + { + return stage == ShaderStage.TessellationControl || + stage == ShaderStage.TessellationEvaluation || + stage == ShaderStage.Geometry; + } + } + + private static bool IsArrayBuiltIn(int attr) + { + if (attr <= AttributeConsts.TessLevelInner1 || + attr == AttributeConsts.TessCoordX || + attr == AttributeConsts.TessCoordY) + { + return false; + } + + return (attr & AttributeConsts.SpecialMask) == 0; + } + public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable) { if (cbIndexable) diff --git a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs index 656e9c44..b446e650 100644 --- a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs +++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -262,6 +262,7 @@ namespace Ryujinx.Graphics.Shader.Decoders int count = 1; bool isStore = false; bool indexed = false; + bool perPatch = false; if (name == InstName.Ast) { @@ -269,14 +270,17 @@ namespace Ryujinx.Graphics.Shader.Decoders count = (int)opAst.AlSize + 1; offset = opAst.Imm11; indexed = opAst.Phys; + perPatch = opAst.P; isStore = true; } else if (name == InstName.Ald) { InstAld opAld = new InstAld(opCode); count = (int)opAld.AlSize + 1; - indexed = opAld.Phys; offset = opAld.Imm11; + indexed = opAld.Phys; + perPatch = opAld.P; + isStore = opAld.O; } else /* if (name == InstName.Ipa) */ { @@ -307,11 +311,11 @@ namespace Ryujinx.Graphics.Shader.Decoders if (isStore) { - config.SetOutputUserAttribute(index); + config.SetOutputUserAttribute(index, perPatch); } else { - config.SetInputUserAttribute(index); + config.SetInputUserAttribute(index, perPatch); } } } diff --git a/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs b/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs index ca4ff12a..b61412c6 100644 --- a/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs +++ b/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs @@ -5175,8 +5175,8 @@ namespace Ryujinx.Graphics.Shader.Decoders public int SrcB => (int)((_opcode >> 20) & 0xFF); public int SrcC => (int)((_opcode >> 39) & 0xFF); public int Pred => (int)((_opcode >> 16) & 0x7); - public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); public bool WriteCC => (_opcode & 0x800000000000) != 0; public bool DFormat => (_opcode & 0x40000000000000) != 0; public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); @@ -5236,6 +5236,7 @@ namespace Ryujinx.Graphics.Shader.Decoders public int SrcB => (int)((_opcode >> 20) & 0xFF); public int Pred => (int)((_opcode >> 16) & 0x7); public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); public IComp VComp => (IComp)((int)((_opcode >> 45) & 0x4) | (int)((_opcode >> 43) & 0x3)); diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 6af42cf2..3fdce8ea 100644 --- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -96,6 +96,21 @@ namespace Ryujinx.Graphics.Shader return InputTopology.Points; } + bool QueryTessCw() + { + return false; + } + + TessPatchType QueryTessPatchType() + { + return TessPatchType.Triangles; + } + + TessSpacing QueryTessSpacing() + { + return TessSpacing.EqualSpacing; + } + TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1) { return TextureFormat.R8G8B8A8Unorm; diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs index 9cc591ca..33c1065a 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs @@ -474,13 +474,6 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Config.GpuAccessor.Log("Shader instruction Vset is not implemented."); } - public static void Vsetp(EmitterContext context) - { - InstVsetp op = context.GetOp(); - - context.Config.GpuAccessor.Log("Shader instruction Vsetp is not implemented."); - } - public static void Vshl(EmitterContext context) { InstVshl op = context.GetOp(); diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs index e865caf2..f82b835c 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -40,19 +40,33 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Config.SetUsedFeature(FeatureFlags.IaIndexing); } - else if (op.SrcB == RegisterConsts.RegisterZeroIndex) + else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P) { - Operand src = Attribute(op.Imm11 + index * 4); + int offset = op.Imm11 + index * 4; - context.FlagAttributeRead(src.Value); + context.FlagAttributeRead(offset); + + if (op.O) + { + offset |= AttributeConsts.LoadOutputMask; + } + + Operand src = op.P ? AttributePerPatch(offset) : Attribute(offset); context.Copy(Register(rd), src); } else { - Operand src = Const(op.Imm11 + index * 4); + int offset = op.Imm11 + index * 4; - context.FlagAttributeRead(src.Value); + context.FlagAttributeRead(offset); + + if (op.O) + { + offset |= AttributeConsts.LoadOutputMask; + } + + Operand src = Const(offset); context.Copy(Register(rd), context.LoadAttribute(src, Const(0), primVertex)); } @@ -83,9 +97,13 @@ namespace Ryujinx.Graphics.Shader.Instructions } else { - Operand dest = Attribute(op.Imm11 + index * 4); + // TODO: Support indirect stores using Ra. - context.FlagAttributeWritten(dest.Value); + int offset = op.Imm11 + index * 4; + + context.FlagAttributeWritten(offset); + + Operand dest = op.P ? AttributePerPatch(offset) : Attribute(offset); context.Copy(dest, Register(rd)); } diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs index 245b2253..240fd6b1 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -79,6 +79,10 @@ namespace Ryujinx.Graphics.Shader.Instructions src = Attribute(AttributeConsts.LaneId); break; + case SReg.InvocationId: + src = Attribute(AttributeConsts.InvocationId); + break; + case SReg.YDirection: src = ConstF(1); // TODO: Use value from Y direction GPU register. break; @@ -87,6 +91,22 @@ namespace Ryujinx.Graphics.Shader.Instructions src = context.Config.Stage == ShaderStage.Fragment ? Attribute(AttributeConsts.ThreadKill) : Const(0); break; + case SReg.InvocationInfo: + if (context.Config.Stage != ShaderStage.Compute && context.Config.Stage != ShaderStage.Fragment) + { + Operand primitiveId = Attribute(AttributeConsts.PrimitiveId); + Operand patchVerticesIn = Attribute(AttributeConsts.PatchVerticesIn); + + patchVerticesIn = context.ShiftLeft(patchVerticesIn, Const(16)); + + src = context.BitwiseOr(primitiveId, patchVerticesIn); + } + else + { + src = Const(0); + } + break; + case SReg.TId: Operand tidX = Attribute(AttributeConsts.ThreadIdX); Operand tidY = Attribute(AttributeConsts.ThreadIdY); diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs index 890b31d6..120d6f22 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs @@ -120,6 +120,68 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(GetDest(op.Dest), res); } + public static void Vsetp(EmitterContext context) + { + InstVsetp op = context.GetOp(); + + Operand srcA = Extend(context, GetSrcReg(context, op.SrcA), op.ASelect); + + Operand srcB; + + if (op.BVideo) + { + srcB = Extend(context, GetSrcReg(context, op.SrcB), op.BSelect); + } + else + { + int imm = op.Imm16; + + if ((op.BSelect & VectorSelect.S8B0) != 0) + { + imm = (imm << 16) >> 16; + } + + srcB = Const(imm); + } + + Operand p0Res; + + bool signedA = (op.ASelect & VectorSelect.S8B0) != 0; + bool signedB = (op.BSelect & VectorSelect.S8B0) != 0; + + if (signedA != signedB) + { + bool a32 = (op.ASelect & ~VectorSelect.S8B0) == VectorSelect.U32; + bool b32 = (op.BSelect & ~VectorSelect.S8B0) == VectorSelect.U32; + + if (!a32 && !b32) + { + // Both values are extended small integer and can always fit in a S32, just do a signed comparison. + p0Res = GetIntComparison(context, op.VComp, srcA, srcB, isSigned: true, extended: false); + } + else + { + // TODO: Mismatching sign case. + p0Res = Const(0); + } + } + else + { + // Sign matches, just do a regular comparison. + p0Res = GetIntComparison(context, op.VComp, srcA, srcB, signedA, extended: false); + } + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + p0Res = InstEmitAluHelper.GetPredLogicalOp(context, op.BoolOp, p0Res, pred); + p1Res = InstEmitAluHelper.GetPredLogicalOp(context, op.BoolOp, p1Res, pred); + + context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res); + context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res); + } + private static Operand Extend(EmitterContext context, Operand src, VectorSelect type) { return type switch diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index b0db56f0..03badec9 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -161,5 +161,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation return false; } + + public static bool IsTextureQuery(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.Lod || inst == Instruction.TextureSize; + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs index 221e278f..7fed861e 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs @@ -15,6 +15,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation return new Operand(OperandType.Attribute, value); } + public static Operand AttributePerPatch(int value) + { + return new Operand(OperandType.AttributePerPatch, value); + } + public static Operand Cbuf(int slot, int offset) { return new Operand(slot, offset); diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs index 3427b103..7566a03f 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs @@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { Argument, Attribute, + AttributePerPatch, Constant, ConstantBuffer, Label, @@ -11,4 +12,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Register, Undefined } + + static class OperandTypeExtensions + { + public static bool IsAttribute(this OperandType type) + { + return type == OperandType.Attribute || type == OperandType.AttributePerPatch; + } + } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs index 95c5731a..e56008f0 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs @@ -19,15 +19,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public static VariableType GetVarType(OperandType type) { - switch (type) + return type switch { - case OperandType.Attribute: return VariableType.F32; - case OperandType.Constant: return VariableType.S32; - case OperandType.ConstantBuffer: return VariableType.F32; - case OperandType.Undefined: return VariableType.S32; - } - - throw new ArgumentException($"Invalid operand type \"{type}\"."); + OperandType.Attribute => VariableType.F32, + OperandType.AttributePerPatch => VariableType.F32, + OperandType.Constant => VariableType.S32, + OperandType.ConstantBuffer => VariableType.F32, + OperandType.Undefined => VariableType.S32, + _ => throw new ArgumentException($"Invalid operand type \"{type}\".") + }; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs index a9e44175..2a39d021 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -282,6 +282,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public AstOperand GetOperandUse(Operand operand) { + // If this flag is set, we're reading from an output attribute instead. + if (operand.Type.IsAttribute() && (operand.Value & AttributeConsts.LoadOutputMask) != 0) + { + return GetOperandDef(operand); + } + return GetOperand(operand); } diff --git a/Ryujinx.Graphics.Shader/TessPatchType.cs b/Ryujinx.Graphics.Shader/TessPatchType.cs new file mode 100644 index 00000000..2361b69f --- /dev/null +++ b/Ryujinx.Graphics.Shader/TessPatchType.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum TessPatchType + { + Isolines = 0, + Triangles = 1, + Quads = 2 + } + + static class TessPatchTypeExtensions + { + public static string ToGlsl(this TessPatchType type) + { + return type switch + { + TessPatchType.Isolines => "isolines", + TessPatchType.Quads => "quads", + _ => "triangles" + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/TessSpacing.cs b/Ryujinx.Graphics.Shader/TessSpacing.cs new file mode 100644 index 00000000..35c44190 --- /dev/null +++ b/Ryujinx.Graphics.Shader/TessSpacing.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum TessSpacing + { + EqualSpacing = 0, + FractionalEventSpacing = 1, + FractionalOddSpacing = 2 + } + + static class TessSpacingExtensions + { + public static string ToGlsl(this TessSpacing spacing) + { + return spacing switch + { + TessSpacing.FractionalEventSpacing => "fractional_even_spacing", + TessSpacing.FractionalOddSpacing => "fractional_odd_spacing", + _ => "equal_spacing" + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs index 3d0d216e..128013d8 100644 --- a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -2,36 +2,45 @@ namespace Ryujinx.Graphics.Shader.Translation { static class AttributeConsts { - public const int Layer = 0x064; - public const int PointSize = 0x06c; - public const int PositionX = 0x070; - public const int PositionY = 0x074; - public const int PositionZ = 0x078; - public const int PositionW = 0x07c; - public const int ClipDistance0 = 0x2c0; - public const int ClipDistance1 = 0x2c4; - public const int ClipDistance2 = 0x2c8; - public const int ClipDistance3 = 0x2cc; - public const int ClipDistance4 = 0x2d0; - public const int ClipDistance5 = 0x2d4; - public const int ClipDistance6 = 0x2d8; - public const int ClipDistance7 = 0x2dc; - public const int PointCoordX = 0x2e0; - public const int PointCoordY = 0x2e4; - public const int TessCoordX = 0x2f0; - public const int TessCoordY = 0x2f4; - public const int InstanceId = 0x2f8; - public const int VertexId = 0x2fc; - public const int FrontFacing = 0x3fc; + public const int TessLevelOuter0 = 0x000; + public const int TessLevelOuter1 = 0x004; + public const int TessLevelOuter2 = 0x008; + public const int TessLevelOuter3 = 0x00c; + public const int TessLevelInner0 = 0x010; + public const int TessLevelInner1 = 0x014; + public const int Layer = 0x064; + public const int PointSize = 0x06c; + public const int PositionX = 0x070; + public const int PositionY = 0x074; + public const int PositionZ = 0x078; + public const int PositionW = 0x07c; + public const int ClipDistance0 = 0x2c0; + public const int ClipDistance1 = 0x2c4; + public const int ClipDistance2 = 0x2c8; + public const int ClipDistance3 = 0x2cc; + public const int ClipDistance4 = 0x2d0; + public const int ClipDistance5 = 0x2d4; + public const int ClipDistance6 = 0x2d8; + public const int ClipDistance7 = 0x2dc; + public const int PointCoordX = 0x2e0; + public const int PointCoordY = 0x2e4; + public const int TessCoordX = 0x2f0; + public const int TessCoordY = 0x2f4; + public const int InstanceId = 0x2f8; + public const int VertexId = 0x2fc; + public const int FrontFacing = 0x3fc; public const int UserAttributesCount = 32; public const int UserAttributeBase = 0x80; public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16; + public const int LoadOutputMask = 1 << 30; + public const int Mask = 0x3fffffff; + // Note: Those attributes are used internally by the translator // only, they don't exist on Maxwell. - public const int SpecialMask = 0xff << 24; + public const int SpecialMask = 0xf << 24; public const int FragmentOutputDepth = 0x1000000; public const int FragmentOutputColorBase = 0x1000010; public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; @@ -49,12 +58,16 @@ namespace Ryujinx.Graphics.Shader.Translation public const int LaneId = 0x2000020; - public const int EqMask = 0x2000024; - public const int GeMask = 0x2000028; - public const int GtMask = 0x200002c; - public const int LeMask = 0x2000030; - public const int LtMask = 0x2000034; + public const int InvocationId = 0x2000024; + public const int PrimitiveId = 0x2000028; + public const int PatchVerticesIn = 0x200002c; - public const int ThreadKill = 0x2000038; + public const int EqMask = 0x2000030; + public const int GeMask = 0x2000034; + public const int GtMask = 0x2000038; + public const int LeMask = 0x200003c; + public const int LtMask = 0x2000040; + + public const int ThreadKill = 0x2000044; } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index bdfd9626..6f2a6c3b 100644 --- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (target.Enabled) { - Config.SetOutputUserAttribute(rtIndex); + Config.SetOutputUserAttribute(rtIndex, perPatch: false); regIndexBase += 4; } } diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs index 72fa7733..ec7e8982 100644 --- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation public bool GpPassthrough { get; } + public int ThreadsPerInputPrimitive { get; } + public OutputTopology OutputTopology { get; } public int MaxOutputVertices { get; } @@ -42,7 +44,9 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly TranslationCounts _counts; public int UsedInputAttributes { get; private set; } + public int UsedInputAttributesPerPatch { get; private set; } public int UsedOutputAttributes { get; private set; } + public int UsedOutputAttributesPerPatch { get; private set; } public int PassthroughAttributes { get; private set; } private int _usedConstantBuffers; @@ -111,15 +115,16 @@ namespace Ryujinx.Graphics.Shader.Translation public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options, TranslationCounts counts) : this(gpuAccessor, options, counts) { - Stage = header.Stage; - GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough; - OutputTopology = header.OutputTopology; - MaxOutputVertices = header.MaxOutputVertexCount; - LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize; - ImapTypes = header.ImapTypes; - OmapTargets = header.OmapTargets; - OmapSampleMask = header.OmapSampleMask; - OmapDepth = header.OmapDepth; + Stage = header.Stage; + GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough; + ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive; + OutputTopology = header.OutputTopology; + MaxOutputVertices = header.MaxOutputVertexCount; + LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize; + ImapTypes = header.ImapTypes; + OmapTargets = header.OmapTargets; + OmapSampleMask = header.OmapSampleMask; + OmapDepth = header.OmapDepth; } public int GetDepthRegister() @@ -169,7 +174,7 @@ namespace Ryujinx.Graphics.Shader.Translation public TextureFormat GetTextureFormatAtomic(int handle, int cbufSlot = -1) { - // Atomic image instructions do not support GL_EXT_shader_image_load_formatted, + // Atomic image instructions do not support GL_EXT_shader_image_load_formatted, // and must have a type specified. Default to R32Sint if not available. var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot); @@ -219,17 +224,31 @@ namespace Ryujinx.Graphics.Shader.Translation } } - public void SetInputUserAttribute(int index) + public void SetInputUserAttribute(int index, bool perPatch) { - UsedInputAttributes |= 1 << index; + if (perPatch) + { + UsedInputAttributesPerPatch |= 1 << index; + } + else + { + UsedInputAttributes |= 1 << index; + } } - public void SetOutputUserAttribute(int index) + public void SetOutputUserAttribute(int index, bool perPatch) { - UsedOutputAttributes |= 1 << index; + if (perPatch) + { + UsedOutputAttributesPerPatch |= 1 << index; + } + else + { + UsedOutputAttributes |= 1 << index; + } } - public void MergeOutputUserAttributes(int mask) + public void MergeOutputUserAttributes(int mask, int maskPerPatch) { if (GpPassthrough) { @@ -238,6 +257,7 @@ namespace Ryujinx.Graphics.Shader.Translation else { UsedOutputAttributes |= mask; + UsedOutputAttributesPerPatch |= maskPerPatch; } } diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs index 0a0ee4a7..0243eba1 100644 --- a/Ryujinx.Graphics.Shader/Translation/Translator.cs +++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -216,27 +216,38 @@ namespace Ryujinx.Graphics.Shader.Translation return; } - void InitializeOutput(int baseAttr) - { - for (int c = 0; c < 4; c++) - { - context.Copy(Attribute(baseAttr + c * 4), ConstF(c == 3 ? 1f : 0f)); - } - } - if (config.Stage == ShaderStage.Vertex) { - InitializeOutput(AttributeConsts.PositionX); + InitializeOutput(context, AttributeConsts.PositionX, perPatch: false); } - int usedAttribtes = context.Config.UsedOutputAttributes; - while (usedAttribtes != 0) + int usedAttributes = context.Config.UsedOutputAttributes; + while (usedAttributes != 0) { - int index = BitOperations.TrailingZeroCount(usedAttribtes); + int index = BitOperations.TrailingZeroCount(usedAttributes); - InitializeOutput(AttributeConsts.UserAttributeBase + index * 16); + InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false); - usedAttribtes &= ~(1 << index); + usedAttributes &= ~(1 << index); + } + + int usedAttributesPerPatch = context.Config.UsedOutputAttributesPerPatch; + while (usedAttributesPerPatch != 0) + { + int index = BitOperations.TrailingZeroCount(usedAttributesPerPatch); + + InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: true); + + usedAttributesPerPatch &= ~(1 << index); + } + } + + private static void InitializeOutput(EmitterContext context, int baseAttr, bool perPatch) + { + for (int c = 0; c < 4; c++) + { + int attrOffset = baseAttr + c * 4; + context.Copy(perPatch ? AttributePerPatch(attrOffset) : Attribute(attrOffset), ConstF(c == 3 ? 1f : 0f)); } } diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index 3c7b3c2b..34b116d6 100644 --- a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -32,10 +32,13 @@ namespace Ryujinx.Graphics.Shader.Translation private static bool IsUserAttribute(Operand operand) { - return operand != null && - operand.Type == OperandType.Attribute && - operand.Value >= AttributeConsts.UserAttributeBase && - operand.Value < AttributeConsts.UserAttributeEnd; + if (operand != null && operand.Type.IsAttribute()) + { + int value = operand.Value & AttributeConsts.Mask; + return value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd; + } + + return false; } private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b, int aStart) @@ -133,14 +136,16 @@ namespace Ryujinx.Graphics.Shader.Translation { if (nextStage != null) { - _config.MergeOutputUserAttributes(nextStage._config.UsedInputAttributes); + _config.MergeOutputUserAttributes( + nextStage._config.UsedInputAttributes, + nextStage._config.UsedInputAttributesPerPatch); } FunctionCode[] code = EmitShader(_cfg, _config, initializeOutputs: other == null, out _); if (other != null) { - other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes); + other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, 0); FunctionCode[] otherCode = EmitShader(other._cfg, other._config, initializeOutputs: true, out int aStart);