Initial tessellation shader support (#2534)

* Initial tessellation shader support

* Nits

* Re-arrange built-in table

* This is not needed anymore

* PR feedback
This commit is contained in:
gdkchan 2021-10-18 18:38:04 -03:00 committed by GitHub
parent 7603dbe3c8
commit d512ce122c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 775 additions and 148 deletions

View file

@ -55,11 +55,15 @@ namespace Ryujinx.Graphics.GAL
void SetImage(int binding, ITexture texture, Format imageFormat); void SetImage(int binding, ITexture texture, Format imageFormat);
void SetLineParameters(float width, bool smooth);
void SetLogicOpState(bool enable, LogicalOp op); void SetLogicOpState(bool enable, LogicalOp op);
void SetLineParameters(float width, bool smooth); void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel);
void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin); void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin);
void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode);
void SetPrimitiveRestart(bool enable, int index); void SetPrimitiveRestart(bool enable, int index);
void SetPrimitiveTopology(PrimitiveTopology topology); void SetPrimitiveTopology(PrimitiveTopology topology);

View file

@ -181,8 +181,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer); SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetLogicOpState] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) => _lookup[(int)CommandType.SetLogicOpState] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer); SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPatchParameters] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPointParameters] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) => _lookup[(int)CommandType.SetPointParameters] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer); SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPolygonMode] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetPolygonModeCommand.Run(ref GetCommand<SetPolygonModeCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPrimitiveRestart] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) => _lookup[(int)CommandType.SetPrimitiveRestart] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetPrimitiveRestartCommand.Run(ref GetCommand<SetPrimitiveRestartCommand>(memory), threaded, renderer); SetPrimitiveRestartCommand.Run(ref GetCommand<SetPrimitiveRestartCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPrimitiveTopology] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) => _lookup[(int)CommandType.SetPrimitiveTopology] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>

View file

@ -72,7 +72,9 @@
SetIndexBuffer, SetIndexBuffer,
SetLineParameters, SetLineParameters,
SetLogicOpState, SetLogicOpState,
SetPatchParameters,
SetPointParameters, SetPointParameters,
SetPolygonMode,
SetPrimitiveRestart, SetPrimitiveRestart,
SetPrimitiveTopology, SetPrimitiveTopology,
SetProgram, SetProgram,

View file

@ -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<float> _defaultOuterLevel;
private Array2<float> _defaultInnerLevel;
public void Set(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> 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());
}
}
}

View file

@ -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);
}
}
}

View file

@ -179,12 +179,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
_renderer.New<SetPatchParametersCommand>().Set(vertices, defaultOuterLevel, defaultInnerLevel);
_renderer.QueueCommand();
}
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
{ {
_renderer.New<SetPointParametersCommand>().Set(size, isProgramPointSize, enablePointSprite, origin); _renderer.New<SetPointParametersCommand>().Set(size, isProgramPointSize, enablePointSprite, origin);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
{
_renderer.New<SetPolygonModeCommand>().Set(frontMode, backMode);
_renderer.QueueCommand();
}
public void SetPrimitiveRestart(bool enable, int index) public void SetPrimitiveRestart(bool enable, int index)
{ {
_renderer.New<SetPrimitiveRestartCommand>().Set(enable, index); _renderer.New<SetPrimitiveRestartCommand>().Set(enable, index);

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum PolygonMode
{
Point = 0x1b00,
Line = 0x1b01,
Fill = 0x1b02
}
}

View file

@ -129,7 +129,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_state.State.SetTexHeaderPoolCMaximumIndex, _state.State.SetTexHeaderPoolCMaximumIndex,
_state.State.SetBindlessTextureConstantBufferSlotSelect, _state.State.SetBindlessTextureConstantBufferSlotSelect,
false, false,
PrimitiveTopology.Points); PrimitiveTopology.Points,
default);
ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader( ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader(
_channel, _channel,

View file

@ -72,6 +72,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.VertexBufferState), nameof(ThreedClassState.VertexBufferState),
nameof(ThreedClassState.VertexBufferEndAddress)), nameof(ThreedClassState.VertexBufferEndAddress)),
new StateUpdateCallbackEntry(UpdateTessellationState,
nameof(ThreedClassState.TessOuterLevel),
nameof(ThreedClassState.TessInnerLevel),
nameof(ThreedClassState.PatchVertices)),
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)), new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)), new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
@ -100,6 +105,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.ViewportExtents), nameof(ThreedClassState.ViewportExtents),
nameof(ThreedClassState.YControl)), nameof(ThreedClassState.YControl)),
new StateUpdateCallbackEntry(UpdatePolygonMode,
nameof(ThreedClassState.PolygonModeFront),
nameof(ThreedClassState.PolygonModeBack)),
new StateUpdateCallbackEntry(UpdateDepthBiasState, new StateUpdateCallbackEntry(UpdateDepthBiasState,
nameof(ThreedClassState.DepthBiasState), nameof(ThreedClassState.DepthBiasState),
nameof(ThreedClassState.DepthBiasFactor), nameof(ThreedClassState.DepthBiasFactor),
@ -259,6 +268,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
} }
/// <summary>
/// Updates tessellation state based on the guest GPU state.
/// </summary>
private void UpdateTessellationState()
{
_context.Renderer.Pipeline.SetPatchParameters(
_state.State.PatchVertices,
_state.State.TessOuterLevel.ToSpan(),
_state.State.TessInnerLevel.ToSpan());
}
/// <summary> /// <summary>
/// Updates transform feedback buffer state based on the guest GPU state. /// Updates transform feedback buffer state based on the guest GPU state.
/// </summary> /// </summary>
@ -544,6 +564,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_context.Renderer.Pipeline.SetViewports(0, viewports); _context.Renderer.Pipeline.SetViewports(0, viewports);
} }
/// <summary>
/// Updates polygon mode state based on current GPU state.
/// </summary>
private void UpdatePolygonMode()
{
_context.Renderer.Pipeline.SetPolygonMode(_state.State.PolygonModeFront, _state.State.PolygonModeBack);
}
/// <summary> /// <summary>
/// Updates host depth bias (also called polygon offset) state based on current GPU state. /// Updates host depth bias (also called polygon offset) state based on current GPU state.
/// </summary> /// </summary>
@ -949,7 +977,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_state.State.TexturePoolState.MaximumId, _state.State.TexturePoolState.MaximumId,
(int)_state.State.TextureBufferIndex, (int)_state.State.TextureBufferIndex,
_state.State.EarlyZForce, _state.State.EarlyZForce,
_drawState.Topology); _drawState.Topology,
_state.State.TessMode);
ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses); ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses);

View file

@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using System; using System;
namespace Ryujinx.Graphics.Gpu.Engine.Threed namespace Ryujinx.Graphics.Gpu.Engine.Threed
@ -19,6 +20,43 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Fragment Fragment
} }
/// <summary>
/// Tessellation mode.
/// </summary>
struct TessMode
{
#pragma warning disable CS0649
public uint Packed;
#pragma warning restore CS0649
/// <summary>
/// Unpacks the tessellation abstract patch type.
/// </summary>
/// <returns>Abtract patch type</returns>
public TessPatchType UnpackPatchType()
{
return (TessPatchType)(Packed & 3);
}
/// <summary>
/// Unpacks the spacing between tessellated vertices of the patch.
/// </summary>
/// <returns>Spacing between tessellated vertices</returns>
public TessSpacing UnpackSpacing()
{
return (TessSpacing)((Packed >> 4) & 3);
}
/// <summary>
/// Unpacks the primitive winding order.
/// </summary>
/// <returns>True if clockwise, false if counter-clockwise</returns>
public bool UnpackCw()
{
return (Packed & (1 << 8)) != 0;
}
}
/// <summary> /// <summary>
/// Transform feedback buffer state. /// Transform feedback buffer state.
/// </summary> /// </summary>
@ -661,7 +699,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public Boolean32 EarlyZForce; public Boolean32 EarlyZForce;
public fixed uint Reserved214[45]; public fixed uint Reserved214[45];
public uint SyncpointAction; public uint SyncpointAction;
public fixed uint Reserved2CC[44]; public fixed uint Reserved2CC[21];
public TessMode TessMode;
public Array4<float> TessOuterLevel;
public Array2<float> TessInnerLevel;
public fixed uint Reserved33C[16];
public Boolean32 RasterizeEnable; public Boolean32 RasterizeEnable;
public Array4<TfBufferState> TfBufferState; public Array4<TfBufferState> TfBufferState;
public fixed uint Reserved400[192]; public fixed uint Reserved400[192];
@ -679,9 +721,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public float ClearDepthValue; public float ClearDepthValue;
public fixed uint ReservedD94[3]; public fixed uint ReservedD94[3];
public uint ClearStencilValue; 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 DepthBiasState DepthBiasState;
public fixed uint ReservedDCC[5]; public int PatchVertices;
public fixed uint ReservedDD0[4];
public uint TextureBarrier; public uint TextureBarrier;
public fixed uint ReservedDE4[7]; public fixed uint ReservedDE4[7];
public Array16<ScissorState> ScissorState; public Array16<ScissorState> ScissorState;

View file

@ -349,6 +349,26 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
return flags; return flags;
} }
/// <summary>
/// Packs the tessellation parameters from the gpu accessor.
/// </summary>
/// <param name="gpuAccessor">The gpu accessor</param>
/// <returns>The packed tessellation parameters</returns>
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;
}
/// <summary> /// <summary>
/// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor. /// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
/// </summary> /// </summary>
@ -364,6 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(), ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(), ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(), PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
TessellationModePacked = GetTessellationModePacked(gpuAccessor),
StateFlags = GetGpuStateFlags(gpuAccessor) StateFlags = GetGpuStateFlags(gpuAccessor)
}; };
} }

View file

@ -49,10 +49,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
/// </summary> /// </summary>
public InputTopology PrimitiveTopology; public InputTopology PrimitiveTopology;
/// <summary>
/// Tessellation parameters (packed to fit on a byte).
/// </summary>
public byte TessellationModePacked;
/// <summary> /// <summary>
/// Unused/reserved. /// Unused/reserved.
/// </summary> /// </summary>
public ushort Reserved2; public byte Reserved2;
/// <summary> /// <summary>
/// GPU boolean state that can influence shader compilation. /// GPU boolean state that can influence shader compilation.

View file

@ -134,6 +134,33 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _header.PrimitiveTopology; return _header.PrimitiveTopology;
} }
/// <summary>
/// Queries the tessellation evaluation shader primitive winding order.
/// </summary>
/// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
public bool QueryTessCw()
{
return (_header.TessellationModePacked & 0x10) != 0;
}
/// <summary>
/// Queries the tessellation evaluation shader abstract patch type.
/// </summary>
/// <returns>Abstract patch type</returns>
public TessPatchType QueryTessPatchType()
{
return (TessPatchType)(_header.TessellationModePacked & 3);
}
/// <summary>
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
/// </summary>
/// <returns>Spacing between tessellated vertices of the patch</returns>
public TessSpacing QueryTessSpacing()
{
return (TessSpacing)((_header.TessellationModePacked >> 2) & 3);
}
/// <summary> /// <summary>
/// Gets the texture descriptor for a given texture on the pool. /// Gets the texture descriptor for a given texture on the pool.
/// </summary> /// </summary>

View file

@ -168,10 +168,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
PrimitiveTopology.TriangleFan => InputTopology.Triangles, PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency, PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
_ => InputTopology.Points, PrimitiveTopology.Patches => _state.TessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points
}; };
} }
/// <summary>
/// Queries the tessellation evaluation shader primitive winding order.
/// </summary>
/// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
public bool QueryTessCw() => _state.TessellationMode.UnpackCw();
/// <summary>
/// Queries the tessellation evaluation shader abstract patch type.
/// </summary>
/// <returns>Abstract patch type</returns>
public TessPatchType QueryTessPatchType() => _state.TessellationMode.UnpackPatchType();
/// <summary>
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
/// </summary>
/// <returns>Spacing between tessellated vertices of the patch</returns>
public TessSpacing QueryTessSpacing() => _state.TessellationMode.UnpackSpacing();
/// <summary> /// <summary>
/// Gets the texture descriptor for a given texture on the pool. /// Gets the texture descriptor for a given texture on the pool.
/// </summary> /// </summary>

View file

@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
@ -32,6 +33,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
public PrimitiveTopology Topology { get; } public PrimitiveTopology Topology { get; }
/// <summary>
/// Tessellation mode.
/// </summary>
public TessMode TessellationMode { get; }
/// <summary> /// <summary>
/// Creates a new instance of the GPU accessor state. /// Creates a new instance of the GPU accessor state.
/// </summary> /// </summary>
@ -40,18 +46,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param> /// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
/// <param name="earlyZForce">Early Z force enable</param> /// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param> /// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
public GpuAccessorState( public GpuAccessorState(
ulong texturePoolGpuVa, ulong texturePoolGpuVa,
int texturePoolMaximumId, int texturePoolMaximumId,
int textureBufferIndex, int textureBufferIndex,
bool earlyZForce, bool earlyZForce,
PrimitiveTopology topology) PrimitiveTopology topology,
TessMode tessellationMode)
{ {
TexturePoolGpuVa = texturePoolGpuVa; TexturePoolGpuVa = texturePoolGpuVa;
TexturePoolMaximumId = texturePoolMaximumId; TexturePoolMaximumId = texturePoolMaximumId;
TextureBufferIndex = textureBufferIndex; TextureBufferIndex = textureBufferIndex;
EarlyZForce = earlyZForce; EarlyZForce = earlyZForce;
Topology = topology; Topology = topology;
TessellationMode = tessellationMode;
} }
} }
} }

View file

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary> /// <summary>
/// Version of the codegen (to be changed when codegen or guest format change). /// Version of the codegen (to be changed when codegen or guest format change).
/// </summary> /// </summary>
private const ulong ShaderCodeGenVersion = 2702; private const ulong ShaderCodeGenVersion = 2534;
// Progress reporting helpers // Progress reporting helpers
private volatile int _shaderCount; private volatile int _shaderCount;

View file

@ -290,6 +290,23 @@ namespace Ryujinx.Graphics.OpenGL
return TextureMinFilter.Nearest; 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) public static PrimitiveType Convert(this PrimitiveTopology topology)
{ {
switch (topology) switch (topology)

View file

@ -830,6 +830,21 @@ namespace Ryujinx.Graphics.OpenGL
GL.LineWidth(width); GL.LineWidth(width);
} }
public unsafe void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> 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) 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. // 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)); 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) public void SetPrimitiveRestart(bool enable, int index)
{ {
if (!enable) if (!enable)

View file

@ -136,6 +136,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine(); 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) if (context.Config.UsedInputAttributes != 0 || context.Config.GpPassthrough)
{ {
@ -150,6 +166,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine(); 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 else
{ {
@ -424,17 +454,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
while (usedAttributes != 0) while (usedAttributes != 0)
{ {
int index = BitOperations.TrailingZeroCount(usedAttributes); int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareInputAttribute(context, info, index); DeclareInputAttribute(context, info, index);
usedAttributes &= ~(1 << 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) 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; string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment) 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) private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
{ {
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing)) if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
@ -477,9 +522,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
while (usedAttributes != 0) while (usedAttributes != 0)
{ {
int index = BitOperations.TrailingZeroCount(usedAttributes); int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareOutputAttribute(context, index); DeclareOutputAttribute(context, index);
usedAttributes &= ~(1 << index); usedAttributes &= ~(1 << index);
} }
} }
@ -487,7 +530,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareOutputAttribute(CodeGenContext context, int attr) 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) 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) private static void DeclareSupportUniformBlock(CodeGenContext context, bool isFragment, int scaleElements)
{ {
if (!isFragment && scaleElements == 0) if (!isFragment && scaleElements == 0)

View file

@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string SamplerNamePrefix = "tex"; public const string SamplerNamePrefix = "tex";
public const string ImageNamePrefix = "img"; public const string ImageNamePrefix = "img";
public const string PerPatchAttributePrefix = "patch_attr_";
public const string IAttributePrefix = "in_attr"; public const string IAttributePrefix = "in_attr";
public const string OAttributePrefix = "out_attr"; public const string OAttributePrefix = "out_attr";

View file

@ -126,9 +126,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string dest; 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 else
{ {

View file

@ -200,7 +200,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (src2 is AstOperand operand && operand.Type == OperandType.Constant) 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 else
{ {
@ -326,7 +327,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (src2 is AstOperand operand && operand.Type == OperandType.Constant) 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 else
{ {

View file

@ -29,6 +29,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static Dictionary<int, BuiltInAttribute> _builtInAttributes = private static Dictionary<int, BuiltInAttribute> _builtInAttributes =
new Dictionary<int, BuiltInAttribute>() new Dictionary<int, BuiltInAttribute>()
{ {
{ 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.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) },
{ AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) },
{ AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) },
@ -61,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) }, { AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) },
{ AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) }, { AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) },
{ AttributeConsts.LaneId, new BuiltInAttribute(null, 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.EqMask, new BuiltInAttribute(null, VariableType.U32) },
{ AttributeConsts.GeMask, new BuiltInAttribute(null, VariableType.U32) }, { AttributeConsts.GeMask, new BuiltInAttribute(null, VariableType.U32) },
{ AttributeConsts.GtMask, 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 return operand.Type switch
{ {
OperandType.Argument => GetArgumentName(operand.Value), 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.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.ConstantBuffer => GetConstantBufferName( OperandType.ConstantBuffer => GetConstantBufferName(operand, config),
operand.CbufSlot,
operand.CbufOffset,
config.Stage,
config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing)),
OperandType.LocalVariable => _locals[operand], OperandType.LocalVariable => _locals[operand],
OperandType.Undefined => DefaultNames.UndefinedName, OperandType.Undefined => DefaultNames.UndefinedName,
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".") _ => 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) public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
{ {
return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}"; 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); 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); char swzMask = GetSwizzleMask((value >> 2) & 3);
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd) if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
@ -160,7 +176,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
? DefaultNames.OAttributePrefix ? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix; : 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; string name = prefix;
@ -175,9 +198,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
string name = $"{prefix}{(value >> 4)}_{swzMask}"; 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; return name;
@ -186,9 +209,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
string name = $"{prefix}{(value >> 4)}"; 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; return name + '.' + swzMask;
@ -250,9 +273,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string name = builtInAttr.Name; 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; return name;
@ -278,6 +301,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return $"{name}[{attrExpr} >> 2][{attrExpr} & 3]"; 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) public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{ {
if (cbIndexable) if (cbIndexable)

View file

@ -262,6 +262,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
int count = 1; int count = 1;
bool isStore = false; bool isStore = false;
bool indexed = false; bool indexed = false;
bool perPatch = false;
if (name == InstName.Ast) if (name == InstName.Ast)
{ {
@ -269,14 +270,17 @@ namespace Ryujinx.Graphics.Shader.Decoders
count = (int)opAst.AlSize + 1; count = (int)opAst.AlSize + 1;
offset = opAst.Imm11; offset = opAst.Imm11;
indexed = opAst.Phys; indexed = opAst.Phys;
perPatch = opAst.P;
isStore = true; isStore = true;
} }
else if (name == InstName.Ald) else if (name == InstName.Ald)
{ {
InstAld opAld = new InstAld(opCode); InstAld opAld = new InstAld(opCode);
count = (int)opAld.AlSize + 1; count = (int)opAld.AlSize + 1;
indexed = opAld.Phys;
offset = opAld.Imm11; offset = opAld.Imm11;
indexed = opAld.Phys;
perPatch = opAld.P;
isStore = opAld.O;
} }
else /* if (name == InstName.Ipa) */ else /* if (name == InstName.Ipa) */
{ {
@ -307,11 +311,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore) if (isStore)
{ {
config.SetOutputUserAttribute(index); config.SetOutputUserAttribute(index, perPatch);
} }
else else
{ {
config.SetInputUserAttribute(index); config.SetInputUserAttribute(index, perPatch);
} }
} }
} }

View file

@ -5175,8 +5175,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
public int SrcB => (int)((_opcode >> 20) & 0xFF); public int SrcB => (int)((_opcode >> 20) & 0xFF);
public int SrcC => (int)((_opcode >> 39) & 0xFF); public int SrcC => (int)((_opcode >> 39) & 0xFF);
public int Pred => (int)((_opcode >> 16) & 0x7); public int Pred => (int)((_opcode >> 16) & 0x7);
public int Imm16 => (int)((_opcode >> 20) & 0xFFFF);
public bool PredInv => (_opcode & 0x80000) != 0; public bool PredInv => (_opcode & 0x80000) != 0;
public int Imm16 => (int)((_opcode >> 20) & 0xFFFF);
public bool WriteCC => (_opcode & 0x800000000000) != 0; public bool WriteCC => (_opcode & 0x800000000000) != 0;
public bool DFormat => (_opcode & 0x40000000000000) != 0; public bool DFormat => (_opcode & 0x40000000000000) != 0;
public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); 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 SrcB => (int)((_opcode >> 20) & 0xFF);
public int Pred => (int)((_opcode >> 16) & 0x7); public int Pred => (int)((_opcode >> 16) & 0x7);
public bool PredInv => (_opcode & 0x80000) != 0; 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 ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7));
public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 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)); public IComp VComp => (IComp)((int)((_opcode >> 45) & 0x4) | (int)((_opcode >> 43) & 0x3));

View file

@ -96,6 +96,21 @@ namespace Ryujinx.Graphics.Shader
return InputTopology.Points; return InputTopology.Points;
} }
bool QueryTessCw()
{
return false;
}
TessPatchType QueryTessPatchType()
{
return TessPatchType.Triangles;
}
TessSpacing QueryTessSpacing()
{
return TessSpacing.EqualSpacing;
}
TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1) TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
{ {
return TextureFormat.R8G8B8A8Unorm; return TextureFormat.R8G8B8A8Unorm;

View file

@ -474,13 +474,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Config.GpuAccessor.Log("Shader instruction Vset is not implemented."); context.Config.GpuAccessor.Log("Shader instruction Vset is not implemented.");
} }
public static void Vsetp(EmitterContext context)
{
InstVsetp op = context.GetOp<InstVsetp>();
context.Config.GpuAccessor.Log("Shader instruction Vsetp is not implemented.");
}
public static void Vshl(EmitterContext context) public static void Vshl(EmitterContext context)
{ {
InstVshl op = context.GetOp<InstVshl>(); InstVshl op = context.GetOp<InstVshl>();

View file

@ -40,19 +40,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Config.SetUsedFeature(FeatureFlags.IaIndexing); 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); context.Copy(Register(rd), src);
} }
else 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)); context.Copy(Register(rd), context.LoadAttribute(src, Const(0), primVertex));
} }
@ -83,9 +97,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
} }
else 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)); context.Copy(dest, Register(rd));
} }

View file

@ -79,6 +79,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
src = Attribute(AttributeConsts.LaneId); src = Attribute(AttributeConsts.LaneId);
break; break;
case SReg.InvocationId:
src = Attribute(AttributeConsts.InvocationId);
break;
case SReg.YDirection: case SReg.YDirection:
src = ConstF(1); // TODO: Use value from Y direction GPU register. src = ConstF(1); // TODO: Use value from Y direction GPU register.
break; break;
@ -87,6 +91,22 @@ namespace Ryujinx.Graphics.Shader.Instructions
src = context.Config.Stage == ShaderStage.Fragment ? Attribute(AttributeConsts.ThreadKill) : Const(0); src = context.Config.Stage == ShaderStage.Fragment ? Attribute(AttributeConsts.ThreadKill) : Const(0);
break; 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: case SReg.TId:
Operand tidX = Attribute(AttributeConsts.ThreadIdX); Operand tidX = Attribute(AttributeConsts.ThreadIdX);
Operand tidY = Attribute(AttributeConsts.ThreadIdY); Operand tidY = Attribute(AttributeConsts.ThreadIdY);

View file

@ -120,6 +120,68 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Copy(GetDest(op.Dest), res); context.Copy(GetDest(op.Dest), res);
} }
public static void Vsetp(EmitterContext context)
{
InstVsetp op = context.GetOp<InstVsetp>();
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) private static Operand Extend(EmitterContext context, Operand src, VectorSelect type)
{ {
return type switch return type switch

View file

@ -161,5 +161,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return false; return false;
} }
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureSize;
}
} }
} }

View file

@ -15,6 +15,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return new Operand(OperandType.Attribute, value); 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) public static Operand Cbuf(int slot, int offset)
{ {
return new Operand(slot, offset); return new Operand(slot, offset);

View file

@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{ {
Argument, Argument,
Attribute, Attribute,
AttributePerPatch,
Constant, Constant,
ConstantBuffer, ConstantBuffer,
Label, Label,
@ -11,4 +12,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Register, Register,
Undefined Undefined
} }
static class OperandTypeExtensions
{
public static bool IsAttribute(this OperandType type)
{
return type == OperandType.Attribute || type == OperandType.AttributePerPatch;
}
}
} }

View file

@ -19,15 +19,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public static VariableType GetVarType(OperandType type) public static VariableType GetVarType(OperandType type)
{ {
switch (type) return type switch
{ {
case OperandType.Attribute: return VariableType.F32; OperandType.Attribute => VariableType.F32,
case OperandType.Constant: return VariableType.S32; OperandType.AttributePerPatch => VariableType.F32,
case OperandType.ConstantBuffer: return VariableType.F32; OperandType.Constant => VariableType.S32,
case OperandType.Undefined: return VariableType.S32; OperandType.ConstantBuffer => VariableType.F32,
} OperandType.Undefined => VariableType.S32,
_ => throw new ArgumentException($"Invalid operand type \"{type}\".")
throw new ArgumentException($"Invalid operand type \"{type}\"."); };
} }
} }
} }

View file

@ -282,6 +282,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public AstOperand GetOperandUse(Operand operand) 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); return GetOperand(operand);
} }

View file

@ -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"
};
}
}
}

View file

@ -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"
};
}
}
}

View file

@ -2,6 +2,12 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
static class AttributeConsts static class AttributeConsts
{ {
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 Layer = 0x064;
public const int PointSize = 0x06c; public const int PointSize = 0x06c;
public const int PositionX = 0x070; public const int PositionX = 0x070;
@ -28,10 +34,13 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int UserAttributeBase = 0x80; public const int UserAttributeBase = 0x80;
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16; 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 // Note: Those attributes are used internally by the translator
// only, they don't exist on Maxwell. // 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 FragmentOutputDepth = 0x1000000;
public const int FragmentOutputColorBase = 0x1000010; public const int FragmentOutputColorBase = 0x1000010;
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
@ -49,12 +58,16 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int LaneId = 0x2000020; public const int LaneId = 0x2000020;
public const int EqMask = 0x2000024; public const int InvocationId = 0x2000024;
public const int GeMask = 0x2000028; public const int PrimitiveId = 0x2000028;
public const int GtMask = 0x200002c; public const int PatchVerticesIn = 0x200002c;
public const int LeMask = 0x2000030;
public const int LtMask = 0x2000034;
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;
} }
} }

View file

@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (target.Enabled) if (target.Enabled)
{ {
Config.SetOutputUserAttribute(rtIndex); Config.SetOutputUserAttribute(rtIndex, perPatch: false);
regIndexBase += 4; regIndexBase += 4;
} }
} }

View file

@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public bool GpPassthrough { get; } public bool GpPassthrough { get; }
public int ThreadsPerInputPrimitive { get; }
public OutputTopology OutputTopology { get; } public OutputTopology OutputTopology { get; }
public int MaxOutputVertices { get; } public int MaxOutputVertices { get; }
@ -42,7 +44,9 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly TranslationCounts _counts; private readonly TranslationCounts _counts;
public int UsedInputAttributes { get; private set; } public int UsedInputAttributes { get; private set; }
public int UsedInputAttributesPerPatch { get; private set; }
public int UsedOutputAttributes { get; private set; } public int UsedOutputAttributes { get; private set; }
public int UsedOutputAttributesPerPatch { get; private set; }
public int PassthroughAttributes { get; private set; } public int PassthroughAttributes { get; private set; }
private int _usedConstantBuffers; private int _usedConstantBuffers;
@ -113,6 +117,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
Stage = header.Stage; Stage = header.Stage;
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough; GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive;
OutputTopology = header.OutputTopology; OutputTopology = header.OutputTopology;
MaxOutputVertices = header.MaxOutputVertexCount; MaxOutputVertices = header.MaxOutputVertexCount;
LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize; LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize;
@ -219,17 +224,31 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
} }
public void SetInputUserAttribute(int index) public void SetInputUserAttribute(int index, bool perPatch)
{
if (perPatch)
{
UsedInputAttributesPerPatch |= 1 << index;
}
else
{ {
UsedInputAttributes |= 1 << index; UsedInputAttributes |= 1 << index;
} }
}
public void SetOutputUserAttribute(int index) public void SetOutputUserAttribute(int index, bool perPatch)
{
if (perPatch)
{
UsedOutputAttributesPerPatch |= 1 << index;
}
else
{ {
UsedOutputAttributes |= 1 << index; UsedOutputAttributes |= 1 << index;
} }
}
public void MergeOutputUserAttributes(int mask) public void MergeOutputUserAttributes(int mask, int maskPerPatch)
{ {
if (GpPassthrough) if (GpPassthrough)
{ {
@ -238,6 +257,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else else
{ {
UsedOutputAttributes |= mask; UsedOutputAttributes |= mask;
UsedOutputAttributesPerPatch |= maskPerPatch;
} }
} }

View file

@ -216,27 +216,38 @@ namespace Ryujinx.Graphics.Shader.Translation
return; return;
} }
void InitializeOutput(int baseAttr) if (config.Stage == ShaderStage.Vertex)
{
InitializeOutput(context, AttributeConsts.PositionX, perPatch: false);
}
int usedAttributes = context.Config.UsedOutputAttributes;
while (usedAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false);
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++) for (int c = 0; c < 4; c++)
{ {
context.Copy(Attribute(baseAttr + c * 4), ConstF(c == 3 ? 1f : 0f)); int attrOffset = baseAttr + c * 4;
} context.Copy(perPatch ? AttributePerPatch(attrOffset) : Attribute(attrOffset), ConstF(c == 3 ? 1f : 0f));
}
if (config.Stage == ShaderStage.Vertex)
{
InitializeOutput(AttributeConsts.PositionX);
}
int usedAttribtes = context.Config.UsedOutputAttributes;
while (usedAttribtes != 0)
{
int index = BitOperations.TrailingZeroCount(usedAttribtes);
InitializeOutput(AttributeConsts.UserAttributeBase + index * 16);
usedAttribtes &= ~(1 << index);
} }
} }

View file

@ -32,10 +32,13 @@ namespace Ryujinx.Graphics.Shader.Translation
private static bool IsUserAttribute(Operand operand) private static bool IsUserAttribute(Operand operand)
{ {
return operand != null && if (operand != null && operand.Type.IsAttribute())
operand.Type == OperandType.Attribute && {
operand.Value >= AttributeConsts.UserAttributeBase && int value = operand.Value & AttributeConsts.Mask;
operand.Value < AttributeConsts.UserAttributeEnd; return value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd;
}
return false;
} }
private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b, int aStart) private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b, int aStart)
@ -133,14 +136,16 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
if (nextStage != null) 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 _); FunctionCode[] code = EmitShader(_cfg, _config, initializeOutputs: other == null, out _);
if (other != null) 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); FunctionCode[] otherCode = EmitShader(other._cfg, other._config, initializeOutputs: true, out int aStart);