Refactor attribute handling on the shader generator (#4565)

* Refactor attribute handling on the shader generator

* Implement gl_ViewportMask[]

* Add back the Intel FrontFacing bug workaround

* Fix GLSL transform feedback outputs mistmatch with fragment stage

* Shader cache version bump

* Fix geometry shader recognition

* PR feedback

* Delete GetOperandDef and GetOperandUse

* Remove replacements that are no longer needed on GLSL compilation on Vulkan

* Fix incorrect load for per-patch outputs

* Fix build
This commit is contained in:
gdkchan 2023-04-25 19:51:07 -03:00 committed by GitHub
parent 097562bc6c
commit 9f12e50a54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 1967 additions and 1746 deletions

View file

@ -35,7 +35,8 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportIndex;
public readonly bool SupportsViewportIndexVertexTessellation;
public readonly bool SupportsViewportMask;
public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters;
@ -80,7 +81,8 @@ namespace Ryujinx.Graphics.GAL
bool supportsNonConstantTextureOffset,
bool supportsShaderBallot,
bool supportsTextureShadowLod,
bool supportsViewportIndex,
bool supportsViewportIndexVertexTessellation,
bool supportsViewportMask,
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
uint maximumUniformBuffersPerStage,
@ -121,7 +123,8 @@ namespace Ryujinx.Graphics.GAL
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsShaderBallot = supportsShaderBallot;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportIndex = supportsViewportIndex;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
SupportsViewportMask = supportsViewportMask;
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;

View file

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 4707;
private const uint CodeGenVersion = 4565;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View file

@ -144,7 +144,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
public bool QueryHostSupportsViewportIndex() => _context.Capabilities.SupportsViewportIndex;
public bool QueryHostSupportsViewportIndexVertexTessellation() => _context.Capabilities.SupportsViewportIndexVertexTessellation;
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
/// <summary>
/// Converts a packed Maxwell texture format to the shader translator texture format.

View file

@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsShaderViewportLayerArray = new Lazy<bool>(() => HasExtension("GL_ARB_shader_viewport_layer_array"));
private static readonly Lazy<bool> _supportsViewportArray2 = new Lazy<bool>(() => HasExtension("GL_NV_viewport_array2"));
private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc"));
private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc"));
private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc"));
@ -65,6 +66,7 @@ namespace Ryujinx.Graphics.OpenGL
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsShaderViewportLayerArray => _supportsShaderViewportLayerArray.Value;
public static bool SupportsViewportArray2 => _supportsViewportArray2.Value;
public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value;
public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value;
public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value;

View file

@ -136,7 +136,8 @@ namespace Ryujinx.Graphics.OpenGL
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportIndex: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?

View file

@ -59,6 +59,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
}
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
{
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
}
context.AppendLine("#pragma optionNV(fastmath off)");
context.AppendLine();
@ -215,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(AttributeConsts.PositionX);
var tfOutput = context.Config.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
@ -552,7 +557,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr)
{
string suffix = AttributeInfo.IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment)
@ -569,8 +574,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
{
int attrOffset = AttributeConsts.UserAttributeBase + attr * 16;
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
if (components > 1)
{
@ -652,13 +656,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareOutputAttribute(CodeGenContext context, int attr)
{
string suffix = AttributeInfo.IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
int attrOffset = AttributeConsts.UserAttributeBase + attr * 16;
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
if (components > 1)
{
@ -672,7 +675,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset);
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, 0);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
@ -687,7 +690,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset + c * 4);
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
@ -726,6 +729,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
}
private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{
foreach (int attr in attrs.Order())

View file

@ -1,5 +1,4 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
@ -126,21 +125,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else if (node is AstAssignment assignment)
{
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination);
AggregateType srcType = OperandManager.GetNodeDestType(context, assignment.Source);
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination, isAsgDest: true);
string dest;
if (assignment.Destination is AstOperand operand && operand.Type.IsAttribute())
{
bool perPatch = operand.Type == OperandType.AttributePerPatch;
dest = OperandManager.GetOutAttributeName(context, operand.Value, perPatch);
}
else
{
dest = InstGen.GetExpression(context, assignment.Destination);
}
string dest = InstGen.GetExpression(context, assignment.Destination);
string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
context.AppendLine(dest + " = " + src + ";");

View file

@ -73,7 +73,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// For shared memory access, the second argument is unused and should be ignored.
// It is there to make both storage and shared access have the same number of arguments.
// For storage, both inputs are consumed when the argument index is 0, so we should skip it here.
if (argIndex == 1 && (atomic || (inst & Instruction.MrMask) == Instruction.MrShared))
if (argIndex == 1 && (atomic || operation.StorageKind == StorageKind.SharedMemory))
{
continue;
}
@ -85,14 +85,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (argIndex == 0 && atomic)
{
Instruction memRegion = inst & Instruction.MrMask;
switch (memRegion)
switch (operation.StorageKind)
{
case Instruction.MrShared: args += LoadShared(context, operation); break;
case Instruction.MrStorage: args += LoadStorage(context, operation); break;
case StorageKind.SharedMemory: args += LoadShared(context, operation); break;
case StorageKind.StorageBuffer: args += LoadStorage(context, operation); break;
default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\".");
default: throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
}
else
@ -166,8 +164,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.ImageAtomic:
return ImageLoadOrStore(context, operation);
case Instruction.LoadAttribute:
return LoadAttribute(context, operation);
case Instruction.Load:
return Load(context, operation);
case Instruction.LoadConstant:
return LoadConstant(context, operation);
@ -193,8 +191,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.PackHalf2x16:
return PackHalf2x16(context, operation);
case Instruction.StoreAttribute:
return StoreAttribute(context, operation);
case Instruction.Store:
return Store(context, operation);
case Instruction.StoreLocal:
return StoreLocal(context, operation);

View file

@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ImageStore, InstType.Special);
Add(Instruction.ImageAtomic, InstType.Special);
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.LoadAttribute, InstType.Special);
Add(Instruction.Load, InstType.Special);
Add(Instruction.LoadConstant, InstType.Special);
Add(Instruction.LoadLocal, InstType.Special);
Add(Instruction.LoadShared, InstType.Special);
@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ShuffleXor, InstType.CallQuaternary, HelperFunctionNames.ShuffleXor);
Add(Instruction.Sine, InstType.CallUnary, "sin");
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
Add(Instruction.StoreAttribute, InstType.Special);
Add(Instruction.Store, InstType.Special);
Add(Instruction.StoreLocal, InstType.Special);
Add(Instruction.StoreShared, InstType.Special);
Add(Instruction.StoreShared16, InstType.Special);

View file

@ -210,30 +210,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return texCallBuilder.ToString();
}
public static string LoadAttribute(CodeGenContext context, AstOperation operation)
public static string Load(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
}
string indexExpr = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = baseAttr.Value + (operand.Value << 2);
return OperandManager.GetAttributeName(context, attrOffset, perPatch: false, isOutAttr: false, indexExpr);
}
else
{
string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
return OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: false, indexExpr);
}
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string LoadConstant(CodeGenContext context, AstOperation operation)
@ -337,33 +316,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}";
}
public static string StoreAttribute(CodeGenContext context, AstOperation operation)
public static string Store(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.StoreAttribute)} must be a constant operand.");
}
string attrName;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = baseAttr.Value + (operand.Value << 2);
attrName = OperandManager.GetAttributeName(context, attrOffset, perPatch: false, isOutAttr: true);
}
else
{
string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
attrName = OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: true);
}
string value = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
return $"{attrName} = {value}";
return GenerateLoadOrStore(context, operation, isStore: true);
}
public static string StoreLocal(CodeGenContext context, AstOperation operation)
@ -847,6 +802,111 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
}
private static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
string varName;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(srcIndex++) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = -1;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(srcIndex++) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
if (IoMap.IsPerVertexBuiltIn(context.Config.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.
if (storageKind == StorageKind.Input)
{
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_in[{expr}].{varName}";
}
else if (storageKind == StorageKind.Output)
{
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_out[{expr}].{varName}";
}
}
int firstSrcIndex = srcIndex;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
if (isStore)
{
varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}";
}
return varName;
}
private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
{
string sbName = OperandManager.GetShaderStagePrefix(stage);

View file

@ -0,0 +1,145 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
static class IoMap
{
public static (string, AggregateType) GetGlslVariable(
ShaderConfig config,
IoVariable ioVariable,
int location,
int component,
bool isOutput,
bool isPerPatch)
{
return ioVariable switch
{
IoVariable.BackColorDiffuse => ("gl_BackColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.BackColorSpecular => ("gl_BackSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.BaseInstance => ("gl_BaseInstanceARB", AggregateType.S32),
IoVariable.BaseVertex => ("gl_BaseVertexARB", AggregateType.S32),
IoVariable.ClipDistance => ("gl_ClipDistance", AggregateType.Array | AggregateType.FP32),
IoVariable.CtaId => ("gl_WorkGroupID", AggregateType.Vector3 | AggregateType.U32),
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.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FragmentOutputIsBgra => (DefaultNames.SupportBlockIsBgraName, AggregateType.Array | AggregateType.Bool),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32),
IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32),
IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32),
IoVariable.Layer => ("gl_Layer", AggregateType.S32),
IoVariable.PatchVertices => ("gl_PatchVerticesIn", AggregateType.S32),
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.SupportBlockRenderScale => (DefaultNames.SupportBlockRenderScaleName, AggregateType.Array | AggregateType.FP32),
IoVariable.SupportBlockViewInverse => (DefaultNames.SupportBlockViewportInverse, AggregateType.Vector2 | AggregateType.FP32),
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.VertexId => ("gl_VertexID", AggregateType.S32),
IoVariable.VertexIndex => ("gl_VertexIndex", AggregateType.S32),
IoVariable.ViewportIndex => ("gl_ViewportIndex", AggregateType.S32),
IoVariable.ViewportMask => ("gl_ViewportMask", AggregateType.Array | AggregateType.S32),
_ => (null, AggregateType.Invalid)
};
}
public static bool IsPerVertexBuiltIn(ShaderStage stage, IoVariable ioVariable, bool isOutput)
{
switch (ioVariable)
{
case IoVariable.Layer:
case IoVariable.ViewportIndex:
case IoVariable.PointSize:
case IoVariable.Position:
case IoVariable.ClipDistance:
case IoVariable.PointCoord:
case IoVariable.ViewportMask:
if (isOutput)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
return false;
}
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderConfig config, int location)
{
if (location < 0)
{
return (DefaultNames.OAttributePrefix, config.GetFragmentOutputColorType(0));
}
string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture);
return (name, config.GetFragmentOutputColorType(location));
}
private static (string, AggregateType) GetPrimitiveIdVariableName(ShaderStage stage, bool isOutput)
{
// The geometry stage has an additional gl_PrimitiveIDIn variable.
return (isOutput || stage != ShaderStage.Geometry ? "gl_PrimitiveID" : "gl_PrimitiveIDIn", AggregateType.S32);
}
private static (string, AggregateType) GetSubgroupMaskVariableName(ShaderConfig config, string cc)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32)
: ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32);
}
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(ShaderConfig config)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? ("gl_SubGroupInvocationARB", AggregateType.U32)
: ("gl_SubgroupInvocationID", AggregateType.U32);
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderConfig config, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
: (isOutput ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix);
if (location < 0)
{
return (name, config.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (config.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
return (name, config.GetUserDefinedType(location, isOutput));
}
}
}

View file

@ -1,10 +1,10 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
@ -12,82 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
class OperandManager
{
private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly struct BuiltInAttribute
{
public string Name { get; }
public AggregateType Type { get; }
public BuiltInAttribute(string name, AggregateType type)
{
Name = name;
Type = type;
}
}
private static Dictionary<int, BuiltInAttribute> _builtInAttributes = new Dictionary<int, BuiltInAttribute>()
{
{ AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", AggregateType.S32) },
{ AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", AggregateType.FP32) },
{ AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", AggregateType.FP32) },
{ AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", AggregateType.FP32) },
{ AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", AggregateType.FP32) },
{ AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", AggregateType.FP32) },
{ AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", AggregateType.FP32) },
{ AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", AggregateType.FP32) },
{ AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", AggregateType.FP32) },
{ AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", AggregateType.FP32) },
{ AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", AggregateType.FP32) },
{ AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", AggregateType.S32) },
{ AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", AggregateType.S32) },
{ AttributeConsts.BaseInstance, new BuiltInAttribute("gl_BaseInstanceARB", AggregateType.S32) },
{ AttributeConsts.BaseVertex, new BuiltInAttribute("gl_BaseVertexARB", AggregateType.S32) },
{ AttributeConsts.InstanceIndex, new BuiltInAttribute("gl_InstanceIndex", AggregateType.S32) },
{ AttributeConsts.VertexIndex, new BuiltInAttribute("gl_VertexIndex", AggregateType.S32) },
{ AttributeConsts.DrawIndex, new BuiltInAttribute("gl_DrawIDARB", AggregateType.S32) },
{ AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", AggregateType.Bool) },
// Special.
{ AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", AggregateType.FP32) },
{ AttributeConsts.ThreadKill, new BuiltInAttribute("gl_HelperInvocation", AggregateType.Bool) },
{ AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", AggregateType.U32) },
{ AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", AggregateType.U32) },
{ AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", AggregateType.U32) },
{ AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", AggregateType.U32) },
{ AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", AggregateType.U32) },
{ AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", AggregateType.U32) },
{ AttributeConsts.LaneId, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.InvocationId, new BuiltInAttribute("gl_InvocationID", AggregateType.S32) },
{ AttributeConsts.PrimitiveId, new BuiltInAttribute("gl_PrimitiveID", AggregateType.S32) },
{ AttributeConsts.PatchVerticesIn, new BuiltInAttribute("gl_PatchVerticesIn", AggregateType.S32) },
{ AttributeConsts.EqMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.GeMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.GtMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.LeMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.LtMask, new BuiltInAttribute(null, AggregateType.U32) },
// Support uniforms.
{ AttributeConsts.FragmentOutputIsBgraBase + 0, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[0]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 4, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[1]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 8, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[2]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 12, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[3]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", AggregateType.Bool) },
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", AggregateType.FP32) },
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", AggregateType.FP32) }
};
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private Dictionary<AstOperand, string> _locals;
@ -110,8 +35,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return operand.Type switch
{
OperandType.Argument => GetArgumentName(operand.Value),
OperandType.Attribute => GetAttributeName(context, operand.Value, perPatch: false),
OperandType.AttributePerPatch => GetAttributeName(context, operand.Value, perPatch: true),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
OperandType.LocalVariable => _locals[operand],
@ -155,177 +78,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetOutAttributeName(CodeGenContext context, int value, bool perPatch)
{
return GetAttributeName(context, value, perPatch, isOutAttr: true);
}
public static string GetAttributeName(CodeGenContext context, int value, bool perPatch, bool isOutAttr = false, string indexExpr = "0")
{
ShaderConfig config = context.Config;
if ((value & AttributeConsts.LoadOutputMask) != 0)
{
isOutAttr = true;
}
value &= AttributeConsts.Mask & ~3;
char swzMask = GetSwizzleMask((value >> 2) & 3);
if (perPatch)
{
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
{
value -= AttributeConsts.UserAttributePerPatchBase;
return $"{DefaultNames.PerPatchAttributePrefix}{(value >> 4)}.{swzMask}";
}
else if (value < AttributeConsts.UserAttributePerPatchBase)
{
return value switch
{
AttributeConsts.TessLevelOuter0 => "gl_TessLevelOuter[0]",
AttributeConsts.TessLevelOuter1 => "gl_TessLevelOuter[1]",
AttributeConsts.TessLevelOuter2 => "gl_TessLevelOuter[2]",
AttributeConsts.TessLevelOuter3 => "gl_TessLevelOuter[3]",
AttributeConsts.TessLevelInner0 => "gl_TessLevelInner[0]",
AttributeConsts.TessLevelInner1 => "gl_TessLevelInner[1]",
_ => null
};
}
}
else if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
{
int attrOffset = value;
value -= AttributeConsts.UserAttributeBase;
string prefix = isOutAttr
? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix;
bool indexable = config.UsedFeatures.HasFlag(isOutAttr ? FeatureFlags.OaIndexing : FeatureFlags.IaIndexing);
if (indexable)
{
string name = prefix;
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
{
name += $"[{indexExpr}]";
}
return name + $"[{(value >> 4)}]." + swzMask;
}
else if (config.TransformFeedbackEnabled &&
((config.LastInVertexPipeline && isOutAttr) ||
(config.Stage == ShaderStage.Fragment && !isOutAttr)))
{
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
string name = components > 1 ? $"{prefix}{(value >> 4)}" : $"{prefix}{(value >> 4)}_{swzMask}";
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
{
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
}
return components > 1 ? name + '.' + swzMask : name;
}
else
{
string name = $"{prefix}{(value >> 4)}";
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
{
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
}
return name + '.' + swzMask;
}
}
else
{
if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
{
value -= AttributeConsts.FragmentOutputColorBase;
return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}";
}
else if (_builtInAttributes.TryGetValue(value, out BuiltInAttribute builtInAttr))
{
string subgroupMask = value switch
{
AttributeConsts.EqMask => "Eq",
AttributeConsts.GeMask => "Ge",
AttributeConsts.GtMask => "Gt",
AttributeConsts.LeMask => "Le",
AttributeConsts.LtMask => "Lt",
_ => null
};
if (subgroupMask != null)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? $"unpackUint2x32(gl_SubGroup{subgroupMask}MaskARB).x"
: $"gl_Subgroup{subgroupMask}Mask.x";
}
else if (value == AttributeConsts.LaneId)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? "gl_SubGroupInvocationARB"
: "gl_SubgroupInvocationID";
}
if (config.Stage == ShaderStage.Fragment)
{
// TODO: There must be a better way to handle this...
switch (value)
{
case AttributeConsts.PositionX: return $"(gl_FragCoord.x / {DefaultNames.SupportBlockRenderScaleName}[0])";
case AttributeConsts.PositionY: return $"(gl_FragCoord.y / {DefaultNames.SupportBlockRenderScaleName}[0])";
case AttributeConsts.PositionZ: return "gl_FragCoord.z";
case AttributeConsts.PositionW: return "gl_FragCoord.w";
case AttributeConsts.FrontFacing:
if (config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// This is required for Intel on Windows, gl_FrontFacing sometimes returns incorrect
// (flipped) values. Doing this seems to fix it.
return "(-floatBitsToInt(float(gl_FrontFacing)) < 0)";
}
break;
}
}
string name = builtInAttr.Name;
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr) && AttributeInfo.IsArrayBuiltIn(value))
{
name = isOutAttr ? $"gl_out[gl_InvocationID].{name}" : $"gl_in[{indexExpr}].{name}";
}
return name;
}
}
// TODO: Warn about unknown built-in attribute.
return isOutAttr ? "// bad_attr0x" + value.ToString("X") : "0.0";
}
public static string GetAttributeName(string attrExpr, ShaderConfig config, bool isOutAttr = false, string indexExpr = "0")
{
string name = isOutAttr
? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix;
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
{
name += $"[{indexExpr}]";
}
return $"{name}[{attrExpr} >> 2][{attrExpr} & 3]";
}
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{
if (cbIndexable)
@ -387,12 +139,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
int index = (int)stage;
if ((uint)index >= StagePrefixes.Length)
if ((uint)index >= _stagePrefixes.Length)
{
return "invalid";
}
return StagePrefixes[index];
return _stagePrefixes[index];
}
private static char GetSwizzleMask(int value)
@ -405,24 +157,54 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
}
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node, bool isAsgDest = false)
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node)
{
// TODO: Get rid of that function entirely and return the type from the operation generation
// functions directly, like SPIR-V does.
if (node is AstOperation operation)
{
if (operation.Inst == Instruction.LoadAttribute)
if (operation.Inst == Instruction.Load)
{
// Load attribute basically just returns the attribute value.
// Some built-in attributes may have different types, so we need
// to return the type based on the attribute that is being read.
if (operation.GetSource(0) is AstOperand operand && operand.Type == OperandType.Constant)
switch (operation.StorageKind)
{
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
{
return builtInAttr.Type;
}
}
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(0) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
return OperandInfo.GetVarType(OperandType.Attribute);
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = operation.StorageKind == StorageKind.Output || operation.StorageKind == StorageKind.OutputPerPatch;
bool isPerPatch = operation.StorageKind == StorageKind.InputPerPatch || operation.StorageKind == StorageKind.OutputPerPatch;
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(1) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
return varType & AggregateType.ElementTypeMask;
}
}
else if (operation.Inst == Instruction.Call)
{
@ -461,45 +243,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return context.CurrentFunction.GetArgumentType(argIndex);
}
return GetOperandVarType(context, operand, isAsgDest);
return OperandInfo.GetVarType(operand);
}
else
{
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
}
}
private static AggregateType GetOperandVarType(CodeGenContext context, AstOperand operand, bool isAsgDest = false)
{
if (operand.Type == OperandType.Attribute)
{
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
{
return builtInAttr.Type;
}
else if (context.Config.Stage == ShaderStage.Vertex && !isAsgDest &&
operand.Value >= AttributeConsts.UserAttributeBase &&
operand.Value < AttributeConsts.UserAttributeEnd)
{
int location = (operand.Value - AttributeConsts.UserAttributeBase) / 16;
AttributeType type = context.Config.GpuAccessor.QueryAttributeType(location);
return type.ToAggregateType();
}
else if (context.Config.Stage == ShaderStage.Fragment && isAsgDest &&
operand.Value >= AttributeConsts.FragmentOutputColorBase &&
operand.Value < AttributeConsts.FragmentOutputColorEnd)
{
int location = (operand.Value - AttributeConsts.FragmentOutputColorBase) / 16;
AttributeType type = context.Config.GpuAccessor.QueryFragmentOutputType(location);
return type.ToAggregateType();
}
}
return OperandInfo.GetVarType(operand);
}
}
}

View file

@ -29,15 +29,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; }
public Instruction InputsArray { get; set; }
public Instruction OutputsArray { get; set; }
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
public Dictionary<TextureMeta, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<TextureMeta, (Instruction, Instruction, Instruction)>();
public Dictionary<TextureMeta, (Instruction, Instruction)> Images { get; } = new Dictionary<TextureMeta, (Instruction, Instruction)>();
public Dictionary<int, Instruction> Inputs { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> Outputs { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> InputsPerPatch { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> OutputsPerPatch { get; } = new Dictionary<int, 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 Instruction CoordTemp { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
@ -163,16 +161,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
mainInterface.AddRange(InputsPerPatch.Values);
mainInterface.AddRange(OutputsPerPatch.Values);
if (InputsArray != null)
{
mainInterface.Add(InputsArray);
}
if (OutputsArray != null)
{
mainInterface.Add(OutputsArray);
}
return mainInterface.ToArray();
}
@ -228,8 +216,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return operand.Type switch
{
IrOperandType.Argument => GetArgument(type, operand),
IrOperandType.Attribute => GetAttribute(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0),
IrOperandType.AttributePerPatch => GetAttributePerPatch(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0),
IrOperandType.Constant => GetConstant(type, operand),
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
IrOperandType.LocalVariable => GetLocal(type, operand),
@ -275,239 +261,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
public Instruction GetAttributeElemPointer(int attr, bool isOutAttr, Instruction index, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrInfo = AttributeInfo.From(Config, attr, isOutAttr);
int attrOffset = attrInfo.BaseValue;
AggregateType type = attrInfo.Type;
Instruction ioVariable, elemIndex;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (isUserAttr &&
((!isOutAttr && Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutAttr && Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))))
{
elemType = AggregateType.FP32;
ioVariable = isOutAttr ? OutputsArray : InputsArray;
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, vecIndex, elemIndex);
}
}
bool isViewportInverse = attr == AttributeConsts.SupportBlockViewInverseX || attr == AttributeConsts.SupportBlockViewInverseY;
if (isViewportInverse)
{
elemType = AggregateType.FP32;
elemIndex = Constant(TypeU32(), (attr - AttributeConsts.SupportBlockViewInverseX) >> 2);
return AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), SupportBuffer, Constant(TypeU32(), 2), elemIndex);
}
elemType = attrInfo.Type & AggregateType.ElementTypeMask;
if (isUserAttr && Config.TransformFeedbackEnabled &&
((isOutAttr && Config.LastInVertexPipeline) ||
(!isOutAttr && Config.Stage == ShaderStage.Fragment)))
{
attrOffset = attr;
type = elemType;
if (isOutAttr)
{
int components = Info.GetTransformFeedbackOutputComponents(attr);
if (components > 1)
{
attrOffset &= ~0xf;
type = components switch
{
2 => AggregateType.Vector2 | AggregateType.FP32,
3 => AggregateType.Vector3 | AggregateType.FP32,
4 => AggregateType.Vector4 | AggregateType.FP32,
_ => AggregateType.FP32
};
attrInfo = new AttributeInfo(attrOffset, (attr - attrOffset) / 4, components, type, false);
}
}
}
ioVariable = isOutAttr ? Outputs[attrOffset] : Inputs[attrOffset];
bool isIndexed = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr));
if ((type & (AggregateType.Array | AggregateType.ElementCountMask)) == 0)
{
if (invocationId != null)
{
return isIndexed
? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index)
: AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId);
}
else
{
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
}
}
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
if (invocationId != null && isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, elemIndex);
}
else if (isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
}
}
public Instruction GetAttributeElemPointer(Instruction attrIndex, bool isOutAttr, Instruction index, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
elemType = AggregateType.FP32;
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, vecIndex, elemIndex);
}
}
public Instruction GetAttribute(AggregateType type, int attr, bool isOutAttr, Instruction index = null)
{
if (!AttributeInfo.Validate(Config, attr, isOutAttr: false))
{
return GetConstant(type, new AstOperand(IrOperandType.Constant, 0));
}
var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType);
var value = Load(GetType(elemType), elemPointer);
if (Config.Stage == ShaderStage.Fragment)
{
if (attr == AttributeConsts.PositionX || attr == AttributeConsts.PositionY)
{
var pointerType = TypePointer(StorageClass.Uniform, TypeFP32());
var fieldIndex = Constant(TypeU32(), 4);
var scaleIndex = Constant(TypeU32(), 0);
var scaleElemPointer = AccessChain(pointerType, SupportBuffer, fieldIndex, scaleIndex);
var scale = Load(TypeFP32(), scaleElemPointer);
value = FDiv(TypeFP32(), value, scale);
}
else if (attr == AttributeConsts.FrontFacing && Config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// Workaround for what appears to be a bug on Intel compiler.
var valueFloat = Select(TypeFP32(), value, Constant(TypeFP32(), 1f), Constant(TypeFP32(), 0f));
var valueAsInt = Bitcast(TypeS32(), valueFloat);
var valueNegated = SNegate(TypeS32(), valueAsInt);
value = SLessThan(TypeBool(), valueNegated, Constant(TypeS32(), 0));
}
}
return BitcastIfNeeded(type, elemType, value);
}
public Instruction GetAttributePerPatchElemPointer(int attr, bool isOutAttr, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrInfo = AttributeInfo.FromPatch(Config, attr, isOutAttr);
int attrOffset = attrInfo.BaseValue;
Instruction ioVariable = isOutAttr ? OutputsPerPatch[attrOffset] : InputsPerPatch[attrOffset];
elemType = attrInfo.Type & AggregateType.ElementTypeMask;
if ((attrInfo.Type & (AggregateType.Array | AggregateType.ElementCountMask)) == 0)
{
return ioVariable;
}
var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
}
public Instruction GetAttributePerPatch(AggregateType type, int attr, bool isOutAttr)
{
if (!AttributeInfo.ValidatePerPatch(Config, attr, isOutAttr: false))
{
return GetConstant(type, new AstOperand(IrOperandType.Constant, 0));
}
var elemPointer = GetAttributePerPatchElemPointer(attr, isOutAttr, out var elemType);
return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
}
public Instruction GetAttribute(AggregateType type, Instruction attr, bool isOutAttr, Instruction index = null)
{
var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType);
return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
}
public Instruction GetConstant(AggregateType type, AstOperand operand)
{
return type switch

View file

@ -1,21 +1,19 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using Spv.Generator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using static Spv.Specification;
using SpvInstruction = Spv.Generator.Instruction;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
static class Declarations
{
// At least 16 attributes are guaranteed by the spec.
public const int MaxAttributes = 16;
private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
public static void DeclareParameters(CodeGenContext context, StructuredFunction function)
@ -59,7 +57,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
StructuredFunction function = functions[funcIndex];
Instruction[] locals = new Instruction[function.InArguments.Length];
SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
for (int i = 0; i < function.InArguments.Length; i++)
{
@ -105,10 +103,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
DeclareSamplers(context, context.Config.GetTextureDescriptors());
DeclareImages(context, context.Config.GetImageDescriptors());
DeclareInputAttributes(context, info, perPatch: false);
DeclareOutputAttributes(context, info, perPatch: false);
DeclareInputAttributes(context, info, perPatch: true);
DeclareOutputAttributes(context, info, perPatch: true);
DeclareInputsAndOutputs(context, info);
}
private static void DeclareLocalMemory(CodeGenContext context, int size)
@ -121,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.SharedMemory = DeclareMemory(context, StorageClass.Workgroup, size);
}
private static Instruction DeclareMemory(CodeGenContext context, StorageClass storage, int size)
private static SpvInstruction DeclareMemory(CodeGenContext context, StorageClass storage, int size)
{
var arrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), size));
var pointerType = context.TypePointer(storage, arrayType);
@ -395,164 +390,104 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
private static void DeclareInputsAndOutputs(CodeGenContext context, StructuredProgramInfo info)
{
bool iaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing);
if (iaIndexing && !perPatch)
foreach (var ioDefinition in info.IoDefinitions)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
var ioVariable = ioDefinition.IoVariable;
if (context.Config.Stage == ShaderStage.Geometry)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
}
var spvType = context.TypePointer(StorageClass.Input, attrType);
var spvVar = context.Variable(spvType, StorageClass.Input);
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.InputsArray = spvVar;
}
var inputs = perPatch ? info.InputsPerPatch : info.Inputs;
foreach (int attr in inputs)
{
if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: false, perPatch))
// Those are actually from constant buffer, rather than being actual inputs or outputs,
// so we must ignore them here as they are declared as part of the support buffer.
// TODO: Delete this after we represent this properly on the IR (as a constant buffer rather than "input").
if (ioVariable == IoVariable.FragmentOutputIsBgra ||
ioVariable == IoVariable.SupportBlockRenderScale ||
ioVariable == IoVariable.SupportBlockViewInverse)
{
continue;
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (iaIndexing && isUserAttr && !perPatch)
{
continue;
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
{
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
}
else
{
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
(_, AggregateType varType) = IoMap.GetSpirvBuiltIn(ioVariable);
AggregateType elemType = varType & AggregateType.ElementTypeMask;
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
if (elemType == AggregateType.S32 || elemType == AggregateType.U32)
{
iq = PixelImap.Constant;
}
}
}
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
}
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
private static void DeclareInputOrOutput(CodeGenContext context, IoDefinition ioDefinition, bool isOutput, bool isPerPatch, PixelImap iq = PixelImap.Unused)
{
bool oaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing);
IoVariable ioVariable = ioDefinition.IoVariable;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
if (oaIndexing && !perPatch)
bool isBuiltIn;
BuiltIn builtIn = default;
AggregateType varType;
if (ioVariable == IoVariable.UserDefined)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
varType = context.Config.GetUserDefinedType(ioDefinition.Location, isOutput);
isBuiltIn = false;
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(ioDefinition.Location);
isBuiltIn = false;
}
else
{
(builtIn, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
isBuiltIn = true;
if (context.Config.Stage == ShaderStage.TessellationControl)
if (varType == AggregateType.Invalid)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
throw new InvalidOperationException($"Unknown variable {ioVariable}.");
}
var spvType = context.TypePointer(StorageClass.Output, attrType);
var spvVar = context.Variable(spvType, StorageClass.Output);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.OutputsArray = spvVar;
}
var outputs = perPatch ? info.OutputsPerPatch : info.Outputs;
bool hasComponent = context.Config.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
foreach (int attr in outputs)
if (hasComponent)
{
if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: true, perPatch))
varType &= AggregateType.ElementTypeMask;
}
else if (ioVariable == IoVariable.UserDefined && context.Config.HasTransformFeedbackOutputs(isOutput))
{
varType &= AggregateType.ElementTypeMask;
varType |= context.Config.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
{
continue;
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (oaIndexing && isUserAttr && !perPatch)
{
continue;
}
DeclareOutputAttribute(context, attr, perPatch);
2 => AggregateType.Vector2,
3 => AggregateType.Vector3,
4 => AggregateType.Vector4,
_ => AggregateType.Invalid
};
}
if (context.Config.Stage == ShaderStage.Vertex)
{
DeclareOutputAttribute(context, AttributeConsts.PositionX, perPatch: false);
}
}
private static void DeclareOutputAttribute(CodeGenContext context, int attr, bool perPatch)
{
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: true);
}
public static void DeclareInvocationId(CodeGenContext context)
{
DeclareInputOrOutput(context, AttributeConsts.LaneId, perPatch: false, isOutAttr: false);
}
private static void DeclareInputOrOutput(CodeGenContext context, int attr, bool perPatch, bool isOutAttr, PixelImap iq = PixelImap.Unused)
{
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (isUserAttr && context.Config.TransformFeedbackEnabled && !perPatch &&
((isOutAttr && context.Config.LastInVertexPipeline) ||
(!isOutAttr && context.Config.Stage == ShaderStage.Fragment)))
{
DeclareTransformFeedbackInputOrOutput(context, attr, isOutAttr, iq);
return;
}
var dict = perPatch
? (isOutAttr ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutAttr ? context.Outputs : context.Inputs);
var attrInfo = perPatch
? AttributeInfo.FromPatch(context.Config, attr, isOutAttr)
: AttributeInfo.From(context.Config, attr, isOutAttr);
if (dict.ContainsKey(attrInfo.BaseValue))
{
return;
}
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrType = context.GetType(attrInfo.Type, attrInfo.Length);
var spvType = context.GetType(varType, IoMap.GetSpirvBuiltInArrayLength(ioVariable));
bool builtInPassthrough = false;
if (AttributeInfo.IsArrayAttributeSpirv(context.Config.Stage, isOutAttr) && !perPatch && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Config.Stage, isOutput))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
@ -560,69 +495,64 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr && !perPatch)
if (context.Config.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
var spvPointerType = context.TypePointer(storageClass, spvType);
var spvVar = context.Variable(spvPointerType, storageClass);
if (builtInPassthrough)
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
if (attrInfo.IsBuiltin)
if (isBuiltIn)
{
if (perPatch)
if (isPerPatch)
{
context.Decorate(spvVar, Decoration.Patch);
}
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && ioVariable == IoVariable.Position)
{
context.Decorate(spvVar, Decoration.Invariant);
}
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(attrInfo.BaseValue);
if (tfOutput.Valid)
{
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)tfOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)tfOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)tfOutput.Offset);
}
}
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)builtIn);
}
else if (perPatch)
else if (isPerPatch)
{
context.Decorate(spvVar, Decoration.Patch);
int location = context.Config.GetPerPatchAttributeLocation((attr - AttributeConsts.UserAttributePerPatchBase) / 16);
if (ioVariable == IoVariable.UserDefined)
{
int location = context.Config.GetPerPatchAttributeLocation(ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
}
else if (isUserAttr)
else if (ioVariable == IoVariable.UserDefined)
{
int location = (attr - AttributeConsts.UserAttributeBase) / 16;
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
if (hasComponent)
{
context.Decorate(spvVar, Decoration.Component, (LiteralInteger)ioDefinition.Component);
}
if (!isOutAttr &&
!perPatch &&
(context.Config.PassthroughAttributes & (1 << location)) != 0 &&
if (!isOutput &&
!isPerPatch &&
(context.Config.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
}
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
else if (ioVariable == IoVariable.FragmentOutputColor)
{
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
int location = ioDefinition.Location;
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
{
@ -646,7 +576,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
if (!isOutAttr)
if (!isOutput)
{
switch (iq)
{
@ -658,143 +588,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
}
context.AddGlobalVariable(spvVar);
dict.Add(attrInfo.BaseValue, spvVar);
}
private static void DeclareTransformFeedbackInputOrOutput(CodeGenContext context, int attr, bool isOutAttr, PixelImap iq = PixelImap.Unused)
{
var dict = isOutAttr ? context.Outputs : context.Inputs;
var attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr);
bool hasComponent = true;
int component = (attr >> 2) & 3;
int components = 1;
var type = attrInfo.Type & AggregateType.ElementTypeMask;
if (isOutAttr)
else if (context.Config.TryGetTransformFeedbackOutput(
ioVariable,
ioDefinition.Location,
ioDefinition.Component,
out var transformFeedbackOutput))
{
components = context.Info.GetTransformFeedbackOutputComponents(attr);
if (components > 1)
{
attr &= ~0xf;
type = components switch
{
2 => AggregateType.Vector2 | AggregateType.FP32,
3 => AggregateType.Vector3 | AggregateType.FP32,
4 => AggregateType.Vector4 | AggregateType.FP32,
_ => AggregateType.FP32
};
hasComponent = false;
}
}
if (dict.ContainsKey(attr))
{
return;
}
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrType = context.GetType(type, components);
if (AttributeInfo.IsArrayAttributeSpirv(context.Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
Debug.Assert(attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd);
int location = (attr - AttributeConsts.UserAttributeBase) / 16;
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
if (hasComponent)
{
context.Decorate(spvVar, Decoration.Component, (LiteralInteger)component);
}
if (isOutAttr)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(attr);
if (tfOutput.Valid)
{
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)tfOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)tfOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)tfOutput.Offset);
}
}
else
{
if ((context.Config.PassthroughAttributes & (1 << location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
switch (iq)
{
case PixelImap.Constant:
context.Decorate(spvVar, Decoration.Flat);
break;
case PixelImap.ScreenLinear:
context.Decorate(spvVar, Decoration.NoPerspective);
break;
}
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)transformFeedbackOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)transformFeedbackOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)transformFeedbackOutput.Offset);
}
context.AddGlobalVariable(spvVar);
dict.Add(attr, spvVar);
}
private static BuiltIn GetBuiltIn(CodeGenContext context, int attr)
{
return attr switch
{
AttributeConsts.TessLevelOuter0 => BuiltIn.TessLevelOuter,
AttributeConsts.TessLevelInner0 => BuiltIn.TessLevelInner,
AttributeConsts.Layer => BuiltIn.Layer,
AttributeConsts.ViewportIndex => BuiltIn.ViewportIndex,
AttributeConsts.PointSize => BuiltIn.PointSize,
AttributeConsts.PositionX => context.Config.Stage == ShaderStage.Fragment ? BuiltIn.FragCoord : BuiltIn.Position,
AttributeConsts.ClipDistance0 => BuiltIn.ClipDistance,
AttributeConsts.PointCoordX => BuiltIn.PointCoord,
AttributeConsts.TessCoordX => BuiltIn.TessCoord,
AttributeConsts.InstanceId => BuiltIn.InstanceId,
AttributeConsts.VertexId => BuiltIn.VertexId,
AttributeConsts.BaseInstance => BuiltIn.BaseInstance,
AttributeConsts.BaseVertex => BuiltIn.BaseVertex,
AttributeConsts.InstanceIndex => BuiltIn.InstanceIndex,
AttributeConsts.VertexIndex => BuiltIn.VertexIndex,
AttributeConsts.DrawIndex => BuiltIn.DrawIndex,
AttributeConsts.FrontFacing => BuiltIn.FrontFacing,
AttributeConsts.FragmentOutputDepth => BuiltIn.FragDepth,
AttributeConsts.ThreadKill => BuiltIn.HelperInvocation,
AttributeConsts.ThreadIdX => BuiltIn.LocalInvocationId,
AttributeConsts.CtaIdX => BuiltIn.WorkgroupId,
AttributeConsts.LaneId => BuiltIn.SubgroupLocalInvocationId,
AttributeConsts.InvocationId => BuiltIn.InvocationId,
AttributeConsts.PrimitiveId => BuiltIn.PrimitiveId,
AttributeConsts.PatchVerticesIn => BuiltIn.PatchVertices,
AttributeConsts.EqMask => BuiltIn.SubgroupEqMask,
AttributeConsts.GeMask => BuiltIn.SubgroupGeMask,
AttributeConsts.GtMask => BuiltIn.SubgroupGtMask,
AttributeConsts.LeMask => BuiltIn.SubgroupLeMask,
AttributeConsts.LtMask => BuiltIn.SubgroupLtMask,
AttributeConsts.SupportBlockViewInverseX => BuiltIn.Position,
AttributeConsts.SupportBlockViewInverseY => BuiltIn.Position,
_ => throw new ArgumentException($"Invalid attribute number 0x{attr:X}.")
};
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
dict.Add(ioDefinition, spvVar);
}
private static string GetStagePrefix(ShaderStage stage)

View file

@ -1,5 +1,4 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System;
using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv

View file

@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ImageLoad, GenerateImageLoad);
Add(Instruction.ImageStore, GenerateImageStore);
Add(Instruction.IsNan, GenerateIsNan);
Add(Instruction.LoadAttribute, GenerateLoadAttribute);
Add(Instruction.Load, GenerateLoad);
Add(Instruction.LoadConstant, GenerateLoadConstant);
Add(Instruction.LoadLocal, GenerateLoadLocal);
Add(Instruction.LoadShared, GenerateLoadShared);
@ -133,7 +133,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ShuffleXor, GenerateShuffleXor);
Add(Instruction.Sine, GenerateSine);
Add(Instruction.SquareRoot, GenerateSquareRoot);
Add(Instruction.StoreAttribute, GenerateStoreAttribute);
Add(Instruction.Store, GenerateStore);
Add(Instruction.StoreLocal, GenerateStoreLocal);
Add(Instruction.StoreShared, GenerateStoreShared);
Add(Instruction.StoreShared16, GenerateStoreShared16);
@ -862,31 +862,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.Bool, result);
}
private static OperationResult GenerateLoadAttribute(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateLoad(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = operation.GetSource(1);
var src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
}
var index = context.Get(AggregateType.S32, src3);
var resultType = AggregateType.FP32;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = (baseAttr.Value & AttributeConsts.Mask) + (operand.Value << 2);
bool isOutAttr = (baseAttr.Value & AttributeConsts.LoadOutputMask) != 0;
return new OperationResult(resultType, context.GetAttribute(resultType, attrOffset, isOutAttr, index));
}
else
{
var attr = context.Get(AggregateType.S32, src2);
return new OperationResult(resultType, context.GetAttribute(resultType, attr, isOutAttr: false, index));
}
return GenerateLoadOrStore(context, operation, isStore: false);
}
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
@ -1224,7 +1202,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@ -1254,7 +1232,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@ -1281,7 +1259,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
@ -1310,7 +1288,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@ -1336,35 +1314,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateUnary(context, operation, context.Delegates.GlslSqrt, null);
}
private static OperationResult GenerateStoreAttribute(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateStore(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = operation.GetSource(1);
var src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.StoreAttribute)} must be a constant operand.");
}
SpvInstruction elemPointer;
AggregateType elemType;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = (baseAttr.Value & AttributeConsts.Mask) + (operand.Value << 2);
elemPointer = context.GetAttributeElemPointer(attrOffset, isOutAttr: true, index: null, out elemType);
}
else
{
var attr = context.Get(AggregateType.S32, src2);
elemPointer = context.GetAttributeElemPointer(attr, isOutAttr: true, index: null, out elemType);
}
var value = context.Get(elemType, src3);
context.Store(elemPointer, value);
return OperationResult.Invalid;
return GenerateLoadOrStore(context, operation, isStore: true);
}
private static OperationResult GenerateStoreLocal(CodeGenContext context, AstOperation operation)
@ -1448,7 +1400,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var three = context.Constant(context.TypeU32(), 3);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var shift = context.BitwiseAnd(context.TypeU32(), threadId, three);
shift = context.ShiftLeftLogical(context.TypeU32(), shift, context.Constant(context.TypeU32(), 1));
var lutIdx = context.ShiftRightLogical(context.TypeU32(), mask, shift);
@ -1982,20 +1934,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var value = context.GetU32(operation.GetSource(2));
SpvInstruction elemPointer;
Instruction mr = operation.Inst & Instruction.MrMask;
if (mr == Instruction.MrStorage)
if (operation.StorageKind == StorageKind.StorageBuffer)
{
elemPointer = GetStorageElemPointer(context, operation);
}
else if (mr == Instruction.MrShared)
else if (operation.StorageKind == StorageKind.SharedMemory)
{
var offset = context.GetU32(operation.GetSource(0));
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
}
else
{
throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
var one = context.Constant(context.TypeU32(), 1);
@ -2010,20 +1961,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var value1 = context.GetU32(operation.GetSource(3));
SpvInstruction elemPointer;
Instruction mr = operation.Inst & Instruction.MrMask;
if (mr == Instruction.MrStorage)
if (operation.StorageKind == StorageKind.StorageBuffer)
{
elemPointer = GetStorageElemPointer(context, operation);
}
else if (mr == Instruction.MrShared)
else if (operation.StorageKind == StorageKind.SharedMemory)
{
var offset = context.GetU32(operation.GetSource(0));
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
}
else
{
throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
var one = context.Constant(context.TypeU32(), 1);
@ -2032,6 +1982,163 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.U32, context.AtomicCompareExchange(context.TypeU32(), elemPointer, one, zero, zero, value1, value0));
}
private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
SpvInstruction pointer;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(srcIndex++) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(srcIndex++) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(location, isOutput);
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(location);
}
else if (ioVariable == IoVariable.FragmentOutputIsBgra)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeU32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 1), elemIndex);
varType = AggregateType.U32;
break;
}
else if (ioVariable == IoVariable.SupportBlockRenderScale)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 4), elemIndex);
varType = AggregateType.FP32;
break;
}
else if (ioVariable == IoVariable.SupportBlockViewInverse)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 2), elemIndex);
varType = AggregateType.FP32;
break;
}
else
{
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
}
varType &= AggregateType.ElementTypeMask;
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
var ioDefinition = new IoDefinition(storageKind, ioVariable, location, component);
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
SpvInstruction baseObj = dict[ioDefinition];
SpvInstruction e0, e1, e2;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
if (isStore)
{
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
return OperationResult.Invalid;
}
else
{
var result = context.Load(context.GetType(varType), pointer);
return new OperationResult(varType, result);
}
}
private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable)
{
(_, var varType) = IoMap.GetSpirvBuiltIn(ioVariable);
varType &= AggregateType.ElementTypeMask;
var ioDefinition = new IoDefinition(StorageKind.Input, ioVariable);
return context.Load(context.GetType(varType), context.Inputs[ioDefinition]);
}
private static void GenerateStoreSharedSmallInt(CodeGenContext context, AstOperation operation, int bitSize)
{
var offset = context.Get(AggregateType.U32, operation.GetSource(0));

View file

@ -0,0 +1,86 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
static class IoMap
{
// At least 16 attributes are guaranteed by the spec.
private const int MaxAttributes = 16;
public static (BuiltIn, AggregateType) GetSpirvBuiltIn(IoVariable ioVariable)
{
return ioVariable switch
{
IoVariable.BaseInstance => (BuiltIn.BaseInstance, AggregateType.S32),
IoVariable.BaseVertex => (BuiltIn.BaseVertex, AggregateType.S32),
IoVariable.ClipDistance => (BuiltIn.ClipDistance, AggregateType.Array | AggregateType.FP32),
IoVariable.CtaId => (BuiltIn.WorkgroupId, AggregateType.Vector3 | AggregateType.U32),
IoVariable.DrawIndex => (BuiltIn.DrawIndex, AggregateType.S32),
IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32),
IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool),
IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32),
IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32),
IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32),
IoVariable.Layer => (BuiltIn.Layer, AggregateType.S32),
IoVariable.PatchVertices => (BuiltIn.PatchVertices, AggregateType.S32),
IoVariable.PointCoord => (BuiltIn.PointCoord, AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => (BuiltIn.PointSize, AggregateType.FP32),
IoVariable.Position => (BuiltIn.Position, AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => (BuiltIn.PrimitiveId, AggregateType.S32),
IoVariable.SubgroupEqMask => (BuiltIn.SubgroupEqMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGeMask => (BuiltIn.SubgroupGeMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGtMask => (BuiltIn.SubgroupGtMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLaneId => (BuiltIn.SubgroupLocalInvocationId, AggregateType.U32),
IoVariable.SubgroupLeMask => (BuiltIn.SubgroupLeMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLtMask => (BuiltIn.SubgroupLtMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.TessellationCoord => (BuiltIn.TessCoord, AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => (BuiltIn.TessLevelInner, AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => (BuiltIn.TessLevelOuter, AggregateType.Array | AggregateType.FP32),
IoVariable.ThreadId => (BuiltIn.LocalInvocationId, AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => (BuiltIn.HelperInvocation, AggregateType.Bool),
IoVariable.VertexId => (BuiltIn.VertexId, AggregateType.S32),
IoVariable.VertexIndex => (BuiltIn.VertexIndex, AggregateType.S32),
IoVariable.ViewportIndex => (BuiltIn.ViewportIndex, AggregateType.S32),
IoVariable.ViewportMask => (BuiltIn.ViewportMaskNV, AggregateType.Array | AggregateType.S32),
_ => (default, AggregateType.Invalid)
};
}
public static int GetSpirvBuiltInArrayLength(IoVariable ioVariable)
{
return ioVariable switch
{
IoVariable.ClipDistance => 8,
IoVariable.TessellationLevelInner => 2,
IoVariable.TessellationLevelOuter => 4,
IoVariable.ViewportMask => 1,
IoVariable.UserDefined => MaxAttributes,
_ => 1
};
}
public static bool IsPerVertex(IoVariable ioVariable, ShaderStage stage, bool isOutput)
{
switch (ioVariable)
{
case IoVariable.Layer:
case IoVariable.ViewportIndex:
case IoVariable.PointSize:
case IoVariable.Position:
case IoVariable.UserDefined:
case IoVariable.ClipDistance:
case IoVariable.PointCoord:
case IoVariable.ViewportMask:
return !isOutput &&
(stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry);
}
return false;
}
}
}

View file

@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var vectorFloat = context.ConvertSToF(vector2Type, vector);
var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scaleNegated);
var fragCoordPointer = context.Inputs[AttributeConsts.PositionX];
var fragCoordPointer = context.Inputs[new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord)];
var fragCoord = context.Load(context.TypeVector(context.TypeFP32(), 4), fragCoordPointer);
var fragCoordXY = context.VectorShuffle(vector2Type, fragCoord, fragCoord, 0, 1);

View file

@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (config.Stage == ShaderStage.Fragment)
{
if (context.Info.Inputs.Contains(AttributeConsts.Layer))
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
{
context.AddCapability(Capability.Geometry);
}
@ -93,13 +93,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.DrawParameters);
}
Declarations.DeclareAll(context, info);
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportMask)))
{
context.AddExtension("SPV_NV_viewport_array2");
context.AddCapability(Capability.ShaderViewportMaskNV);
}
if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
{
Declarations.DeclareInvocationId(context);
info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.SubgroupLaneId));
}
Declarations.DeclareAll(context, info);
for (int funcIndex = 0; funcIndex < info.Functions.Count; funcIndex++)
{
var function = info.Functions[funcIndex];
@ -203,7 +209,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here aswell.
// We invert the front face on Vulkan backend, so we need to do that here as well.
tessCw = !tessCw;
}
@ -250,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
? ExecutionMode.OriginUpperLeft
: ExecutionMode.OriginLowerLeft);
if (context.Outputs.ContainsKey(AttributeConsts.FragmentOutputDepth))
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.FragmentOutputDepth)))
{
context.AddExecutionMode(spvFunc, ExecutionMode.DepthReplacing);
}
@ -389,21 +395,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var source = context.Get(dest.VarType, assignment.Source);
context.Store(context.GetLocalPointer(dest), source);
}
else if (dest.Type == OperandType.Attribute || dest.Type == OperandType.AttributePerPatch)
{
bool perPatch = dest.Type == OperandType.AttributePerPatch;
if (AttributeInfo.Validate(context.Config, dest.Value, isOutAttr: true, perPatch))
{
AggregateType elemType;
var elemPointer = perPatch
? context.GetAttributePerPatchElemPointer(dest.Value, true, out elemType)
: context.GetAttributeElemPointer(dest.Value, true, null, out elemType);
context.Store(elemPointer, context.Get(elemType, assignment.Source));
}
}
else if (dest.Type == OperandType.Argument)
{
var source = context.Get(dest.VarType, assignment.Source);

View file

@ -295,10 +295,12 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetAllOutputUserAttributes();
config.SetUsedFeature(FeatureFlags.OaIndexing);
}
else
{
config.SetAllInputUserAttributes();
config.SetUsedFeature(FeatureFlags.IaIndexing);
}
}
else
@ -340,7 +342,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
if (!isStore &&
((attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr == AttributeConsts.FogCoord ||
(attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
{
config.SetUsedFeature(FeatureFlags.FixedFuncAttr);

View file

@ -305,9 +305,9 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries host support for writes to Layer from vertex or tessellation shader stages.
/// Queries host support for writes to the layer from vertex or tessellation shader stages.
/// </summary>
/// <returns>True if writes to layer from vertex or tessellation are supported, false otherwise</returns>
/// <returns>True if writes to the layer from vertex or tessellation are supported, false otherwise</returns>
bool QueryHostSupportsLayerVertexTessellation()
{
return true;
@ -350,10 +350,19 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries host GPU shader viewport index output support.
/// Queries host support for writes to the viewport index from vertex or tessellation shader stages.
/// </summary>
/// <returns>True if the GPU and driver supports shader viewport index output, false otherwise</returns>
bool QueryHostSupportsViewportIndex()
/// <returns>True if writes to the viewport index from vertex or tessellation are supported, false otherwise</returns>
bool QueryHostSupportsViewportIndexVertexTessellation()
{
return true;
}
/// <summary>
/// Queries host GPU shader viewport mask output support.
/// </summary>
/// <returns>True if the GPU and driver supports shader viewport mask output, false otherwise</returns>
bool QueryHostSupportsViewportMask()
{
return true;
}

View file

@ -0,0 +1,351 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Instructions
{
static class AttributeMap
{
private enum StagesMask : byte
{
None = 0,
Compute = 1 << (int)ShaderStage.Compute,
Vertex = 1 << (int)ShaderStage.Vertex,
TessellationControl = 1 << (int)ShaderStage.TessellationControl,
TessellationEvaluation = 1 << (int)ShaderStage.TessellationEvaluation,
Geometry = 1 << (int)ShaderStage.Geometry,
Fragment = 1 << (int)ShaderStage.Fragment,
Tessellation = TessellationControl | TessellationEvaluation,
VertexTessellationGeometry = Vertex | Tessellation | Geometry,
TessellationGeometryFragment = Tessellation | Geometry | Fragment,
AllGraphics = Vertex | Tessellation | Geometry | Fragment
}
private struct AttributeEntry
{
public int BaseOffset { get; }
public AggregateType Type { get; }
public IoVariable IoVariable { get; }
public StagesMask InputMask { get; }
public StagesMask OutputMask { get; }
public AttributeEntry(
int baseOffset,
AggregateType type,
IoVariable ioVariable,
StagesMask inputMask,
StagesMask outputMask)
{
BaseOffset = baseOffset;
Type = type;
IoVariable = ioVariable;
InputMask = inputMask;
OutputMask = outputMask;
}
}
private static readonly IReadOnlyDictionary<int, AttributeEntry> _attributes;
private static readonly IReadOnlyDictionary<int, AttributeEntry> _attributesPerPatch;
static AttributeMap()
{
_attributes = CreateMap();
_attributesPerPatch = CreatePerPatchMap();
}
private static IReadOnlyDictionary<int, AttributeEntry> CreateMap()
{
var map = new Dictionary<int, AttributeEntry>();
Add(map, 0x060, AggregateType.S32, IoVariable.PrimitiveId, StagesMask.TessellationGeometryFragment, StagesMask.Geometry);
Add(map, 0x064, AggregateType.S32, IoVariable.Layer, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x068, AggregateType.S32, IoVariable.ViewportIndex, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x06c, AggregateType.FP32, IoVariable.PointSize, StagesMask.None, StagesMask.VertexTessellationGeometry);
Add(map, 0x070, AggregateType.Vector4 | AggregateType.FP32, IoVariable.Position, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x080, AggregateType.Vector4 | AggregateType.FP32, IoVariable.UserDefined, StagesMask.AllGraphics, StagesMask.VertexTessellationGeometry, 32);
Add(map, 0x280, AggregateType.Vector4 | AggregateType.FP32, IoVariable.FrontColorDiffuse, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x290, AggregateType.Vector4 | AggregateType.FP32, IoVariable.FrontColorSpecular, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2a0, AggregateType.Vector4 | AggregateType.FP32, IoVariable.BackColorDiffuse, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2b0, AggregateType.Vector4 | AggregateType.FP32, IoVariable.BackColorSpecular, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2c0, AggregateType.Array | AggregateType.FP32, IoVariable.ClipDistance, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry, 8);
Add(map, 0x2e0, AggregateType.Vector2 | AggregateType.FP32, IoVariable.PointCoord, StagesMask.Fragment, StagesMask.None);
Add(map, 0x2e8, AggregateType.FP32, IoVariable.FogCoord, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2f0, AggregateType.Vector2 | AggregateType.FP32, IoVariable.TessellationCoord, StagesMask.TessellationEvaluation, StagesMask.None);
Add(map, 0x2f8, AggregateType.S32, IoVariable.InstanceId, StagesMask.Vertex, StagesMask.None);
Add(map, 0x2fc, AggregateType.S32, IoVariable.VertexId, StagesMask.Vertex, StagesMask.None);
Add(map, 0x300, AggregateType.Vector4 | AggregateType.FP32, IoVariable.TextureCoord, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x3a0, AggregateType.Array | AggregateType.S32, IoVariable.ViewportMask, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x3fc, AggregateType.Bool, IoVariable.FrontFacing, StagesMask.Fragment, StagesMask.None);
return map;
}
private static IReadOnlyDictionary<int, AttributeEntry> CreatePerPatchMap()
{
var map = new Dictionary<int, AttributeEntry>();
Add(map, 0x000, AggregateType.Vector4 | AggregateType.FP32, IoVariable.TessellationLevelOuter, StagesMask.TessellationEvaluation, StagesMask.TessellationControl);
Add(map, 0x010, AggregateType.Vector2 | AggregateType.FP32, IoVariable.TessellationLevelInner, StagesMask.TessellationEvaluation, StagesMask.TessellationControl);
Add(map, 0x018, AggregateType.Vector4 | AggregateType.FP32, IoVariable.UserDefined, StagesMask.TessellationEvaluation, StagesMask.TessellationControl, 31, 0x200);
return map;
}
private static void Add(
Dictionary<int, AttributeEntry> attributes,
int offset,
AggregateType type,
IoVariable ioVariable,
StagesMask inputMask,
StagesMask outputMask,
int count = 1,
int upperBound = 0x400)
{
int baseOffset = offset;
int elementsCount = GetElementCount(type);
for (int index = 0; index < count; index++)
{
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
attributes.Add(offset, new AttributeEntry(baseOffset, type, ioVariable, inputMask, outputMask));
offset += 4;
if (offset >= upperBound)
{
return;
}
}
}
}
public static Operand GenerateAttributeLoad(EmitterContext context, Operand primVertex, int offset, bool isOutput, bool isPerPatch)
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.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)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
return Const(0);
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.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}.");
return Const(0);
}
if (HasInvocationId(context.Config.Stage, isOutput) && !isPerPatch)
{
primVertex = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
int innerOffset = offset - entry.BaseOffset;
int innerIndex = innerOffset / 4;
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);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasElementIndex = elementCount > 1;
if (hasArrayIndex && hasElementIndex)
{
int arrayIndex = innerIndex / elementCount;
int elementIndex = innerIndex - (arrayIndex * elementCount);
return primVertex == null || isArray
? context.Load(storageKind, ioVariable, primVertex, Const(arrayIndex), Const(elementIndex))
: context.Load(storageKind, ioVariable, Const(arrayIndex), primVertex, Const(elementIndex));
}
else if (hasArrayIndex || hasElementIndex)
{
return primVertex == null || isArray || !hasArrayIndex
? context.Load(storageKind, ioVariable, primVertex, Const(innerIndex))
: context.Load(storageKind, ioVariable, Const(innerIndex), primVertex);
}
else
{
return context.Load(storageKind, ioVariable, primVertex);
}
}
public static void GenerateAttributeStore(EmitterContext context, int offset, bool isPerPatch, Operand value)
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return;
}
if (((StagesMask)(1 << (int)context.Config.Stage) & entry.OutputMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
return;
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.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}.");
return;
}
Operand invocationId = null;
if (HasInvocationId(context.Config.Stage, isOutput: true) && !isPerPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
int innerOffset = offset - entry.BaseOffset;
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);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasElementIndex = elementCount > 1;
if (hasArrayIndex && hasElementIndex)
{
int arrayIndex = innerIndex / elementCount;
int elementIndex = innerIndex - (arrayIndex * elementCount);
if (invocationId == null || isArray)
{
context.Store(storageKind, ioVariable, invocationId, Const(arrayIndex), Const(elementIndex), value);
}
else
{
context.Store(storageKind, ioVariable, Const(arrayIndex), invocationId, Const(elementIndex), value);
}
}
else if (hasArrayIndex || hasElementIndex)
{
if (invocationId == null || isArray || !hasArrayIndex)
{
context.Store(storageKind, ioVariable, invocationId, Const(innerIndex), value);
}
else
{
context.Store(storageKind, ioVariable, Const(innerIndex), invocationId, value);
}
}
else
{
context.Store(storageKind, ioVariable, invocationId, value);
}
}
private static bool IsSupportedByHost(IGpuAccessor gpuAccessor, ShaderStage stage, IoVariable ioVariable)
{
if (ioVariable == IoVariable.ViewportIndex && stage != ShaderStage.Geometry && stage != ShaderStage.Fragment)
{
return gpuAccessor.QueryHostSupportsViewportIndexVertexTessellation();
}
else if (ioVariable == IoVariable.ViewportMask)
{
return gpuAccessor.QueryHostSupportsViewportMask();
}
return true;
}
public static IoVariable GetIoVariable(ShaderConfig config, int offset, out int location)
{
location = 0;
if (!_attributes.TryGetValue(offset, out AttributeEntry entry))
{
return IoVariable.Invalid;
}
if (((StagesMask)(1 << (int)config.Stage) & entry.OutputMask) == StagesMask.None)
{
return IoVariable.Invalid;
}
if (config.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
{
location = (offset - entry.BaseOffset) / 16;
}
return GetIoVariable(config.Stage, in entry);
}
private static IoVariable GetIoVariable(ShaderStage stage, in AttributeEntry entry)
{
if (entry.IoVariable == IoVariable.Position && stage == ShaderStage.Fragment)
{
return IoVariable.FragmentCoord;
}
return entry.IoVariable;
}
private static AggregateType GetType(ShaderConfig config, bool isOutput, int innerIndex, in AttributeEntry entry)
{
AggregateType type = entry.Type;
if (entry.IoVariable == IoVariable.UserDefined)
{
type = config.GetUserDefinedType(innerIndex / 4, isOutput);
}
else if (entry.IoVariable == IoVariable.FragmentOutputColor)
{
type = config.GetFragmentOutputColorType(innerIndex / 4);
}
return type;
}
public static bool HasPrimitiveVertex(ShaderStage stage, bool isOutput)
{
if (isOutput)
{
return false;
}
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
public static bool HasInvocationId(ShaderStage stage, bool isOutput)
{
return isOutput && stage == ShaderStage.TessellationControl;
}
private static int GetElementCount(AggregateType type)
{
return (type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1
};
}
}
}

View file

@ -20,7 +20,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstAld op = context.GetOp<InstAld>();
Operand primVertex = context.Copy(GetSrcReg(context, op.SrcB));
// 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;
if (!op.Phys)
{
hasPrimitiveVertex &= HasPrimitiveVertex(op.Imm11);
}
Operand primVertex = hasPrimitiveVertex ? context.Copy(GetSrcReg(context, op.SrcB)) : null;
for (int index = 0; index < (int)op.AlSize + 1; index++)
{
@ -33,12 +42,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Phys)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
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));
context.Copy(Register(rd), context.LoadAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, primVertex));
StorageKind storageKind = op.O ? StorageKind.Output : StorageKind.Input;
context.Config.SetUsedFeature(FeatureFlags.IaIndexing);
context.Copy(Register(rd), context.Load(storageKind, IoVariable.UserDefined, primVertex, vecIndex, elemIndex));
}
else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
{
@ -46,14 +56,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeRead(offset);
if (op.O && CanLoadOutput(offset))
bool isOutput = op.O && CanLoadOutput(offset);
if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value))
{
offset |= AttributeConsts.LoadOutputMask;
context.Copy(Register(rd), value);
}
else
{
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P));
}
Operand src = op.P ? AttributePerPatch(offset) : CreateInputAttribute(context, offset);
context.Copy(Register(rd), src);
}
else
{
@ -61,14 +73,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeRead(offset);
if (op.O && CanLoadOutput(offset))
{
offset |= AttributeConsts.LoadOutputMask;
}
bool isOutput = op.O && CanLoadOutput(offset);
Operand src = Const(offset);
context.Copy(Register(rd), context.LoadAttribute(src, Const(0), primVertex));
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false));
}
}
}
@ -88,12 +95,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Phys)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
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)
? context.Load(StorageKind.Input, IoVariable.InvocationId)
: null;
context.StoreAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, Register(rd));
context.Config.SetUsedFeature(FeatureFlags.OaIndexing);
context.Store(StorageKind.Output, IoVariable.UserDefined, invocationId, vecIndex, elemIndex, Register(rd));
}
else
{
@ -110,9 +119,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeWritten(offset);
Operand dest = op.P ? AttributePerPatch(offset) : Attribute(offset);
context.Copy(dest, Register(rd));
AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd));
}
}
}
@ -129,13 +136,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Idx)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
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));
res = context.LoadAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, Const(0));
res = context.FPMultiply(res, Attribute(AttributeConsts.PositionW));
context.Config.SetUsedFeature(FeatureFlags.IaIndexing);
res = context.Load(StorageKind.Input, IoVariable.UserDefined, null, vecIndex, elemIndex);
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
else
{
@ -147,9 +153,21 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.Config.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
{
res = context.FPMultiply(res, Attribute(AttributeConsts.PositionW));
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
}
else if (op.Imm10 == AttributeConsts.PositionX || op.Imm10 == AttributeConsts.PositionY)
{
// FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.Input, IoVariable.SupportBlockRenderScale, null, Const(0)));
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
// This weird trick makes it behave.
res = context.ICompareLess(context.INegate(context.IConvertS32ToFP32(res)), Const(0));
}
}
if (op.IpaOp == IpaOp.Multiply && !isFixedFunc)
@ -216,17 +234,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (tempXLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(0)), tempXLocal);
}
if (tempYLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(1)), tempYLocal);
}
if (tempZLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(2)), tempZLocal);
}
}
else
@ -241,6 +259,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
private static bool HasPrimitiveVertex(int attr)
{
return attr != AttributeConsts.PrimitiveId &&
attr != AttributeConsts.TessCoordX &&
attr != AttributeConsts.TessCoordY;
}
private static bool CanLoadOutput(int attr)
{
return attr != AttributeConsts.TessCoordX && attr != AttributeConsts.TessCoordY;
@ -252,13 +277,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.
int index = (attr - AttributeConsts.FrontColorDiffuseR) >> 4;
int userAttrIndex = context.Config.GetFreeUserAttribute(isOutput: false, index);
Operand frontAttr = Attribute(AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf));
context.Config.SetInputUserAttributeFixedFunc(userAttrIndex);
selectedAttr = frontAttr;
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, 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));
return true;
}
else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
@ -268,14 +293,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
selectedAttr = Attribute(FixedFuncToUserAttribute(context.Config, attr, AttributeConsts.TexCoordBase, 4, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
return true;
}
selectedAttr = Attribute(attr);
selectedAttr = GenerateIpaLoad(context, attr);
return false;
}
private static Operand GenerateIpaLoad(EmitterContext context, int offset)
{
return AttributeMap.GenerateAttributeLoad(context, null, offset, isOutput: false, isPerPatch: false);
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
@ -286,13 +316,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.Layer, 0, isOutput);
config.SetLayerOutputAttribute(attr);
}
else if (attr == AttributeConsts.FogCoord)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
}
else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr, isOutput);
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 4, isOutput);
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
}
return attr;
@ -301,11 +335,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, int baseAttr, int baseIndex, bool isOutput)
{
int index = (attr - baseAttr) >> 4;
int userAttrIndex = config.GetFreeUserAttribute(isOutput, index);
int userAttrIndex = config.GetFreeUserAttribute(isOutput, baseIndex + index);
if ((uint)userAttrIndex < Constants.MaxAttributes)
{
userAttrIndex += baseIndex;
attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
if (isOutput)
@ -317,25 +350,34 @@ namespace Ryujinx.Graphics.Shader.Instructions
config.SetInputUserAttributeFixedFunc(userAttrIndex);
}
}
else
{
config.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
}
return attr;
}
private static Operand CreateInputAttribute(EmitterContext context, int attr)
private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value)
{
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
{
if (attr == AttributeConsts.InstanceId)
{
return context.ISubtract(Attribute(AttributeConsts.InstanceIndex), Attribute(AttributeConsts.BaseInstance));
value = context.ISubtract(
context.Load(StorageKind.Input, IoVariable.InstanceIndex),
context.Load(StorageKind.Input, IoVariable.BaseInstance));
return true;
}
else if (attr == AttributeConsts.VertexId)
{
return Attribute(AttributeConsts.VertexIndex);
value = context.Load(StorageKind.Input, IoVariable.VertexIndex);
return true;
}
}
return Attribute(attr);
value = null;
return false;
}
}
}

View file

@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand value = GetSrcReg(context, op.SrcB);
Operand res = EmitAtomicOp(context, Instruction.MrGlobal, op.Op, op.Size, addrLow, addrHigh, value);
Operand res = EmitAtomicOp(context, StorageKind.GlobalMemory, op.Op, op.Size, addrLow, addrHigh, value);
context.Copy(GetDest(op.Dest), res);
}
@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
_ => AtomSize.U32
};
Operand res = EmitAtomicOp(context, Instruction.MrShared, op.AtomOp, size, offset, Const(0), value);
Operand res = EmitAtomicOp(context, StorageKind.SharedMemory, op.AtomOp, size, offset, Const(0), value);
context.Copy(GetDest(op.Dest), res);
}
@ -130,7 +130,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, op.Imm20);
EmitAtomicOp(context, Instruction.MrGlobal, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB));
EmitAtomicOp(context, StorageKind.GlobalMemory, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB));
}
public static void Stg(EmitterContext context)
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static Operand EmitAtomicOp(
EmitterContext context,
Instruction mr,
StorageKind storageKind,
AtomOp op,
AtomSize type,
Operand addrLow,
@ -170,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Add:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicAdd(mr, addrLow, addrHigh, value);
res = context.AtomicAdd(storageKind, addrLow, addrHigh, value);
}
else
{
@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.And:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicAnd(mr, addrLow, addrHigh, value);
res = context.AtomicAnd(storageKind, addrLow, addrHigh, value);
}
else
{
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Xor:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicXor(mr, addrLow, addrHigh, value);
res = context.AtomicXor(storageKind, addrLow, addrHigh, value);
}
else
{
@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Or:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicOr(mr, addrLow, addrHigh, value);
res = context.AtomicOr(storageKind, addrLow, addrHigh, value);
}
else
{
@ -210,11 +210,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Max:
if (type == AtomSize.S32)
{
res = context.AtomicMaxS32(mr, addrLow, addrHigh, value);
res = context.AtomicMaxS32(storageKind, addrLow, addrHigh, value);
}
else if (type == AtomSize.U32)
{
res = context.AtomicMaxU32(mr, addrLow, addrHigh, value);
res = context.AtomicMaxU32(storageKind, addrLow, addrHigh, value);
}
else
{
@ -224,11 +224,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Min:
if (type == AtomSize.S32)
{
res = context.AtomicMinS32(mr, addrLow, addrHigh, value);
res = context.AtomicMinS32(storageKind, addrLow, addrHigh, value);
}
else if (type == AtomSize.U32)
{
res = context.AtomicMinU32(mr, addrLow, addrHigh, value);
res = context.AtomicMinU32(storageKind, addrLow, addrHigh, value);
}
else
{

View file

@ -76,11 +76,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (op.SReg)
{
case SReg.LaneId:
src = Attribute(AttributeConsts.LaneId);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
break;
case SReg.InvocationId:
src = Attribute(AttributeConsts.InvocationId);
src = context.Load(StorageKind.Input, IoVariable.InvocationId);
break;
case SReg.YDirection:
@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.ThreadKill:
src = context.Config.Stage == ShaderStage.Fragment ? Attribute(AttributeConsts.ThreadKill) : Const(0);
src = context.Config.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
break;
case SReg.InvocationInfo:
@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.Config.Stage == ShaderStage.TessellationControl ||
context.Config.Stage == ShaderStage.TessellationEvaluation)
{
src = context.ShiftLeft(Attribute(AttributeConsts.PatchVerticesIn), Const(16));
src = context.ShiftLeft(context.Load(StorageKind.Input, IoVariable.PatchVertices), Const(16));
}
else
{
@ -115,9 +115,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.TId:
Operand tidX = Attribute(AttributeConsts.ThreadIdX);
Operand tidY = Attribute(AttributeConsts.ThreadIdY);
Operand tidZ = Attribute(AttributeConsts.ThreadIdZ);
Operand tidX = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(0));
Operand tidY = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(1));
Operand tidZ = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(2));
tidY = context.ShiftLeft(tidY, Const(16));
tidZ = context.ShiftLeft(tidZ, Const(26));
@ -126,39 +126,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.TIdX:
src = Attribute(AttributeConsts.ThreadIdX);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(0));
break;
case SReg.TIdY:
src = Attribute(AttributeConsts.ThreadIdY);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(1));
break;
case SReg.TIdZ:
src = Attribute(AttributeConsts.ThreadIdZ);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(2));
break;
case SReg.CtaIdX:
src = Attribute(AttributeConsts.CtaIdX);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(0));
break;
case SReg.CtaIdY:
src = Attribute(AttributeConsts.CtaIdY);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(1));
break;
case SReg.CtaIdZ:
src = Attribute(AttributeConsts.CtaIdZ);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(2));
break;
case SReg.EqMask:
src = Attribute(AttributeConsts.EqMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupEqMask, null, Const(0));
break;
case SReg.LtMask:
src = Attribute(AttributeConsts.LtMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLtMask, null, Const(0));
break;
case SReg.LeMask:
src = Attribute(AttributeConsts.LeMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLeMask, null, Const(0));
break;
case SReg.GtMask:
src = Attribute(AttributeConsts.GtMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupGtMask, null, Const(0));
break;
case SReg.GeMask:
src = Attribute(AttributeConsts.GeMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupGeMask, null, Const(0));
break;
default:

View file

@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ImageStore,
ImageAtomic,
IsNan,
LoadAttribute,
Load,
LoadConstant,
LoadGlobal,
LoadLocal,
@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ShuffleXor,
Sine,
SquareRoot,
StoreAttribute,
Store,
StoreGlobal,
StoreGlobal16,
StoreGlobal8,
@ -144,13 +144,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
FP32 = 1 << 16,
FP64 = 1 << 17,
MrShift = 18,
MrGlobal = 0 << MrShift,
MrShared = 1 << MrShift,
MrStorage = 2 << MrShift,
MrMask = 3 << MrShift,
Mask = 0xffff
}

View file

@ -0,0 +1,51 @@
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
enum IoVariable
{
Invalid,
BackColorDiffuse,
BackColorSpecular,
BaseInstance,
BaseVertex,
ClipDistance,
CtaId,
DrawIndex,
FogCoord,
FragmentCoord,
FragmentOutputColor,
FragmentOutputDepth,
FragmentOutputIsBgra, // TODO: Remove and use constant buffer access.
FrontColorDiffuse,
FrontColorSpecular,
FrontFacing,
InstanceId,
InstanceIndex,
InvocationId,
Layer,
PatchVertices,
PointCoord,
PointSize,
Position,
PrimitiveId,
SubgroupEqMask,
SubgroupGeMask,
SubgroupGtMask,
SubgroupLaneId,
SubgroupLeMask,
SubgroupLtMask,
SupportBlockViewInverse, // TODO: Remove and use constant buffer access.
SupportBlockRenderScale, // TODO: Remove and use constant buffer access.
TessellationCoord,
TessellationLevelInner,
TessellationLevelOuter,
TextureCoord,
ThreadId,
ThreadKill,
UserDefined,
VertexId,
VertexIndex,
ViewportIndex,
ViewportMask
}
}

View file

@ -10,16 +10,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return new Operand(OperandType.Argument, value);
}
public static Operand Attribute(int value)
{
return new Operand(OperandType.Attribute, value);
}
public static Operand AttributePerPatch(int value)
{
return new Operand(OperandType.AttributePerPatch, value);
}
public static Operand Cbuf(int slot, int offset)
{
return new Operand(slot, offset);

View file

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

View file

@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
class Operation : INode
{
public Instruction Inst { get; private set; }
public StorageKind StorageKind { get; }
private Operand[] _dests;
@ -99,6 +100,23 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
}
}
public Operation(Instruction inst, StorageKind storageKind, Operand dest, params Operand[] sources) : this(sources)
{
Inst = inst;
StorageKind = storageKind;
if (dest != null)
{
dest.AsgOp = this;
_dests = new[] { dest };
}
else
{
_dests = Array.Empty<Operand>();
}
}
public Operation(Instruction inst, int index, Operand dest, params Operand[] sources) : this(inst, dest, sources)
{
Index = index;

View file

@ -0,0 +1,39 @@
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
enum StorageKind
{
None,
Input,
InputPerPatch,
Output,
OutputPerPatch,
ConstantBuffer,
StorageBuffer,
LocalMemory,
SharedMemory,
GlobalMemory
}
static class StorageKindExtensions
{
public static bool IsInputOrOutput(this StorageKind storageKind)
{
return storageKind == StorageKind.Input ||
storageKind == StorageKind.InputPerPatch ||
storageKind == StorageKind.Output ||
storageKind == StorageKind.OutputPerPatch;
}
public static bool IsOutput(this StorageKind storageKind)
{
return storageKind == StorageKind.Output ||
storageKind == StorageKind.OutputPerPatch;
}
public static bool IsPerPatch(this StorageKind storageKind)
{
return storageKind == StorageKind.InputPerPatch ||
storageKind == StorageKind.OutputPerPatch;
}
}
}

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
class AstOperation : AstNode
{
public Instruction Inst { get; }
public StorageKind StorageKind { get; }
public int Index { get; }
@ -16,9 +17,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int SourcesCount => _sources.Length;
public AstOperation(Instruction inst, IAstNode[] sources, int sourcesCount)
public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount)
{
Inst = inst;
Inst = inst;
StorageKind = storageKind;
_sources = sources;
for (int index = 0; index < sources.Length; index++)
@ -36,12 +38,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Index = 0;
}
public AstOperation(Instruction inst, int index, IAstNode[] sources, int sourcesCount) : this(inst, sources, sourcesCount)
public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount)
{
Index = index;
}
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, sources, sources.Length)
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length)
{
}

View file

@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot,
int handle,
int index,
params IAstNode[] sources) : base(inst, index, sources, sources.Length)
params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length)
{
Type = type;
Format = format;

View file

@ -89,7 +89,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.ImageStore, AggregateType.Void);
Add(Instruction.ImageAtomic, AggregateType.S32);
Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
Add(Instruction.LoadAttribute, AggregateType.FP32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.Load, AggregateType.FP32);
Add(Instruction.LoadConstant, AggregateType.FP32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadGlobal, AggregateType.U32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.StoreAttribute, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.FP32);
Add(Instruction.Store, AggregateType.Void);
Add(Instruction.StoreGlobal, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
Add(Instruction.StoreLocal, AggregateType.Void, AggregateType.S32, AggregateType.U32);
Add(Instruction.StoreShared, AggregateType.Void, AggregateType.S32, AggregateType.U32);
@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
return AggregateType.FP32;
}
else if (inst == Instruction.Call)
else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store)
{
return AggregateType.S32;
}

View file

@ -0,0 +1,44 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
readonly struct IoDefinition : IEquatable<IoDefinition>
{
public StorageKind StorageKind { get; }
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public IoDefinition(StorageKind storageKind, IoVariable ioVariable, int location = 0, int component = 0)
{
StorageKind = storageKind;
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is IoDefinition ioDefinition && Equals(ioDefinition);
}
public bool Equals(IoDefinition other)
{
return StorageKind == other.StorageKind &&
IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)StorageKind | ((int)IoVariable << 8) | (Location << 16) | (Component << 24);
}
public override string ToString()
{
return $"{StorageKind}.{IoVariable}.{Location}.{Component}";
}
}
}

View file

@ -23,8 +23,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return type switch
{
OperandType.Argument => AggregateType.S32,
OperandType.Attribute => AggregateType.FP32,
OperandType.AttributePerPatch => AggregateType.FP32,
OperandType.Constant => AggregateType.S32,
OperandType.ConstantBuffer => AggregateType.FP32,
OperandType.Undefined => AggregateType.S32,

View file

@ -65,49 +65,35 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
context.LeaveFunction();
}
if (config.TransformFeedbackEnabled && (config.LastInVertexPipeline || config.Stage == ShaderStage.Fragment))
{
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte location = locations[i];
if (location < 0xc0)
{
context.Info.TransformFeedbackOutputs[location] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
}
}
}
}
return context.Info;
}
private static void AddOperation(StructuredProgramContext context, Operation operation)
{
Instruction inst = operation.Inst;
StorageKind storageKind = operation.StorageKind;
if (inst == Instruction.LoadAttribute)
if ((inst == Instruction.Load || inst == Instruction.Store) && storageKind.IsInputOrOutput())
{
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
bool isOutput = storageKind.IsOutput();
bool perPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (src1.Type == OperandType.Constant && src2.Type == OperandType.Constant)
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
int attrOffset = (src1.Value & AttributeConsts.Mask) + (src2.Value << 2);
location = operation.GetSource(1).Value;
if ((src1.Value & AttributeConsts.LoadOutputMask) != 0)
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
{
context.Info.Outputs.Add(attrOffset);
}
else
{
context.Info.Inputs.Add(attrOffset);
component = operation.GetSource(2).Value;
}
}
context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component));
}
bool vectorDest = IsVectorDestInst(inst);
@ -119,12 +105,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int index = 0; index < operation.SourcesCount; index++)
{
sources[index] = context.GetOperandUse(operation.GetSource(index));
sources[index] = context.GetOperand(operation.GetSource(index));
}
for (int index = 0; index < outDestsCount; index++)
{
AstOperand oper = context.GetOperandDef(operation.GetDest(1 + index));
AstOperand oper = context.GetOperand(operation.GetDest(1 + index));
oper.VarType = InstructionInfo.GetSrcVarType(inst, sourcesCount + index);
@ -163,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
source = new AstOperation(inst, operation.Index, sources, operation.SourcesCount);
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
}
AggregateType destElemType = destType;
@ -181,17 +167,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int i = 0; i < operation.DestsCount; i++)
{
AstOperand dest = context.GetOperandDef(operation.GetDest(i));
AstOperand dest = context.GetOperand(operation.GetDest(i));
AstOperand index = new AstOperand(OperandType.Constant, i);
dest.VarType = destElemType;
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, new[] { destVec, index }, 2)));
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2)));
}
}
else if (operation.Dest != null)
{
AstOperand dest = context.GetOperandDef(operation.Dest);
AstOperand dest = context.GetOperand(operation.Dest);
// If all the sources are bool, it's better to use short-circuiting
// logical operations, rather than forcing a cast to int and doing
@ -234,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else if (!isCopy)
{
source = new AstOperation(inst, operation.Index, sources, operation.SourcesCount);
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
}
else
{
@ -255,7 +241,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
context.AddNode(new AstOperation(inst, operation.Index, sources, operation.SourcesCount));
context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount));
}
// Those instructions needs to be emulated by using helper functions,
@ -263,13 +249,16 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
// decide which helper functions are needed on the final generated code.
switch (operation.Inst)
{
case Instruction.AtomicMaxS32 | Instruction.MrShared:
case Instruction.AtomicMinS32 | Instruction.MrShared:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
break;
case Instruction.AtomicMaxS32 | Instruction.MrStorage:
case Instruction.AtomicMinS32 | Instruction.MrStorage:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
case Instruction.AtomicMaxS32:
case Instruction.AtomicMinS32:
if (operation.StorageKind == StorageKind.SharedMemory)
{
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
}
else if (operation.StorageKind == StorageKind.StorageBuffer)
{
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
}
break;
case Instruction.MultiplyHighS32:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32;

View file

@ -37,43 +37,26 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Config = config;
if (config.Stage == ShaderStage.TessellationControl)
{
// Required to index outputs.
Info.Inputs.Add(AttributeConsts.InvocationId);
}
else if (config.GpPassthrough)
if (config.GpPassthrough)
{
int passthroughAttributes = config.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
int attrBase = AttributeConsts.UserAttributeBase + index * 16;
Info.Inputs.Add(attrBase);
Info.Inputs.Add(attrBase + 4);
Info.Inputs.Add(attrBase + 8);
Info.Inputs.Add(attrBase + 12);
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.UserDefined, index));
passthroughAttributes &= ~(1 << index);
}
Info.Inputs.Add(AttributeConsts.PositionX);
Info.Inputs.Add(AttributeConsts.PositionY);
Info.Inputs.Add(AttributeConsts.PositionZ);
Info.Inputs.Add(AttributeConsts.PositionW);
Info.Inputs.Add(AttributeConsts.PointSize);
for (int i = 0; i < 8; i++)
{
Info.Inputs.Add(AttributeConsts.ClipDistance0 + i * 4);
}
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.Position));
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.Inputs.Add(AttributeConsts.PositionX);
Info.Inputs.Add(AttributeConsts.PositionY);
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord));
}
}
@ -281,7 +264,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
cond = GetOperandUse(branchOp.GetSource(0));
cond = GetOperand(branchOp.GetSource(0));
Instruction invInst = type == AstBlockType.If
? Instruction.BranchIfTrue
@ -315,41 +298,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return newTemp;
}
public AstOperand GetOperandDef(Operand operand)
{
if (operand.Type == OperandType.Attribute)
{
Info.Outputs.Add(operand.Value & AttributeConsts.Mask);
}
else if (operand.Type == OperandType.AttributePerPatch)
{
Info.OutputsPerPatch.Add(operand.Value & AttributeConsts.Mask);
}
return GetOperand(operand);
}
public AstOperand GetOperandUse(Operand operand)
{
// If this flag is set, we're reading from an output attribute instead.
if (operand.Type.IsAttribute() && (operand.Value & AttributeConsts.LoadOutputMask) != 0)
{
return GetOperandDef(operand);
}
if (operand.Type == OperandType.Attribute)
{
Info.Inputs.Add(operand.Value);
}
else if (operand.Type == OperandType.AttributePerPatch)
{
Info.InputsPerPatch.Add(operand.Value);
}
return GetOperand(operand);
}
private AstOperand GetOperand(Operand operand)
public AstOperand GetOperand(Operand operand)
{
if (operand == null)
{

View file

@ -22,60 +22,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public List<StructuredFunction> Functions { get; }
public HashSet<int> Inputs { get; }
public HashSet<int> Outputs { get; }
public HashSet<int> InputsPerPatch { get; }
public HashSet<int> OutputsPerPatch { get; }
public HashSet<IoDefinition> IoDefinitions { get; }
public HelperFunctionsMask HelperFunctionsMask { get; set; }
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
public StructuredProgramInfo()
{
Functions = new List<StructuredFunction>();
Inputs = new HashSet<int>();
Outputs = new HashSet<int>();
InputsPerPatch = new HashSet<int>();
OutputsPerPatch = new HashSet<int>();
TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int attr)
{
int index = attr / 4;
return TransformFeedbackOutputs[index];
}
public int GetTransformFeedbackOutputComponents(int attr)
{
int index = attr / 4;
int baseIndex = index & ~3;
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;
IoDefinitions = new HashSet<IoDefinition>();
}
}
}

View file

@ -2,104 +2,35 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class AttributeConsts
{
public const int TessLevelOuter0 = 0x000;
public const int TessLevelOuter1 = 0x004;
public const int TessLevelOuter2 = 0x008;
public const int TessLevelOuter3 = 0x00c;
public const int TessLevelInner0 = 0x010;
public const int TessLevelInner1 = 0x014;
public const int PrimitiveId = 0x060;
public const int Layer = 0x064;
public const int ViewportIndex = 0x068;
public const int PointSize = 0x06c;
public const int PositionX = 0x070;
public const int PositionY = 0x074;
public const int PositionZ = 0x078;
public const int PositionW = 0x07c;
public const int FrontColorDiffuseR = 0x280;
public const int FrontColorDiffuseG = 0x284;
public const int FrontColorDiffuseB = 0x288;
public const int FrontColorDiffuseA = 0x28c;
public const int FrontColorSpecularR = 0x290;
public const int FrontColorSpecularG = 0x294;
public const int FrontColorSpecularB = 0x298;
public const int FrontColorSpecularA = 0x29c;
public const int BackColorDiffuseR = 0x2a0;
public const int BackColorDiffuseG = 0x2a4;
public const int BackColorDiffuseB = 0x2a8;
public const int BackColorDiffuseA = 0x2ac;
public const int BackColorSpecularR = 0x2b0;
public const int BackColorSpecularG = 0x2b4;
public const int BackColorSpecularB = 0x2b8;
public const int BackColorSpecularA = 0x2bc;
public const int ClipDistance0 = 0x2c0;
public const int ClipDistance1 = 0x2c4;
public const int ClipDistance2 = 0x2c8;
public const int ClipDistance3 = 0x2cc;
public const int ClipDistance4 = 0x2d0;
public const int ClipDistance5 = 0x2d4;
public const int ClipDistance6 = 0x2d8;
public const int ClipDistance7 = 0x2dc;
public const int PointCoordX = 0x2e0;
public const int PointCoordY = 0x2e4;
public const int TessCoordX = 0x2f0;
public const int TessCoordY = 0x2f4;
public const int InstanceId = 0x2f8;
public const int VertexId = 0x2fc;
public const int TexCoordCount = 10;
public const int TexCoordBase = 0x300;
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
public const int FrontFacing = 0x3fc;
public const int PrimitiveId = 0x060;
public const int Layer = 0x064;
public const int PositionX = 0x070;
public const int PositionY = 0x074;
public const int FrontColorDiffuseR = 0x280;
public const int BackColorDiffuseR = 0x2a0;
public const int ClipDistance0 = 0x2c0;
public const int ClipDistance1 = 0x2c4;
public const int ClipDistance2 = 0x2c8;
public const int ClipDistance3 = 0x2cc;
public const int ClipDistance4 = 0x2d0;
public const int ClipDistance5 = 0x2d4;
public const int ClipDistance6 = 0x2d8;
public const int ClipDistance7 = 0x2dc;
public const int FogCoord = 0x2e8;
public const int TessCoordX = 0x2f0;
public const int TessCoordY = 0x2f4;
public const int InstanceId = 0x2f8;
public const int VertexId = 0x2fc;
public const int TexCoordCount = 10;
public const int TexCoordBase = 0x300;
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
public const int FrontFacing = 0x3fc;
public const int UserAttributesCount = 32;
public const int UserAttributeBase = 0x80;
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
public const int UserAttributeBase = 0x80;
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
public const int UserAttributePerPatchBase = 0x18;
public const int UserAttributePerPatchEnd = 0x200;
public const int LoadOutputMask = 1 << 30;
public const int Mask = 0x3fffffff;
// Note: Those attributes are used internally by the translator
// only, they don't exist on Maxwell.
public const int SpecialMask = 0xf << 24;
public const int FragmentOutputDepth = 0x1000000;
public const int FragmentOutputColorBase = 0x1000010;
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
public const int FragmentOutputIsBgraBase = 0x1000100;
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
public const int SupportBlockViewInverseX = 0x1000200;
public const int SupportBlockViewInverseY = 0x1000204;
public const int ThreadIdX = 0x2000000;
public const int ThreadIdY = 0x2000004;
public const int ThreadIdZ = 0x2000008;
public const int CtaIdX = 0x2000010;
public const int CtaIdY = 0x2000014;
public const int CtaIdZ = 0x2000018;
public const int LaneId = 0x2000020;
public const int InvocationId = 0x2000024;
public const int PatchVerticesIn = 0x2000028;
public const int EqMask = 0x2000030;
public const int GeMask = 0x2000034;
public const int GtMask = 0x2000038;
public const int LeMask = 0x200003c;
public const int LtMask = 0x2000040;
public const int ThreadKill = 0x2000044;
public const int BaseInstance = 0x2000050;
public const int BaseVertex = 0x2000054;
public const int InstanceIndex = 0x2000058;
public const int VertexIndex = 0x200005c;
public const int DrawIndex = 0x2000060;
public const int UserAttributePerPatchEnd = 0x200;
}
}

View file

@ -1,210 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
readonly struct AttributeInfo
{
private static readonly Dictionary<int, AttributeInfo> _builtInAttributes = new Dictionary<int, AttributeInfo>()
{
{ AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 0, 1, AggregateType.S32) },
{ AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 0, 1, AggregateType.FP32) },
{ AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 0, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionX, 1, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionX, 2, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionX, 3, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 0, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance0, 1, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance0, 2, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance0, 3, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance0, 4, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance0, 5, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance0, 6, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance0, 7, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 0, 2, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordX, 1, 2, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 0, 3, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordX, 1, 3, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 0, 1, AggregateType.S32) },
{ AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 0, 1, AggregateType.S32) },
{ AttributeConsts.BaseInstance, new AttributeInfo(AttributeConsts.BaseInstance, 0, 1, AggregateType.S32) },
{ AttributeConsts.BaseVertex, new AttributeInfo(AttributeConsts.BaseVertex, 0, 1, AggregateType.S32) },
{ AttributeConsts.InstanceIndex, new AttributeInfo(AttributeConsts.InstanceIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.VertexIndex, new AttributeInfo(AttributeConsts.VertexIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.DrawIndex, new AttributeInfo(AttributeConsts.DrawIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 0, 1, AggregateType.Bool) },
// Special.
{ AttributeConsts.FragmentOutputDepth, new AttributeInfo(AttributeConsts.FragmentOutputDepth, 0, 1, AggregateType.FP32) },
{ AttributeConsts.ThreadKill, new AttributeInfo(AttributeConsts.ThreadKill, 0, 1, AggregateType.Bool) },
{ AttributeConsts.ThreadIdX, new AttributeInfo(AttributeConsts.ThreadIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.ThreadIdY, new AttributeInfo(AttributeConsts.ThreadIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.ThreadIdZ, new AttributeInfo(AttributeConsts.ThreadIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdX, new AttributeInfo(AttributeConsts.CtaIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdY, new AttributeInfo(AttributeConsts.CtaIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdZ, new AttributeInfo(AttributeConsts.CtaIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.LaneId, new AttributeInfo(AttributeConsts.LaneId, 0, 1, AggregateType.U32) },
{ AttributeConsts.InvocationId, new AttributeInfo(AttributeConsts.InvocationId, 0, 1, AggregateType.S32) },
{ AttributeConsts.PrimitiveId, new AttributeInfo(AttributeConsts.PrimitiveId, 0, 1, AggregateType.S32) },
{ AttributeConsts.PatchVerticesIn, new AttributeInfo(AttributeConsts.PatchVerticesIn, 0, 1, AggregateType.S32) },
{ AttributeConsts.EqMask, new AttributeInfo(AttributeConsts.EqMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.GeMask, new AttributeInfo(AttributeConsts.GeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.GtMask, new AttributeInfo(AttributeConsts.GtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.LeMask, new AttributeInfo(AttributeConsts.LeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.LtMask, new AttributeInfo(AttributeConsts.LtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
};
private static readonly Dictionary<int, AttributeInfo> _builtInAttributesPerPatch = new Dictionary<int, AttributeInfo>()
{
{ AttributeConsts.TessLevelOuter0, new AttributeInfo(AttributeConsts.TessLevelOuter0, 0, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter1, new AttributeInfo(AttributeConsts.TessLevelOuter0, 1, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter2, new AttributeInfo(AttributeConsts.TessLevelOuter0, 2, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter3, new AttributeInfo(AttributeConsts.TessLevelOuter0, 3, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelInner0, new AttributeInfo(AttributeConsts.TessLevelInner0, 0, 2, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelInner1, new AttributeInfo(AttributeConsts.TessLevelInner0, 1, 2, AggregateType.Array | AggregateType.FP32) },
};
public int BaseValue { get; }
public int Value { get; }
public int Length { get; }
public AggregateType Type { get; }
public bool IsBuiltin { get; }
public bool IsValid => Type != AggregateType.Invalid;
public AttributeInfo(int baseValue, int index, int length, AggregateType type, bool isBuiltin = true)
{
BaseValue = baseValue;
Value = baseValue + index * 4;
Length = length;
Type = type;
IsBuiltin = isBuiltin;
}
public int GetInnermostIndex()
{
return (Value - BaseValue) / 4;
}
public static bool Validate(ShaderConfig config, int value, bool isOutAttr, bool perPatch)
{
return perPatch ? ValidatePerPatch(config, value, isOutAttr) : Validate(config, value, isOutAttr);
}
public static bool Validate(ShaderConfig config, int value, bool isOutAttr)
{
if (value == AttributeConsts.ViewportIndex && !config.GpuAccessor.QueryHostSupportsViewportIndex())
{
return false;
}
return From(config, value, isOutAttr).IsValid;
}
public static bool ValidatePerPatch(ShaderConfig config, int value, bool isOutAttr)
{
return FromPatch(config, value, isOutAttr).IsValid;
}
public static AttributeInfo From(ShaderConfig config, int value, bool isOutAttr)
{
value &= ~3;
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
{
int location = (value - AttributeConsts.UserAttributeBase) / 16;
AggregateType elemType;
if (config.Stage == ShaderStage.Vertex && !isOutAttr)
{
elemType = config.GpuAccessor.QueryAttributeType(location).ToAggregateType();
}
else
{
elemType = AggregateType.FP32;
}
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
}
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
{
int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
{
AttributeType.Sint => AggregateType.S32,
AttributeType.Uint => AggregateType.U32,
_ => AggregateType.FP32
};
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
}
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
{
return new AttributeInfo(value, 0, 1, AggregateType.FP32);
}
else if (_builtInAttributes.TryGetValue(value, out AttributeInfo info))
{
return info;
}
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
}
public static AttributeInfo FromPatch(ShaderConfig config, int value, bool isOutAttr)
{
value &= ~3;
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
{
int offset = (value - AttributeConsts.UserAttributePerPatchBase) & 0xf;
return new AttributeInfo(value - offset, offset >> 2, 4, AggregateType.Vector4 | AggregateType.FP32, false);
}
else if (_builtInAttributesPerPatch.TryGetValue(value, out AttributeInfo info))
{
return info;
}
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
}
public static bool IsArrayBuiltIn(int attr)
{
if (attr <= AttributeConsts.TessLevelInner1 ||
attr == AttributeConsts.TessCoordX ||
attr == AttributeConsts.TessCoordY)
{
return false;
}
return (attr & AttributeConsts.SpecialMask) == 0;
}
public static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
public static bool IsArrayAttributeSpirv(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return false;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
}
}

View file

@ -67,7 +67,7 @@ namespace Ryujinx.Graphics.Shader.Translation
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
{
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
this.Copy(Attribute(AttributeConsts.PointSize), ConstF(Config.GpuAccessor.QueryPointSize()));
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(Config.GpuAccessor.QueryPointSize()));
}
}
@ -87,6 +87,15 @@ namespace Ryujinx.Graphics.Shader.Translation
return dest;
}
public Operand Add(Instruction inst, StorageKind storageKind, Operand dest = null, params Operand[] sources)
{
Operation operation = new Operation(inst, storageKind, dest, sources);
_operations.Add(operation);
return dest;
}
public (Operand, Operand) Add(Instruction inst, (Operand, Operand) dest, params Operand[] sources)
{
Operand[] dests = new[] { dest.Item1, dest.Item2 };
@ -223,30 +232,35 @@ namespace Ryujinx.Graphics.Shader.Translation
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
Operand y = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1));
Operand xScale = this.Load(StorageKind.Input, IoVariable.SupportBlockViewInverse, null, Const(0));
Operand yScale = this.Load(StorageKind.Input, IoVariable.SupportBlockViewInverse, null, Const(1));
Operand negativeOne = ConstF(-1.0f);
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
{
Operand z = Attribute(AttributeConsts.PositionZ | AttributeConsts.LoadOutputMask);
Operand w = Attribute(AttributeConsts.PositionW | AttributeConsts.LoadOutputMask);
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
Operand halfW = this.FPMultiply(w, ConstF(0.5f));
this.Copy(Attribute(AttributeConsts.PositionZ), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
}
if (Config.Stage != ShaderStage.Geometry && Config.HasLayerInputAttribute)
{
Config.SetUsedFeature(FeatureFlags.RtLayer);
this.Copy(Attribute(AttributeConsts.Layer), Attribute(Config.GpLayerInputAttribute | AttributeConsts.LoadOutputMask));
int attrVecIndex = Config.GpLayerInputAttribute >> 2;
int attrComponentIndex = Config.GpLayerInputAttribute & 3;
Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex));
this.Store(StorageKind.Output, IoVariable.Layer, null, layer);
}
}
@ -255,9 +269,9 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
oldXLocal = Local();
this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
this.Copy(oldXLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(0)));
oldYLocal = Local();
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
this.Copy(oldYLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(1)));
}
else
{
@ -268,7 +282,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
{
oldZLocal = Local();
this.Copy(oldZLocal, Attribute(AttributeConsts.PositionZ | AttributeConsts.LoadOutputMask));
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
}
else
{
@ -293,17 +307,30 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (Config.Stage == ShaderStage.Geometry)
{
void WriteOutput(int index, int primIndex)
void WritePositionOutput(int primIndex)
{
Operand x = this.LoadAttribute(Const(index), Const(0), Const(primIndex));
Operand y = this.LoadAttribute(Const(index + 4), Const(0), Const(primIndex));
Operand z = this.LoadAttribute(Const(index + 8), Const(0), Const(primIndex));
Operand w = this.LoadAttribute(Const(index + 12), Const(0), Const(primIndex));
Operand x = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(0));
Operand y = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(1));
Operand z = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(2));
Operand w = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(3));
this.Copy(Attribute(index), x);
this.Copy(Attribute(index + 4), y);
this.Copy(Attribute(index + 8), z);
this.Copy(Attribute(index + 12), w);
this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), x);
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), y);
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), z);
this.Store(StorageKind.Output, IoVariable.Position, null, Const(3), w);
}
void WriteUserDefinedOutput(int index, int primIndex)
{
Operand x = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(primIndex), Const(index), Const(0));
Operand y = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(primIndex), Const(index), Const(1));
Operand z = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(primIndex), Const(index), Const(2));
Operand w = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(primIndex), Const(index), Const(3));
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(0), x);
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(1), y);
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(2), z);
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(3), w);
}
if (Config.GpPassthrough && !Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
@ -312,13 +339,13 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int primIndex = 0; primIndex < inputVertices; primIndex++)
{
WriteOutput(AttributeConsts.PositionX, primIndex);
WritePositionOutput(primIndex);
int passthroughAttributes = Config.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
WriteOutput(AttributeConsts.UserAttributeBase + index * 16, primIndex);
WriteUserDefinedOutput(index, primIndex);
Config.SetOutputUserAttribute(index);
passthroughAttributes &= ~(1 << index);
}
@ -337,11 +364,9 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Config.OmapDepth)
{
Operand dest = Attribute(AttributeConsts.FragmentOutputDepth);
Operand src = Register(Config.GetDepthRegister(), RegisterType.Gpr);
this.Copy(dest, src);
this.Store(StorageKind.Output, IoVariable.FragmentOutputDepth, null, src);
}
AlphaTestOp alphaTestOp = Config.GpuAccessor.QueryAlphaTestCompare();
@ -390,32 +415,30 @@ namespace Ryujinx.Graphics.Shader.Translation
continue;
}
int fragmentOutputColorAttr = AttributeConsts.FragmentOutputColorBase + rtIndex * 16;
Operand src = Register(regIndexBase + component, RegisterType.Gpr);
// Perform B <-> R swap if needed, for BGRA formats (not supported on OpenGL).
if (!supportsBgra && (component == 0 || component == 2))
{
Operand isBgra = Attribute(AttributeConsts.FragmentOutputIsBgraBase + rtIndex * 4);
Operand isBgra = this.Load(StorageKind.Input, IoVariable.FragmentOutputIsBgra, null, Const(rtIndex));
Operand lblIsBgra = Label();
Operand lblEnd = Label();
this.BranchIfTrue(lblIsBgra, isBgra);
this.Copy(Attribute(fragmentOutputColorAttr + component * 4), src);
this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(component), src);
this.Branch(lblEnd);
MarkLabel(lblIsBgra);
this.Copy(Attribute(fragmentOutputColorAttr + (2 - component) * 4), src);
this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(2 - component), src);
MarkLabel(lblEnd);
}
else
{
this.Copy(Attribute(fragmentOutputColorAttr + component * 4), src);
this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(component), src);
}
}
@ -441,8 +464,11 @@ namespace Ryujinx.Graphics.Shader.Translation
// 11 01 01 01 01 00 00 00
Operand ditherMask = Const(unchecked((int)0xfbb99110u));
Operand x = this.BitwiseAnd(this.FP32ConvertToU32(Attribute(AttributeConsts.PositionX)), Const(1));
Operand y = this.BitwiseAnd(this.FP32ConvertToU32(Attribute(AttributeConsts.PositionY)), Const(1));
Operand fragCoordX = this.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0));
Operand fragCoordY = this.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1));
Operand x = this.BitwiseAnd(this.FP32ConvertToU32(fragCoordX), Const(1));
Operand y = this.BitwiseAnd(this.FP32ConvertToU32(fragCoordY), Const(1));
Operand xy = this.BitwiseOr(x, this.ShiftLeft(y, Const(1)));
Operand alpha = Register(3, RegisterType.Gpr);

View file

@ -7,54 +7,54 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class EmitterContextInsts
{
public static Operand AtomicAdd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicAdd(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicAdd | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicAdd, storageKind, Local(), a, b, c);
}
public static Operand AtomicAnd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicAnd(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicAnd | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicAnd, storageKind, Local(), a, b, c);
}
public static Operand AtomicCompareAndSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c, Operand d)
public static Operand AtomicCompareAndSwap(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c, Operand d)
{
return context.Add(Instruction.AtomicCompareAndSwap | mr, Local(), a, b, c, d);
return context.Add(Instruction.AtomicCompareAndSwap, storageKind, Local(), a, b, c, d);
}
public static Operand AtomicMaxS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicMaxS32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicMaxS32 | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicMaxS32, storageKind, Local(), a, b, c);
}
public static Operand AtomicMaxU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicMaxU32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicMaxU32 | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicMaxU32, storageKind, Local(), a, b, c);
}
public static Operand AtomicMinS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicMinS32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicMinS32 | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicMinS32, storageKind, Local(), a, b, c);
}
public static Operand AtomicMinU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicMinU32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicMinU32 | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicMinU32, storageKind, Local(), a, b, c);
}
public static Operand AtomicOr(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicOr(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicOr | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicOr, storageKind, Local(), a, b, c);
}
public static Operand AtomicSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicSwap(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicSwap | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicSwap, storageKind, Local(), a, b, c);
}
public static Operand AtomicXor(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c)
public static Operand AtomicXor(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c)
{
return context.Add(Instruction.AtomicXor | mr, Local(), a, b, c);
return context.Add(Instruction.AtomicXor, storageKind, Local(), a, b, c);
}
public static Operand Ballot(this EmitterContext context, Operand a)
@ -549,9 +549,36 @@ namespace Ryujinx.Graphics.Shader.Translation
return context.Add(fpType | Instruction.IsNan, Local(), a);
}
public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b, Operand c)
public static Operand Load(this EmitterContext context, StorageKind storageKind, IoVariable ioVariable, Operand primVertex = null)
{
return context.Add(Instruction.LoadAttribute, Local(), a, b, c);
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable));
}
public static Operand Load(
this EmitterContext context,
StorageKind storageKind,
IoVariable ioVariable,
Operand primVertex,
Operand elemIndex)
{
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex, elemIndex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), elemIndex);
}
public static Operand Load(
this EmitterContext context,
StorageKind storageKind,
IoVariable ioVariable,
Operand primVertex,
Operand arrayIndex,
Operand elemIndex)
{
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex, arrayIndex, elemIndex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), arrayIndex, elemIndex);
}
public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b)
@ -662,9 +689,43 @@ namespace Ryujinx.Graphics.Shader.Translation
return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c);
}
public static Operand StoreAttribute(this EmitterContext context, Operand a, Operand b, Operand c)
public static Operand Store(
this EmitterContext context,
StorageKind storageKind,
IoVariable ioVariable,
Operand invocationId,
Operand value)
{
return context.Add(Instruction.StoreAttribute, null, a, b, c);
return invocationId != null
? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, value)
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), value);
}
public static Operand Store(
this EmitterContext context,
StorageKind storageKind,
IoVariable ioVariable,
Operand invocationId,
Operand elemIndex,
Operand value)
{
return invocationId != null
? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, elemIndex, value)
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), elemIndex, value);
}
public static Operand Store(
this EmitterContext context,
StorageKind storageKind,
IoVariable ioVariable,
Operand invocationId,
Operand arrayIndex,
Operand elemIndex,
Operand value)
{
return invocationId != null
? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, arrayIndex, elemIndex, value)
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
}
public static Operand StoreGlobal(this EmitterContext context, Operand a, Operand b, Operand c)

View file

@ -16,20 +16,15 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int UbeDescsSize = StorageDescSize * UbeMaxCount;
public const int UbeFirstCbuf = 8;
public static bool UsesGlobalMemory(Instruction inst)
public static bool UsesGlobalMemory(Instruction inst, StorageKind storageKind)
{
return (inst.IsAtomic() && IsGlobalMr(inst)) ||
return (inst.IsAtomic() && storageKind == StorageKind.GlobalMemory) ||
inst == Instruction.LoadGlobal ||
inst == Instruction.StoreGlobal ||
inst == Instruction.StoreGlobal16 ||
inst == Instruction.StoreGlobal8;
}
private static bool IsGlobalMr(Instruction inst)
{
return (inst & Instruction.MrMask) == Instruction.MrGlobal;
}
public static int GetStorageCbOffset(ShaderStage stage, int slot)
{
return GetStorageBaseCbOffset(stage) + slot * StorageDescSize;

View file

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
if (UsesGlobalMemory(operation.Inst))
if (UsesGlobalMemory(operation.Inst, operation.StorageKind))
{
Operand source = operation.GetSource(0);
@ -104,9 +104,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (isAtomic)
{
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
storageOp = new Operation(inst, operation.Dest, sources);
storageOp = new Operation(operation.Inst, StorageKind.StorageBuffer, operation.Dest, sources);
}
else if (operation.Inst == Instruction.LoadGlobal)
{

View file

@ -170,10 +170,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return false;
}
return x.Type == OperandType.Attribute ||
x.Type == OperandType.AttributePerPatch ||
x.Type == OperandType.Constant ||
x.Type == OperandType.ConstantBuffer;
// TODO: Handle Load operations with the same storage and the same constant parameters.
return x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
}
private static bool PropagatePack(Operation packOp)

View file

@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
if (hasConstantBufferDrawParameters)
{
if (ReplaceConstantBufferWithDrawParameters(operation))
if (ReplaceConstantBufferWithDrawParameters(node, operation))
{
config.SetUsedFeature(FeatureFlags.DrawParameters);
}
@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Shader.Translation
nextNode = node.Next;
}
else if (UsesGlobalMemory(operation.Inst))
else if (UsesGlobalMemory(operation.Inst, operation.StorageKind))
{
nextNode = RewriteGlobalAccess(node, config)?.Next ?? nextNode;
}
@ -169,9 +169,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (isAtomic)
{
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
storageOp = new Operation(inst, operation.Dest, sources);
storageOp = new Operation(operation.Inst, StorageKind.StorageBuffer, operation.Dest, sources);
}
else if (operation.Inst == Instruction.LoadGlobal)
{
@ -708,8 +706,15 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static bool ReplaceConstantBufferWithDrawParameters(Operation operation)
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++)
@ -721,15 +726,15 @@ namespace Ryujinx.Graphics.Shader.Translation
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
operation.SetSource(srcIndex, Attribute(AttributeConsts.BaseVertex));
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex));
modified = true;
break;
case Constants.NvnBaseInstanceByteOffset / 4:
operation.SetSource(srcIndex, Attribute(AttributeConsts.BaseInstance));
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance));
modified = true;
break;
case Constants.NvnDrawIndexByteOffset / 4:
operation.SetSource(srcIndex, Attribute(AttributeConsts.DrawIndex));
operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex));
modified = true;
break;
}

View file

@ -41,6 +41,46 @@ namespace Ryujinx.Graphics.Shader.Translation
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; }
@ -102,6 +142,8 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor = gpuAccessor;
Options = options;
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
AccessibleStorageBuffersMask = (1 << GlobalMemory.StorageMaxCount) - 1;
AccessibleConstantBuffersMask = (1 << GlobalMemory.UbeMaxCount) - 1;
@ -147,6 +189,173 @@ namespace Ryujinx.Graphics.Shader.Translation
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
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.
@ -184,7 +393,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return format;
}
private bool FormatSupportsAtomic(TextureFormat format)
private static bool FormatSupportsAtomic(TextureFormat format)
{
return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
}

View file

@ -53,40 +53,80 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
if (operation.Inst == Instruction.StoreAttribute)
if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output)
{
return false;
}
Operand src = operation.GetSource(operation.SourcesCount - 1);
Operation srcAttributeAsgOp = null;
if (operation.Inst == Instruction.Copy && operation.Dest.Type == OperandType.Attribute)
{
Operand src = operation.GetSource(0);
if (src.Type == OperandType.LocalVariable && src.AsgOp is Operation asgOp && asgOp.Inst == Instruction.LoadAttribute)
if (src.Type == OperandType.LocalVariable &&
src.AsgOp is Operation asgOp &&
asgOp.Inst == Instruction.Load &&
asgOp.StorageKind.IsInputOrOutput())
{
src = Attribute(asgOp.GetSource(0).Value);
if (asgOp.StorageKind != StorageKind.Input)
{
return false;
}
srcAttributeAsgOp = asgOp;
}
if (src.Type == OperandType.Attribute)
if (srcAttributeAsgOp != null)
{
if (operation.Dest.Value == AttributeConsts.Layer)
IoVariable dstAttribute = (IoVariable)operation.GetSource(0).Value;
IoVariable srcAttribute = (IoVariable)srcAttributeAsgOp.GetSource(0).Value;
if (dstAttribute == IoVariable.Layer && srcAttribute == IoVariable.UserDefined)
{
if ((src.Value & AttributeConsts.LoadOutputMask) != 0)
if (srcAttributeAsgOp.SourcesCount != 4)
{
return false;
}
writesLayer = true;
layerInputAttr = src.Value;
layerInputAttr = srcAttributeAsgOp.GetSource(1).Value * 4 + srcAttributeAsgOp.GetSource(3).Value;;
}
else if (src.Value != operation.Dest.Value)
else
{
return false;
if (dstAttribute != srcAttribute)
{
return false;
}
int inputsCount = operation.SourcesCount - 2;
if (dstAttribute == IoVariable.UserDefined)
{
if (operation.GetSource(1).Value != srcAttributeAsgOp.GetSource(1).Value)
{
return false;
}
inputsCount--;
}
for (int i = 0; i < inputsCount; i++)
{
int dstIndex = operation.SourcesCount - 2 - i;
int srcIndex = srcAttributeAsgOp.SourcesCount - 1 - i;
if ((dstIndex | srcIndex) < 0)
{
return false;
}
if (operation.GetSource(dstIndex).Type != OperandType.Constant ||
srcAttributeAsgOp.GetSource(srcIndex).Type != OperandType.Constant ||
operation.GetSource(dstIndex).Value != srcAttributeAsgOp.GetSource(srcIndex).Value)
{
return false;
}
}
}
}
else if (src.Type == OperandType.Constant)
{
int dstComponent = (operation.Dest.Value >> 2) & 3;
int dstComponent = operation.GetSource(operation.SourcesCount - 2).Value;
float expectedValue = dstComponent == 3 ? 1f : 0f;
if (src.AsFloat() != expectedValue)

View file

@ -177,7 +177,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (config.Stage == ShaderStage.Vertex)
{
InitializeOutput(context, AttributeConsts.PositionX, perPatch: false);
InitializePositionOutput(context);
}
UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
@ -194,20 +194,23 @@ namespace Ryujinx.Graphics.Shader.Translation
continue;
}
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: false);
InitializeOutputComponent(context, vecIndex, index & 3, perPatch: false);
}
if (context.Config.NextUsedInputAttributesPerPatch != null)
{
foreach (int vecIndex in context.Config.NextUsedInputAttributesPerPatch.Order())
{
InitializeOutput(context, AttributeConsts.UserAttributePerPatchBase + vecIndex * 16, perPatch: true);
InitializeOutput(context, vecIndex, perPatch: true);
}
}
if (config.NextUsesFixedFuncAttributes)
{
for (int i = 0; i < 4 + AttributeConsts.TexCoordCount; i++)
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
for (int i = fixedStartAttr; i < fixedStartAttr + 5 + AttributeConsts.TexCoordCount; i++)
{
int index = config.GetFreeUserAttribute(isOutput: true, i);
if (index < 0)
@ -215,26 +218,58 @@ namespace Ryujinx.Graphics.Shader.Translation
break;
}
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false);
InitializeOutput(context, index, perPatch: false);
config.SetOutputUserAttributeFixedFunc(index);
}
}
}
private static void InitializeOutput(EmitterContext context, int baseAttr, bool perPatch)
private static void InitializePositionOutput(EmitterContext context)
{
for (int c = 0; c < 4; c++)
{
int attrOffset = baseAttr + c * 4;
InitializeOutputComponent(context, attrOffset, perPatch);
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f));
}
}
private static void InitializeOutputComponent(EmitterContext context, int attrOffset, bool perPatch)
private static void InitializeOutput(EmitterContext context, int location, bool perPatch)
{
int c = (attrOffset >> 2) & 3;
context.Copy(perPatch ? AttributePerPatch(attrOffset) : Attribute(attrOffset), ConstF(c == 3 ? 1f : 0f));
for (int c = 0; c < 4; c++)
{
InitializeOutputComponent(context, location, c, perPatch);
}
}
private static void InitializeOutputComponent(EmitterContext context, int location, int c, bool perPatch)
{
StorageKind storageKind = perPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
{
Operand invocationId = null;
if (context.Config.Stage == ShaderStage.TessellationControl && !perPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
int index = location * 4 + c;
context.Store(storageKind, IoVariable.UserDefined, invocationId, Const(index), ConstF(c == 3 ? 1f : 0f));
}
else
{
if (context.Config.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));
}
else
{
context.Store(storageKind, IoVariable.UserDefined, null, Const(location), Const(c), ConstF(c == 3 ? 1f : 0f));
}
}
}
private static void EmitOps(EmitterContext context, Block block)

View file

@ -34,15 +34,16 @@ namespace Ryujinx.Graphics.Shader.Translation
_config = config;
}
private static bool IsUserAttribute(Operand operand)
private static bool IsLoadUserDefined(Operation operation)
{
if (operand != null && operand.Type.IsAttribute())
{
int value = operand.Value & AttributeConsts.Mask;
return value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd;
}
// TODO: Check if sources count match and all sources are constant.
return operation.Inst == Instruction.Load && (IoVariable)operation.GetSource(0).Value == IoVariable.UserDefined;
}
return false;
private static bool IsStoreUserDefined(Operation operation)
{
// TODO: Check if sources count match and all sources are constant.
return operation.Inst == Instruction.Store && (IoVariable)operation.GetSource(0).Value == IoVariable.UserDefined;
}
private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b, int aStart)
@ -68,9 +69,9 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operation operation = a[0].Code[index];
if (IsUserAttribute(operation.Dest))
if (IsStoreUserDefined(operation))
{
int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
int tIndex = operation.GetSource(1).Value * 4 + operation.GetSource(2).Value;
Operand temp = temps[tIndex];
@ -82,6 +83,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
operation.Dest = temp;
operation.TurnIntoCopy(operation.GetSource(operation.SourcesCount - 1));
}
if (operation.Inst == Instruction.Return)
@ -100,18 +102,15 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operation operation = b[0].Code[index];
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
if (IsLoadUserDefined(operation))
{
Operand src = operation.GetSource(srcIndex);
int tIndex = operation.GetSource(1).Value * 4 + operation.GetSource(2).Value;
if (IsUserAttribute(src))
Operand temp = temps[tIndex];
if (temp != null)
{
Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
if (temp != null)
{
operation.SetSource(srcIndex, temp);
}
operation.TurnIntoCopy(temp);
}
}
@ -209,15 +208,15 @@ namespace Ryujinx.Graphics.Shader.Translation
{
int attr = AttributeConsts.UserAttributeBase + attrIndex * 16 + c * 4;
Operand value = context.LoadAttribute(Const(attr), Const(0), Const(v));
Operand value = context.Load(StorageKind.Input, IoVariable.UserDefined, Const(v), Const(attrIndex), Const(c));
if (attr == layerOutputAttr)
{
context.Copy(Attribute(AttributeConsts.Layer), value);
context.Store(StorageKind.Output, IoVariable.Layer, null, value);
}
else
{
context.Copy(Attribute(attr), value);
context.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(attrIndex), Const(c), value);
config.SetOutputUserAttribute(attrIndex);
}
@ -227,11 +226,9 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int c = 0; c < 4; c++)
{
int attr = AttributeConsts.PositionX + c * 4;
Operand value = context.Load(StorageKind.Input, IoVariable.Position, Const(v), Const(c));
Operand value = context.LoadAttribute(Const(attr), Const(0), Const(v));
context.Copy(Attribute(attr), value);
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), value);
}
context.EmitVertex();

View file

@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsPreciseOcclusionQueries;
public readonly bool SupportsPipelineStatisticsQuery;
public readonly bool SupportsGeometryShader;
public readonly bool SupportsViewportArray2;
public readonly uint MinSubgroupSize;
public readonly uint MaxSubgroupSize;
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsPreciseOcclusionQueries,
bool supportsPipelineStatisticsQuery,
bool supportsGeometryShader,
bool supportsViewportArray2,
uint minSubgroupSize,
uint maxSubgroupSize,
ShaderStageFlags requiredSubgroupSizeStages,
@ -105,6 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries;
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
SupportsGeometryShader = supportsGeometryShader;
SupportsViewportArray2 = supportsViewportArray2;
MinSubgroupSize = minSubgroupSize;
MaxSubgroupSize = maxSubgroupSize;
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;

View file

@ -76,10 +76,6 @@ namespace Ryujinx.Graphics.Vulkan
private unsafe static byte[] GlslToSpirv(string glsl, ShaderStage stage)
{
// TODO: We should generate the correct code on the shader translator instead of doing this compensation.
glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)");
glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)");
Options options;
lock (_shaderOptionsLock)

View file

@ -39,7 +39,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_shader_subgroup_ballot",
"VK_EXT_subgroup_size_control",
"VK_NV_geometry_shader_passthrough",
"VK_KHR_portability_subset", // By spec, we should enable this if present.
"VK_NV_viewport_array2",
"VK_KHR_portability_subset" // As per spec, we should enable this if present.
};
private static readonly string[] _requiredExtensions = new string[]

View file

@ -306,6 +306,7 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.OcclusionQueryPrecise,
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
propertiesSubgroupSizeControl.MinSubgroupSize,
propertiesSubgroupSizeControl.MaxSubgroupSize,
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
@ -568,7 +569,8 @@ namespace Ryujinx.Graphics.Vulkan
supportsNonConstantTextureOffset: false,
supportsShaderBallot: false,
supportsTextureShadowLod: false,
supportsViewportIndex: featuresVk12.ShaderOutputViewportIndex,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
supportsViewportMask: Capabilities.SupportsViewportArray2,
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,

View file

@ -86,8 +86,8 @@ namespace Ryujinx.ShaderTools
static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options => HandleArguments(options))
.WithNotParsed(errors => errors.Output());
.WithParsed(options => HandleArguments(options))
.WithNotParsed(errors => errors.Output());
}
}
}