Delete ShaderConfig and organize shader resources/definitions better (#5509)

* Move some properties out of ShaderConfig

* Stop using ShaderConfig on backends

* Replace ShaderConfig usages on Translator and passes

* Move remaining properties out of ShaderConfig and delete ShaderConfig

* Remove ResourceManager property from TranslatorContext

* Move Rewriter passes to separate transform pass files

* Fix TransformPasses.RunPass on cases where a node is removed

* Move remaining ClipDistancePrimitivesWritten and UsedFeatures updates to decode stage

* Reduce excessive parameter passing a bit by using structs more

* Remove binding parameter from ShaderProperties methods since it is redundant

* Replace decoder instruction checks with switch statement

* Put GLSL on the same plan as SPIR-V for input/output declaration

* Stop mutating TranslatorContext state when Translate is called

* Pass most of the graphics state using a struct instead of individual query methods

* Auto-format

* Auto-format

* Add backend logging interface

* Auto-format

* Remove unnecessary use of interpolated strings

* Remove more modifications of AttributeUsage after decode

* PR feedback

* gl_Layer is not supported on compute
This commit is contained in:
gdkchan 2023-08-13 22:26:42 -03:00 committed by GitHub
parent 8edfb2bc7b
commit b423197619
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 2653 additions and 2407 deletions

View file

@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -44,6 +43,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_newSpecState = newSpecState;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
newSpecState.RecordPrimitiveTopology();
}
}
/// <inheritdoc/>
@ -69,48 +74,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return MemoryMarshal.Cast<byte, ulong>(_data.Span[(int)address..]);
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _oldSpecState.GraphicsState.AlphaToCoverageEnable && _oldSpecState.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _oldSpecState.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _oldSpecState.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
@ -133,55 +96,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.ConstantBufferUse[_stageIndex];
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_newSpecState.RecordPrimitiveTopology();
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _oldSpecState.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackSpacing();
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -204,12 +130,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _oldSpecState.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@ -228,31 +148,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_newSpecState.RecordEarlyZForce();
return _oldSpecState.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryHasUnalignedStorageBuffer()
{
return _oldSpecState.GraphicsState.HasUnalignedStorageBuffer || _oldSpecState.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _oldSpecState.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View file

@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -36,6 +35,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
_channel = channel;
_state = state;
_stageIndex = stageIndex;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
_state.SpecializationState.RecordPrimitiveTopology();
}
}
/// <summary>
@ -74,58 +79,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _state.GraphicsState.AlphaToCoverageEnable && _state.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _state.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference()
{
return _state.GraphicsState.AlphaTestReference;
}
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _state.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_state.SpecializationState?.RecordEarlyZForce();
return _state.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _state.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
@ -152,6 +105,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return useMask;
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
@ -164,49 +123,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _state.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_state.SpecializationState?.RecordPrimitiveTopology();
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _state.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _state.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _state.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _state.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _state.GraphicsState.TessellationMode.UnpackSpacing();
}
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -258,12 +174,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _state.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@ -282,18 +192,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _state.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View file

@ -1,6 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -232,33 +231,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
#pragma warning restore IDE0055
};
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
protected static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View file

@ -103,64 +103,81 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool YNegateEnabled;
/// <summary>
/// Creates a new GPU graphics state.
/// Creates a new graphics state from this state that can be used for shader generation.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
/// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param>
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates whether Y negate of the fragment coordinates is enabled</param>
public GpuChannelGraphicsState(
bool earlyZForce,
PrimitiveTopology topology,
TessMode tessellationMode,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool viewportTransformDisable,
bool depthMode,
bool programPointSizeEnable,
float pointSize,
bool alphaTestEnable,
CompareOp alphaTestCompare,
float alphaTestReference,
ref Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters,
bool hasUnalignedStorageBuffer,
ref Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled)
/// <param name="hostSupportsAlphaTest">Indicates if the host API supports alpha test operations</param>
/// <returns>GPU graphics state that can be used for shader translation</returns>
public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
ViewportTransformDisable = viewportTransformDisable;
DepthMode = depthMode;
ProgramPointSizeEnable = programPointSizeEnable;
PointSize = pointSize;
AlphaTestEnable = alphaTestEnable;
AlphaTestCompare = alphaTestCompare;
AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
AlphaTestOp alphaTestOp;
if (hostSupportsAlphaTest || !AlphaTestEnable)
{
alphaTestOp = AlphaTestOp.Always;
}
else
{
alphaTestOp = AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
return new GpuGraphicsState(
EarlyZForce,
ConvertToInputTopology(Topology, TessellationMode),
TessellationMode.UnpackCw(),
TessellationMode.UnpackPatchType(),
TessellationMode.UnpackSpacing(),
AlphaToCoverageEnable,
AlphaToCoverageDitherEnable,
ViewportTransformDisable,
DepthMode,
ProgramPointSizeEnable,
PointSize,
alphaTestOp,
AlphaTestReference,
in AttributeTypes,
HasConstantBufferDrawParameters,
in FragmentOutputTypes,
DualSourceBlendEnable,
YNegateEnabled,
originUpperLeft);
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
private static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View file

@ -28,9 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
[Flags]
private enum QueriedStateFlags
{
EarlyZForce = 1 << 0,
PrimitiveTopology = 1 << 1,
TessellationMode = 1 << 2,
TransformFeedback = 1 << 3,
}
@ -264,14 +262,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
PipelineState = pipelineState;
}
/// <summary>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
public void RecordEarlyZForce()
{
_queriedState |= QueriedStateFlags.EarlyZForce;
}
/// <summary>
/// Indicates that the shader accesses the primitive topology state.
/// </summary>
@ -280,14 +270,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
_queriedState |= QueriedStateFlags.PrimitiveTopology;
}
/// <summary>
/// Indicates that the shader accesses the tessellation mode state.
/// </summary>
public void RecordTessellationMode()
{
_queriedState |= QueriedStateFlags.TessellationMode;
}
/// <summary>
/// Indicates that the shader accesses the constant buffer use state.
/// </summary>

View file

@ -13,17 +13,6 @@ namespace Ryujinx.Graphics.Shader
static class AttributeTypeExtensions
{
public static string ToVec4Type(this AttributeType type)
{
return type switch
{
AttributeType.Float => "vec4",
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => throw new ArgumentException($"Invalid attribute type \"{type}\"."),
};
}
public static AggregateType ToAggregateType(this AttributeType type)
{
return type switch

View file

@ -0,0 +1,31 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Shader.CodeGen
{
readonly struct CodeGenParameters
{
public readonly AttributeUsage AttributeUsage;
public readonly ShaderDefinitions Definitions;
public readonly ShaderProperties Properties;
public readonly HostCapabilities HostCapabilities;
public readonly ILogger Logger;
public readonly TargetApi TargetApi;
public CodeGenParameters(
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ShaderProperties properties,
HostCapabilities hostCapabilities,
ILogger logger,
TargetApi targetApi)
{
AttributeUsage = attributeUsage;
Definitions = definitions;
Properties = properties;
HostCapabilities = hostCapabilities;
Logger = logger;
TargetApi = targetApi;
}
}
}

View file

@ -12,7 +12,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public OperandManager OperandManager { get; }
@ -22,10 +27,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private string _indentation;
public CodeGenContext(StructuredProgramInfo info, ShaderConfig config)
public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
OperandManager = new OperandManager();

View file

@ -1,4 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
@ -13,10 +14,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{
context.AppendLine(context.Config.Options.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
context.AppendLine("#extension GL_ARB_shader_ballot : enable");
}
@ -30,24 +31,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
if (context.Config.Stage == ShaderStage.Compute)
if (context.Definitions.Stage == ShaderStage.Compute)
{
context.AppendLine("#extension GL_ARB_compute_shader : enable");
}
else if (context.Config.Stage == ShaderStage.Fragment)
else if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AppendLine("#extension GL_ARB_fragment_shader_interlock : enable");
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
context.AppendLine("#extension GL_INTEL_fragment_shader_ordering : enable");
}
}
else
{
if (context.Config.Stage == ShaderStage.Vertex)
if (context.Definitions.Stage == ShaderStage.Vertex)
{
context.AppendLine("#extension GL_ARB_shader_draw_parameters : enable");
}
@ -55,12 +56,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable");
}
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
}
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
if (context.HostCapabilities.SupportsViewportMask)
{
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
}
@ -71,23 +72,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
context.AppendLine();
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Config.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Config.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Config.Properties.Textures.Values);
DeclareImages(context, context.Config.Properties.Images.Values);
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Properties.Textures.Values);
DeclareImages(context, context.Properties.Images.Values);
if (context.Config.Stage != ShaderStage.Compute)
if (context.Definitions.Stage != ShaderStage.Compute)
{
if (context.Config.Stage == ShaderStage.Geometry)
if (context.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology();
string inPrimitive = inputTopology.ToGlslString();
string inPrimitive = context.Definitions.InputTopology.ToGlslString();
context.AppendLine($"layout (invocations = {context.Config.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine($"layout (passthrough) in gl_PerVertex");
context.EnterScope();
@ -98,87 +98,75 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else
{
string outPrimitive = context.Config.OutputTopology.ToGlslString();
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
int maxOutputVertices = context.Config.GpPassthrough
? inputTopology.ToInputVertices()
: context.Config.MaxOutputVertices;
int maxOutputVertices = context.Definitions.GpPassthrough
? context.Definitions.InputTopology.ToInputVertices()
: context.Definitions.MaxOutputVertices;
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
}
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationControl)
else if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
int threadsPerInputPrimitive = context.Config.ThreadsPerInputPrimitive;
int threadsPerInputPrimitive = context.Definitions.ThreadsPerInputPrimitive;
context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;");
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationEvaluation)
else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
bool tessCw = context.Config.GpuAccessor.QueryTessCw();
bool tessCw = context.Definitions.TessCw;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here aswell.
tessCw = !tessCw;
}
string patchType = context.Config.GpuAccessor.QueryTessPatchType().ToGlsl();
string spacing = context.Config.GpuAccessor.QueryTessSpacing().ToGlsl();
string patchType = context.Definitions.TessPatchType.ToGlsl();
string spacing = context.Definitions.TessSpacing.ToGlsl();
string windingOrder = tessCw ? "cw" : "ccw";
context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;");
context.AppendLine();
}
if (context.Config.UsedInputAttributes != 0 || context.Config.GpPassthrough)
static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
{
DeclareInputAttributes(context, info);
context.AppendLine();
return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
}
if (context.Config.UsedOutputAttributes != 0 || context.Config.Stage != ShaderStage.Fragment)
static bool IsUserDefinedOutput(ShaderStage stage, IoDefinition ioDefinition)
{
DeclareOutputAttributes(context, info);
context.AppendLine();
IoVariable ioVariable = stage == ShaderStage.Fragment ? IoVariable.FragmentOutputColor : IoVariable.UserDefined;
return ioDefinition.StorageKind == StorageKind.Output && ioDefinition.IoVariable == ioVariable;
}
if (context.Config.UsedInputAttributesPerPatch.Count != 0)
DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
DeclareOutputAttributes(context, info.IoDefinitions.Where(x => IsUserDefinedOutput(context.Definitions.Stage, x)));
DeclareInputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.InputPerPatch)));
DeclareOutputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.OutputPerPatch)));
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
DeclareInputAttributesPerPatch(context, context.Config.UsedInputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.UsedOutputAttributesPerPatch.Count != 0)
{
DeclareUsedOutputAttributesPerPatch(context, context.Config.UsedOutputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
var tfOutput = context.Config.GetTransformFeedbackOutput(AttributeConsts.PositionX);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
context.EnterScope();
context.AppendLine("vec4 gl_Position;");
context.LeaveScope(context.Config.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
context.LeaveScope(context.Definitions.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
}
}
}
else
{
string localSizeX = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeX());
string localSizeY = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeY());
string localSizeZ = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeZ());
string localSizeX = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeX);
string localSizeY = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeY);
string localSizeZ = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeZ);
context.AppendLine(
"layout (" +
@ -188,15 +176,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryEarlyZForce())
if (context.Definitions.EarlyZForce)
{
context.AppendLine("layout (early_fragment_tests) in;");
context.AppendLine();
}
if (context.Config.Properties.OriginUpperLeft)
if (context.Definitions.OriginUpperLeft)
{
context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;");
context.AppendLine();
@ -251,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
{
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
if (context.HostCapabilities.ReducedPrecision)
{
precise = false;
}
@ -305,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string set = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
set = $"set = {buffer.Set}, ";
}
@ -390,7 +378,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string layout = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}";
}
@ -435,7 +423,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = ", " + layout;
}
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}{layout}";
}
@ -444,42 +432,50 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing))
if (context.Definitions.IaIndexing)
{
string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
string suffix = context.Definitions.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
context.AppendLine($"layout (location = 0) in vec4 {DefaultNames.IAttributePrefix}{suffix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedInputAttributes | context.Config.PassthroughAttributes;
while (usedAttributes != 0)
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareInputAttribute(context, info, index);
usedAttributes &= ~(1 << index);
DeclareInputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (inputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareInputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
private static void DeclareInputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
foreach (int attr in attrs.Order())
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
DeclareInputAttributePerPatch(context, attr);
DeclareInputAttributePerPatch(context, ioDefinition.Location);
}
if (inputs.Any())
{
context.AppendLine();
}
}
private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr)
private static void DeclareInputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: false) ? "[]" : string.Empty;
string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
iq = context.Config.ImapTypes[attr].GetFirstUsedType() switch
iq = context.Definitions.ImapTypes[location].GetFirstUsedType() switch
{
PixelImap.Constant => "flat ",
PixelImap.ScreenLinear => "noperspective ",
@ -487,14 +483,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
};
}
string name = $"{DefaultNames.IAttributePrefix}{attr}";
string name = $"{DefaultNames.IAttributePrefix}{location}";
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.Stage == ShaderStage.Fragment)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: false);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
context.AppendLine($"layout (location = {location}, component = {component}) {iq}in float {name}_{swzMask}{suffix};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@ -503,85 +507,89 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
_ => "float",
};
context.AppendLine($"layout (location = {attr}) in {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
context.AppendLine($"layout (location = {attr}, component = {c}) {iq}in float {name}_{swzMask}{suffix};");
context.AppendLine($"layout (location = {location}) in {type} {name};");
}
}
else
{
bool passthrough = (context.Config.PassthroughAttributes & (1 << attr)) != 0;
string pass = passthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough() ? "passthrough, " : string.Empty;
string type;
bool passthrough = (context.AttributeUsage.PassthroughAttributes & (1 << location)) != 0;
string pass = passthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough ? "passthrough, " : string.Empty;
string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(location, isOutput: false), false);
if (context.Config.Stage == ShaderStage.Vertex)
{
type = context.Config.GpuAccessor.QueryAttributeType(attr).ToVec4Type();
}
else
{
type = AttributeType.Float.ToVec4Type();
}
context.AppendLine($"layout ({pass}location = {attr}) {iq}in {type} {name}{suffix};");
context.AppendLine($"layout ({pass}location = {location}) {iq}in {type} {name}{suffix};");
}
}
private static void DeclareInputAttributePerPatch(CodeGenContext context, int attr)
private static void DeclareInputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch in vec4 {name};");
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
if (context.Definitions.OaIndexing)
{
context.AppendLine($"layout (location = 0) out vec4 {DefaultNames.OAttributePrefix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedOutputAttributes;
outputs = outputs.OrderBy(x => x.Location);
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
int mask = 3 << firstOutput;
IoDefinition firstOutput = outputs.ElementAtOrDefault(0);
IoDefinition secondOutput = outputs.ElementAtOrDefault(1);
if ((usedAttributes & mask) == mask)
if (firstOutput.Location + 1 == secondOutput.Location)
{
usedAttributes &= ~mask;
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
DeclareOutputDualSourceBlendAttribute(context, firstOutput.Location);
outputs = outputs.Skip(2);
}
}
while (usedAttributes != 0)
foreach (var ioDefinition in outputs)
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareOutputAttribute(context, index);
usedAttributes &= ~(1 << index);
DeclareOutputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (outputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareOutputAttribute(CodeGenContext context, int attr)
private static void DeclareOutputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{location}{suffix}";
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: true);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
string xfb = string.Empty;
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, component);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {location}, component = {component}{xfb}) out float {name}_{swzMask};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@ -592,58 +600,59 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, 0);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, 0);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}{xfb}) out {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
context.AppendLine($"layout (location = {location}{xfb}) out {type} {name};");
}
}
else
{
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
{
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => "vec4",
};
string type = context.Definitions.Stage != ShaderStage.Fragment ? "vec4" :
GetVarTypeName(context, context.Definitions.GetFragmentOutputColorType(location), false);
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
if (context.HostCapabilities.ReducedPrecision && context.Definitions.Stage == ShaderStage.Vertex && location == 0)
{
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
context.AppendLine($"layout (location = {location}) invariant out {type} {name};");
}
else
{
context.AppendLine($"layout (location = {attr}) out {type} {name};");
context.AppendLine($"layout (location = {location}) out {type} {name};");
}
}
}
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int location)
{
string name = $"{DefaultNames.OAttributePrefix}{attr}";
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
string name = $"{DefaultNames.OAttributePrefix}{location}";
string name2 = $"{DefaultNames.OAttributePrefix}{(location + 1)}";
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
context.AppendLine($"layout (location = {location}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {location}, index = 1) out vec4 {name2};");
}
private static void DeclareOutputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
foreach (var ioDefinition in outputs)
{
DeclareOutputAttributePerPatch(context, ioDefinition.Location);
}
if (outputs.Any())
{
context.AppendLine();
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
@ -660,29 +669,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{
foreach (int attr in attrs.Order())
{
DeclareOutputAttributePerPatch(context, attr);
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int attr)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);
code = code.Replace("\t", CodeGenContext.Tab);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB");
code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB");

View file

@ -11,9 +11,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
private const string MainFunctionName = "main";
public static string Generate(StructuredProgramInfo info, ShaderConfig config)
public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
CodeGenContext context = new(info, config);
CodeGenContext context = new(info, parameters);
Declarations.Declare(context, info);
@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
};
bool supportsBarrierDivergence = context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence();
bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence;
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}

View file

@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string arg = GetSoureExpr(context, operation.GetSource(0), dstType);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
return $"unpackUint2x32(ballotARB({arg})).x";
}

View file

@ -4,11 +4,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
public static string FSIBegin(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "beginInvocationInterlockARB()";
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
return "beginFragmentShaderOrderingINTEL()";
}
@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
public static string FSIEnd(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "endInvocationInterlockARB()";
}

View file

@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string imageName = GetImageName(context.Config, texOp, indexExpr);
string imageName = GetImageName(context.Properties, texOp, indexExpr);
texCallBuilder.Append('(');
texCallBuilder.Append(imageName);
@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// 2D Array and Cube shadow samplers with LOD level or bias requires an extension.
// If the extension is not supported, just remove the LOD parameter.
if (isArray && isShadow && (is2D || isCube) && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isArray && isShadow && (is2D || isCube) && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodBias = false;
hasLodLevel = false;
@ -281,7 +281,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// Cube shadow samplers with LOD level requires an extension.
// If the extension is not supported, just remove the LOD level parameter.
if (isShadow && isCube && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isShadow && isCube && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodLevel = false;
}
@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
texCall += "(" + samplerName;
@ -538,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
if (texOp.Index == 3)
{
@ -546,7 +546,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
context.Config.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall;
@ -593,8 +593,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[binding]
: context.Config.Properties.StorageBuffers[binding];
? context.Properties.ConstantBuffers[binding]
: context.Properties.StorageBuffers[binding];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
@ -614,8 +614,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
MemoryDefinition memory = storageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
varName = memory.Name;
varType = memory.Type;
@ -636,7 +636,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int location = -1;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -648,16 +648,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(varName, varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
if (IoMap.IsPerVertexBuiltIn(context.Config.Stage, ioVariable, isOutput))
if (IoMap.IsPerVertexBuiltIn(context.Definitions.Stage, ioVariable, isOutput))
{
// Since those exist both as input and output on geometry and tessellation shaders,
// we need the gl_in and gl_out prefixes to disambiguate.
@ -692,7 +699,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
else if (srcIndex == firstSrcIndex && context.Definitions.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
@ -715,9 +722,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return varName;
}
private static string GetSamplerName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Textures[texOp.Binding].Name;
string name = resourceDefinitions.Textures[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{
@ -727,9 +734,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return name;
}
private static string GetImageName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Images[texOp.Binding].Name;
string name = resourceDefinitions.Images[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{

View file

@ -7,7 +7,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
static class IoMap
{
public static (string, AggregateType) GetGlslVariable(
ShaderConfig config,
ShaderDefinitions definitions,
HostCapabilities hostCapabilities,
IoVariable ioVariable,
int location,
int component,
@ -25,7 +26,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.DrawIndex => ("gl_DrawIDARB", AggregateType.S32),
IoVariable.FogCoord => ("gl_FogFragCoord", AggregateType.FP32), // Deprecated.
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(definitions, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
@ -38,20 +39,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.PointCoord => ("gl_PointCoord", AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => ("gl_PointSize", AggregateType.FP32),
IoVariable.Position => ("gl_Position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(config.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(config, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(config, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(config, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(definitions.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(hostCapabilities.SupportsShaderBallot),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Lt"),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),
IoVariable.TextureCoord => ("gl_TexCoord", AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.ThreadId => ("gl_LocalInvocationID", AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => ("gl_HelperInvocation", AggregateType.Bool),
IoVariable.UserDefined => GetUserDefinedVariableName(config, location, component, isOutput, isPerPatch),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch),
IoVariable.VertexId => ("gl_VertexID", AggregateType.S32),
IoVariable.VertexIndex => ("gl_VertexIndex", AggregateType.S32),
IoVariable.ViewportIndex => ("gl_ViewportIndex", AggregateType.S32),
@ -86,16 +87,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return false;
}
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderConfig config, int location)
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderDefinitions definitions, int location)
{
if (location < 0)
{
return (DefaultNames.OAttributePrefix, config.GetFragmentOutputColorType(0));
return (DefaultNames.OAttributePrefix, definitions.GetFragmentOutputColorType(0));
}
string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture);
return (name, config.GetFragmentOutputColorType(location));
return (name, definitions.GetFragmentOutputColorType(location));
}
private static (string, AggregateType) GetPrimitiveIdVariableName(ShaderStage stage, bool isOutput)
@ -104,21 +105,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return (isOutput || stage != ShaderStage.Geometry ? "gl_PrimitiveID" : "gl_PrimitiveIDIn", AggregateType.S32);
}
private static (string, AggregateType) GetSubgroupMaskVariableName(ShaderConfig config, string cc)
private static (string, AggregateType) GetSubgroupMaskVariableName(bool supportsShaderBallot, string cc)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32)
: ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32);
}
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(ShaderConfig config)
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(bool supportsShaderBallot)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ("gl_SubGroupInvocationARB", AggregateType.U32)
: ("gl_SubgroupInvocationID", AggregateType.U32);
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderConfig config, int location, int component, bool isOutput, bool isPerPatch)
private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
@ -126,17 +127,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (location < 0)
{
return (name, config.GetUserDefinedType(0, isOutput));
return (name, definitions.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (config.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
return (name, config.GetUserDefinedType(location, isOutput));
return (name, definitions.GetUserDefinedType(location, isOutput));
}
}
}

View file

@ -68,8 +68,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
@ -82,8 +82,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
return memory.Type & AggregateType.ElementTypeMask;
@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(1) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -114,13 +114,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(_, AggregateType varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
return varType & AggregateType.ElementTypeMask;
}

View file

@ -20,21 +20,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public int InputVertices { get; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> LocalMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> SharedMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new Dictionary<int, SamplerType>();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<int, (Instruction, Instruction, Instruction)>();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new Dictionary<int, (Instruction, Instruction)>();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
public Dictionary<int, Instruction> LocalMemories { get; } = new();
public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new();
public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new();
@ -81,25 +89,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public CodeGenContext(
StructuredProgramInfo info,
ShaderConfig config,
CodeGenParameters parameters,
GeneratorPool<Instruction> instPool,
GeneratorPool<LiteralInteger> integerPool) : base(SpirvVersionPacked, instPool, integerPool)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
if (config.Stage == ShaderStage.Geometry)
if (parameters.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inPrimitive = config.GpuAccessor.QueryPrimitiveTopology();
InputVertices = inPrimitive switch
InputVertices = parameters.Definitions.InputTopology switch
{
InputTopology.Points => 1,
InputTopology.Lines => 2,
InputTopology.LinesAdjacency => 2,
InputTopology.Triangles => 3,
InputTopology.TrianglesAdjacency => 3,
_ => throw new InvalidOperationException($"Invalid input topology \"{inPrimitive}\"."),
_ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."),
};
}

View file

@ -5,7 +5,6 @@ using Spv.Generator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using static Spv.Specification;
using SpvInstruction = Spv.Generator.Instruction;
@ -66,12 +65,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
{
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Config.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
DeclareMemories(context, context.Config.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
DeclareSamplers(context, context.Config.Properties.Textures.Values);
DeclareImages(context, context.Config.Properties.Images.Values);
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
DeclareSamplers(context, context.Properties.Textures.Values);
DeclareImages(context, context.Properties.Images.Values);
DeclareInputsAndOutputs(context, info);
}
@ -108,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
foreach (BufferDefinition buffer in buffers)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
int alignmentMask = alignment - 1;
int offset = 0;
@ -181,7 +180,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var sampler in samplers)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch
{
@ -220,7 +219,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var image in images)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? image.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0;
var dim = GetDim(image.Type);
@ -314,16 +313,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void DeclareInputsAndOutputs(CodeGenContext context, StructuredProgramInfo info)
{
int firstLocation = int.MaxValue;
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
foreach (var ioDefinition in info.IoDefinitions)
{
if (ioDefinition.IoVariable == IoVariable.FragmentOutputColor && ioDefinition.Location < firstLocation)
{
firstLocation = ioDefinition.Location;
}
}
}
foreach (var ioDefinition in info.IoDefinitions)
{
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
var ioVariable = ioDefinition.IoVariable;
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
iq = context.Definitions.ImapTypes[ioDefinition.Location].GetFirstUsedType();
}
else
{
@ -340,11 +352,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation);
}
}
private static void DeclareInputOrOutput(CodeGenContext context, IoDefinition ioDefinition, bool isOutput, bool isPerPatch, PixelImap iq = PixelImap.Unused)
private static void DeclareInputOrOutput(
CodeGenContext context,
IoDefinition ioDefinition,
bool isOutput,
bool isPerPatch,
PixelImap iq = PixelImap.Unused,
int firstLocation = 0)
{
IoVariable ioVariable = ioDefinition.IoVariable;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
@ -355,12 +373,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(ioDefinition.Location, isOutput);
varType = context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput);
isBuiltIn = false;
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(ioDefinition.Location);
varType = context.Definitions.GetFragmentOutputColorType(ioDefinition.Location);
isBuiltIn = false;
}
else
@ -374,16 +392,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
bool hasComponent = context.Config.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
if (hasComponent)
{
varType &= AggregateType.ElementTypeMask;
}
else if (ioVariable == IoVariable.UserDefined && context.Config.HasTransformFeedbackOutputs(isOutput))
else if (ioVariable == IoVariable.UserDefined && context.Definitions.HasTransformFeedbackOutputs(isOutput))
{
varType &= AggregateType.ElementTypeMask;
varType |= context.Config.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
varType |= context.Definitions.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
{
2 => AggregateType.Vector2,
3 => AggregateType.Vector3,
@ -395,20 +413,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var spvType = context.GetType(varType, IoMap.GetSpirvBuiltInArrayLength(ioVariable));
bool builtInPassthrough = false;
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Config.Stage, isOutput))
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
builtInPassthrough = true;
}
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
if (context.Definitions.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
{
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Definitions.ThreadsPerInputPrimitive));
}
var spvPointerType = context.TypePointer(storageClass, spvType);
@ -426,7 +444,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.Decorate(spvVar, Decoration.Patch);
}
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && ioVariable == IoVariable.Position)
if (context.HostCapabilities.ReducedPrecision && ioVariable == IoVariable.Position)
{
context.Decorate(spvVar, Decoration.Invariant);
}
@ -439,7 +457,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
int location = context.Config.GetPerPatchAttributeLocation(ioDefinition.Location);
int location = context.AttributeUsage.GetPerPatchAttributeLocation(ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
@ -455,8 +473,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (!isOutput &&
!isPerPatch &&
(context.Config.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
(context.AttributeUsage.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
@ -465,13 +483,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
int location = ioDefinition.Location;
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
int index = location - firstLocation;
int mask = 3 << firstLocation;
if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
if ((uint)index < 2)
{
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
@ -499,7 +515,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
}
else if (context.Config.TryGetTransformFeedbackOutput(
else if (context.Definitions.TryGetTransformFeedbackOutput(
ioVariable,
ioDefinition.Location,
ioDefinition.Component,

View file

@ -240,10 +240,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (!context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence() &&
if (!context.HostCapabilities.SupportsShaderBarrierDivergence &&
(context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction))
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed.");
return OperationResult.Invalid;
}
@ -546,7 +546,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateFSIBegin(CodeGenContext context, AstOperation operation)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.BeginInvocationInterlockEXT();
}
@ -556,7 +556,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateFSIEnd(CodeGenContext context, AstOperation operation)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.EndInvocationInterlockEXT();
}
@ -1446,7 +1446,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
lodBias = Src(AggregateType.FP32);
}
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Config.Stage != ShaderStage.Fragment)
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Definitions.Stage != ShaderStage.Fragment)
{
// Implicit LOD is only valid on fragment.
// Use the LOD bias as explicit LOD if available.
@ -1804,8 +1804,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform;
@ -1825,13 +1825,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (storageKind == StorageKind.LocalMemory)
{
storageClass = StorageClass.Private;
varType = context.Config.Properties.LocalMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
varType = context.Properties.LocalMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
baseObj = context.LocalMemories[bindingId.Value];
}
else
{
storageClass = StorageClass.Workgroup;
varType = context.Config.Properties.SharedMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
varType = context.Properties.SharedMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
baseObj = context.SharedMemories[bindingId.Value];
}
break;
@ -1851,7 +1851,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -1863,7 +1863,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
@ -1872,11 +1872,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(location, isOutput);
varType = context.Definitions.GetUserDefinedType(location, isOutput);
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(location);
varType = context.Definitions.GetFragmentOutputColorType(location);
}
else
{
@ -2076,7 +2076,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2087,7 +2087,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2147,7 +2147,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2158,7 +2158,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}

View file

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
HelperFunctionsMask.ShuffleXor |
HelperFunctionsMask.SwizzleAdd;
public static byte[] Generate(StructuredProgramInfo info, ShaderConfig config)
public static byte[] Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
SpvInstructionPool instPool;
SpvLiteralIntegerPool integerPool;
@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
integerPool = _integerPool.Allocate();
}
CodeGenContext context = new(info, config, instPool, integerPool);
CodeGenContext context = new(info, parameters, instPool, integerPool);
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
@ -56,39 +56,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{
context.AddCapability(Capability.TransformFeedback);
}
if (config.Stage == ShaderStage.Fragment)
if (parameters.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
{
context.AddCapability(Capability.Geometry);
}
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AddCapability(Capability.FragmentShaderPixelInterlockEXT);
context.AddExtension("SPV_EXT_fragment_shader_interlock");
}
}
else if (config.Stage == ShaderStage.Geometry)
else if (parameters.Definitions.Stage == ShaderStage.Geometry)
{
context.AddCapability(Capability.Geometry);
if (config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (parameters.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AddExtension("SPV_NV_geometry_shader_passthrough");
context.AddCapability(Capability.GeometryShaderPassthroughNV);
}
}
else if (config.Stage == ShaderStage.TessellationControl || config.Stage == ShaderStage.TessellationEvaluation)
else if (parameters.Definitions.Stage == ShaderStage.TessellationControl ||
parameters.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
context.AddCapability(Capability.Tessellation);
}
else if (config.Stage == ShaderStage.Vertex)
else if (parameters.Definitions.Stage == ShaderStage.Vertex)
{
context.AddCapability(Capability.DrawParameters);
}
@ -170,15 +171,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (funcIndex == 0)
{
context.AddEntryPoint(context.Config.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
context.AddEntryPoint(context.Definitions.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
if (context.Config.Stage == ShaderStage.TessellationControl)
if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Config.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive);
}
else if (context.Config.Stage == ShaderStage.TessellationEvaluation)
else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
switch (context.Config.GpuAccessor.QueryTessPatchType())
switch (context.Definitions.TessPatchType)
{
case TessPatchType.Isolines:
context.AddExecutionMode(spvFunc, ExecutionMode.Isolines);
@ -191,7 +192,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
switch (context.Config.GpuAccessor.QueryTessSpacing())
switch (context.Definitions.TessSpacing)
{
case TessSpacing.EqualSpacing:
context.AddExecutionMode(spvFunc, ExecutionMode.SpacingEqual);
@ -204,9 +205,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
bool tessCw = context.Config.GpuAccessor.QueryTessCw();
bool tessCw = context.Definitions.TessCw;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here as well.
tessCw = !tessCw;
@ -221,37 +222,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCcw);
}
}
else if (context.Config.Stage == ShaderStage.Geometry)
else if (context.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology();
context.AddExecutionMode(spvFunc, inputTopology switch
context.AddExecutionMode(spvFunc, context.Definitions.InputTopology switch
{
InputTopology.Points => ExecutionMode.InputPoints,
InputTopology.Lines => ExecutionMode.InputLines,
InputTopology.LinesAdjacency => ExecutionMode.InputLinesAdjacency,
InputTopology.Triangles => ExecutionMode.Triangles,
InputTopology.TrianglesAdjacency => ExecutionMode.InputTrianglesAdjacency,
_ => throw new InvalidOperationException($"Invalid input topology \"{inputTopology}\"."),
_ => throw new InvalidOperationException($"Invalid input topology \"{context.Definitions.InputTopology}\"."),
});
context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.Config.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, context.Config.OutputTopology switch
context.AddExecutionMode(spvFunc, context.Definitions.OutputTopology switch
{
OutputTopology.PointList => ExecutionMode.OutputPoints,
OutputTopology.LineStrip => ExecutionMode.OutputLineStrip,
OutputTopology.TriangleStrip => ExecutionMode.OutputTriangleStrip,
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Config.OutputTopology}\"."),
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."),
});
int maxOutputVertices = context.Config.GpPassthrough ? context.InputVertices : context.Config.MaxOutputVertices;
int maxOutputVertices = context.Definitions.GpPassthrough ? context.InputVertices : context.Definitions.MaxOutputVertices;
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices);
}
else if (context.Config.Stage == ShaderStage.Fragment)
else if (context.Definitions.Stage == ShaderStage.Fragment)
{
context.AddExecutionMode(spvFunc, context.Config.Properties.OriginUpperLeft
context.AddExecutionMode(spvFunc, context.Definitions.OriginUpperLeft
? ExecutionMode.OriginUpperLeft
: ExecutionMode.OriginLowerLeft);
@ -260,22 +259,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddExecutionMode(spvFunc, ExecutionMode.DepthReplacing);
}
if (context.Config.GpuAccessor.QueryEarlyZForce())
if (context.Definitions.EarlyZForce)
{
context.AddExecutionMode(spvFunc, ExecutionMode.EarlyFragmentTests);
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.FSI) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AddExecutionMode(spvFunc, ExecutionMode.PixelInterlockOrderedEXT);
}
}
else if (context.Config.Stage == ShaderStage.Compute)
else if (context.Definitions.Stage == ShaderStage.Compute)
{
var localSizeX = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeX();
var localSizeY = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeY();
var localSizeZ = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeZ();
var localSizeX = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeX;
var localSizeY = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeY;
var localSizeZ = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeZ;
context.AddExecutionMode(
spvFunc,
@ -285,7 +284,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
localSizeZ);
}
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
context.AddExecutionMode(spvFunc, ExecutionMode.Xfb);
}

View file

@ -1,3 +1,4 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections;
using System.Collections.Generic;
@ -11,11 +12,26 @@ namespace Ryujinx.Graphics.Shader.Decoders
private readonly List<DecodedFunction> _functionsWithId;
public int FunctionsWithIdCount => _functionsWithId.Count;
public DecodedProgram(DecodedFunction mainFunction, IReadOnlyDictionary<ulong, DecodedFunction> functions)
public AttributeUsage AttributeUsage { get; }
public FeatureFlags UsedFeatures { get; }
public byte ClipDistancesWritten { get; }
public int Cb1DataSize { get; }
public DecodedProgram(
DecodedFunction mainFunction,
IReadOnlyDictionary<ulong, DecodedFunction> functions,
AttributeUsage attributeUsage,
FeatureFlags usedFeatures,
byte clipDistancesWritten,
int cb1DataSize)
{
MainFunction = mainFunction;
_functions = functions;
_functionsWithId = new List<DecodedFunction>();
_functionsWithId = new();
AttributeUsage = attributeUsage;
UsedFeatures = usedFeatures;
ClipDistancesWritten = clipDistancesWritten;
Cb1DataSize = cb1DataSize;
}
public DecodedFunction GetFunctionByAddress(ulong address)

View file

@ -9,8 +9,45 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
static class Decoder
{
public static DecodedProgram Decode(ShaderConfig config, ulong startAddress)
private class Context
{
public AttributeUsage AttributeUsage { get; }
public FeatureFlags UsedFeatures { get; private set; }
public byte ClipDistancesWritten { get; private set; }
public int Cb1DataSize { get; private set; }
private readonly IGpuAccessor _gpuAccessor;
public Context(IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
AttributeUsage = new(gpuAccessor);
}
public uint ConstantBuffer1Read(int offset)
{
if (Cb1DataSize < offset + 4)
{
Cb1DataSize = offset + 4;
}
return _gpuAccessor.ConstantBuffer1Read(offset);
}
public void SetUsedFeature(FeatureFlags flags)
{
UsedFeatures |= flags;
}
public void SetClipDistanceWritten(int index)
{
ClipDistancesWritten |= (byte)(1 << index);
}
}
public static DecodedProgram Decode(ShaderDefinitions definitions, IGpuAccessor gpuAccessor, ulong startAddress)
{
Context context = new(gpuAccessor);
Queue<DecodedFunction> functionsQueue = new();
Dictionary<ulong, DecodedFunction> functionsVisited = new();
@ -89,7 +126,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
}
FillBlock(config, currBlock, limitAddress, startAddress);
FillBlock(definitions, gpuAccessor, context, currBlock, limitAddress, startAddress);
if (currBlock.OpCodes.Count != 0)
{
@ -148,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
// Try to find targets for BRX (indirect branch) instructions.
hasNewTarget = FindBrxTargets(config, blocks, GetBlock);
hasNewTarget = FindBrxTargets(context, blocks, GetBlock);
// If we discovered new branch targets from the BRX instruction,
// we need another round of decoding to decode the new blocks.
@ -160,7 +197,13 @@ namespace Ryujinx.Graphics.Shader.Decoders
currentFunction.SetBlocks(blocks.ToArray());
}
return new DecodedProgram(mainFunction, functionsVisited);
return new DecodedProgram(
mainFunction,
functionsVisited,
context.AttributeUsage,
context.UsedFeatures,
context.ClipDistancesWritten,
context.Cb1DataSize);
}
private static bool BinarySearch(List<Block> blocks, ulong address, out int index)
@ -198,10 +241,14 @@ namespace Ryujinx.Graphics.Shader.Decoders
return false;
}
private static void FillBlock(ShaderConfig config, Block block, ulong limitAddress, ulong startAddress)
private static void FillBlock(
ShaderDefinitions definitions,
IGpuAccessor gpuAccessor,
Context context,
Block block,
ulong limitAddress,
ulong startAddress)
{
IGpuAccessor gpuAccessor = config.GpuAccessor;
ulong address = block.Address;
int bufferOffset = 0;
ReadOnlySpan<ulong> buffer = ReadOnlySpan<ulong>.Empty;
@ -235,27 +282,31 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (op.Props.HasFlag(InstProps.TexB))
{
config.SetUsedFeature(FeatureFlags.Bindless);
context.SetUsedFeature(FeatureFlags.Bindless);
}
if (op.Name == InstName.Ald || op.Name == InstName.Ast || op.Name == InstName.Ipa)
switch (op.Name)
{
SetUserAttributeUses(config, op.Name, opCode);
}
else if (op.Name == InstName.Pbk || op.Name == InstName.Pcnt || op.Name == InstName.Ssy)
{
block.AddPushOp(op);
}
else if (op.Name == InstName.Ldl || op.Name == InstName.Stl)
{
config.SetUsedFeature(FeatureFlags.LocalMemory);
}
else if (op.Name == InstName.Atoms ||
op.Name == InstName.AtomsCas ||
op.Name == InstName.Lds ||
op.Name == InstName.Sts)
{
config.SetUsedFeature(FeatureFlags.SharedMemory);
case InstName.Ald:
case InstName.Ast:
case InstName.Ipa:
SetUserAttributeUses(definitions, context, op.Name, opCode);
break;
case InstName.Pbk:
case InstName.Pcnt:
case InstName.Ssy:
block.AddPushOp(op);
break;
case InstName.Ldl:
case InstName.Stl:
context.SetUsedFeature(FeatureFlags.LocalMemory);
break;
case InstName.Atoms:
case InstName.AtomsCas:
case InstName.Lds:
case InstName.Sts:
context.SetUsedFeature(FeatureFlags.SharedMemory);
break;
}
block.OpCodes.Add(op);
@ -267,7 +318,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
block.EndAddress = address;
}
private static void SetUserAttributeUses(ShaderConfig config, InstName name, ulong opCode)
private static void SetUserAttributeUses(ShaderDefinitions definitions, Context context, InstName name, ulong opCode)
{
int offset;
int count = 1;
@ -304,13 +355,13 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
if (isStore)
{
config.SetAllOutputUserAttributes();
config.SetUsedFeature(FeatureFlags.OaIndexing);
context.AttributeUsage.SetAllOutputUserAttributes();
definitions.EnableOutputIndexing();
}
else
{
config.SetAllInputUserAttributes();
config.SetUsedFeature(FeatureFlags.IaIndexing);
context.AttributeUsage.SetAllInputUserAttributes();
definitions.EnableInputIndexing();
}
}
else
@ -328,11 +379,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetOutputUserAttributePerPatch(index);
context.AttributeUsage.SetOutputUserAttributePerPatch(index);
}
else
{
config.SetInputUserAttributePerPatch(index);
context.AttributeUsage.SetInputUserAttributePerPatch(index);
}
}
}
@ -343,11 +394,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetOutputUserAttribute(index);
context.AttributeUsage.SetOutputUserAttribute(index);
}
else
{
config.SetInputUserAttribute(index, (userAttr >> 2) & 3);
context.AttributeUsage.SetInputUserAttribute(index, (userAttr >> 2) & 3);
}
}
@ -356,7 +407,54 @@ namespace Ryujinx.Graphics.Shader.Decoders
(attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
{
config.SetUsedFeature(FeatureFlags.FixedFuncAttr);
context.SetUsedFeature(FeatureFlags.FixedFuncAttr);
}
else
{
if (isStore)
{
switch (attr)
{
case AttributeConsts.Layer:
if (definitions.Stage != ShaderStage.Compute && definitions.Stage != ShaderStage.Fragment)
{
context.SetUsedFeature(FeatureFlags.RtLayer);
}
break;
case AttributeConsts.ClipDistance0:
case AttributeConsts.ClipDistance1:
case AttributeConsts.ClipDistance2:
case AttributeConsts.ClipDistance3:
case AttributeConsts.ClipDistance4:
case AttributeConsts.ClipDistance5:
case AttributeConsts.ClipDistance6:
case AttributeConsts.ClipDistance7:
if (definitions.Stage == ShaderStage.Vertex)
{
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
}
break;
}
}
else
{
switch (attr)
{
case AttributeConsts.PositionX:
case AttributeConsts.PositionY:
if (definitions.Stage == ShaderStage.Fragment)
{
context.SetUsedFeature(FeatureFlags.FragCoordXY);
}
break;
case AttributeConsts.InstanceId:
if (definitions.Stage == ShaderStage.Vertex)
{
context.SetUsedFeature(FeatureFlags.InstanceId);
}
break;
}
}
}
}
}
@ -379,7 +477,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
return condOp.Pred == RegisterConsts.PredicateTrueIndex && !condOp.PredInv;
}
private static bool FindBrxTargets(ShaderConfig config, IEnumerable<Block> blocks, Func<ulong, Block> getBlock)
private static bool FindBrxTargets(Context context, IEnumerable<Block> blocks, Func<ulong, Block> getBlock)
{
bool hasNewTarget = false;
@ -406,7 +504,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
for (int i = 0; i < cbOffsetsCount; i++)
{
uint targetOffset = config.ConstantBuffer1Read(cbBaseOffset + i * 4);
uint targetOffset = context.ConstantBuffer1Read(cbBaseOffset + i * 4);
ulong targetAddress = baseOffset + targetOffset;
if (visited.Add(targetAddress))

View file

@ -0,0 +1,169 @@
using Ryujinx.Common.Memory;
namespace Ryujinx.Graphics.Shader
{
/// <summary>
/// GPU graphics state that the shader depends on.
/// </summary>
public readonly struct GpuGraphicsState
{
/// <summary>
/// Early Z force enable.
/// </summary>
public readonly bool EarlyZForce;
/// <summary>
/// Primitive topology of current draw.
/// </summary>
public readonly InputTopology Topology;
/// <summary>
/// Tessellation winding order.
/// </summary>
public readonly bool TessCw;
/// <summary>
/// Tessellation patch type.
/// </summary>
public readonly TessPatchType TessPatchType;
/// <summary>
/// Tessellation spacing.
/// </summary>
public readonly TessSpacing TessSpacing;
/// <summary>
/// Indicates whether alpha-to-coverage is enabled.
/// </summary>
public readonly bool AlphaToCoverageEnable;
/// <summary>
/// Indicates whether alpha-to-coverage dithering is enabled.
/// </summary>
public readonly bool AlphaToCoverageDitherEnable;
/// <summary>
/// Indicates whether the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Depth mode zero to one or minus one to one.
/// </summary>
public readonly bool DepthMode;
/// <summary>
/// Indicates if the point size is set on the shader or is fixed.
/// </summary>
public readonly bool ProgramPointSizeEnable;
/// <summary>
/// Point size used if <see cref="ProgramPointSizeEnable" /> is false.
/// </summary>
public readonly float PointSize;
/// <summary>
/// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded.
/// </summary>
public readonly AlphaTestOp AlphaTestCompare;
/// <summary>
/// When alpha test is enabled, indicates the value to compare with the fragment output alpha.
/// </summary>
public readonly float AlphaTestReference;
/// <summary>
/// Type of the vertex attributes consumed by the shader.
/// </summary>
public readonly Array32<AttributeType> AttributeTypes;
/// <summary>
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
/// </summary>
public readonly bool HasConstantBufferDrawParameters;
/// <summary>
/// Type of the fragment shader outputs.
/// </summary>
public readonly Array8<AttributeType> FragmentOutputTypes;
/// <summary>
/// Indicates whether dual source blend is enabled.
/// </summary>
public readonly bool DualSourceBlendEnable;
/// <summary>
/// Indicates if negation of the viewport Y axis is enabled.
/// </summary>
public readonly bool YNegateEnabled;
/// <summary>
/// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner.
/// </summary>
public readonly bool OriginUpperLeft;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessCw">Tessellation winding order (clockwise or counter-clockwise)</param>
/// <param name="tessPatchType">Tessellation patch type</param>
/// <param name="tessSpacing">Tessellation spacing</param>
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates if negation of the viewport Y axis is enabled</param>
/// <param name="originUpperLeft">If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner</param>
public GpuGraphicsState(
bool earlyZForce,
InputTopology topology,
bool tessCw,
TessPatchType tessPatchType,
TessSpacing tessSpacing,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool viewportTransformDisable,
bool depthMode,
bool programPointSizeEnable,
float pointSize,
AlphaTestOp alphaTestCompare,
float alphaTestReference,
in Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters,
in Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled,
bool originUpperLeft)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessCw = tessCw;
TessPatchType = tessPatchType;
TessSpacing = tessSpacing;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
ViewportTransformDisable = viewportTransformDisable;
DepthMode = depthMode;
ProgramPointSizeEnable = programPointSizeEnable;
PointSize = pointSize;
AlphaTestCompare = alphaTestCompare;
AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
OriginUpperLeft = originUpperLeft;
}
}
}

View file

@ -1,21 +1,13 @@
using System;
using Ryujinx.Graphics.Shader.CodeGen;
using System;
namespace Ryujinx.Graphics.Shader
{
/// <summary>
/// GPU state access interface.
/// </summary>
public interface IGpuAccessor
public interface IGpuAccessor : ILogger
{
/// <summary>
/// Prints a log message.
/// </summary>
/// <param name="message">Message to print</param>
void Log(string message)
{
// No default log output.
}
/// <summary>
/// Reads data from the constant buffer 1.
/// </summary>
@ -34,44 +26,6 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Queries the alpha test comparison operator that is being used currently.
/// If alpha test is disabled, it should be set to <see cref="AlphaTestOp.Always"/>.
/// </summary>
/// <returns>Current alpha test comparison</returns>
AlphaTestOp QueryAlphaTestCompare()
{
return AlphaTestOp.Always;
}
/// <summary>
/// Queries the current alpha test reference value used by the comparison.
/// </summary>
/// <returns>Current alpha test reference value</returns>
float QueryAlphaTestReference()
{
return 0f;
}
/// <summary>
/// Queries the type of the vertex shader input attribute at the specified <paramref name="location"/>.
/// </summary>
/// <param name="location">Location of the input attribute</param>
/// <returns>Input type</returns>
AttributeType QueryAttributeType(int location)
{
return AttributeType.Float;
}
/// <summary>
/// Queries whenever the alpha-to-coverage dithering feature is enabled.
/// </summary>
/// <returns>True if the feature is enabled, false otherwise</returns>
bool QueryAlphaToCoverageDitherEnable()
{
return false;
}
/// <summary>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -114,16 +68,6 @@ namespace Ryujinx.Graphics.Shader
return index;
}
/// <summary>
/// Queries output type for fragment shaders.
/// </summary>
/// <param name="location">Location of the framgent output</param>
/// <returns>Output location</returns>
AttributeType QueryFragmentOutputType(int location)
{
return AttributeType.Float;
}
/// <summary>
/// Queries Local Size X for compute shaders.
/// </summary>
@ -179,12 +123,12 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries if host state forces early depth testing.
/// Queries specialized GPU graphics state that the shader depends on.
/// </summary>
/// <returns>True if early depth testing is forced</returns>
bool QueryEarlyZForce()
/// <returns>GPU graphics state</returns>
GpuGraphicsState QueryGraphicsState()
{
return false;
return default;
}
/// <summary>
@ -223,15 +167,6 @@ namespace Ryujinx.Graphics.Shader
return false;
}
/// <summary>
/// Queries dual source blend state.
/// </summary>
/// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
bool QueryDualSourceBlendEnable()
{
return false;
}
/// <summary>
/// Queries host about the presence of the FrontFacing built-in variable bug.
/// </summary>
@ -412,25 +347,6 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
/// </summary>
/// <returns>Current point size</returns>
float QueryPointSize()
{
return 1f;
}
/// <summary>
/// Queries the state that indicates if the program point size should be explicitly set on the shader
/// or read from the GPU state.
/// </summary>
/// <returns>True if the shader is expected to set the point size explicitly, false otherwise</returns>
bool QueryProgramPointSize()
{
return true;
}
/// <summary>
/// Queries sampler type information.
/// </summary>
@ -453,42 +369,6 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries current primitive topology for geometry shaders.
/// </summary>
/// <returns>Current primitive topology</returns>
InputTopology QueryPrimitiveTopology()
{
return 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>
bool QueryTessCw()
{
return false;
}
/// <summary>
/// Queries the tessellation evaluation shader abstract patch type.
/// </summary>
/// <returns>Abstract patch type</returns>
TessPatchType QueryTessPatchType()
{
return TessPatchType.Triangles;
}
/// <summary>
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
/// </summary>
/// <returns>Spacing between tessellated vertices of the patch</returns>
TessSpacing QueryTessSpacing()
{
return TessSpacing.EqualSpacing;
}
/// <summary>
/// Queries texture format information, for shaders using image load or store.
/// </summary>
@ -504,15 +384,6 @@ namespace Ryujinx.Graphics.Shader
return TextureFormat.R8G8B8A8Unorm;
}
/// <summary>
/// Queries depth mode information from the GPU state.
/// </summary>
/// <returns>True if current depth mode is -1 to 1, false if 0 to 1</returns>
bool QueryTransformDepthMinusOneToOne()
{
return false;
}
/// <summary>
/// Queries transform feedback enable state.
/// </summary>
@ -542,24 +413,6 @@ namespace Ryujinx.Graphics.Shader
return 0;
}
/// <summary>
/// Queries if host state disables the viewport transform.
/// </summary>
/// <returns>True if the viewport transform is disabled</returns>
bool QueryViewportTransformDisable()
{
return false;
}
/// <summary>
/// Queries Y negate enable state.
/// </summary>
/// <returns>True if Y negate of the fragment coordinates is enabled, false otherwise</returns>
bool QueryYNegateEnabled()
{
return false;
}
/// <summary>
/// Registers a texture used by the shader.
/// </summary>

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.Shader.CodeGen
{
/// <summary>
/// Shader code generation logging interface.
/// </summary>
public interface ILogger
{
/// <summary>
/// Prints a log message.
/// </summary>
/// <param name="message">Message to print</param>
void Log(string message)
{
// No default log output.
}
}
}

View file

@ -127,25 +127,25 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return Const(0);
}
StagesMask validUseMask = isOutput ? entry.OutputMask : entry.InputMask;
if (((StagesMask)(1 << (int)context.Config.Stage) & validUseMask) == StagesMask.None)
if (((StagesMask)(1 << (int)context.TranslatorContext.Definitions.Stage) & validUseMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}.");
return Const(0);
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}.");
return Const(0);
}
if (HasInvocationId(context.Config.Stage, isOutput) && !isPerPatch)
if (HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput) && !isPerPatch)
{
primVertex = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -156,12 +156,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
StorageKind storageKind = isPerPatch
? (isOutput ? StorageKind.OutputPerPatch : StorageKind.InputPerPatch)
: (isOutput ? StorageKind.Output : StorageKind.Input);
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput, innerIndex, in entry);
IoVariable ioVariable = GetIoVariable(context.TranslatorContext.Definitions.Stage, in entry);
AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasElementIndex = elementCount > 1;
@ -190,25 +190,25 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return;
}
if (((StagesMask)(1 << (int)context.Config.Stage) & entry.OutputMask) == StagesMask.None)
if (((StagesMask)(1 << (int)context.TranslatorContext.Definitions.Stage) & entry.OutputMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}.");
return;
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}.");
return;
}
Operand invocationId = null;
if (HasInvocationId(context.Config.Stage, isOutput: true) && !isPerPatch)
if (HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput: true) && !isPerPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -217,12 +217,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
int innerIndex = innerOffset / 4;
StorageKind storageKind = isPerPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput: true, innerIndex, in entry);
IoVariable ioVariable = GetIoVariable(context.TranslatorContext.Definitions.Stage, in entry);
AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput: true, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasElementIndex = elementCount > 1;
@ -271,7 +271,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return true;
}
public static IoVariable GetIoVariable(ShaderConfig config, int offset, out int location)
public static IoVariable GetIoVariable(ShaderDefinitions definitions, int offset, out int location)
{
location = 0;
@ -280,17 +280,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
return IoVariable.Invalid;
}
if (((StagesMask)(1 << (int)config.Stage) & entry.OutputMask) == StagesMask.None)
if (((StagesMask)(1 << (int)definitions.Stage) & entry.OutputMask) == StagesMask.None)
{
return IoVariable.Invalid;
}
if (config.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
if (definitions.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
{
location = (offset - entry.BaseOffset) / 16;
}
return GetIoVariable(config.Stage, in entry);
return GetIoVariable(definitions.Stage, in entry);
}
private static IoVariable GetIoVariable(ShaderStage stage, in AttributeEntry entry)
@ -303,17 +303,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
return entry.IoVariable;
}
private static AggregateType GetType(ShaderConfig config, bool isOutput, int innerIndex, in AttributeEntry entry)
private static AggregateType GetType(ShaderDefinitions definitions, bool isOutput, int innerIndex, in AttributeEntry entry)
{
AggregateType type = entry.Type;
if (entry.IoVariable == IoVariable.UserDefined)
{
type = config.GetUserDefinedType(innerIndex / 4, isOutput);
type = definitions.GetUserDefinedType(innerIndex / 4, isOutput);
}
else if (entry.IoVariable == IoVariable.FragmentOutputColor)
{
type = config.GetFragmentOutputColorType(innerIndex / 4);
type = definitions.GetFragmentOutputColorType(innerIndex / 4);
}
return type;

View file

@ -9,350 +9,350 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
context.GetOp<InstAtomCas>();
context.Config.GpuAccessor.Log("Shader instruction AtomCas is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomCas is not implemented.");
}
public static void AtomsCas(EmitterContext context)
{
context.GetOp<InstAtomsCas>();
context.Config.GpuAccessor.Log("Shader instruction AtomsCas is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomsCas is not implemented.");
}
public static void B2r(EmitterContext context)
{
context.GetOp<InstB2r>();
context.Config.GpuAccessor.Log("Shader instruction B2r is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction B2r is not implemented.");
}
public static void Bpt(EmitterContext context)
{
context.GetOp<InstBpt>();
context.Config.GpuAccessor.Log("Shader instruction Bpt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Bpt is not implemented.");
}
public static void Cctl(EmitterContext context)
{
context.GetOp<InstCctl>();
context.Config.GpuAccessor.Log("Shader instruction Cctl is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctl is not implemented.");
}
public static void Cctll(EmitterContext context)
{
context.GetOp<InstCctll>();
context.Config.GpuAccessor.Log("Shader instruction Cctll is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctll is not implemented.");
}
public static void Cctlt(EmitterContext context)
{
context.GetOp<InstCctlt>();
context.Config.GpuAccessor.Log("Shader instruction Cctlt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctlt is not implemented.");
}
public static void Cs2r(EmitterContext context)
{
context.GetOp<InstCs2r>();
context.Config.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
}
public static void FchkR(EmitterContext context)
{
context.GetOp<InstFchkR>();
context.Config.GpuAccessor.Log("Shader instruction FchkR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkR is not implemented.");
}
public static void FchkI(EmitterContext context)
{
context.GetOp<InstFchkI>();
context.Config.GpuAccessor.Log("Shader instruction FchkI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkI is not implemented.");
}
public static void FchkC(EmitterContext context)
{
context.GetOp<InstFchkC>();
context.Config.GpuAccessor.Log("Shader instruction FchkC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkC is not implemented.");
}
public static void Getcrsptr(EmitterContext context)
{
context.GetOp<InstGetcrsptr>();
context.Config.GpuAccessor.Log("Shader instruction Getcrsptr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Getcrsptr is not implemented.");
}
public static void Getlmembase(EmitterContext context)
{
context.GetOp<InstGetlmembase>();
context.Config.GpuAccessor.Log("Shader instruction Getlmembase is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Getlmembase is not implemented.");
}
public static void Ide(EmitterContext context)
{
context.GetOp<InstIde>();
context.Config.GpuAccessor.Log("Shader instruction Ide is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ide is not implemented.");
}
public static void IdpR(EmitterContext context)
{
context.GetOp<InstIdpR>();
context.Config.GpuAccessor.Log("Shader instruction IdpR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpR is not implemented.");
}
public static void IdpC(EmitterContext context)
{
context.GetOp<InstIdpC>();
context.Config.GpuAccessor.Log("Shader instruction IdpC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpC is not implemented.");
}
public static void ImadspR(EmitterContext context)
{
context.GetOp<InstImadspR>();
context.Config.GpuAccessor.Log("Shader instruction ImadspR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspR is not implemented.");
}
public static void ImadspI(EmitterContext context)
{
context.GetOp<InstImadspI>();
context.Config.GpuAccessor.Log("Shader instruction ImadspI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspI is not implemented.");
}
public static void ImadspC(EmitterContext context)
{
context.GetOp<InstImadspC>();
context.Config.GpuAccessor.Log("Shader instruction ImadspC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspC is not implemented.");
}
public static void ImadspRc(EmitterContext context)
{
context.GetOp<InstImadspRc>();
context.Config.GpuAccessor.Log("Shader instruction ImadspRc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspRc is not implemented.");
}
public static void Jcal(EmitterContext context)
{
context.GetOp<InstJcal>();
context.Config.GpuAccessor.Log("Shader instruction Jcal is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jcal is not implemented.");
}
public static void Jmp(EmitterContext context)
{
context.GetOp<InstJmp>();
context.Config.GpuAccessor.Log("Shader instruction Jmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmp is not implemented.");
}
public static void Jmx(EmitterContext context)
{
context.GetOp<InstJmx>();
context.Config.GpuAccessor.Log("Shader instruction Jmx is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmx is not implemented.");
}
public static void Ld(EmitterContext context)
{
context.GetOp<InstLd>();
context.Config.GpuAccessor.Log("Shader instruction Ld is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ld is not implemented.");
}
public static void Lepc(EmitterContext context)
{
context.GetOp<InstLepc>();
context.Config.GpuAccessor.Log("Shader instruction Lepc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Lepc is not implemented.");
}
public static void Longjmp(EmitterContext context)
{
context.GetOp<InstLongjmp>();
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
}
public static void Pexit(EmitterContext context)
{
context.GetOp<InstPexit>();
context.Config.GpuAccessor.Log("Shader instruction Pexit is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pexit is not implemented.");
}
public static void Pixld(EmitterContext context)
{
context.GetOp<InstPixld>();
context.Config.GpuAccessor.Log("Shader instruction Pixld is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pixld is not implemented.");
}
public static void Plongjmp(EmitterContext context)
{
context.GetOp<InstPlongjmp>();
context.Config.GpuAccessor.Log("Shader instruction Plongjmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Plongjmp is not implemented.");
}
public static void Pret(EmitterContext context)
{
context.GetOp<InstPret>();
context.Config.GpuAccessor.Log("Shader instruction Pret is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pret is not implemented.");
}
public static void PrmtR(EmitterContext context)
{
context.GetOp<InstPrmtR>();
context.Config.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
}
public static void PrmtI(EmitterContext context)
{
context.GetOp<InstPrmtI>();
context.Config.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
}
public static void PrmtC(EmitterContext context)
{
context.GetOp<InstPrmtC>();
context.Config.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
}
public static void PrmtRc(EmitterContext context)
{
context.GetOp<InstPrmtRc>();
context.Config.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
}
public static void R2b(EmitterContext context)
{
context.GetOp<InstR2b>();
context.Config.GpuAccessor.Log("Shader instruction R2b is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction R2b is not implemented.");
}
public static void Ram(EmitterContext context)
{
context.GetOp<InstRam>();
context.Config.GpuAccessor.Log("Shader instruction Ram is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ram is not implemented.");
}
public static void Rtt(EmitterContext context)
{
context.GetOp<InstRtt>();
context.Config.GpuAccessor.Log("Shader instruction Rtt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Rtt is not implemented.");
}
public static void Sam(EmitterContext context)
{
context.GetOp<InstSam>();
context.Config.GpuAccessor.Log("Shader instruction Sam is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Sam is not implemented.");
}
public static void Setcrsptr(EmitterContext context)
{
context.GetOp<InstSetcrsptr>();
context.Config.GpuAccessor.Log("Shader instruction Setcrsptr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Setcrsptr is not implemented.");
}
public static void Setlmembase(EmitterContext context)
{
context.GetOp<InstSetlmembase>();
context.Config.GpuAccessor.Log("Shader instruction Setlmembase is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Setlmembase is not implemented.");
}
public static void St(EmitterContext context)
{
context.GetOp<InstSt>();
context.Config.GpuAccessor.Log("Shader instruction St is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction St is not implemented.");
}
public static void Stp(EmitterContext context)
{
context.GetOp<InstStp>();
context.Config.GpuAccessor.Log("Shader instruction Stp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Stp is not implemented.");
}
public static void Txa(EmitterContext context)
{
context.GetOp<InstTxa>();
context.Config.GpuAccessor.Log("Shader instruction Txa is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Txa is not implemented.");
}
public static void Vabsdiff(EmitterContext context)
{
context.GetOp<InstVabsdiff>();
context.Config.GpuAccessor.Log("Shader instruction Vabsdiff is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff is not implemented.");
}
public static void Vabsdiff4(EmitterContext context)
{
context.GetOp<InstVabsdiff4>();
context.Config.GpuAccessor.Log("Shader instruction Vabsdiff4 is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff4 is not implemented.");
}
public static void Vadd(EmitterContext context)
{
context.GetOp<InstVadd>();
context.Config.GpuAccessor.Log("Shader instruction Vadd is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vadd is not implemented.");
}
public static void Votevtg(EmitterContext context)
{
context.GetOp<InstVotevtg>();
context.Config.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
}
public static void Vset(EmitterContext context)
{
context.GetOp<InstVset>();
context.Config.GpuAccessor.Log("Shader instruction Vset is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vset is not implemented.");
}
public static void Vshl(EmitterContext context)
{
context.GetOp<InstVshl>();
context.Config.GpuAccessor.Log("Shader instruction Vshl is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshl is not implemented.");
}
public static void Vshr(EmitterContext context)
{
context.GetOp<InstVshr>();
context.Config.GpuAccessor.Log("Shader instruction Vshr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshr is not implemented.");
}
}
}

View file

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// Some of those attributes are per invocation,
// so we should ignore any primitive vertex indexing for those.
bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.Config.Stage, op.O) && !op.P;
bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.TranslatorContext.Definitions.Stage, op.O) && !op.P;
if (!op.Phys)
{
@ -52,10 +52,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
{
int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
context.FlagAttributeRead(offset);
int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
bool isOutput = op.O && CanLoadOutput(offset);
if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value))
@ -69,10 +66,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
context.FlagAttributeRead(offset);
int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
bool isOutput = op.O && CanLoadOutput(offset);
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false));
@ -98,7 +92,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand vecIndex = context.ShiftRightU32(offset, Const(4));
Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
Operand invocationId = AttributeMap.HasInvocationId(context.Config.Stage, isOutput: true)
Operand invocationId = AttributeMap.HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput: true)
? context.Load(StorageKind.Input, IoVariable.InvocationId)
: null;
@ -110,15 +104,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
int offset = op.Imm11 + index * 4;
if (!context.Config.IsUsedOutputAttribute(offset))
if (!context.TranslatorContext.AttributeUsage.IsUsedOutputAttribute(offset))
{
return;
}
offset = FixedFuncToUserAttribute(context.Config, offset, isOutput: true);
context.FlagAttributeWritten(offset);
offset = FixedFuncToUserAttribute(context.TranslatorContext, offset, isOutput: true);
AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd));
}
}
@ -128,8 +119,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstIpa op = context.GetOp<InstIpa>();
context.FlagAttributeRead(op.Imm10);
Operand res;
bool isFixedFunc = false;
@ -151,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
int index = (op.Imm10 - AttributeConsts.UserAttributeBase) >> 4;
if (context.Config.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
if (context.TranslatorContext.Definitions.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
{
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
@ -162,11 +151,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
if (op.Imm10 == AttributeConsts.PositionY && context.Config.Options.TargetApi != TargetApi.OpenGL)
if (op.Imm10 == AttributeConsts.PositionY && context.TranslatorContext.Options.TargetApi != TargetApi.OpenGL)
{
// If YNegate is enabled, we need to flip the fragment coordinates vertically, unless
// the API supports changing the origin (only OpenGL does).
if (context.Config.GpuAccessor.QueryYNegateEnabled())
if (context.TranslatorContext.Definitions.YNegateEnabled)
{
Operand viewportHeight = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.ViewportSize), Const(1));
@ -174,7 +163,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug())
{
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
// This weird trick makes it behave.
@ -231,12 +220,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(emit || cut))
{
context.Config.GpuAccessor.Log("Invalid OUT encoding.");
context.TranslatorContext.GpuAccessor.Log("Invalid OUT encoding.");
}
if (emit)
{
if (context.Config.LastInVertexPipeline)
if (context.TranslatorContext.Definitions.LastInVertexPipeline)
{
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
@ -289,13 +278,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// TODO: If two sided rendering is enabled, then this should return
// FrontColor if the fragment is front facing, and back color otherwise.
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
else if (attr == AttributeConsts.FogCoord)
{
// TODO: We likely need to emulate the fixed-function functionality for FogCoord here.
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
@ -305,7 +294,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
@ -318,53 +307,44 @@ namespace Ryujinx.Graphics.Shader.Instructions
return AttributeMap.GenerateAttributeLoad(context, null, offset, isOutput: false, isPerPatch: false);
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, bool isOutput)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
bool supportsLayerFromVertexOrTess = translatorContext.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
if (attr == AttributeConsts.Layer && config.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
if (attr == AttributeConsts.Layer && translatorContext.Definitions.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.Layer, 0, isOutput);
config.SetLayerOutputAttribute(attr);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.Layer, 0, isOutput);
translatorContext.SetLayerOutputAttribute(attr);
}
else if (attr == AttributeConsts.FogCoord)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
}
else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
}
return attr;
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, int baseAttr, int baseIndex, bool isOutput)
private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, int baseAttr, int baseIndex, bool isOutput)
{
int index = (attr - baseAttr) >> 4;
int userAttrIndex = config.GetFreeUserAttribute(isOutput, baseIndex + index);
int userAttrIndex = translatorContext.AttributeUsage.GetFreeUserAttribute(isOutput, baseIndex + index);
if ((uint)userAttrIndex < Constants.MaxAttributes)
{
attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
if (isOutput)
{
config.SetOutputUserAttributeFixedFunc(userAttrIndex);
}
else
{
config.SetInputUserAttributeFixedFunc(userAttrIndex);
}
}
else
{
config.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
translatorContext.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
}
return attr;
@ -372,7 +352,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value)
{
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
{
if (attr == AttributeConsts.InstanceId)
{

View file

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid barrier mode: {op.BarOp}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid barrier mode: {op.BarOp}.");
}
}

View file

@ -174,7 +174,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (dstType == IDstFmt.U64)
{
context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
context.TranslatorContext.GpuAccessor.Log("Unimplemented 64-bits F2I.");
}
Instruction fpType = srcType.ToInstFPType();
@ -297,7 +297,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if ((srcType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32 || (dstType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32)
{
context.Config.GpuAccessor.Log("Invalid I2I encoding.");
context.TranslatorContext.GpuAccessor.Log("Invalid I2I encoding.");
return;
}

View file

@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (scaleConst.AsFloat() == 1f)
{
context.Config.GpuAccessor.Log($"Invalid FP multiply scale \"{scale}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid FP multiply scale \"{scale}\".");
}
if (isFP64)

View file

@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.CurrBlock.Successors.Count <= startIndex)
{
context.Config.GpuAccessor.Log($"Failed to find targets for BRX instruction at 0x{currOp.Address:X}.");
context.TranslatorContext.GpuAccessor.Log($"Failed to find targets for BRX instruction at 0x{currOp.Address:X}.");
return;
}
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.IsNonMain)
{
context.Config.GpuAccessor.Log("Invalid exit on non-main function.");
context.TranslatorContext.GpuAccessor.Log("Invalid exit on non-main function.");
return;
}
@ -218,7 +218,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log("Invalid return on main function.");
context.TranslatorContext.GpuAccessor.Log("Invalid return on main function.");
}
}

View file

@ -371,7 +371,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Iadd3 has invalid component selection {part}.");
context.TranslatorContext.GpuAccessor.Log($"Iadd3 has invalid component selection {part}.");
}
return src;
@ -555,7 +555,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
modeConv = XmadCop.Csfu;
break;
default:
context.Config.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
return;
}
@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
default:
context.Config.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
return;
}

View file

@ -26,9 +26,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Atoms(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Atoms instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Atoms instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
_ => AtomSize.U32,
};
Operand id = Const(context.Config.ResourceManager.SharedMemoryId);
Operand id = Const(context.ResourceManager.SharedMemoryId);
Operand res = EmitAtomicOp(context, StorageKind.SharedMemory, op.AtomOp, size, id, offset, value);
context.Copy(GetDest(op.Dest), res);
@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.LsSize > LsSize2.B64)
{
context.Config.GpuAccessor.Log($"Invalid LDC size: {op.LsSize}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid LDC size: {op.LsSize}.");
return;
}
@ -119,9 +119,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Lds(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Lds instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Lds instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -155,9 +155,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Sts(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Sts instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Sts instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -173,19 +173,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (slot.Type == OperandType.Constant)
{
int binding = context.Config.ResourceManager.GetConstantBufferBinding(slot.Value);
int binding = context.ResourceManager.GetConstantBufferBinding(slot.Value);
return context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
}
else
{
Operand value = Const(0);
uint cbUseMask = context.Config.GpuAccessor.QueryConstantBufferUse();
uint cbUseMask = context.TranslatorContext.GpuAccessor.QueryConstantBufferUse();
while (cbUseMask != 0)
{
int cbIndex = BitOperations.TrailingZeroCount(cbUseMask);
int binding = context.Config.ResourceManager.GetConstantBufferBinding(cbIndex);
int binding = context.ResourceManager.GetConstantBufferBinding(cbIndex);
Operand isCurrent = context.ICompareEqual(slot, Const(cbIndex));
Operand currentValue = context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.And:
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Xor:
@ -239,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Or:
@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Max:
@ -263,7 +263,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Min:
@ -277,7 +277,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
}
@ -295,13 +295,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid load size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid load size: {size}.");
return;
}
int id = storageKind == StorageKind.LocalMemory
? context.Config.ResourceManager.LocalMemoryId
: context.Config.ResourceManager.SharedMemoryId;
? context.ResourceManager.LocalMemoryId
: context.ResourceManager.SharedMemoryId;
bool isSmallInt = size < LsSize2.B32;
int count = size switch
@ -376,13 +376,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid store size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}.");
return;
}
int id = storageKind == StorageKind.LocalMemory
? context.Config.ResourceManager.LocalMemoryId
: context.Config.ResourceManager.SharedMemoryId;
? context.ResourceManager.LocalMemoryId
: context.ResourceManager.SharedMemoryId;
bool isSmallInt = size < LsSize2.B32;
int count = size switch
@ -444,7 +444,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid store size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}.");
return;
}

View file

@ -88,24 +88,24 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.ThreadKill:
src = context.Config.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
src = context.TranslatorContext.Definitions.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
break;
case SReg.InvocationInfo:
if (context.Config.Stage != ShaderStage.Compute && context.Config.Stage != ShaderStage.Fragment)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute && context.TranslatorContext.Definitions.Stage != ShaderStage.Fragment)
{
// Note: Lowest 8-bits seems to contain some primitive index,
// but it seems to be NVIDIA implementation specific as it's only used
// to calculate ISBE offsets, so we can just keep it as zero.
if (context.Config.Stage == ShaderStage.TessellationControl ||
context.Config.Stage == ShaderStage.TessellationEvaluation)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl ||
context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
src = context.ShiftLeft(context.Load(StorageKind.Input, IoVariable.PatchVertices), Const(16));
}
else
{
src = Const(context.Config.GpuAccessor.QueryPrimitiveTopology().ToInputVertices() << 16);
src = Const(context.TranslatorContext.Definitions.InputTopology.ToInputVertices() << 16);
}
}
else

View file

@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
default:
context.Config.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\".");
break;
}

View file

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
@ -194,7 +195,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image atomic sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image atomic sampler type.");
return;
}
@ -258,7 +259,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// TODO: FP and 64-bit formats.
TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64
? (isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormatAtomic(imm))
? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm))
: GetTextureFormat(size);
if (compareAndSwap)
@ -277,7 +278,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@ -309,13 +310,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
SamplerType type = ConvertSamplerType(dimensions);
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image store sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type.");
return;
}
@ -388,9 +387,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
Array.Resize(ref dests, outputIndex);
}
TextureFormat format = isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormat(handle);
TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle);
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@ -433,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = GetTextureFormat(size);
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@ -477,7 +476,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image reduction sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image reduction sampler type.");
return;
}
@ -539,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// TODO: FP and 64-bit formats.
TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64
? (isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormatAtomic(imm))
? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm))
: GetTextureFormat(size);
sourcesList.Add(Rb());
@ -553,7 +552,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@ -582,7 +581,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image store sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type.");
return;
}
@ -647,7 +646,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (!isBindless)
{
format = context.Config.GetTextureFormat(imm);
format = ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, imm);
}
}
else
@ -680,7 +679,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Coherent;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageStore,
type,
format,

View file

@ -57,8 +57,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstTld op = context.GetOp<InstTld>();
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
var lod = op.Lod ? Lod.Ll : Lod.Lz;
EmitTex(context, TextureFlags.IntCoords, op.Dim, lod, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Ms, false, op.Toff);
@ -68,8 +66,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstTldB op = context.GetOp<InstTldB>();
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
var flags = TextureFlags.IntCoords | TextureFlags.Bindless;
var lod = op.Lod ? Lod.Ll : Lod.Lz;
@ -224,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// For bindless, we don't have any way to know the texture type,
// so we assume it's texture buffer when the sampler type is 1D, since that's more common.
bool isTypeBuffer = isBindless || context.Config.GpuAccessor.QuerySamplerType(imm) == SamplerType.TextureBuffer;
bool isTypeBuffer = isBindless || context.TranslatorContext.GpuAccessor.QuerySamplerType(imm) == SamplerType.TextureBuffer;
if (isTypeBuffer)
{
type = SamplerType.TextureBuffer;
@ -386,7 +382,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid texture sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid texture sampler type.");
return;
}
@ -478,16 +474,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid texel fetch sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid texel fetch sampler type.");
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
if (tldsOp.Target == TldsTarget.Texture1DLodZero &&
context.Config.GpuAccessor.QuerySamplerType(tldsOp.TidB) == SamplerType.TextureBuffer)
context.TranslatorContext.GpuAccessor.QuerySamplerType(tldsOp.TidB) == SamplerType.TextureBuffer)
{
type = SamplerType.TextureBuffer;
flags &= ~TextureFlags.LodLevel;
@ -884,7 +878,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(dest++, RegisterType.Gpr);
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.Lod,
type,
TextureFormat.Unknown,
@ -1065,8 +1059,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand Ra()
{
if (srcA > RegisterConsts.RegisterZeroIndex)
@ -1106,12 +1098,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
type = context.Config.GpuAccessor.QuerySamplerType(imm);
type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm);
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSize,
type,
TextureFormat.Unknown,
@ -1147,7 +1139,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] dests,
Operand[] sources)
{
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSample,
type,
TextureFormat.Unknown,

View file

@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid vote operation: {op.VoteMode}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid vote operation: {op.VoteMode}.");
}
if (op.Dest != RegisterConsts.RegisterZeroIndex)

View file

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
// When debug mode is enabled, we disable expression propagation
// (this makes comparison with the disassembly easier).
if (!context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
if (!context.DebugMode)
{
AstBlockVisitor visitor = new(mainBlock);

View file

@ -18,8 +18,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
public readonly bool OriginUpperLeft;
public ShaderProperties()
{
_constantBuffers = new Dictionary<int, BufferDefinition>();
@ -30,29 +28,24 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
_sharedMemories = new Dictionary<int, MemoryDefinition>();
}
public ShaderProperties(bool originUpperLeft) : this()
public void AddOrUpdateConstantBuffer(BufferDefinition definition)
{
OriginUpperLeft = originUpperLeft;
_constantBuffers[definition.Binding] = definition;
}
public void AddOrUpdateConstantBuffer(int binding, BufferDefinition definition)
public void AddOrUpdateStorageBuffer(BufferDefinition definition)
{
_constantBuffers[binding] = definition;
_storageBuffers[definition.Binding] = definition;
}
public void AddOrUpdateStorageBuffer(int binding, BufferDefinition definition)
public void AddOrUpdateTexture(TextureDefinition definition)
{
_storageBuffers[binding] = definition;
_textures[definition.Binding] = definition;
}
public void AddOrUpdateTexture(int binding, TextureDefinition descriptor)
public void AddOrUpdateImage(TextureDefinition definition)
{
_textures[binding] = descriptor;
}
public void AddOrUpdateImage(int binding, TextureDefinition descriptor)
{
_images[binding] = descriptor;
_images[definition.Binding] = definition;
}
public int AddLocalMemory(MemoryDefinition definition)
@ -70,5 +63,48 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return id;
}
public static TextureFormat GetTextureFormat(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1)
{
// When the formatted load extension is supported, we don't need to
// specify a format, we can just declare it without a format and the GPU will handle it.
if (gpuAccessor.QueryHostSupportsImageLoadFormatted())
{
return TextureFormat.Unknown;
}
var format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (format == TextureFormat.Unknown)
{
gpuAccessor.Log($"Unknown format for texture {handle}.");
format = TextureFormat.R8G8B8A8Unorm;
}
return format;
}
private static bool FormatSupportsAtomic(TextureFormat format)
{
return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
}
public static TextureFormat GetTextureFormatAtomic(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1)
{
// 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);
if (!FormatSupportsAtomic(format))
{
gpuAccessor.Log($"Unsupported format for texture {handle}: {format}.");
format = TextureFormat.R32Sint;
}
return format;
}
}
}

View file

@ -8,9 +8,14 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList<Function> functions, ShaderConfig config)
public static StructuredProgramInfo MakeStructuredProgram(
IReadOnlyList<Function> functions,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
bool debugMode)
{
StructuredProgramContext context = new(config);
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
@ -82,13 +87,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
location = operation.GetSource(1).Value;
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
{
component = operation.GetSource(2).Value;
}
@ -98,7 +103,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else if (storageKind == StorageKind.ConstantBuffer && operation.GetSource(0).Type == OperandType.Constant)
{
context.Config.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value);
context.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value);
}
}

View file

@ -28,17 +28,25 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public ShaderDefinitions Definitions { get; }
public ResourceManager ResourceManager { get; }
public bool DebugMode { get; }
public StructuredProgramContext(ShaderConfig config)
public StructuredProgramContext(
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
bool debugMode)
{
Info = new StructuredProgramInfo();
Config = config;
Definitions = definitions;
ResourceManager = resourceManager;
DebugMode = debugMode;
if (config.GpPassthrough)
if (definitions.GpPassthrough)
{
int passthroughAttributes = config.PassthroughAttributes;
int passthroughAttributes = attributeUsage.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
@ -52,11 +60,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.PointSize));
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.ClipDistance));
}
else if (config.Stage == ShaderStage.Fragment)
{
// Potentially used for texture coordinate scaling.
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord));
}
}
public void EnterFunction(
@ -304,11 +307,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot = operand.GetCbufSlot();
int cbufOffset = operand.GetCbufOffset();
int binding = Config.ResourceManager.GetConstantBufferBinding(cbufSlot);
int binding = ResourceManager.GetConstantBufferBinding(cbufSlot);
int vecIndex = cbufOffset >> 2;
int elemIndex = cbufOffset & 3;
Config.ResourceManager.SetUsedConstantBufferBinding(binding);
ResourceManager.SetUsedConstantBufferBinding(binding);
IAstNode[] sources = new IAstNode[]
{

View file

@ -2,22 +2,6 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
readonly struct TransformFeedbackOutput
{
public readonly bool Valid;
public readonly int Buffer;
public readonly int Offset;
public readonly int Stride;
public TransformFeedbackOutput(int buffer, int offset, int stride)
{
Valid = true;
Buffer = buffer;
Offset = offset;
Stride = stride;
}
}
class StructuredProgramInfo
{
public List<StructuredFunction> Functions { get; }

View file

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class AttributeUsage
{
public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; }
public int UsedOutputAttributes { get; private set; }
public HashSet<int> UsedInputAttributesPerPatch { get; }
public HashSet<int> UsedOutputAttributesPerPatch { get; }
public HashSet<int> NextUsedInputAttributesPerPatch { get; private set; }
public int PassthroughAttributes { get; private set; }
private int _nextUsedInputAttributes;
private int _thisUsedInputAttributes;
private Dictionary<int, int> _perPatchAttributeLocations;
private readonly IGpuAccessor _gpuAccessor;
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
public AttributeUsage(IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
UsedInputAttributesPerPatch = new();
UsedOutputAttributesPerPatch = new();
}
public void SetInputUserAttribute(int index, int component)
{
int mask = 1 << index;
UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.One << (index * 4 + component);
}
public void SetInputUserAttributePerPatch(int index)
{
UsedInputAttributesPerPatch.Add(index);
}
public void SetOutputUserAttribute(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetOutputUserAttributePerPatch(int index)
{
UsedOutputAttributesPerPatch.Add(index);
}
public void MergeFromtNextStage(bool gpPassthrough, bool nextUsesFixedFunctionAttributes, AttributeUsage nextStage)
{
NextInputAttributesComponents = nextStage.ThisInputAttributesComponents;
NextUsedInputAttributesPerPatch = nextStage.UsedInputAttributesPerPatch;
NextUsesFixedFuncAttributes = nextUsesFixedFunctionAttributes;
MergeOutputUserAttributes(gpPassthrough, nextStage.UsedInputAttributes, nextStage.UsedInputAttributesPerPatch);
if (UsedOutputAttributesPerPatch.Count != 0)
{
// Regular and per-patch input/output locations can't overlap,
// so we must assign on our location using unused regular input/output locations.
Dictionary<int, int> locationsMap = new();
int freeMask = ~UsedOutputAttributes;
foreach (int attr in UsedOutputAttributesPerPatch)
{
int location = BitOperations.TrailingZeroCount(freeMask);
if (location == 32)
{
_gpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}.");
break;
}
locationsMap.Add(attr, location);
freeMask &= ~(1 << location);
}
// Both stages must agree on the locations, so use the same "map" for both.
_perPatchAttributeLocations = locationsMap;
nextStage._perPatchAttributeLocations = locationsMap;
}
}
private void MergeOutputUserAttributes(bool gpPassthrough, int mask, IEnumerable<int> perPatch)
{
_nextUsedInputAttributes = mask;
if (gpPassthrough)
{
PassthroughAttributes = mask & ~UsedOutputAttributes;
}
else
{
UsedOutputAttributes |= mask;
UsedOutputAttributesPerPatch.UnionWith(perPatch);
}
}
public int GetPerPatchAttributeLocation(int index)
{
if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location))
{
return index;
}
return location;
}
public bool IsUsedOutputAttribute(int attr)
{
// The check for fixed function attributes on the next stage is conservative,
// returning false if the output is just not used by the next stage is also valid.
if (NextUsesFixedFuncAttributes &&
attr >= AttributeConsts.UserAttributeBase &&
attr < AttributeConsts.UserAttributeEnd)
{
int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
return (_nextUsedInputAttributes & (1 << index)) != 0;
}
return true;
}
public int GetFreeUserAttribute(bool isOutput, int index)
{
int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
int bit = -1;
while (useMask != -1)
{
bit = BitOperations.TrailingZeroCount(~useMask);
if (bit == 32)
{
bit = -1;
break;
}
else if (index < 1)
{
break;
}
useMask |= 1 << bit;
index--;
}
return bit;
}
public void SetAllInputUserAttributes()
{
UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
}
public void SetAllOutputUserAttributes()
{
UsedOutputAttributes |= Constants.AllAttributesMask;
}
}
}

View file

@ -11,7 +11,8 @@ namespace Ryujinx.Graphics.Shader.Translation
class EmitterContext
{
public DecodedProgram Program { get; }
public ShaderConfig Config { get; }
public TranslatorContext TranslatorContext { get; }
public ResourceManager ResourceManager { get; }
public bool IsNonMain { get; }
@ -54,10 +55,15 @@ namespace Ryujinx.Graphics.Shader.Translation
_labels = new Dictionary<ulong, BlockLabel>();
}
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this()
public EmitterContext(
TranslatorContext translatorContext,
ResourceManager resourceManager,
DecodedProgram program,
bool isNonMain) : this()
{
TranslatorContext = translatorContext;
ResourceManager = resourceManager;
Program = program;
Config = config;
IsNonMain = isNonMain;
EmitStart();
@ -65,12 +71,12 @@ namespace Ryujinx.Graphics.Shader.Translation
private void EmitStart()
{
if (Config.Stage == ShaderStage.Vertex &&
Config.Options.TargetApi == TargetApi.Vulkan &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex &&
TranslatorContext.Options.TargetApi == TargetApi.Vulkan &&
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
{
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(Config.GpuAccessor.QueryPointSize()));
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
}
}
@ -115,49 +121,6 @@ namespace Ryujinx.Graphics.Shader.Translation
_operations.Add(operation);
}
public void FlagAttributeRead(int attribute)
{
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)
{
Config.SetUsedFeature(FeatureFlags.InstanceId);
}
else if (Config.Stage == ShaderStage.Fragment)
{
switch (attribute)
{
case AttributeConsts.PositionX:
case AttributeConsts.PositionY:
Config.SetUsedFeature(FeatureFlags.FragCoordXY);
break;
}
}
}
public void FlagAttributeWritten(int attribute)
{
if (Config.Stage == ShaderStage.Vertex)
{
switch (attribute)
{
case AttributeConsts.ClipDistance0:
case AttributeConsts.ClipDistance1:
case AttributeConsts.ClipDistance2:
case AttributeConsts.ClipDistance3:
case AttributeConsts.ClipDistance4:
case AttributeConsts.ClipDistance5:
case AttributeConsts.ClipDistance6:
case AttributeConsts.ClipDistance7:
Config.SetClipDistanceWritten((attribute - AttributeConsts.ClipDistance0) / 4);
break;
}
}
if (Config.Stage != ShaderStage.Fragment && attribute == AttributeConsts.Layer)
{
Config.SetUsedFeature(FeatureFlags.RtLayer);
}
}
public void MarkLabel(Operand label)
{
Add(Instruction.MarkLabel, label);
@ -203,14 +166,14 @@ namespace Ryujinx.Graphics.Shader.Translation
public void PrepareForVertexReturn()
{
if (!Config.GpuAccessor.QueryHostSupportsTransformFeedback() && Config.GpuAccessor.QueryTransformFeedbackEnabled())
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled())
{
Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1));
for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++)
{
var locations = Config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = Config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex));
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
@ -242,7 +205,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
if (Config.GpuAccessor.QueryViewportTransformDisable())
if (TranslatorContext.Definitions.ViewportTransformDisable)
{
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
Operand y = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1));
@ -254,7 +217,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl())
{
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
@ -263,12 +226,10 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
}
if (Config.Stage != ShaderStage.Geometry && Config.HasLayerInputAttribute)
if (TranslatorContext.Definitions.Stage != ShaderStage.Geometry && TranslatorContext.HasLayerInputAttribute)
{
Config.SetUsedFeature(FeatureFlags.RtLayer);
int attrVecIndex = Config.GpLayerInputAttribute >> 2;
int attrComponentIndex = Config.GpLayerInputAttribute & 3;
int attrVecIndex = TranslatorContext.GpLayerInputAttribute >> 2;
int attrComponentIndex = TranslatorContext.GpLayerInputAttribute & 3;
Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex));
@ -278,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
if (TranslatorContext.Definitions.ViewportTransformDisable)
{
oldXLocal = Local();
this.Copy(oldXLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(0)));
@ -291,7 +252,7 @@ namespace Ryujinx.Graphics.Shader.Translation
oldYLocal = null;
}
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl())
{
oldZLocal = Local();
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
@ -311,13 +272,13 @@ namespace Ryujinx.Graphics.Shader.Translation
return true;
}
if (Config.LastInVertexPipeline &&
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
if (TranslatorContext.Definitions.LastInVertexPipeline &&
(TranslatorContext.Definitions.Stage == ShaderStage.Vertex || TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation) &&
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
{
PrepareForVertexReturn();
}
else if (Config.Stage == ShaderStage.Geometry)
else if (TranslatorContext.Definitions.Stage == ShaderStage.Geometry)
{
void WritePositionOutput(int primIndex)
{
@ -345,20 +306,19 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(3), w);
}
if (Config.GpPassthrough && !Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
int inputVertices = Config.GpuAccessor.QueryPrimitiveTopology().ToInputVertices();
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
for (int primIndex = 0; primIndex < inputVertices; primIndex++)
{
WritePositionOutput(primIndex);
int passthroughAttributes = Config.PassthroughAttributes;
int passthroughAttributes = TranslatorContext.AttributeUsage.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
WriteUserDefinedOutput(index, primIndex);
Config.SetOutputUserAttribute(index);
passthroughAttributes &= ~(1 << index);
}
@ -368,20 +328,20 @@ namespace Ryujinx.Graphics.Shader.Translation
this.EndPrimitive();
}
}
else if (Config.Stage == ShaderStage.Fragment)
else if (TranslatorContext.Definitions.Stage == ShaderStage.Fragment)
{
GenerateAlphaToCoverageDitherDiscard();
bool supportsBgra = Config.GpuAccessor.QueryHostSupportsBgraFormat();
bool supportsBgra = TranslatorContext.GpuAccessor.QueryHostSupportsBgraFormat();
if (Config.OmapDepth)
if (TranslatorContext.Definitions.OmapDepth)
{
Operand src = Register(Config.GetDepthRegister(), RegisterType.Gpr);
Operand src = Register(TranslatorContext.GetDepthRegister(), RegisterType.Gpr);
this.Store(StorageKind.Output, IoVariable.FragmentOutputDepth, null, src);
}
AlphaTestOp alphaTestOp = Config.GpuAccessor.QueryAlphaTestCompare();
AlphaTestOp alphaTestOp = TranslatorContext.Definitions.AlphaTestCompare;
if (alphaTestOp != AlphaTestOp.Always)
{
@ -389,7 +349,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
this.Discard();
}
else if ((Config.OmapTargets & 8) != 0)
else if ((TranslatorContext.Definitions.OmapTargets & 8) != 0)
{
Instruction comparator = alphaTestOp switch
{
@ -405,7 +365,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Debug.Assert(comparator != 0, $"Invalid alpha test operation \"{alphaTestOp}\".");
Operand alpha = Register(3, RegisterType.Gpr);
Operand alphaRef = ConstF(Config.GpuAccessor.QueryAlphaTestReference());
Operand alphaRef = ConstF(TranslatorContext.Definitions.AlphaTestReference);
Operand alphaPass = Add(Instruction.FP32 | comparator, Local(), alpha, alphaRef);
Operand alphaPassLabel = Label();
@ -427,7 +387,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
for (int component = 0; component < 4; component++)
{
bool componentEnabled = (Config.OmapTargets & (1 << (rtIndex * 4 + component))) != 0;
bool componentEnabled = (TranslatorContext.Definitions.OmapTargets & (1 << (rtIndex * 4 + component))) != 0;
if (!componentEnabled)
{
continue;
@ -460,10 +420,9 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
bool targetEnabled = (Config.OmapTargets & (0xf << (rtIndex * 4))) != 0;
bool targetEnabled = (TranslatorContext.Definitions.OmapTargets & (0xf << (rtIndex * 4))) != 0;
if (targetEnabled)
{
Config.SetOutputUserAttribute(rtIndex);
regIndexBase += 4;
}
}
@ -475,7 +434,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private void GenerateAlphaToCoverageDitherDiscard()
{
// If the feature is disabled, or alpha is not written, then we're done.
if (!Config.GpuAccessor.QueryAlphaToCoverageDitherEnable() || (Config.OmapTargets & 8) == 0)
if (!TranslatorContext.Definitions.AlphaToCoverageDitherEnable || (TranslatorContext.Definitions.OmapTargets & 8) == 0)
{
return;
}

View file

@ -12,15 +12,12 @@ namespace Ryujinx.Graphics.Shader.Translation
None = 0,
// Affected by resolution scaling.
IntegerSampling = 1 << 0,
FragCoordXY = 1 << 1,
Bindless = 1 << 2,
InstanceId = 1 << 3,
DrawParameters = 1 << 4,
RtLayer = 1 << 5,
IaIndexing = 1 << 7,
OaIndexing = 1 << 8,
FixedFuncAttr = 1 << 9,
LocalMemory = 1 << 10,
SharedMemory = 1 << 11,

View file

@ -0,0 +1,34 @@
namespace Ryujinx.Graphics.Shader.Translation
{
class HostCapabilities
{
public readonly bool ReducedPrecision;
public readonly bool SupportsFragmentShaderInterlock;
public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
public HostCapabilities(
bool reducedPrecision,
bool supportsFragmentShaderInterlock,
bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
ReducedPrecision = reducedPrecision;
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}
}
}

View file

@ -1,12 +1,13 @@
using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
class BindlessElimination
{
public static void RunPass(BasicBlock block, ShaderConfig config)
public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// We can turn a bindless into regular access by recognizing the pattern
// produced by the compiler for separate texture and sampler.
@ -43,7 +44,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
SetHandle(config, texOp, bindlessHandle.GetCbufOffset(), bindlessHandle.GetCbufSlot(), rewriteSamplerType, isImage: false);
SetHandle(
resourceManager,
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
continue;
}
@ -140,7 +149,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
SetHandle(
config,
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
@ -150,7 +160,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
config,
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
@ -173,17 +184,17 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
if (texOp.Inst == Instruction.ImageAtomic)
{
texOp.Format = config.GetTextureFormatAtomic(cbufOffset, cbufSlot);
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
}
else
{
texOp.Format = config.GetTextureFormat(cbufOffset, cbufSlot);
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
}
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
SetHandle(config, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
}
}
}
@ -220,11 +231,18 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return null;
}
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot, bool rewriteSamplerType, bool isImage)
private static void SetHandle(
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TextureOperation texOp,
int cbufOffset,
int cbufSlot,
bool rewriteSamplerType,
bool isImage)
{
if (rewriteSamplerType)
{
SamplerType newType = config.GpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
SamplerType newType = gpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
if (texOp.Inst.IsTextureQuery())
{
@ -253,7 +271,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
int binding = config.ResourceManager.GetTextureOrImageBinding(
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,

View file

@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
private const int NvnTextureBufferIndex = 2;
public static void RunPass(BasicBlock block, ShaderConfig config)
public static void RunPass(BasicBlock block, ResourceManager resourceManager)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
TurnIntoIndexed(config, texOp, addSrc1.Value / 4);
TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4);
Operand index = Local();
@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle)
{
int binding = config.ResourceManager.GetTextureOrImageBinding(
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type | SamplerType.Indexed,
texOp.Format,

View file

@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class ConstantFolding
{
public static void RunPass(ShaderConfig config, Operation operation)
public static void RunPass(ResourceManager resourceManager, Operation operation)
{
if (!AreAllSourcesConstant(operation))
{
@ -158,7 +158,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
int binding = operation.GetSource(0).Value;
int fieldIndex = operation.GetSource(1).Value;
if (config.ResourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0)
if (resourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0)
{
int vecIndex = operation.GetSource(2).Value;
int elemIndex = operation.GetSource(3).Value;

View file

@ -205,7 +205,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static void RunPass(
HelperFunctionManager hfm,
BasicBlock[] blocks,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage)
{
GtsContext gtsContext = new(hfm);
@ -220,14 +225,20 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (IsGlobalMemory(operation.StorageKind))
{
LinkedListNode<INode> nextNode = ReplaceGlobalMemoryWithStorage(gtsContext, config, block, node);
LinkedListNode<INode> nextNode = ReplaceGlobalMemoryWithStorage(
gtsContext,
resourceManager,
gpuAccessor,
targetLanguage,
block,
node);
if (nextNode == null)
{
// The returned value being null means that the global memory replacement failed,
// so we just make loads read 0 and stores do nothing.
config.GpuAccessor.Log($"Failed to reserve storage buffer for global memory operation \"{operation.Inst}\".");
gpuAccessor.Log($"Failed to reserve storage buffer for global memory operation \"{operation.Inst}\".");
if (operation.Dest != null)
{
@ -286,7 +297,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static LinkedListNode<INode> ReplaceGlobalMemoryWithStorage(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
BasicBlock block,
LinkedListNode<INode> node)
{
@ -303,7 +316,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand offset = result.Offset;
bool storageUnaligned = config.GpuAccessor.QueryHasUnalignedStorageBuffer();
bool storageUnaligned = gpuAccessor.QueryHasUnalignedStorageBuffer();
if (storageUnaligned)
{
@ -312,7 +325,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand baseAddressMasked = Local();
Operand hostOffset = Local();
int alignment = config.GpuAccessor.QueryHostStorageBufferOffsetAlignment();
int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment();
Operation maskOp = new(Instruction.BitwiseAnd, baseAddressMasked, baseAddress, Const(-alignment));
Operation subOp = new(Instruction.Subtract, hostOffset, globalAddress, baseAddressMasked);
@ -333,13 +346,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
offset = newOffset;
}
if (CanUseInlineStorageOp(operation, config.Options.TargetLanguage))
if (CanUseInlineStorageOp(operation, targetLanguage))
{
return GenerateInlineStorageOp(config, node, operation, offset, result);
return GenerateInlineStorageOp(resourceManager, node, operation, offset, result);
}
else
{
if (!TryGenerateSingleTargetStorageOp(gtsContext, config, operation, result, out int functionId))
if (!TryGenerateSingleTargetStorageOp(
gtsContext,
resourceManager,
targetLanguage,
operation,
result,
out int functionId))
{
return null;
}
@ -354,7 +373,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// the base address might be stored.
// Generate a helper function that will check all possible storage buffers and use the right one.
if (!TryGenerateMultiTargetStorageOp(gtsContext, config, block, operation, out int functionId))
if (!TryGenerateMultiTargetStorageOp(
gtsContext,
resourceManager,
gpuAccessor,
targetLanguage,
block,
operation,
out int functionId))
{
return null;
}
@ -375,14 +401,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
private static LinkedListNode<INode> GenerateInlineStorageOp(
ShaderConfig config,
ResourceManager resourceManager,
LinkedListNode<INode> node,
Operation operation,
Operand offset,
SearchResult result)
{
bool isStore = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
if (!config.ResourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
{
return null;
}
@ -474,7 +500,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static bool TryGenerateSingleTargetStorageOp(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
Operation operation,
SearchResult result,
out int functionId)
@ -514,7 +541,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
if (!TryGenerateStorageOp(
config,
resourceManager,
targetLanguage,
context,
operation.Inst,
operation.StorageKind,
@ -555,7 +583,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static bool TryGenerateMultiTargetStorageOp(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
BasicBlock block,
Operation operation,
out int functionId)
@ -624,7 +654,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (targetCbs.Count == 0)
{
config.GpuAccessor.Log($"Failed to find storage buffer for global memory operation \"{operation.Inst}\".");
gpuAccessor.Log($"Failed to find storage buffer for global memory operation \"{operation.Inst}\".");
}
if (gtsContext.TryGetFunctionId(operation, isMultiTarget: true, targetCbs, out functionId))
@ -685,13 +715,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
SearchResult result = new(sbCbSlot, sbCbOffset);
int alignment = config.GpuAccessor.QueryHostStorageBufferOffsetAlignment();
int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment();
Operand baseAddressMasked = context.BitwiseAnd(baseAddrLow, Const(-alignment));
Operand hostOffset = context.ISubtract(globalAddressLow, baseAddressMasked);
if (!TryGenerateStorageOp(
config,
resourceManager,
targetLanguage,
context,
operation.Inst,
operation.StorageKind,
@ -781,7 +812,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
private static bool TryGenerateStorageOp(
ShaderConfig config,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
EmitterContext context,
Instruction inst,
StorageKind storageKind,
@ -794,7 +826,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = null;
bool isStore = inst.IsAtomic() || inst == Instruction.Store;
if (!config.ResourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
{
return false;
}
@ -820,7 +852,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = context.AtomicCompareAndSwap(StorageKind.StorageBuffer, binding, Const(0), wordOffset, compare, value);
break;
case Instruction.AtomicMaxS32:
if (config.Options.TargetLanguage == TargetLanguage.Spirv)
if (targetLanguage == TargetLanguage.Spirv)
{
resultValue = context.AtomicMaxS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
}
@ -836,7 +868,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = context.AtomicMaxU32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
break;
case Instruction.AtomicMinS32:
if (config.Options.TargetLanguage == TargetLanguage.Spirv)
if (targetLanguage == TargetLanguage.Spirv)
{
resultValue = context.AtomicMinS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
}

View file

@ -7,40 +7,40 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class Optimizer
{
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static void RunPass(TransformContext context)
{
RunOptimizationPasses(blocks, config);
RunOptimizationPasses(context.Blocks, context.ResourceManager);
// TODO: Some of those are not optimizations and shouldn't be here.
GlobalToStorage.RunPass(hfm, blocks, config);
GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage);
bool hostSupportsShaderFloat64 = config.GpuAccessor.QueryHostSupportsShaderFloat64();
bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64();
// Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
BindlessToIndexed.RunPass(blocks[blkIndex], config);
BindlessElimination.RunPass(blocks[blkIndex], config);
BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager);
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages.
if (config.Stage == ShaderStage.Fragment)
if (context.Stage == ShaderStage.Fragment)
{
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
EliminateMultiplyByFragmentCoordW(context.Blocks[blkIndex]);
}
// If the host does not support double operations, we need to turn them into float operations.
if (!hostSupportsShaderFloat64)
{
DoubleToFloat.RunPass(hfm, blocks[blkIndex]);
DoubleToFloat.RunPass(context.Hfm, context.Blocks[blkIndex]);
}
}
// Run optimizations one last time to remove any code that is now optimizable after above passes.
RunOptimizationPasses(blocks, config);
RunOptimizationPasses(context.Blocks, context.ResourceManager);
}
private static void RunOptimizationPasses(BasicBlock[] blocks, ShaderConfig config)
private static void RunOptimizationPasses(BasicBlock[] blocks, ResourceManager resourceManager)
{
bool modified;
@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
ConstantFolding.RunPass(config, operation);
ConstantFolding.RunPass(resourceManager, operation);
Simplification.RunPass(operation);
if (DestIsLocalVar(operation))

View file

@ -50,10 +50,10 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderProperties Properties { get; }
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties)
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
Properties = properties;
Properties = new();
_stage = stage;
_stagePrefix = GetShaderStagePrefix(stage);
@ -62,15 +62,15 @@ namespace Ryujinx.Graphics.Shader.Translation
_cbSlotToBindingMap.AsSpan().Fill(-1);
_sbSlotToBindingMap.AsSpan().Fill(-1);
_sbSlots = new Dictionary<int, int>();
_sbSlotsReverse = new Dictionary<int, int>();
_sbSlots = new();
_sbSlotsReverse = new();
_usedConstantBufferBindings = new HashSet<int>();
_usedConstantBufferBindings = new();
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
_usedTextures = new();
_usedImages = new();
properties.AddOrUpdateConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType()));
LocalMemoryId = -1;
SharedMemoryId = -1;
@ -312,11 +312,11 @@ namespace Ryujinx.Graphics.Shader.Translation
if (isImage)
{
Properties.AddOrUpdateImage(binding, definition);
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(binding, definition);
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
@ -500,7 +500,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
});
Properties.AddOrUpdateConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, binding, name, type));
}
private void AddNewStorageBuffer(int binding, string name)
@ -510,7 +510,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
});
Properties.AddOrUpdateStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, 1, binding, name, type));
}
public static string GetShaderStagePrefix(ShaderStage stage)

View file

@ -1,639 +0,0 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderConfig
{
private const int ThreadsPerWarp = 32;
public ShaderStage Stage { get; }
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; private set; }
public bool HasLayerInputAttribute { get; private set; }
public int GpLayerInputAttribute { get; private set; }
public int ThreadsPerInputPrimitive { get; }
public OutputTopology OutputTopology { get; }
public int MaxOutputVertices { get; }
public int LocalMemorySize { get; }
public ImapPixelType[] ImapTypes { get; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public IGpuAccessor GpuAccessor { get; }
public TranslationOptions Options { get; }
public ShaderProperties Properties => ResourceManager.Properties;
public ResourceManager ResourceManager { get; set; }
public bool TransformFeedbackEnabled { get; }
private TransformFeedbackOutput[] _transformFeedbackOutputs;
readonly struct TransformFeedbackVariable : IEquatable<TransformFeedbackVariable>
{
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0)
{
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is TransformFeedbackVariable tfbVar && Equals(tfbVar);
}
public bool Equals(TransformFeedbackVariable other)
{
return IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)IoVariable | (Location << 8) | (Component << 16);
}
public override string ToString()
{
return $"{IoVariable}.{Location}.{Component}";
}
}
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
public int Size { get; private set; }
public byte ClipDistancesWritten { get; private set; }
public FeatureFlags UsedFeatures { get; private set; }
public int Cb1DataSize { get; private set; }
public bool LayerOutputWritten { get; private set; }
public int LayerOutputAttribute { get; private set; }
public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; }
public int UsedOutputAttributes { get; private set; }
public HashSet<int> UsedInputAttributesPerPatch { get; }
public HashSet<int> UsedOutputAttributesPerPatch { get; }
public HashSet<int> NextUsedInputAttributesPerPatch { get; private set; }
public int PassthroughAttributes { get; private set; }
private int _nextUsedInputAttributes;
private int _thisUsedInputAttributes;
private Dictionary<int, int> _perPatchAttributeLocations;
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options, int localMemorySize)
{
Stage = stage;
GpuAccessor = gpuAccessor;
Options = options;
LocalMemorySize = localMemorySize;
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
TransformFeedbackEnabled =
stage != ShaderStage.Compute &&
gpuAccessor.QueryTransformFeedbackEnabled() &&
gpuAccessor.QueryHostSupportsTransformFeedback();
UsedInputAttributesPerPatch = new HashSet<int>();
UsedOutputAttributesPerPatch = new HashSet<int>();
ShaderProperties properties;
switch (stage)
{
case ShaderStage.Fragment:
bool originUpperLeft = options.TargetApi == TargetApi.Vulkan || gpuAccessor.QueryYNegateEnabled();
properties = new ShaderProperties(originUpperLeft);
break;
default:
properties = new ShaderProperties();
break;
}
ResourceManager = new ResourceManager(stage, gpuAccessor, properties);
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
{
StructureType tfeInfoStruct = new(new StructureField[]
{
new(AggregateType.Array | AggregateType.U32, "base_offset", 4),
new(AggregateType.U32, "vertex_count"),
});
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
StructureType tfeDataStruct = new(new StructureField[]
{
new(AggregateType.Array | AggregateType.U32, "data", 0),
});
for (int i = 0; i < Constants.TfeBuffersCount; i++)
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
}
}
}
public ShaderConfig(
ShaderStage stage,
OutputTopology outputTopology,
int maxOutputVertices,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(stage, gpuAccessor, options, 0)
{
ThreadsPerInputPrimitive = 1;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
}
public ShaderConfig(
ShaderHeader header,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(header.Stage, gpuAccessor, options, GetLocalMemorySize(header))
{
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive;
OutputTopology = header.OutputTopology;
MaxOutputVertices = header.MaxOutputVertexCount;
ImapTypes = header.ImapTypes;
OmapTargets = header.OmapTargets;
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
private static int GetLocalMemorySize(ShaderHeader header)
{
return header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp);
}
private void EnsureTransformFeedbackInitialized()
{
if (HasTransformFeedbackOutputs() && _transformFeedbackOutputs == null)
{
TransformFeedbackOutput[] transformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
ulong vecMap = 0UL;
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte wordOffset = locations[i];
if (wordOffset < 0xc0)
{
transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
vecMap |= 1UL << (wordOffset / 4);
}
}
}
_transformFeedbackOutputs = transformFeedbackOutputs;
while (vecMap != 0)
{
int vecIndex = BitOperations.TrailingZeroCount(vecMap);
for (int subIndex = 0; subIndex < 4; subIndex++)
{
int wordOffset = vecIndex * 4 + subIndex;
int byteOffset = wordOffset * 4;
if (transformFeedbackOutputs[wordOffset].Valid)
{
IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location);
int component = 0;
if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true))
{
component = subIndex;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
_transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]);
}
}
vecMap &= ~(1UL << vecIndex);
}
}
}
public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
{
EnsureTransformFeedbackInitialized();
return _transformFeedbackOutputs;
}
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
{
EnsureTransformFeedbackInitialized();
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput);
}
private bool HasTransformFeedbackOutputs()
{
return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment);
}
public bool HasTransformFeedbackOutputs(bool isOutput)
{
return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment));
}
public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput)
{
if (ioVariable == IoVariable.UserDefined)
{
return (!isOutput && !UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutput && !UsedFeatures.HasFlag(FeatureFlags.OaIndexing));
}
return ioVariable == IoVariable.FragmentOutputColor;
}
public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput)
{
if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput))
{
return false;
}
return GetTransformFeedbackOutputComponents(location, component) == 1;
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset)
{
EnsureTransformFeedbackInitialized();
return _transformFeedbackOutputs[wordOffset];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
{
return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component);
}
public int GetTransformFeedbackOutputComponents(int location, int component)
{
EnsureTransformFeedbackInitialized();
int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4;
int index = baseIndex + component;
int count = 1;
for (; count < 4; count++)
{
ref var prev = ref _transformFeedbackOutputs[baseIndex + count - 1];
ref var curr = ref _transformFeedbackOutputs[baseIndex + count];
int prevOffset = prev.Offset;
int currOffset = curr.Offset;
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
{
break;
}
}
if (baseIndex + count <= index)
{
return 1;
}
return count;
}
public AggregateType GetFragmentOutputColorType(int location)
{
return AggregateType.Vector4 | GpuAccessor.QueryFragmentOutputType(location).ToAggregateType();
}
public AggregateType GetUserDefinedType(int location, bool isOutput)
{
if ((!isOutput && UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutput && UsedFeatures.HasFlag(FeatureFlags.OaIndexing)))
{
return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32;
}
AggregateType type = AggregateType.Vector4;
if (Stage == ShaderStage.Vertex && !isOutput)
{
type |= GpuAccessor.QueryAttributeType(location).ToAggregateType();
}
else
{
type |= AggregateType.FP32;
}
return type;
}
public int GetDepthRegister()
{
// The depth register is always two registers after the last color output.
return BitOperations.PopCount((uint)OmapTargets) + 1;
}
public uint ConstantBuffer1Read(int offset)
{
if (Cb1DataSize < offset + 4)
{
Cb1DataSize = offset + 4;
}
return GpuAccessor.ConstantBuffer1Read(offset);
}
public TextureFormat GetTextureFormat(int handle, int cbufSlot = -1)
{
// When the formatted load extension is supported, we don't need to
// specify a format, we can just declare it without a format and the GPU will handle it.
if (GpuAccessor.QueryHostSupportsImageLoadFormatted())
{
return TextureFormat.Unknown;
}
var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (format == TextureFormat.Unknown)
{
GpuAccessor.Log($"Unknown format for texture {handle}.");
format = TextureFormat.R8G8B8A8Unorm;
}
return format;
}
private static bool FormatSupportsAtomic(TextureFormat format)
{
return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
}
public TextureFormat GetTextureFormatAtomic(int handle, int cbufSlot = -1)
{
// 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);
if (!FormatSupportsAtomic(format))
{
GpuAccessor.Log($"Unsupported format for texture {handle}: {format}.");
format = TextureFormat.R32Sint;
}
return format;
}
public void SizeAdd(int size)
{
Size += size;
}
public void InheritFrom(ShaderConfig other)
{
ClipDistancesWritten |= other.ClipDistancesWritten;
UsedFeatures |= other.UsedFeatures;
UsedInputAttributes |= other.UsedInputAttributes;
UsedOutputAttributes |= other.UsedOutputAttributes;
}
public void SetLayerOutputAttribute(int attr)
{
LayerOutputWritten = true;
LayerOutputAttribute = attr;
}
public void SetGeometryShaderLayerInputAttribute(int attr)
{
HasLayerInputAttribute = true;
GpLayerInputAttribute = attr;
}
public void SetLastInVertexPipeline()
{
LastInVertexPipeline = true;
}
public void SetInputUserAttributeFixedFunc(int index)
{
UsedInputAttributes |= 1 << index;
}
public void SetOutputUserAttributeFixedFunc(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetInputUserAttribute(int index, int component)
{
int mask = 1 << index;
UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.One << (index * 4 + component);
}
public void SetInputUserAttributePerPatch(int index)
{
UsedInputAttributesPerPatch.Add(index);
}
public void SetOutputUserAttribute(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetOutputUserAttributePerPatch(int index)
{
UsedOutputAttributesPerPatch.Add(index);
}
public void MergeFromtNextStage(ShaderConfig config)
{
NextInputAttributesComponents = config.ThisInputAttributesComponents;
NextUsedInputAttributesPerPatch = config.UsedInputAttributesPerPatch;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
if (UsedOutputAttributesPerPatch.Count != 0)
{
// Regular and per-patch input/output locations can't overlap,
// so we must assign on our location using unused regular input/output locations.
Dictionary<int, int> locationsMap = new();
int freeMask = ~UsedOutputAttributes;
foreach (int attr in UsedOutputAttributesPerPatch)
{
int location = BitOperations.TrailingZeroCount(freeMask);
if (location == 32)
{
config.GpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}.");
break;
}
locationsMap.Add(attr, location);
freeMask &= ~(1 << location);
}
// Both stages must agree on the locations, so use the same "map" for both.
_perPatchAttributeLocations = locationsMap;
config._perPatchAttributeLocations = locationsMap;
}
// We don't consider geometry shaders using the geometry shader passthrough feature
// as being the last because when this feature is used, it can't actually modify any of the outputs,
// so the stage that comes before it is the last one that can do modifications.
if (config.Stage != ShaderStage.Fragment && (config.Stage != ShaderStage.Geometry || !config.GpPassthrough))
{
LastInVertexPipeline = false;
}
}
public void MergeOutputUserAttributes(int mask, IEnumerable<int> perPatch)
{
_nextUsedInputAttributes = mask;
if (GpPassthrough)
{
PassthroughAttributes = mask & ~UsedOutputAttributes;
}
else
{
UsedOutputAttributes |= mask;
UsedOutputAttributesPerPatch.UnionWith(perPatch);
}
}
public int GetPerPatchAttributeLocation(int index)
{
if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location))
{
return index;
}
return location;
}
public bool IsUsedOutputAttribute(int attr)
{
// The check for fixed function attributes on the next stage is conservative,
// returning false if the output is just not used by the next stage is also valid.
if (NextUsesFixedFuncAttributes &&
attr >= AttributeConsts.UserAttributeBase &&
attr < AttributeConsts.UserAttributeEnd)
{
int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
return (_nextUsedInputAttributes & (1 << index)) != 0;
}
return true;
}
public int GetFreeUserAttribute(bool isOutput, int index)
{
int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
int bit = -1;
while (useMask != -1)
{
bit = BitOperations.TrailingZeroCount(~useMask);
if (bit == 32)
{
bit = -1;
break;
}
else if (index < 1)
{
break;
}
useMask |= 1 << bit;
index--;
}
return bit;
}
public void SetAllInputUserAttributes()
{
UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
}
public void SetAllOutputUserAttributes()
{
UsedOutputAttributes |= Constants.AllAttributesMask;
}
public void SetClipDistanceWritten(int index)
{
ClipDistancesWritten |= (byte)(1 << index);
}
public void SetUsedFeature(FeatureFlags flags)
{
UsedFeatures |= flags;
}
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
{
return new ShaderProgramInfo(
ResourceManager.GetConstantBufferDescriptors(),
ResourceManager.GetStorageBufferDescriptors(),
ResourceManager.GetTextureDescriptors(),
ResourceManager.GetImageDescriptors(),
identification,
GpLayerInputAttribute,
Stage,
UsedFeatures.HasFlag(FeatureFlags.FragCoordXY),
UsedFeatures.HasFlag(FeatureFlags.InstanceId),
UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
UsedFeatures.HasFlag(FeatureFlags.RtLayer),
ClipDistancesWritten,
OmapTargets);
}
}
}

View file

@ -0,0 +1,315 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderDefinitions
{
private readonly GpuGraphicsState _graphicsState;
public ShaderStage Stage { get; }
public int ComputeLocalSizeX { get; }
public int ComputeLocalSizeY { get; }
public int ComputeLocalSizeZ { get; }
public bool TessCw => _graphicsState.TessCw;
public TessPatchType TessPatchType => _graphicsState.TessPatchType;
public TessSpacing TessSpacing => _graphicsState.TessSpacing;
public bool AlphaToCoverageDitherEnable => _graphicsState.AlphaToCoverageEnable && _graphicsState.AlphaToCoverageDitherEnable;
public bool ViewportTransformDisable => _graphicsState.ViewportTransformDisable;
public bool DepthMode => _graphicsState.DepthMode;
public float PointSize => _graphicsState.PointSize;
public AlphaTestOp AlphaTestCompare => _graphicsState.AlphaTestCompare;
public float AlphaTestReference => _graphicsState.AlphaTestReference;
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; set; }
public int ThreadsPerInputPrimitive { get; }
public InputTopology InputTopology => _graphicsState.Topology;
public OutputTopology OutputTopology { get; }
public int MaxOutputVertices { get; }
public bool DualSourceBlend => _graphicsState.DualSourceBlendEnable;
public bool EarlyZForce => _graphicsState.EarlyZForce;
public bool YNegateEnabled => _graphicsState.YNegateEnabled;
public bool OriginUpperLeft => _graphicsState.OriginUpperLeft;
public ImapPixelType[] ImapTypes { get; }
public bool IaIndexing { get; private set; }
public bool OaIndexing { get; private set; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public bool TransformFeedbackEnabled { get; }
private readonly TransformFeedbackOutput[] _transformFeedbackOutputs;
readonly struct TransformFeedbackVariable : IEquatable<TransformFeedbackVariable>
{
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0)
{
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is TransformFeedbackVariable tfbVar && Equals(tfbVar);
}
public bool Equals(TransformFeedbackVariable other)
{
return IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)IoVariable | (Location << 8) | (Component << 16);
}
public override string ToString()
{
return $"{IoVariable}.{Location}.{Component}";
}
}
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
public ShaderDefinitions(ShaderStage stage)
{
Stage = stage;
}
public ShaderDefinitions(
ShaderStage stage,
int computeLocalSizeX,
int computeLocalSizeY,
int computeLocalSizeZ)
{
Stage = stage;
ComputeLocalSizeX = computeLocalSizeX;
ComputeLocalSizeY = computeLocalSizeY;
ComputeLocalSizeZ = computeLocalSizeZ;
}
public ShaderDefinitions(
ShaderStage stage,
GpuGraphicsState graphicsState,
bool gpPassthrough,
int threadsPerInputPrimitive,
OutputTopology outputTopology,
int maxOutputVertices)
{
Stage = stage;
_graphicsState = graphicsState;
GpPassthrough = gpPassthrough;
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
}
public ShaderDefinitions(
ShaderStage stage,
GpuGraphicsState graphicsState,
bool gpPassthrough,
int threadsPerInputPrimitive,
OutputTopology outputTopology,
int maxOutputVertices,
ImapPixelType[] imapTypes,
int omapTargets,
bool omapSampleMask,
bool omapDepth,
bool transformFeedbackEnabled,
ulong transformFeedbackVecMap,
TransformFeedbackOutput[] transformFeedbackOutputs)
{
Stage = stage;
_graphicsState = graphicsState;
GpPassthrough = gpPassthrough;
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
ImapTypes = imapTypes;
OmapTargets = omapTargets;
OmapSampleMask = omapSampleMask;
OmapDepth = omapDepth;
LastInVertexPipeline = stage < ShaderStage.Fragment;
TransformFeedbackEnabled = transformFeedbackEnabled;
_transformFeedbackOutputs = transformFeedbackOutputs;
_transformFeedbackDefinitions = new();
while (transformFeedbackVecMap != 0)
{
int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap);
for (int subIndex = 0; subIndex < 4; subIndex++)
{
int wordOffset = vecIndex * 4 + subIndex;
int byteOffset = wordOffset * 4;
if (transformFeedbackOutputs[wordOffset].Valid)
{
IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location);
int component = 0;
if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true))
{
component = subIndex;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
_transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]);
}
}
transformFeedbackVecMap &= ~(1UL << vecIndex);
}
}
public void EnableInputIndexing()
{
IaIndexing = true;
}
public void EnableOutputIndexing()
{
OaIndexing = true;
}
public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
{
if (!HasTransformFeedbackOutputs())
{
return null;
}
return _transformFeedbackOutputs;
}
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
{
if (!HasTransformFeedbackOutputs())
{
transformFeedbackOutput = default;
return false;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput);
}
private bool HasTransformFeedbackOutputs()
{
return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment);
}
public bool HasTransformFeedbackOutputs(bool isOutput)
{
return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment));
}
public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput)
{
if (ioVariable == IoVariable.UserDefined)
{
return (!isOutput && !IaIndexing) || (isOutput && !OaIndexing);
}
return ioVariable == IoVariable.FragmentOutputColor;
}
public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput)
{
if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput))
{
return false;
}
return GetTransformFeedbackOutputComponents(location, component) == 1;
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset)
{
return _transformFeedbackOutputs[wordOffset];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
{
return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component);
}
public int GetTransformFeedbackOutputComponents(int location, int component)
{
int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4;
int index = baseIndex + component;
int count = 1;
for (; count < 4; count++)
{
ref var prev = ref _transformFeedbackOutputs[baseIndex + count - 1];
ref var curr = ref _transformFeedbackOutputs[baseIndex + count];
int prevOffset = prev.Offset;
int currOffset = curr.Offset;
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
{
break;
}
}
if (baseIndex + count <= index)
{
return 1;
}
return count;
}
public AggregateType GetFragmentOutputColorType(int location)
{
return AggregateType.Vector4 | _graphicsState.FragmentOutputTypes[location].ToAggregateType();
}
public AggregateType GetUserDefinedType(int location, bool isOutput)
{
if ((!isOutput && IaIndexing) || (isOutput && OaIndexing))
{
return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32;
}
AggregateType type = AggregateType.Vector4;
if (Stage == ShaderStage.Vertex && !isOutput)
{
type |= _graphicsState.AttributeTypes[location].ToAggregateType();
}
else
{
type |= AggregateType.FP32;
}
return type;
}
}
}

View file

@ -5,18 +5,22 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class ShaderIdentifier
{
public static ShaderIdentification Identify(IReadOnlyList<Function> functions, ShaderConfig config)
public static ShaderIdentification Identify(
IReadOnlyList<Function> functions,
IGpuAccessor gpuAccessor,
ShaderStage stage,
InputTopology inputTopology,
out int layerInputAttr)
{
if (config.Stage == ShaderStage.Geometry &&
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
!config.GpuAccessor.QueryHostSupportsGeometryShader() &&
IsLayerPassthroughGeometryShader(functions, out int layerInputAttr))
if (stage == ShaderStage.Geometry &&
inputTopology == InputTopology.Triangles &&
!gpuAccessor.QueryHostSupportsGeometryShader() &&
IsLayerPassthroughGeometryShader(functions, out layerInputAttr))
{
config.SetGeometryShaderLayerInputAttribute(layerInputAttr);
return ShaderIdentification.GeometryLayerPassthrough;
}
layerInputAttr = 0;
return ShaderIdentification.None;
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
namespace Ryujinx.Graphics.Shader.Translation
{
readonly ref struct TransformContext
{
public readonly HelperFunctionManager Hfm;
public readonly BasicBlock[] Blocks;
public readonly ResourceManager ResourceManager;
public readonly IGpuAccessor GpuAccessor;
public readonly TargetLanguage TargetLanguage;
public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures;
public TransformContext(
HelperFunctionManager hfm,
BasicBlock[] blocks,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
ShaderStage stage,
ref FeatureFlags usedFeatures)
{
Hfm = hfm;
Blocks = blocks;
ResourceManager = resourceManager;
GpuAccessor = gpuAccessor;
TargetLanguage = targetLanguage;
Stage = stage;
UsedFeatures = ref usedFeatures;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Shader.Translation
{
readonly struct TransformFeedbackOutput
{
public readonly bool Valid;
public readonly int Buffer;
public readonly int Offset;
public readonly int Stride;
public TransformFeedbackOutput(int buffer, int offset, int stride)
{
Valid = true;
Buffer = buffer;
Offset = offset;
Stride = stride;
}
}
}

View file

@ -0,0 +1,93 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class DrawParametersReplace : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Vertex;
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
if (context.GpuAccessor.QueryHasConstantBufferDrawParameters())
{
if (ReplaceConstantBufferWithDrawParameters(node, operation))
{
context.UsedFeatures |= FeatureFlags.DrawParameters;
}
}
else if (HasConstantBufferDrawParameters(operation))
{
context.UsedFeatures |= FeatureFlags.DrawParameters;
}
return node;
}
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
{
Operand GenerateLoad(IoVariable ioVariable)
{
Operand value = Local();
node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, value, Const((int)ioVariable)));
return value;
}
bool modified = false;
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex));
modified = true;
break;
case Constants.NvnBaseInstanceByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance));
modified = true;
break;
case Constants.NvnDrawIndexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex));
modified = true;
break;
}
}
}
return modified;
}
private static bool HasConstantBufferDrawParameters(Operation operation)
{
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
case Constants.NvnBaseInstanceByteOffset / 4:
case Constants.NvnDrawIndexByteOffset / 4:
return true;
}
}
}
return false;
}
}
}

View file

@ -0,0 +1,36 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class ForcePreciseEnable : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Fragment && gpuAccessor.QueryHostReducedPrecision();
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
// There are some cases where a small bias is added to values to prevent division by zero.
// When operating with reduced precision, it is possible for this bias to get rounded to 0
// and cause a division by zero.
// To prevent that, we force those operations to be precise even if the host wants
// imprecise operations for performance.
Operation operation = (Operation)node.Value;
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
operation.GetSource(0).Type == OperandType.Constant &&
operation.GetSource(0).AsFloat() == 1f &&
operation.GetSource(1).AsgOp is Operation addOp &&
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
addOp.GetSource(1).Type == OperandType.Constant)
{
addOp.ForcePrecise = true;
}
return node;
}
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
interface ITransformPass
{
abstract static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures);
abstract static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node);
}
}

View file

@ -0,0 +1,58 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class SharedAtomicSignedCas : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return targetLanguage != TargetLanguage.Spirv && stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory);
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.Inst == Instruction.AtomicMaxS32)
{
name = HelperFunctionName.SharedAtomicMaxS32;
}
else if (operation.Inst == Instruction.AtomicMinS32)
{
name = HelperFunctionName.SharedAtomicMinS32;
}
else
{
return node;
}
if (operation.StorageKind != StorageKind.SharedMemory)
{
return node;
}
Operand result = operation.Dest;
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
}
}

View file

@ -0,0 +1,57 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class SharedStoreSmallIntCas : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory);
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.StorageKind == StorageKind.SharedMemory8)
{
name = HelperFunctionName.SharedStore8;
}
else if (operation.StorageKind == StorageKind.SharedMemory16)
{
name = HelperFunctionName.SharedStore16;
}
else
{
return node;
}
if (operation.Inst != Instruction.Store)
{
return node;
}
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
}
}

View file

@ -1,268 +1,45 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
static class Rewriter
class TexturePass : ITransformPass
{
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
bool isVertexShader = config.Stage == ShaderStage.Vertex;
bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
return true;
}
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
if (node.Value is TextureOperation texOp)
{
BasicBlock block = blocks[blkIndex];
node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage);
node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage);
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
if (texOp.Inst == Instruction.TextureSample)
{
if (node.Value is not Operation operation)
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{
continue;
}
if (isVertexShader)
{
if (hasConstantBufferDrawParameters)
{
if (ReplaceConstantBufferWithDrawParameters(node, operation))
{
config.SetUsedFeature(FeatureFlags.DrawParameters);
}
}
else if (HasConstantBufferDrawParameters(operation))
{
config.SetUsedFeature(FeatureFlags.DrawParameters);
}
}
if (isImpreciseFragmentShader)
{
EnableForcePreciseIfNeeded(operation);
}
if (hasVectorIndexingBug)
{
InsertVectorComponentSelect(node, config);
}
if (operation is TextureOperation texOp)
{
node = InsertTexelFetchScale(hfm, node, config);
node = InsertTextureSizeUnscale(hfm, node, config);
if (texOp.Inst == Instruction.TextureSample)
{
node = InsertCoordNormalization(hfm, node, config);
node = InsertCoordGatherBias(node, config);
node = InsertConstOffsets(node, config);
if (texOp.Type == SamplerType.TextureBuffer && !supportsSnormBufferTextureFormat)
{
node = InsertSnormNormalization(node, config);
}
}
}
else
{
node = InsertSharedStoreSmallInt(hfm, node);
if (config.Options.TargetLanguage != TargetLanguage.Spirv)
{
node = InsertSharedAtomicSigned(hfm, node);
}
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
}
}
}
return node;
}
private static void EnableForcePreciseIfNeeded(Operation operation)
{
// There are some cases where a small bias is added to values to prevent division by zero.
// When operating with reduced precision, it is possible for this bias to get rounded to 0
// and cause a division by zero.
// To prevent that, we force those operations to be precise even if the host wants
// imprecise operations for performance.
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
operation.GetSource(0).Type == OperandType.Constant &&
operation.GetSource(0).AsFloat() == 1f &&
operation.GetSource(1).AsgOp is Operation addOp &&
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
addOp.GetSource(1).Type == OperandType.Constant)
{
addOp.ForcePrecise = true;
}
}
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
{
Operation operation = (Operation)node.Value;
if (operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.ConstantBuffer ||
operation.SourcesCount < 3)
{
return;
}
Operand bindingIndex = operation.GetSource(0);
Operand fieldIndex = operation.GetSource(1);
Operand elemIndex = operation.GetSource(operation.SourcesCount - 1);
if (bindingIndex.Type != OperandType.Constant ||
fieldIndex.Type != OperandType.Constant ||
elemIndex.Type == OperandType.Constant)
{
return;
}
BufferDefinition buffer = config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1,
};
if (elemCount == 1)
{
return;
}
Operand result = null;
for (int i = 0; i < elemCount; i++)
{
Operand value = Local();
Operand[] inputs = new Operand[operation.SourcesCount];
for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++)
{
inputs[srcIndex] = operation.GetSource(srcIndex);
}
inputs[^1] = Const(i);
Operation loadOp = new(Instruction.Load, StorageKind.ConstantBuffer, value, inputs);
node.List.AddBefore(node, loadOp);
if (i == 0)
{
result = value;
}
else
{
Operand isCurrentIndex = Local();
Operand selection = Local();
Operation compareOp = new(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) });
Operation selectOp = new(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result });
node.List.AddBefore(node, compareOp);
node.List.AddBefore(node, selectOp);
result = selection;
}
}
operation.TurnIntoCopy(result);
}
private static LinkedListNode<INode> InsertSharedStoreSmallInt(HelperFunctionManager hfm, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.StorageKind == StorageKind.SharedMemory8)
{
name = HelperFunctionName.SharedStore8;
}
else if (operation.StorageKind == StorageKind.SharedMemory16)
{
name = HelperFunctionName.SharedStore16;
}
else
{
return node;
}
if (operation.Inst != Instruction.Store)
{
return node;
}
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
private static LinkedListNode<INode> InsertSharedAtomicSigned(HelperFunctionManager hfm, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.Inst == Instruction.AtomicMaxS32)
{
name = HelperFunctionName.SharedAtomicMaxS32;
}
else if (operation.Inst == Instruction.AtomicMinS32)
{
name = HelperFunctionName.SharedAtomicMinS32;
}
else
{
return node;
}
if (operation.StorageKind != StorageKind.SharedMemory)
{
return node;
}
Operand result = operation.Dest;
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertTexelFetchScale(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
ShaderStage stage)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -280,20 +57,20 @@ namespace Ryujinx.Graphics.Shader.Translation
(intCoords || isImage) &&
!isBindless &&
!isIndexed &&
config.Stage.SupportsRenderScale() &&
stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
int samplerIndex = isImage
? config.ResourceManager.GetTextureDescriptors().Length + config.ResourceManager.FindImageDescriptorIndex(texOp.Binding)
: config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = 0; index < coordsCount; index++)
{
Operand scaledCoord = Local();
Operand[] callArgs;
if (config.Stage == ShaderStage.Fragment)
if (stage == ShaderStage.Fragment)
{
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
}
@ -311,7 +88,11 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static LinkedListNode<INode> InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertTextureSizeUnscale(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
ShaderStage stage)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -322,11 +103,11 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Index < 2 &&
!isBindless &&
!isIndexed &&
config.Stage.SupportsRenderScale() &&
stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
int samplerIndex = config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = texOp.DestsCount - 1; index >= 0; index--)
{
@ -356,19 +137,12 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static bool IsImageInstructionWithScale(Instruction inst)
{
// Currently, we don't support scaling images that are modified,
// so we only need to care about the load instruction.
return inst == Instruction.ImageLoad;
}
private static bool TypeSupportsScale(SamplerType type)
{
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
private static LinkedListNode<INode> InsertCoordNormalization(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertCoordNormalization(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
ShaderStage stage)
{
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
@ -386,9 +160,9 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
bool isCoordNormalized = config.GpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
if (isCoordNormalized || intCoords)
{
@ -400,8 +174,6 @@ namespace Ryujinx.Graphics.Shader.Translation
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
config.SetUsedFeature(FeatureFlags.IntegerSampling);
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
for (int index = 0; index < normCoordsCount; index++)
@ -429,7 +201,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new[] { coordSize },
texSizeSources));
config.ResourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
Operand source = texOp.GetSource(coordsIndex + index);
@ -439,13 +211,13 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.SetSource(coordsIndex + index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, config);
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
}
return node;
}
private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// The gather behavior when the coordinate sits right in the middle of two texels is not well defined.
// To ensure the correct texel is sampled, we add a small bias value to the coordinate.
@ -457,25 +229,18 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
int gatherBiasPrecision = config.GpuAccessor.QueryHostGatherBiasPrecision();
int gatherBiasPrecision = gpuAccessor.QueryHostGatherBiasPrecision();
if (!isGather || gatherBiasPrecision == 0)
{
return node;
}
#pragma warning disable IDE0059 // Remove unnecessary value assignment
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
#pragma warning restore IDE0059
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
config.SetUsedFeature(FeatureFlags.IntegerSampling);
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
for (int index = 0; index < normCoordsCount; index++)
@ -524,7 +289,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that.
@ -540,7 +305,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.GpuAccessor.QueryHostSupportsNonConstantTextureOffset();
bool hasInvalidOffset = (hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset();
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
@ -673,8 +438,6 @@ namespace Ryujinx.Graphics.Shader.Translation
if (isGather && !isShadow)
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand[] newSources = new Operand[sources.Length];
sources.CopyTo(newSources, 0);
@ -741,8 +504,6 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
for (int index = 0; index < coordsCount; index++)
@ -840,7 +601,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return texSizes;
}
private static LinkedListNode<INode> InsertSnormNormalization(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertSnormNormalization(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -851,9 +612,9 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
TextureFormat format = config.GpuAccessor.QueryTextureFormat(handle, cbufSlot);
TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
int maxPositive = format switch
{
@ -926,63 +687,16 @@ namespace Ryujinx.Graphics.Shader.Translation
return res;
}
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
private static bool IsImageInstructionWithScale(Instruction inst)
{
Operand GenerateLoad(IoVariable ioVariable)
{
Operand value = Local();
node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, value, Const((int)ioVariable)));
return value;
}
bool modified = false;
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex));
modified = true;
break;
case Constants.NvnBaseInstanceByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance));
modified = true;
break;
case Constants.NvnDrawIndexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex));
modified = true;
break;
}
}
}
return modified;
// Currently, we don't support scaling images that are modified,
// so we only need to care about the load instruction.
return inst == Instruction.ImageLoad;
}
private static bool HasConstantBufferDrawParameters(Operation operation)
private static bool TypeSupportsScale(SamplerType type)
{
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
case Constants.NvnBaseInstanceByteOffset / 4:
case Constants.NvnDrawIndexByteOffset / 4:
return true;
}
}
}
return false;
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
}
}

View file

@ -0,0 +1,41 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
static class TransformPasses
{
public static void RunPass(TransformContext context)
{
RunPass<DrawParametersReplace>(context);
RunPass<ForcePreciseEnable>(context);
RunPass<VectorComponentSelect>(context);
RunPass<TexturePass>(context);
RunPass<SharedStoreSmallIntCas>(context);
RunPass<SharedAtomicSignedCas>(context);
}
private static void RunPass<T>(TransformContext context) where T : ITransformPass
{
if (!T.IsEnabled(context.GpuAccessor, context.Stage, context.TargetLanguage, context.UsedFeatures))
{
return;
}
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
BasicBlock block = context.Blocks[blkIndex];
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not Operation)
{
continue;
}
node = T.RunPass(context, node);
}
}
}
}
}

View file

@ -0,0 +1,96 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class VectorComponentSelect : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return gpuAccessor.QueryHostHasVectorIndexingBug();
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
if (operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.ConstantBuffer ||
operation.SourcesCount < 3)
{
return node;
}
Operand bindingIndex = operation.GetSource(0);
Operand fieldIndex = operation.GetSource(1);
Operand elemIndex = operation.GetSource(operation.SourcesCount - 1);
if (bindingIndex.Type != OperandType.Constant ||
fieldIndex.Type != OperandType.Constant ||
elemIndex.Type == OperandType.Constant)
{
return node;
}
BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1
};
if (elemCount == 1)
{
return node;
}
Operand result = null;
for (int i = 0; i < elemCount; i++)
{
Operand value = Local();
Operand[] inputs = new Operand[operation.SourcesCount];
for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++)
{
inputs[srcIndex] = operation.GetSource(srcIndex);
}
inputs[^1] = Const(i);
Operation loadOp = new(Instruction.Load, StorageKind.ConstantBuffer, value, inputs);
node.List.AddBefore(node, loadOp);
if (i == 0)
{
result = value;
}
else
{
Operand isCurrentIndex = Local();
Operand selection = Local();
Operation compareOp = new(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) });
Operation selectOp = new(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result });
node.List.AddBefore(node, compareOp);
node.List.AddBefore(node, selectOp);
result = selection;
}
}
operation.TurnIntoCopy(result);
return node;
}
}
}

View file

@ -1,11 +1,6 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System;
using System.Collections.Generic;
using System.Linq;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@ -13,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
public static class Translator
{
private const int ThreadsPerWarp = 32;
private const int HeaderSize = 0x50;
internal readonly struct FunctionCode
@ -30,94 +26,31 @@ namespace Ryujinx.Graphics.Shader.Translation
return DecodeShader(address, gpuAccessor, options);
}
internal static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
for (int i = 0; i < functions.Length; i++)
{
cfgs[i] = ControlFlowGraph.Create(functions[i].Code);
if (i != 0)
{
frus[i] = RegisterUsage.RunPass(cfgs[i]);
}
}
List<Function> funcs = new(functions.Length);
for (int i = 0; i < functions.Length; i++)
{
funcs.Add(null);
}
HelperFunctionManager hfm = new(funcs, config.Stage);
for (int i = 0; i < functions.Length; i++)
{
var cfg = cfgs[i];
int inArgumentsCount = 0;
int outArgumentsCount = 0;
if (i != 0)
{
var fru = frus[i];
inArgumentsCount = fru.InArguments.Length;
outArgumentsCount = fru.OutArguments.Length;
}
if (cfg.Blocks.Length != 0)
{
RegisterUsage.FixupCalls(cfg.Blocks, frus);
Dominance.FindDominators(cfg);
Dominance.FindDominanceFrontiers(cfg.Blocks);
Ssa.Rename(cfg.Blocks);
Optimizer.RunPass(hfm, cfg.Blocks, config);
Rewriter.RunPass(hfm, cfg.Blocks, config);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
}
var identification = ShaderIdentifier.Identify(funcs, config);
var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
var info = config.CreateProgramInfo(identification);
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString()),
};
}
private static TranslatorContext DecodeShader(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options)
{
ShaderConfig config;
int localMemorySize;
ShaderDefinitions definitions;
DecodedProgram program;
ulong maxEndAddress = 0;
if (options.Flags.HasFlag(TranslationFlags.Compute))
{
config = new ShaderConfig(ShaderStage.Compute, gpuAccessor, options, gpuAccessor.QueryComputeLocalMemorySize());
definitions = CreateComputeDefinitions(gpuAccessor);
localMemorySize = gpuAccessor.QueryComputeLocalMemorySize();
program = Decoder.Decode(config, address);
program = Decoder.Decode(definitions, gpuAccessor, address);
}
else
{
config = new ShaderConfig(new ShaderHeader(gpuAccessor, address), gpuAccessor, options);
ShaderHeader header = new(gpuAccessor, address);
program = Decoder.Decode(config, address + HeaderSize);
definitions = CreateGraphicsDefinitions(gpuAccessor, header);
localMemorySize = GetLocalMemorySize(header);
program = Decoder.Decode(definitions, gpuAccessor, address + HeaderSize);
}
ulong maxEndAddress = 0;
foreach (DecodedFunction function in program)
{
foreach (Block block in function.Blocks)
@ -129,12 +62,76 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
config.SizeAdd((int)maxEndAddress + (options.Flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize));
int size = (int)maxEndAddress + (options.Flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize);
return new TranslatorContext(address, program, config);
return new TranslatorContext(address, size, localMemorySize, definitions, gpuAccessor, options, program);
}
internal static FunctionCode[] EmitShader(DecodedProgram program, ShaderConfig config, bool initializeOutputs, out int initializationOperations)
private static ShaderDefinitions CreateComputeDefinitions(IGpuAccessor gpuAccessor)
{
return new ShaderDefinitions(
ShaderStage.Compute,
gpuAccessor.QueryComputeLocalSizeX(),
gpuAccessor.QueryComputeLocalSizeY(),
gpuAccessor.QueryComputeLocalSizeZ());
}
private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header)
{
bool transformFeedbackEnabled =
gpuAccessor.QueryTransformFeedbackEnabled() &&
gpuAccessor.QueryHostSupportsTransformFeedback();
TransformFeedbackOutput[] transformFeedbackOutputs = null;
ulong transformFeedbackVecMap = 0UL;
if (transformFeedbackEnabled)
{
transformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = gpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = gpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte wordOffset = locations[i];
if (wordOffset < 0xc0)
{
transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
transformFeedbackVecMap |= 1UL << (wordOffset / 4);
}
}
}
}
return new ShaderDefinitions(
header.Stage,
gpuAccessor.QueryGraphicsState(),
header.Stage == ShaderStage.Geometry && header.GpPassthrough,
header.ThreadsPerInputPrimitive,
header.OutputTopology,
header.MaxOutputVertexCount,
header.ImapTypes,
header.OmapTargets,
header.OmapSampleMask,
header.OmapDepth,
transformFeedbackEnabled,
transformFeedbackVecMap,
transformFeedbackOutputs);
}
private static int GetLocalMemorySize(ShaderHeader header)
{
return header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp);
}
internal static FunctionCode[] EmitShader(
TranslatorContext translatorContext,
ResourceManager resourceManager,
DecodedProgram program,
bool initializeOutputs,
out int initializationOperations)
{
initializationOperations = 0;
@ -149,11 +146,11 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int index = 0; index < functions.Length; index++)
{
EmitterContext context = new(program, config, index != 0);
EmitterContext context = new(translatorContext, resourceManager, program, index != 0);
if (initializeOutputs && index == 0)
{
EmitOutputsInitialization(context, config);
EmitOutputsInitialization(context, translatorContext.AttributeUsage, translatorContext.GpuAccessor, translatorContext.Stage);
initializationOperations = context.OperationsCount;
}
@ -168,27 +165,27 @@ namespace Ryujinx.Graphics.Shader.Translation
EmitOps(context, block);
}
functions[index] = new FunctionCode(context.GetOperations());
functions[index] = new(context.GetOperations());
}
return functions;
}
private static void EmitOutputsInitialization(EmitterContext context, ShaderConfig config)
private static void EmitOutputsInitialization(EmitterContext context, AttributeUsage attributeUsage, IGpuAccessor gpuAccessor, ShaderStage stage)
{
// Compute has no output attributes, and fragment is the last stage, so we
// don't need to initialize outputs on those stages.
if (config.Stage == ShaderStage.Compute || config.Stage == ShaderStage.Fragment)
if (stage == ShaderStage.Compute || stage == ShaderStage.Fragment)
{
return;
}
if (config.Stage == ShaderStage.Vertex)
if (stage == ShaderStage.Vertex)
{
InitializePositionOutput(context);
}
UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
while (usedAttributes != UInt128.Zero)
{
int index = (int)UInt128.TrailingZeroCount(usedAttributes);
@ -197,7 +194,7 @@ namespace Ryujinx.Graphics.Shader.Translation
usedAttributes &= ~(UInt128.One << index);
// We don't need to initialize passthrough attributes.
if ((context.Config.PassthroughAttributes & (1 << vecIndex)) != 0)
if ((context.TranslatorContext.AttributeUsage.PassthroughAttributes & (1 << vecIndex)) != 0)
{
continue;
}
@ -205,30 +202,28 @@ namespace Ryujinx.Graphics.Shader.Translation
InitializeOutputComponent(context, vecIndex, index & 3, perPatch: false);
}
if (context.Config.NextUsedInputAttributesPerPatch != null)
if (context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch != null)
{
foreach (int vecIndex in context.Config.NextUsedInputAttributesPerPatch.Order())
foreach (int vecIndex in context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch.Order())
{
InitializeOutput(context, vecIndex, perPatch: true);
}
}
if (config.NextUsesFixedFuncAttributes)
if (attributeUsage.NextUsesFixedFuncAttributes)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
bool supportsLayerFromVertexOrTess = gpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
for (int i = fixedStartAttr; i < fixedStartAttr + 5 + AttributeConsts.TexCoordCount; i++)
{
int index = config.GetFreeUserAttribute(isOutput: true, i);
int index = attributeUsage.GetFreeUserAttribute(isOutput: true, i);
if (index < 0)
{
break;
}
InitializeOutput(context, index, perPatch: false);
config.SetOutputUserAttributeFixedFunc(index);
}
}
}
@ -253,11 +248,11 @@ namespace Ryujinx.Graphics.Shader.Translation
{
StorageKind storageKind = perPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
if (context.TranslatorContext.Definitions.OaIndexing)
{
Operand invocationId = null;
if (context.Config.Stage == ShaderStage.TessellationControl && !perPatch)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -268,7 +263,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else
{
if (context.Config.Stage == ShaderStage.TessellationControl && !perPatch)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch)
{
Operand invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
context.Store(storageKind, IoVariable.UserDefined, Const(location), invocationId, Const(c), ConstF(c == 3 ? 1f : 0f));
@ -286,7 +281,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
InstOp op = block.OpCodes[opIndex];
if (context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
if (context.TranslatorContext.Options.Flags.HasFlag(TranslationFlags.DebugMode))
{
string instName;
@ -298,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
instName = "???";
context.Config.GpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
context.TranslatorContext.GpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
}
string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}";

View file

@ -1,8 +1,11 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using Ryujinx.Graphics.Shader.Translation.Transforms;
using System;
using System.Collections.Generic;
using System.Linq;
@ -15,22 +18,47 @@ namespace Ryujinx.Graphics.Shader.Translation
public class TranslatorContext
{
private readonly DecodedProgram _program;
private readonly ShaderConfig _config;
private readonly int _localMemorySize;
public ulong Address { get; }
public int Size { get; }
public int Cb1DataSize => _program.Cb1DataSize;
public ShaderStage Stage => _config.Stage;
public int Size => _config.Size;
public int Cb1DataSize => _config.Cb1DataSize;
public bool LayerOutputWritten => _config.LayerOutputWritten;
internal bool HasLayerInputAttribute { get; private set; }
internal int GpLayerInputAttribute { get; private set; }
public IGpuAccessor GpuAccessor => _config.GpuAccessor;
internal AttributeUsage AttributeUsage => _program.AttributeUsage;
internal TranslatorContext(ulong address, DecodedProgram program, ShaderConfig config)
internal ShaderDefinitions Definitions { get; }
public ShaderStage Stage => Definitions.Stage;
internal IGpuAccessor GpuAccessor { get; }
internal TranslationOptions Options { get; }
internal FeatureFlags UsedFeatures { get; private set; }
public bool LayerOutputWritten { get; private set; }
public int LayerOutputAttribute { get; private set; }
internal TranslatorContext(
ulong address,
int size,
int localMemorySize,
ShaderDefinitions definitions,
IGpuAccessor gpuAccessor,
TranslationOptions options,
DecodedProgram program)
{
Address = address;
Size = size;
_program = program;
_config = config;
_localMemorySize = localMemorySize;
Definitions = definitions;
GpuAccessor = gpuAccessor;
Options = options;
UsedFeatures = program.UsedFeatures;
}
private static bool IsLoadUserDefined(Operation operation)
@ -131,63 +159,259 @@ namespace Ryujinx.Graphics.Shader.Translation
return output;
}
public void SetNextStage(TranslatorContext nextStage)
internal int GetDepthRegister()
{
_config.MergeFromtNextStage(nextStage._config);
// The depth register is always two registers after the last color output.
return BitOperations.PopCount((uint)Definitions.OmapTargets) + 1;
}
public void SetLayerOutputAttribute(int attr)
{
LayerOutputWritten = true;
LayerOutputAttribute = attr;
}
public void SetGeometryShaderLayerInputAttribute(int attr)
{
_config.SetGeometryShaderLayerInputAttribute(attr);
UsedFeatures |= FeatureFlags.RtLayer;
HasLayerInputAttribute = true;
GpLayerInputAttribute = attr;
}
public void SetLastInVertexPipeline()
{
_config.SetLastInVertexPipeline();
Definitions.LastInVertexPipeline = true;
}
public ShaderProgram Translate(TranslatorContext other = null)
public void SetNextStage(TranslatorContext nextStage)
{
bool usesLocalMemory = _config.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
AttributeUsage.MergeFromtNextStage(
Definitions.GpPassthrough,
nextStage.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr),
nextStage.AttributeUsage);
_config.ResourceManager.SetCurrentLocalMemory(_config.LocalMemorySize, usesLocalMemory);
if (_config.Stage == ShaderStage.Compute)
// We don't consider geometry shaders using the geometry shader passthrough feature
// as being the last because when this feature is used, it can't actually modify any of the outputs,
// so the stage that comes before it is the last one that can do modifications.
if (nextStage.Definitions.Stage != ShaderStage.Fragment &&
(nextStage.Definitions.Stage != ShaderStage.Geometry || !nextStage.Definitions.GpPassthrough))
{
bool usesSharedMemory = _config.UsedFeatures.HasFlag(FeatureFlags.SharedMemory);
Definitions.LastInVertexPipeline = false;
}
}
_config.ResourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
public ShaderProgram Translate()
{
ResourceManager resourceManager = CreateResourceManager();
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
if (Stage == ShaderStage.Compute)
{
bool usesSharedMemory = _program.UsedFeatures.HasFlag(FeatureFlags.SharedMemory);
resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
}
FunctionCode[] code = EmitShader(_program, _config, initializeOutputs: other == null, out _);
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: true, out _);
if (other != null)
return Translate(code, resourceManager, UsedFeatures, _program.ClipDistancesWritten);
}
public ShaderProgram Translate(TranslatorContext other)
{
ResourceManager resourceManager = CreateResourceManager();
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: false, out _);
bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory);
FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, initializeOutputs: true, out int aStart);
code = Combine(otherCode, code, aStart);
return Translate(
code,
resourceManager,
UsedFeatures | other.UsedFeatures,
(byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten));
}
private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
for (int i = 0; i < functions.Length; i++)
{
other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, Enumerable.Empty<int>());
cfgs[i] = ControlFlowGraph.Create(functions[i].Code);
// We need to share the resource manager since both shaders accesses the same constant buffers.
other._config.ResourceManager = _config.ResourceManager;
other._config.ResourceManager.SetCurrentLocalMemory(other._config.LocalMemorySize, other._config.UsedFeatures.HasFlag(FeatureFlags.LocalMemory));
FunctionCode[] otherCode = EmitShader(other._program, other._config, initializeOutputs: true, out int aStart);
code = Combine(otherCode, code, aStart);
_config.InheritFrom(other._config);
if (i != 0)
{
frus[i] = RegisterUsage.RunPass(cfgs[i]);
}
}
return Translator.Translate(code, _config);
List<Function> funcs = new(functions.Length);
for (int i = 0; i < functions.Length; i++)
{
funcs.Add(null);
}
HelperFunctionManager hfm = new(funcs, Definitions.Stage);
for (int i = 0; i < functions.Length; i++)
{
var cfg = cfgs[i];
int inArgumentsCount = 0;
int outArgumentsCount = 0;
if (i != 0)
{
var fru = frus[i];
inArgumentsCount = fru.InArguments.Length;
outArgumentsCount = fru.OutArguments.Length;
}
if (cfg.Blocks.Length != 0)
{
RegisterUsage.FixupCalls(cfg.Blocks, frus);
Dominance.FindDominators(cfg);
Dominance.FindDominanceFrontiers(cfg.Blocks);
Ssa.Rename(cfg.Blocks);
TransformContext context = new(
hfm,
cfg.Blocks,
resourceManager,
GpuAccessor,
Options.TargetLanguage,
Definitions.Stage,
ref usedFeatures);
Optimizer.RunPass(context);
TransformPasses.RunPass(context);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
}
var identification = ShaderIdentifier.Identify(funcs, GpuAccessor, Definitions.Stage, Definitions.InputTopology, out int layerInputAttr);
return Generate(
funcs,
AttributeUsage,
Definitions,
resourceManager,
usedFeatures,
clipDistancesWritten,
identification,
layerInputAttr);
}
private ShaderProgram Generate(
IReadOnlyList<Function> funcs,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
FeatureFlags usedFeatures,
byte clipDistancesWritten,
ShaderIdentification identification = ShaderIdentification.None,
int layerInputAttr = 0)
{
var sInfo = StructuredProgram.MakeStructuredProgram(
funcs,
attributeUsage,
definitions,
resourceManager,
Options.Flags.HasFlag(TranslationFlags.DebugMode));
var info = new ShaderProgramInfo(
resourceManager.GetConstantBufferDescriptors(),
resourceManager.GetStorageBufferDescriptors(),
resourceManager.GetTextureDescriptors(),
resourceManager.GetImageDescriptors(),
identification,
layerInputAttr,
definitions.Stage,
usedFeatures.HasFlag(FeatureFlags.FragCoordXY),
usedFeatures.HasFlag(FeatureFlags.InstanceId),
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
usedFeatures.HasFlag(FeatureFlags.RtLayer),
clipDistancesWritten,
definitions.OmapTargets);
var hostCapabilities = new HostCapabilities(
GpuAccessor.QueryHostReducedPrecision(),
GpuAccessor.QueryHostSupportsFragmentShaderInterlock(),
GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel(),
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());
var parameters = new CodeGenParameters(attributeUsage, definitions, resourceManager.Properties, hostCapabilities, GpuAccessor, Options.TargetApi);
return Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, parameters)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, parameters)),
_ => throw new NotImplementedException(Options.TargetLanguage.ToString()),
};
}
private ResourceManager CreateResourceManager()
{
ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor);
if (!GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled())
{
StructureType tfeInfoStruct = new(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "base_offset", 4),
new StructureField(AggregateType.U32, "vertex_count")
});
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeInfoBuffer);
StructureType tfeDataStruct = new(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
});
for (int i = 0; i < Constants.TfeBuffersCount; i++)
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
}
}
return resourceManager;
}
public ShaderProgram GenerateGeometryPassthrough()
{
int outputAttributesMask = _config.UsedOutputAttributes;
int layerOutputAttr = _config.LayerOutputAttribute;
int outputAttributesMask = AttributeUsage.UsedOutputAttributes;
int layerOutputAttr = LayerOutputAttribute;
OutputTopology outputTopology;
int maxOutputVertices;
switch (GpuAccessor.QueryPrimitiveTopology())
switch (Definitions.InputTopology)
{
case InputTopology.Points:
outputTopology = OutputTopology.PointList;
@ -204,9 +428,10 @@ namespace Ryujinx.Graphics.Shader.Translation
break;
}
ShaderConfig config = new(ShaderStage.Geometry, outputTopology, maxOutputVertices, GpuAccessor, _config.Options);
var attributeUsage = new AttributeUsage(GpuAccessor);
var resourceManager = new ResourceManager(ShaderStage.Geometry, GpuAccessor);
EmitterContext context = new(default, config, false);
var context = new EmitterContext();
for (int v = 0; v < maxOutputVertices; v++)
{
@ -231,10 +456,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else
{
context.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(attrIndex), Const(c), value);
config.SetOutputUserAttribute(attrIndex);
}
config.SetInputUserAttribute(attrIndex, c);
}
}
@ -254,16 +476,15 @@ namespace Ryujinx.Graphics.Shader.Translation
var cfg = ControlFlowGraph.Create(operations);
var function = new Function(cfg.Blocks, "main", false, 0, 0);
var sInfo = StructuredProgram.MakeStructuredProgram(new[] { function }, config);
var definitions = new ShaderDefinitions(
ShaderStage.Geometry,
GpuAccessor.QueryGraphicsState(),
false,
1,
outputTopology,
maxOutputVertices);
var info = config.CreateProgramInfo();
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString()),
};
return Generate(new[] { function }, attributeUsage, definitions, resourceManager, FeatureFlags.RtLayer, 0);
}
}
}