diff options
Diffstat (limited to 'Ryujinx.Graphics.Shader')
179 files changed, 12677 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/Ryujinx.Graphics.Shader/BufferDescriptor.cs new file mode 100644 index 00000000..4cc99998 --- /dev/null +++ b/Ryujinx.Graphics.Shader/BufferDescriptor.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct BufferDescriptor + { + public string Name { get; } + + public int Slot { get; } + + public BufferDescriptor(string name, int slot) + { + Name = name; + Slot = slot; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs new file mode 100644 index 00000000..322bfbf5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class CodeGenContext + { + private const string Tab = " "; + + public ShaderConfig Config { get; } + + public List<BufferDescriptor> CBufferDescriptors { get; } + public List<BufferDescriptor> SBufferDescriptors { get; } + public List<TextureDescriptor> TextureDescriptors { get; } + + public OperandManager OperandManager { get; } + + private StringBuilder _sb; + + private int _level; + + private string _indentation; + + public CodeGenContext(ShaderConfig config) + { + Config = config; + + CBufferDescriptors = new List<BufferDescriptor>(); + SBufferDescriptors = new List<BufferDescriptor>(); + TextureDescriptors = new List<TextureDescriptor>(); + + OperandManager = new OperandManager(); + + _sb = new StringBuilder(); + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string str) + { + _sb.AppendLine(_indentation + str); + } + + public string GetCode() + { + return _sb.ToString(); + } + + public void EnterScope() + { + AppendLine("{"); + + _level++; + + UpdateIndentation(); + } + + public void LeaveScope(string suffix = "") + { + if (_level == 0) + { + return; + } + + _level--; + + UpdateIndentation(); + + AppendLine("}" + suffix); + } + + private void UpdateIndentation() + { + _indentation = GetIndentation(_level); + } + + private static string GetIndentation(int level) + { + string indentation = string.Empty; + + for (int index = 0; index < level; index++) + { + indentation += Tab; + } + + return indentation; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs new file mode 100644 index 00000000..3644b21a --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,319 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Declarations + { + // At least 16 attributes are guaranteed by the spec. + public const int MaxAttributes = 16; + + public static void Declare(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("#version 420 core"); + context.AppendLine("#extension GL_ARB_shader_storage_buffer_object : enable"); + + if (context.Config.Stage == ShaderStage.Compute) + { + context.AppendLine("#extension GL_ARB_compute_shader : enable"); + } + + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + context.AppendLine(); + + if (context.Config.Stage == ShaderStage.Geometry) + { + string inPrimitive = "points"; + + if ((context.Config.Flags & TranslationFlags.Unspecialized) != 0) + { + inPrimitive = DefineNames.InputTopologyName; + } + + context.AppendLine($"layout ({inPrimitive}) in;"); + + string outPrimitive = "triangle_strip"; + + switch (context.Config.OutputTopology) + { + case OutputTopology.LineStrip: outPrimitive = "line_strip"; break; + case OutputTopology.PointList: outPrimitive = "points"; break; + case OutputTopology.TriangleStrip: outPrimitive = "triangle_strip"; break; + } + + int maxOutputVertices = context.Config.MaxOutputVertices; + + context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); + context.AppendLine(); + } + + context.AppendLine("layout (std140) uniform Extra"); + + context.EnterScope(); + + context.AppendLine("vec2 flip;"); + context.AppendLine("int instance;"); + + context.LeaveScope(";"); + + context.AppendLine(); + + context.AppendLine($"precise float {DefaultNames.LocalMemoryName}[0x100];"); + context.AppendLine(); + + if (info.CBuffers.Count != 0) + { + DeclareUniforms(context, info); + + context.AppendLine(); + } + + if (info.SBuffers.Count != 0) + { + DeclareStorage(context, info); + + context.AppendLine(); + } + + if (info.Samplers.Count != 0) + { + DeclareSamplers(context, info); + + context.AppendLine(); + } + + if (context.Config.Stage != ShaderStage.Compute) + { + if (info.IAttributes.Count != 0) + { + DeclareInputAttributes(context, info); + + context.AppendLine(); + } + + if (info.OAttributes.Count != 0 || context.Config.Stage != ShaderStage.Fragment) + { + DeclareOutputAttributes(context, info); + + context.AppendLine(); + } + } + else + { + string localSizeX = "1"; + string localSizeY = "1"; + string localSizeZ = "1"; + + if ((context.Config.Flags & TranslationFlags.Unspecialized) != 0) + { + localSizeX = DefineNames.LocalSizeX; + localSizeY = DefineNames.LocalSizeY; + localSizeZ = DefineNames.LocalSizeZ; + } + + context.AppendLine( + $"layout (" + + $"local_size_x = {localSizeX}, " + + $"local_size_y = {localSizeY}, " + + $"local_size_z = {localSizeZ}) in;"); + context.AppendLine(); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info) + { + foreach (AstOperand decl in info.Locals) + { + string name = context.OperandManager.DeclareLocal(decl); + + context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); + } + } + + private static string GetVarTypeName(VariableType type) + { + switch (type) + { + case VariableType.Bool: return "bool"; + case VariableType.F32: return "precise float"; + case VariableType.S32: return "int"; + case VariableType.U32: return "uint"; + } + + throw new ArgumentException($"Invalid variable type \"{type}\"."); + } + + private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) + { + string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; + + context.CBufferDescriptors.Add(new BufferDescriptor(ubName, cbufSlot)); + + context.AppendLine("layout (std140) uniform " + ubName); + + context.EnterScope(); + + string ubSize = "[" + NumberFormatter.FormatInt(context.Config.MaxCBufferSize / 16) + "]"; + + context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, cbufSlot) + ubSize + ";"); + + context.LeaveScope(";"); + } + } + + private static void DeclareStorage(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int sbufSlot in info.SBuffers.OrderBy(x => x)) + { + string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + sbName += "_" + DefaultNames.StorageNamePrefix + sbufSlot; + + context.SBufferDescriptors.Add(new BufferDescriptor(sbName, sbufSlot)); + + context.AppendLine("layout (std430) buffer " + sbName); + + context.EnterScope(); + + context.AppendLine("precise float " + OperandManager.GetSbName(context.Config.Stage, sbufSlot) + "[];"); + + context.LeaveScope(";"); + } + } + + private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary<string, AstTextureOperation> samplers = new Dictionary<string, AstTextureOperation>(); + + foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle)) + { + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp); + + if (!samplers.TryAdd(samplerName, texOp)) + { + continue; + } + + string samplerTypeName = GetSamplerTypeName(texOp.Target); + + context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); + } + + foreach (KeyValuePair<string, AstTextureOperation> kv in samplers) + { + string samplerName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + TextureDescriptor desc; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + desc = new TextureDescriptor(samplerName, texOp.Target, operand.CbufSlot, operand.CbufOffset); + } + else + { + desc = new TextureDescriptor(samplerName, texOp.Target, texOp.Handle); + } + + context.TextureDescriptors.Add(desc); + } + } + + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty; + + foreach (int attr in info.IAttributes.OrderBy(x => x)) + { + string iq = info.InterpolationQualifiers[attr].ToGlslQualifier(); + + if (iq != string.Empty) + { + iq += " "; + } + + context.AppendLine($"layout (location = {attr}) {iq}in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); + } + } + + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + if (context.Config.Stage == ShaderStage.Fragment) + { + DeclareUsedOutputAttributes(context, info); + } + else + { + DeclareAllOutputAttributes(context, info); + } + } + + private static void DeclareUsedOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int attr in info.OAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static void DeclareAllOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + for (int attr = 0; attr < MaxAttributes; attr++) + { + string iq = $"{DefineNames.OutQualifierPrefixName}{attr} "; + + context.AppendLine($"layout (location = {attr}) {iq}out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + + foreach (int attr in info.OAttributes.OrderBy(x => x).Where(x => x >= MaxAttributes)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static string GetSamplerTypeName(TextureTarget type) + { + string typeName; + + switch (type & TextureTarget.Mask) + { + case TextureTarget.Texture1D: typeName = "sampler1D"; break; + case TextureTarget.Texture2D: typeName = "sampler2D"; break; + case TextureTarget.Texture3D: typeName = "sampler3D"; break; + case TextureTarget.TextureCube: typeName = "samplerCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & TextureTarget.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & TextureTarget.Array) != 0) + { + typeName += "Array"; + } + + if ((type & TextureTarget.Shadow) != 0) + { + typeName += "Shadow"; + } + + return typeName; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs new file mode 100644 index 00000000..67de3b43 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class DefaultNames + { + public const string LocalNamePrefix = "temp"; + + public const string SamplerNamePrefix = "tex"; + + public const string IAttributePrefix = "in_attr"; + public const string OAttributePrefix = "out_attr"; + + public const string StorageNamePrefix = "s"; + public const string StorageNameSuffix = "data"; + + public const string UniformNamePrefix = "c"; + public const string UniformNameSuffix = "data"; + + public const string LocalMemoryName = "local_mem"; + + public const string UndefinedName = "undef"; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs new file mode 100644 index 00000000..65246d97 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class GlslGenerator + { + public static GlslProgram Generate(StructuredProgramInfo info, ShaderConfig config) + { + CodeGenContext context = new CodeGenContext(config); + + Declarations.Declare(context, info); + + PrintMainBlock(context, info); + + return new GlslProgram( + context.CBufferDescriptors.ToArray(), + context.SBufferDescriptors.ToArray(), + context.TextureDescriptors.ToArray(), + context.GetCode()); + } + + private static void PrintMainBlock(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("void main()"); + + context.EnterScope(); + + Declarations.DeclareLocals(context, info); + + // Ensure that unused attributes are set, otherwise the downstream + // compiler may eliminate them. + // (Not needed for fragment shader as it is the last stage). + if (context.Config.Stage != ShaderStage.Compute && + context.Config.Stage != ShaderStage.Fragment) + { + for (int attr = 0; attr < Declarations.MaxAttributes; attr++) + { + if (info.OAttributes.Contains(attr)) + { + continue; + } + + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr} = vec4(0);"); + } + } + + PrintBlock(context, info.MainBlock); + + context.LeaveScope(); + } + + private static void PrintBlock(CodeGenContext context, AstBlock block) + { + AstBlockVisitor visitor = new AstBlockVisitor(block); + + visitor.BlockEntered += (sender, e) => + { + switch (e.Block.Type) + { + case AstBlockType.DoWhile: + context.AppendLine("do"); + break; + + case AstBlockType.Else: + context.AppendLine("else"); + break; + + case AstBlockType.ElseIf: + context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + case AstBlockType.If: + context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + default: throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\"."); + } + + context.EnterScope(); + }; + + visitor.BlockLeft += (sender, e) => + { + context.LeaveScope(); + + if (e.Block.Type == AstBlockType.DoWhile) + { + context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});"); + } + }; + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstOperation operation) + { + context.AppendLine(InstGen.GetExpression(context, operation) + ";"); + } + else if (node is AstAssignment assignment) + { + VariableType srcType = OperandManager.GetNodeDestType(assignment.Source); + VariableType dstType = OperandManager.GetNodeDestType(assignment.Destination); + + string dest; + + if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute) + { + dest = OperandManager.GetOutAttributeName(operand, context.Config.Stage); + } + else + { + dest = InstGen.GetExpression(context, assignment.Destination); + } + + string src = ReinterpretCast(context, assignment.Source, srcType, dstType); + + context.AppendLine(dest + " = " + src + ";"); + } + else if (node is AstComment comment) + { + context.AppendLine("// " + comment.Comment); + } + else + { + throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } + + private static string GetCondExpr(CodeGenContext context, IAstNode cond) + { + VariableType srcType = OperandManager.GetNodeDestType(cond); + + return ReinterpretCast(context, cond, srcType, VariableType.Bool); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs new file mode 100644 index 00000000..7807cb49 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class GlslProgram + { + public BufferDescriptor[] CBufferDescriptors { get; } + public BufferDescriptor[] SBufferDescriptors { get; } + public TextureDescriptor[] TextureDescriptors { get; } + + public string Code { get; } + + public GlslProgram( + BufferDescriptor[] cBufferDescriptors, + BufferDescriptor[] sBufferDescriptors, + TextureDescriptor[] textureDescriptors, + string code) + { + CBufferDescriptors = cBufferDescriptors; + SBufferDescriptors = sBufferDescriptors; + TextureDescriptors = textureDescriptors; + Code = code; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs new file mode 100644 index 00000000..350de348 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -0,0 +1,127 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGen + { + public static string GetExpression(CodeGenContext context, IAstNode node) + { + if (node is AstOperation operation) + { + return GetExpression(context, operation); + } + else if (node is AstOperand operand) + { + return context.OperandManager.GetExpression(operand, context.Config.Stage); + } + + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + + private static string GetExpression(CodeGenContext context, AstOperation operation) + { + Instruction inst = operation.Inst; + + InstInfo info = GetInstructionInfo(inst); + + if ((info.Type & InstType.Call) != 0) + { + int arity = (int)(info.Type & InstType.ArityMask); + + string args = string.Empty; + + for (int argIndex = 0; argIndex < arity; argIndex++) + { + if (argIndex != 0) + { + args += ", "; + } + + VariableType dstType = GetSrcVarType(inst, argIndex); + + args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); + } + + return info.OpName + "(" + args + ")"; + } + else if ((info.Type & InstType.Op) != 0) + { + string op = info.OpName; + + int arity = (int)(info.Type & InstType.ArityMask); + + string[] expr = new string[arity]; + + for (int index = 0; index < arity; index++) + { + IAstNode src = operation.GetSource(index); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); + + bool isLhs = arity == 2 && index == 0; + + expr[index] = Enclose(srcExpr, src, inst, info, isLhs); + } + + switch (arity) + { + case 0: + return op; + + case 1: + return op + expr[0]; + + case 2: + return $"{expr[0]} {op} {expr[1]}"; + + case 3: + return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}"; + } + } + else if ((info.Type & InstType.Special) != 0) + { + switch (inst) + { + case Instruction.LoadAttribute: + return InstGenMemory.LoadAttribute(context, operation); + + case Instruction.LoadConstant: + return InstGenMemory.LoadConstant(context, operation); + + case Instruction.LoadLocal: + return InstGenMemory.LoadLocal(context, operation); + + case Instruction.LoadStorage: + return InstGenMemory.LoadStorage(context, operation); + + case Instruction.PackHalf2x16: + return InstGenPacking.PackHalf2x16(context, operation); + + case Instruction.StoreLocal: + return InstGenMemory.StoreLocal(context, operation); + + case Instruction.StoreStorage: + return InstGenMemory.StoreStorage(context, operation); + + case Instruction.TextureSample: + return InstGenMemory.TextureSample(context, operation); + + case Instruction.TextureSize: + return InstGenMemory.TextureSize(context, operation); + + case Instruction.UnpackHalf2x16: + return InstGenPacking.UnpackHalf2x16(context, operation); + } + } + + return "0"; + + throw new InvalidOperationException($"Unexpected instruction type \"{info.Type}\"."); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs new file mode 100644 index 00000000..6989e997 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -0,0 +1,175 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenHelper + { + private static InstInfo[] _infoTbl; + + static InstGenHelper() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + Add(Instruction.Absolute, InstType.CallUnary, "abs"); + Add(Instruction.Add, InstType.OpBinaryCom, "+", 2); + Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert"); + Add(Instruction.BitfieldReverse, InstType.CallUnary, "bitfieldReverse"); + Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6); + Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7); + Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0); + Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8); + Add(Instruction.Ceiling, InstType.CallUnary, "ceil"); + Add(Instruction.Clamp, InstType.CallTernary, "clamp"); + Add(Instruction.ClampU32, InstType.CallTernary, "clamp"); + Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5); + Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4); + Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4); + Add(Instruction.CompareLess, InstType.OpBinary, "<", 4); + Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4); + Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5); + Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12); + Add(Instruction.ConvertFPToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertS32ToFP, InstType.CallUnary, "float"); + Add(Instruction.ConvertU32ToFP, InstType.CallUnary, "float"); + Add(Instruction.Cosine, InstType.CallUnary, "cos"); + Add(Instruction.Discard, InstType.OpNullary, "discard"); + Add(Instruction.Divide, InstType.OpBinary, "/", 1); + Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex"); + Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive"); + Add(Instruction.ExponentB2, InstType.CallUnary, "exp2"); + Add(Instruction.Floor, InstType.CallUnary, "floor"); + Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma"); + Add(Instruction.IsNan, InstType.CallUnary, "isnan"); + Add(Instruction.LoadAttribute, InstType.Special); + Add(Instruction.LoadConstant, InstType.Special); + Add(Instruction.LoadLocal, InstType.Special); + Add(Instruction.LoadStorage, InstType.Special); + Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); + Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); + Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); + Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0); + Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11); + Add(Instruction.LoopBreak, InstType.OpNullary, "break"); + Add(Instruction.LoopContinue, InstType.OpNullary, "continue"); + Add(Instruction.PackHalf2x16, InstType.Special); + Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); + Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); + Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); + Add(Instruction.Maximum, InstType.CallBinary, "max"); + Add(Instruction.MaximumU32, InstType.CallBinary, "max"); + Add(Instruction.Minimum, InstType.CallBinary, "min"); + Add(Instruction.MinimumU32, InstType.CallBinary, "min"); + Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); + Add(Instruction.Negate, InstType.OpUnary, "-", 0); + Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt"); + Add(Instruction.Return, InstType.OpNullary, "return"); + Add(Instruction.Sine, InstType.CallUnary, "sin"); + Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); + Add(Instruction.StoreLocal, InstType.Special); + Add(Instruction.StoreStorage, InstType.Special); + Add(Instruction.Subtract, InstType.OpBinary, "-", 2); + Add(Instruction.TextureSample, InstType.Special); + Add(Instruction.TextureSize, InstType.Special); + Add(Instruction.Truncate, InstType.CallUnary, "trunc"); + Add(Instruction.UnpackHalf2x16, InstType.Special); + } + + private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0) + { + _infoTbl[(int)inst] = new InstInfo(flags, opName, precedence); + } + + public static InstInfo GetInstructionInfo(Instruction inst) + { + return _infoTbl[(int)(inst & Instruction.Mask)]; + } + + public static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType) + { + return ReinterpretCast(context, node, OperandManager.GetNodeDestType(node), dstType); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs) + { + InstInfo pInfo = GetInstructionInfo(pInst); + + return Enclose(expr, node, pInst, pInfo, isLhs); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false) + { + if (NeedsParenthesis(node, pInst, pInfo, isLhs)) + { + expr = "(" + expr + ")"; + } + + return expr; + } + + public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs) + { + // If the node isn't a operation, then it can only be a operand, + // and those never needs to be surrounded in parenthesis. + if (!(node is AstOperation operation)) + { + // This is sort of a special case, if this is a negative constant, + // and it is consumed by a unary operation, we need to put on the parenthesis, + // as in GLSL a sequence like --2 or ~-1 is not valid. + if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary) + { + return true; + } + + return false; + } + + if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + InstInfo info = _infoTbl[(int)(operation.Inst & Instruction.Mask)]; + + if ((info.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + if (info.Precedence < pInfo.Precedence) + { + return false; + } + + if (info.Precedence == pInfo.Precedence && isLhs) + { + return false; + } + + if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom) + { + return false; + } + + return true; + } + + private static bool IsNegativeConst(IAstNode node) + { + if (!(node is AstOperand operand)) + { + return false; + } + + return operand.Type == OperandType.Constant && operand.Value < 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs new file mode 100644 index 00000000..5c9ec830 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -0,0 +1,320 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenMemory + { + public static string LoadAttribute(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + if (!(src1 is AstOperand attr) || attr.Type != OperandType.Attribute) + { + throw new InvalidOperationException("First source of LoadAttribute must be a attribute."); + } + + string indexExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return OperandManager.GetAttributeName(attr, context.Config.Stage, isOutAttr: false, indexExpr); + } + + public static string LoadConstant(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true); + + return OperandManager.GetConstantBufferName(src1, offsetExpr, context.Config.Stage); + } + + public static string LoadLocal(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + return $"{DefaultNames.LocalMemoryName}[{offsetExpr}]"; + } + + public static string LoadStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return OperandManager.GetStorageBufferName(src1, offsetExpr, context.Config.Stage); + } + + public static string StoreLocal(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + VariableType srcType = OperandManager.GetNodeDestType(src2); + + string src = TypeConversion.ReinterpretCast(context, src2, srcType, VariableType.F32); + + return $"{DefaultNames.LocalMemoryName}[{offsetExpr}] = {src}"; + } + + public static string StoreStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + IAstNode src3 = operation.GetSource(2); + + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + VariableType srcType = OperandManager.GetNodeDestType(src3); + + string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.F32); + + string sbName = OperandManager.GetStorageBufferName(src1, offsetExpr, context.Config.Stage); + + return $"{sbName} = {src}"; + } + + public static string TextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool isArray = (texOp.Target & TextureTarget.Array) != 0; + bool isMultisample = (texOp.Target & TextureTarget.Multisample) != 0; + bool isShadow = (texOp.Target & TextureTarget.Shadow) != 0; + + // This combination is valid, but not available on GLSL. + // For now, ignore the LOD level and do a normal sample. + // TODO: How to implement it properly? + if (hasLodLevel && isArray && isShadow) + { + hasLodLevel = false; + } + + string texCall = intCoords ? "texelFetch" : "texture"; + + if (isGather) + { + texCall += "Gather"; + } + else if (hasLodLevel && !intCoords) + { + texCall += "Lod"; + } + + if (hasOffset) + { + texCall += "Offset"; + } + else if (hasOffsets) + { + texCall += "Offsets"; + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp); + + texCall += "(" + samplerName; + + int coordsCount = texOp.Target.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + // The sampler 1D shadow overload expects a + // dummy value on the middle of the vector, who knows why... + bool hasDummy1DShadowElem = texOp.Target == (TextureTarget.Texture1D | TextureTarget.Shadow); + + if (hasDummy1DShadowElem) + { + pCount++; + } + + if (isShadow && !isGather) + { + pCount++; + } + + // On textureGather*, the comparison value is + // always specified as an extra argument. + bool hasExtraCompareArg = isShadow && isGather; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + void Append(string str) + { + texCall += ", " + str; + } + + VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32; + + string AssemblePVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(VariableType.S32); + + if (!intCoords) + { + elems[index] = "float(" + elems[index] + ")"; + } + } + else if (index == 1 && hasDummy1DShadowElem) + { + elems[index] = NumberFormatter.FormatFloat(0); + } + else + { + elems[index] = Src(coordType); + } + } + + string prefix = intCoords ? "i" : string.Empty; + + return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(coordType); + } + } + + Append(AssemblePVector(pCount)); + + if (hasExtraCompareArg) + { + Append(Src(VariableType.F32)); + } + + if (isMultisample) + { + Append(Src(VariableType.S32)); + } + else if (hasLodLevel) + { + Append(Src(coordType)); + } + + string AssembleOffsetVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.S32); + } + + return "ivec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.S32); + } + } + + if (hasOffset) + { + Append(AssembleOffsetVector(coordsCount)); + } + else if (hasOffsets) + { + texCall += $", ivec{coordsCount}[4]("; + + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ")"; + } + + if (hasLodBias) + { + Append(Src(VariableType.F32)); + } + + // textureGather* optional extra component index, + // not needed for shadow samplers. + if (isGather && !isShadow) + { + Append(Src(VariableType.S32)); + } + + texCall += ")" + (isGather || !isShadow ? GetMask(texOp.ComponentMask) : ""); + + return texCall; + } + + public static string TextureSize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp); + + IAstNode src0 = operation.GetSource(isBindless ? 1 : 0); + + string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + + return $"textureSize({samplerName}, {src0Expr}){GetMask(texOp.ComponentMask)}"; + } + + private static string GetMask(int compMask) + { + string mask = "."; + + for (int index = 0; index < 4; index++) + { + if ((compMask & (1 << index)) != 0) + { + mask += "rgba".Substring(index, 1); + } + } + + return mask; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs new file mode 100644 index 00000000..4a40032c --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -0,0 +1,45 @@ +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenPacking + { + public static string PackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src0 = operation.GetSource(0); + IAstNode src1 = operation.GetSource(1); + + string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; + } + + public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackHalf2x16({srcExpr}){GetMask(operation.ComponentMask)}"; + } + + private static string GetMask(int compMask) + { + string mask = "."; + + for (int index = 0; index < 2; index++) + { + if ((compMask & (1 << index)) != 0) + { + mask += "xy".Substring(index, 1); + } + } + + return mask; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs new file mode 100644 index 00000000..fc9aef7e --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + struct InstInfo + { + public InstType Type { get; } + + public string OpName { get; } + + public int Precedence { get; } + + public InstInfo(InstType type, string opName, int precedence) + { + Type = type; + OpName = opName; + Precedence = precedence; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs new file mode 100644 index 00000000..121cd079 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + [Flags] + enum InstType + { + OpNullary = Op | 0, + OpUnary = Op | 1, + OpBinary = Op | 2, + OpTernary = Op | 3, + OpBinaryCom = OpBinary | Commutative, + + CallNullary = Call | 0, + CallUnary = Call | 1, + CallBinary = Call | 2, + CallTernary = Call | 3, + CallQuaternary = Call | 4, + + Commutative = 1 << 8, + Op = 1 << 9, + Call = 1 << 10, + Special = 1 << 11, + + ArityMask = 0xff + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs new file mode 100644 index 00000000..2ec44277 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Globalization; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class NumberFormatter + { + private const int MaxDecimal = 256; + + public static bool TryFormat(int value, VariableType dstType, out string formatted) + { + if (dstType == VariableType.F32) + { + return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted); + } + else if (dstType == VariableType.S32) + { + formatted = FormatInt(value); + } + else if (dstType == VariableType.U32) + { + formatted = FormatUint((uint)value); + } + else if (dstType == VariableType.Bool) + { + formatted = value != 0 ? "true" : "false"; + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + + return true; + } + + public static string FormatFloat(float value) + { + if (!TryFormatFloat(value, out string formatted)) + { + throw new ArgumentException("Failed to convert float value to string."); + } + + return formatted; + } + + public static bool TryFormatFloat(float value, out string formatted) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + formatted = null; + + return false; + } + + formatted = value.ToString("G9", CultureInfo.InvariantCulture); + + if (!(formatted.Contains('.') || + formatted.Contains('e') || + formatted.Contains('E'))) + { + formatted += ".0"; + } + + return true; + } + + public static string FormatInt(int value, VariableType dstType) + { + if (dstType == VariableType.S32) + { + return FormatInt(value); + } + else if (dstType == VariableType.U32) + { + return FormatUint((uint)value); + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + } + + public static string FormatInt(int value) + { + if (value <= MaxDecimal && value >= -MaxDecimal) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture); + } + + public static string FormatUint(uint value) + { + if (value <= MaxDecimal && value >= 0) + { + return value.ToString(CultureInfo.InvariantCulture) + "u"; + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u"; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs new file mode 100644 index 00000000..88095920 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -0,0 +1,298 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class OperandManager + { + private static string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; + + private struct BuiltInAttribute + { + public string Name { get; } + + public VariableType Type { get; } + + public BuiltInAttribute(string name, VariableType type) + { + Name = name; + Type = type; + } + } + + private static Dictionary<int, BuiltInAttribute> _builtInAttributes = + new Dictionary<int, BuiltInAttribute>() + { + { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) }, + { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, + { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, + { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) }, + { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) }, + { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) }, + { AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", VariableType.F32) }, + { AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", VariableType.F32) }, + { AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", VariableType.F32) }, + { AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", VariableType.F32) }, + { AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", VariableType.F32) }, + { AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", VariableType.F32) }, + { AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", VariableType.F32) }, + { AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", VariableType.F32) }, + { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) }, + { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, + { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, + { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, + { AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", VariableType.S32) }, + { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, + { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, + + // Special. + { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }, + { AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", VariableType.U32) }, + { AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", VariableType.U32) }, + { AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", VariableType.U32) }, + { AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", VariableType.U32) }, + { AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) }, + { AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) }, + }; + + private Dictionary<AstOperand, string> _locals; + + public OperandManager() + { + _locals = new Dictionary<AstOperand, string>(); + } + + public string DeclareLocal(AstOperand operand) + { + string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}"; + + _locals.Add(operand, name); + + return name; + } + + public string GetExpression(AstOperand operand, ShaderStage stage) + { + switch (operand.Type) + { + case OperandType.Attribute: + return GetAttributeName(operand, stage); + + case OperandType.Constant: + return NumberFormatter.FormatInt(operand.Value); + + case OperandType.ConstantBuffer: + return GetConstantBufferName(operand, stage); + + case OperandType.LocalVariable: + return _locals[operand]; + + case OperandType.Undefined: + return DefaultNames.UndefinedName; + } + + throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."); + } + + public static string GetConstantBufferName(AstOperand cbuf, ShaderStage stage) + { + string ubName = GetUbName(stage, cbuf.CbufSlot); + + ubName += "[" + (cbuf.CbufOffset >> 2) + "]"; + + return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3); + } + + public static string GetStorageBufferName(IAstNode slot, string offsetExpr, ShaderStage stage) + { + // Non-constant slots are not supported. + // It is expected that upstream stages are never going to generate non-constant + // slot access. + AstOperand operand = (AstOperand)slot; + + string sbName = GetSbName(stage, operand.Value); + + return $"{sbName}[{offsetExpr}]"; + } + + public static string GetConstantBufferName(IAstNode slot, string offsetExpr, ShaderStage stage) + { + // Non-constant slots are not supported. + // It is expected that upstream stages are never going to generate non-constant + // slot access. + AstOperand operand = (AstOperand)slot; + + string ubName = GetUbName(stage, operand.Value); + + string index0 = "[" + offsetExpr + " >> 2]"; + string index1 = "[" + offsetExpr + " & 3]"; + + return ubName + index0 + index1; + } + + public static string GetOutAttributeName(AstOperand attr, ShaderStage stage) + { + return GetAttributeName(attr, stage, isOutAttr: true); + } + + public static string GetAttributeName(AstOperand attr, ShaderStage stage, bool isOutAttr = false, string indexExpr = "0") + { + int value = attr.Value; + + string swzMask = GetSwizzleMask((value >> 2) & 3); + + if (value >= AttributeConsts.UserAttributeBase && + value < AttributeConsts.UserAttributeEnd) + { + value -= AttributeConsts.UserAttributeBase; + + string prefix = isOutAttr + ? DefaultNames.OAttributePrefix + : DefaultNames.IAttributePrefix; + + string name = $"{prefix}{(value >> 4)}"; + + if (stage == ShaderStage.Geometry && !isOutAttr) + { + name += $"[{indexExpr}]"; + } + + name += "." + swzMask; + + return name; + } + else + { + if (value >= AttributeConsts.FragmentOutputColorBase && + value < AttributeConsts.FragmentOutputColorEnd) + { + value -= AttributeConsts.FragmentOutputColorBase; + + return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}"; + } + else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr)) + { + // TODO: There must be a better way to handle this... + if (stage == ShaderStage.Fragment) + { + switch (value & ~3) + { + case AttributeConsts.PositionX: return "gl_FragCoord.x"; + case AttributeConsts.PositionY: return "gl_FragCoord.y"; + case AttributeConsts.PositionZ: return "gl_FragCoord.z"; + case AttributeConsts.PositionW: return "1.0"; + } + } + + string name = builtInAttr.Name; + + if (stage == ShaderStage.Geometry && !isOutAttr) + { + 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 GetSbName(ShaderStage stage, int slot) + { + string sbName = GetShaderStagePrefix(stage); + + sbName += "_" + DefaultNames.StorageNamePrefix + slot; + + return sbName + "_" + DefaultNames.StorageNameSuffix; + } + + public static string GetUbName(ShaderStage stage, int slot) + { + string ubName = GetShaderStagePrefix(stage); + + ubName += "_" + DefaultNames.UniformNamePrefix + slot; + + return ubName + "_" + DefaultNames.UniformNameSuffix; + } + + public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp) + { + string suffix; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + suffix = "_cb" + operand.CbufSlot + "_" + operand.CbufOffset; + } + else + { + suffix = (texOp.Handle - 8).ToString(); + } + + return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix; + } + + public static string GetShaderStagePrefix(ShaderStage stage) + { + int index = (int)stage; + + if ((uint)index >= _stagePrefixes.Length) + { + return "invalid"; + } + + return _stagePrefixes[index]; + } + + private static string GetSwizzleMask(int value) + { + return "xyzw".Substring(value, 1); + } + + public static VariableType GetNodeDestType(IAstNode node) + { + if (node is AstOperation operation) + { + // 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.Inst == Instruction.LoadAttribute) + { + return GetOperandVarType((AstOperand)operation.GetSource(0)); + } + + return GetDestVarType(operation.Inst); + } + else if (node is AstOperand operand) + { + return GetOperandVarType(operand); + } + else + { + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + + private static VariableType GetOperandVarType(AstOperand operand) + { + if (operand.Type == OperandType.Attribute) + { + if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr)) + { + return builtInAttr.Type; + } + } + + return OperandInfo.GetVarType(operand); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs new file mode 100644 index 00000000..7adc5ad3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs @@ -0,0 +1,85 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class TypeConversion + { + public static string ReinterpretCast( + CodeGenContext context, + IAstNode node, + VariableType srcType, + VariableType dstType) + { + if (node is AstOperand operand && operand.Type == OperandType.Constant) + { + if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted)) + { + return formatted; + } + } + + string expr = InstGen.GetExpression(context, node); + + return ReinterpretCast(expr, node, srcType, dstType); + } + + private static string ReinterpretCast(string expr, IAstNode node, VariableType srcType, VariableType dstType) + { + if (srcType == dstType) + { + return expr; + } + + if (srcType == VariableType.F32) + { + switch (dstType) + { + case VariableType.S32: return $"floatBitsToInt({expr})"; + case VariableType.U32: return $"floatBitsToUint({expr})"; + } + } + else if (dstType == VariableType.F32) + { + switch (srcType) + { + case VariableType.Bool: return $"intBitsToFloat({ReinterpretBoolToInt(expr, node, VariableType.S32)})"; + case VariableType.S32: return $"intBitsToFloat({expr})"; + case VariableType.U32: return $"uintBitsToFloat({expr})"; + } + } + else if (srcType == VariableType.Bool) + { + return ReinterpretBoolToInt(expr, node, dstType); + } + else if (dstType == VariableType.Bool) + { + expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true); + + return $"({expr} != 0)"; + } + else if (dstType == VariableType.S32) + { + return $"int({expr})"; + } + else if (dstType == VariableType.U32) + { + return $"uint({expr})"; + } + + throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\"."); + } + + private static string ReinterpretBoolToInt(string expr, IAstNode node, VariableType dstType) + { + string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType); + string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType); + + expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false); + + return $"({expr} ? {trueExpr} : {falseExpr})"; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs b/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs new file mode 100644 index 00000000..3bb9bc1f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class BitfieldExtensions + { + public static bool Extract(this int value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this int value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + + public static bool Extract(this long value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this long value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Block.cs b/Ryujinx.Graphics.Shader/Decoders/Block.cs new file mode 100644 index 00000000..b5e610d7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Block.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public List<OpCode> OpCodes { get; } + public List<OpCodeSsy> SsyOpCodes { get; } + + public Block(ulong address) + { + Address = address; + + OpCodes = new List<OpCode>(); + SsyOpCodes = new List<OpCodeSsy>(); + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + rightBlock.UpdateSsyOpCodes(); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + + UpdateSsyOpCodes(); + } + + private static int BinarySearch(List<OpCode> opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == opCode.Address) + { + break; + } + + if (address < opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public OpCode GetLastOp() + { + if (OpCodes.Count != 0) + { + return OpCodes[OpCodes.Count - 1]; + } + + return null; + } + + public void UpdateSsyOpCodes() + { + SsyOpCodes.Clear(); + + for (int index = 0; index < OpCodes.Count; index++) + { + if (!(OpCodes[index] is OpCodeSsy op)) + { + continue; + } + + SsyOpCodes.Add(op); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Condition.cs b/Ryujinx.Graphics.Shader/Decoders/Condition.cs new file mode 100644 index 00000000..10400f94 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Condition.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum Condition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + Nan = 1 << 3, + Unsigned = 1 << 4, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + LessUnordered = Less | Nan, + EqualUnordered = Equal | Nan, + LessOrEqualUnordered = LessOrEqual | Nan, + GreaterUnordered = Greater | Nan, + NotEqualUnordered = NotEqual | Nan, + GreaterOrEqualUnordered = GreaterOrEqual | Nan, + + Always = 0xf, + + Off = Unsigned | Never, + Lower = Unsigned | Less, + Sff = Unsigned | Equal, + LowerOrSame = Unsigned | LessOrEqual, + Higher = Unsigned | Greater, + Sft = Unsigned | NotEqual, + HigherOrSame = Unsigned | GreaterOrEqual, + Oft = Unsigned | Always, + + CsmTa = 0x18, + CsmTr = 0x19, + CsmMx = 0x1a, + FcsmTa = 0x1b, + FcsmTr = 0x1c, + FcsmMx = 0x1d, + Rle = 0x1e, + Rgt = 0x1f + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs new file mode 100644 index 00000000..4fc31e84 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ConditionalOperation + { + False = 0, + True = 1, + Zero = 2, + NotZero = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs new file mode 100644 index 00000000..dd5347d9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -0,0 +1,407 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class Decoder + { + private delegate object OpActivator(InstEmitter emitter, ulong address, long opCode); + + private static ConcurrentDictionary<Type, OpActivator> _opActivators; + + static Decoder() + { + _opActivators = new ConcurrentDictionary<Type, OpActivator>(); + } + + public static Block[] Decode(Span<byte> code, ulong headerSize) + { + List<Block> blocks = new List<Block>(); + + Queue<Block> workQueue = new Queue<Block>(); + + Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>(); + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + ulong startAddress = headerSize; + + GetBlock(startAddress); + + while (workQueue.TryDequeue(out Block currBlock)) + { + // Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + // If we have a block after the current one, set the limit address. + ulong limitAddress = (ulong)code.Length; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + FillBlock(code, currBlock, limitAddress, startAddress); + + if (currBlock.OpCodes.Count != 0) + { + foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes) + { + GetBlock(ssyOp.GetAbsoluteAddress()); + } + + // Set child blocks. "Branch" is the block the branch instruction + // points to (when taken), "Next" is the block at the next address, + // executed when the branch is not taken. For Unconditional Branches + // or end of program, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + if (lastOp is OpCodeBranch op) + { + currBlock.Branch = GetBlock(op.GetAbsoluteAddress()); + } + + if (!IsUnconditionalBranch(lastOp)) + { + currBlock.Next = GetBlock(currBlock.EndAddress); + } + } + + // Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0)) + { + for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) + { + PropagateSsy(visited, ssyBlock, ssyIndex); + } + } + + return blocks.ToArray(); + } + + private static bool BinarySearch(List<Block> blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + Span<byte> code, + Block block, + ulong limitAddress, + ulong startAddress) + { + ulong address = block.Address; + + do + { + if (address >= limitAddress) + { + break; + } + + // Ignore scheduling instructions, which are written every 32 bytes. + if (((address - startAddress) & 0x1f) == 0) + { + address += 8; + + continue; + } + + uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)address)); + uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)address + 4)); + + ulong opAddress = address; + + address += 8; + + long opCode = word0 | (long)word1 << 32; + + (InstEmitter emitter, Type opCodeType) = OpCodeTable.GetEmitter(opCode); + + if (emitter == null) + { + // TODO: Warning, illegal encoding. + + block.OpCodes.Add(new OpCode(null, opAddress, opCode)); + + continue; + } + + OpCode op = MakeOpCode(opCodeType, emitter, opAddress, opCode); + + block.OpCodes.Add(op); + } + while (!IsBranch(block.GetLastOp())); + + block.EndAddress = address; + + block.UpdateSsyOpCodes(); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return IsUnconditional(opCode) && IsBranch(opCode); + } + + private static bool IsUnconditional(OpCode opCode) + { + if (opCode is OpCodeExit op && op.Condition != Condition.Always) + { + return false; + } + + return opCode.Predicate.Index == RegisterConsts.PredicateTrueIndex && !opCode.InvertPredicate; + } + + private static bool IsBranch(OpCode opCode) + { + return (opCode is OpCodeBranch && opCode.Emitter != InstEmit.Ssy) || + opCode is OpCodeSync || + opCode is OpCodeExit; + } + + private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + OpActivator createInstance = _opActivators.GetOrAdd(type, CacheOpActivator); + + return (OpCode)createInstance(emitter, address, opCode); + } + + private static OpActivator CacheOpActivator(Type type) + { + Type[] argTypes = new Type[] { typeof(InstEmitter), typeof(ulong), typeof(long) }; + + DynamicMethod mthd = new DynamicMethod($"Make{type.Name}", type, argTypes); + + ILGenerator generator = mthd.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Newobj, type.GetConstructor(argTypes)); + generator.Emit(OpCodes.Ret); + + return (OpActivator)mthd.CreateDelegate(typeof(OpActivator)); + } + + private struct PathBlockState + { + public Block Block { get; } + + private enum RestoreType + { + None, + PopSsy, + PushSync + } + + private RestoreType _restoreType; + + private ulong _restoreValue; + + public bool ReturningFromVisit => _restoreType != RestoreType.None; + + public PathBlockState(Block block) + { + Block = block; + _restoreType = RestoreType.None; + _restoreValue = 0; + } + + public PathBlockState(int oldSsyStackSize) + { + Block = null; + _restoreType = RestoreType.PopSsy; + _restoreValue = (ulong)oldSsyStackSize; + } + + public PathBlockState(ulong syncAddress) + { + Block = null; + _restoreType = RestoreType.PushSync; + _restoreValue = syncAddress; + } + + public void RestoreStackState(Stack<ulong> ssyStack) + { + if (_restoreType == RestoreType.PushSync) + { + ssyStack.Push(_restoreValue); + } + else if (_restoreType == RestoreType.PopSsy) + { + while (ssyStack.Count > (uint)_restoreValue) + { + ssyStack.Pop(); + } + } + } + } + + private static void PropagateSsy(Dictionary<ulong, Block> blocks, Block ssyBlock, int ssyIndex) + { + OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; + + Stack<PathBlockState> workQueue = new Stack<PathBlockState>(); + + HashSet<Block> visited = new HashSet<Block>(); + + Stack<ulong> ssyStack = new Stack<ulong>(); + + void Push(PathBlockState pbs) + { + if (pbs.Block == null || visited.Add(pbs.Block)) + { + workQueue.Push(pbs); + } + } + + Push(new PathBlockState(ssyBlock)); + + while (workQueue.TryPop(out PathBlockState pbs)) + { + if (pbs.ReturningFromVisit) + { + pbs.RestoreStackState(ssyStack); + + continue; + } + + Block current = pbs.Block; + + int ssyOpCodesCount = current.SsyOpCodes.Count; + + if (ssyOpCodesCount != 0) + { + Push(new PathBlockState(ssyStack.Count)); + + for (int index = ssyIndex; index < ssyOpCodesCount; index++) + { + ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); + } + } + + ssyIndex = 0; + + if (current.Next != null) + { + Push(new PathBlockState(current.Next)); + } + + if (current.Branch != null) + { + Push(new PathBlockState(current.Branch)); + } + else if (current.GetLastOp() is OpCodeSync op) + { + ulong syncAddress = ssyStack.Pop(); + + if (ssyStack.Count == 0) + { + ssyStack.Push(syncAddress); + + op.Targets.Add(ssyOp, op.Targets.Count); + + ssyOp.Syncs.TryAdd(op, Local()); + } + else + { + Push(new PathBlockState(syncAddress)); + Push(new PathBlockState(blocks[syncAddress])); + } + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs new file mode 100644 index 00000000..77cd1bf7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs @@ -0,0 +1,58 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class DecoderHelper + { + public static int DecodeS20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool sign = opCode.Extract(56); + + if (sign) + { + imm = (imm << 13) >> 13; + } + + return imm; + } + + public static int Decode2xF10Immediate(long opCode) + { + int immH0 = opCode.Extract(20, 9); + int immH1 = opCode.Extract(30, 9); + + bool negateH0 = opCode.Extract(29); + bool negateH1 = opCode.Extract(56); + + if (negateH0) + { + immH0 |= 1 << 9; + } + + if (negateH1) + { + immH1 |= 1 << 9; + } + + return immH1 << 22 | immH0 << 6; + } + + public static float DecodeF20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool negate = opCode.Extract(56); + + imm <<= 12; + + if (negate) + { + imm |= 1 << 31; + } + + return BitConverter.Int32BitsToSingle(imm); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs new file mode 100644 index 00000000..3ddf17cf --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPHalfSwizzle + { + FP16 = 0, + FP32 = 1, + DupH0 = 2, + DupH1 = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FPType.cs b/Ryujinx.Graphics.Shader/Decoders/FPType.cs new file mode 100644 index 00000000..e602ad45 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FPType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPType + { + FP16 = 1, + FP32 = 2, + FP64 = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FmulScale.cs b/Ryujinx.Graphics.Shader/Decoders/FmulScale.cs new file mode 100644 index 00000000..c35c6e48 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FmulScale.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FmulScale + { + None = 0, + Divide2 = 1, + Divide4 = 2, + Divide8 = 3, + Multiply8 = 4, + Multiply4 = 5, + Multiply2 = 6 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs new file mode 100644 index 00000000..dd6ad79a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCode + { + InstEmitter Emitter { get; } + + ulong Address { get; } + long RawOpCode { get; } + + Register Predicate { get; } + + bool InvertPredicate { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs new file mode 100644 index 00000000..d840d49d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeAlu : IOpCodeRd, IOpCodeRa + { + Register Predicate39 { get; } + + bool InvertP { get; } + bool Extended { get; } + bool SetCondCode { get; } + bool Saturate { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs new file mode 100644 index 00000000..42a17451 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeCbuf : IOpCode + { + int Offset { get; } + int Slot { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs new file mode 100644 index 00000000..d68ccf59 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeFArith : IOpCodeAlu + { + RoundingMode RoundingMode { get; } + + FmulScale Scale { get; } + + bool FlushToZero { get; } + bool AbsoluteA { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs new file mode 100644 index 00000000..4638f660 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeHfma : IOpCode + { + bool NegateB { get; } + bool NegateC { get; } + bool Saturate { get; } + + FPHalfSwizzle SwizzleA { get; } + FPHalfSwizzle SwizzleB { get; } + FPHalfSwizzle SwizzleC { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs new file mode 100644 index 00000000..9cfcd69b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImm : IOpCode + { + int Immediate { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs new file mode 100644 index 00000000..629eff79 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImmF : IOpCode + { + float Immediate { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs new file mode 100644 index 00000000..62c87bf4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeLop : IOpCodeAlu + { + LogicalOperation LogicalOp { get; } + + bool InvertA { get; } + bool InvertB { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs new file mode 100644 index 00000000..e5902110 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRa : IOpCode + { + Register Ra { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs new file mode 100644 index 00000000..bb806b95 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRc : IOpCode + { + Register Rc { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs new file mode 100644 index 00000000..099c4061 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRd : IOpCode + { + Register Rd { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs new file mode 100644 index 00000000..3ed157e8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeReg : IOpCode + { + Register Rb { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs new file mode 100644 index 00000000..429f01bb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRegCbuf : IOpCodeRc + { + int Offset { get; } + int Slot { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs new file mode 100644 index 00000000..a1937c2f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerCondition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + Always = 7 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs new file mode 100644 index 00000000..b779f44d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerHalfPart + { + B32 = 0, + H0 = 1, + H1 = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs new file mode 100644 index 00000000..ce4d9f3b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerShift + { + NoShift = 0, + ShiftRight = 1, + ShiftLeft = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs new file mode 100644 index 00000000..3ff8e1b2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs new file mode 100644 index 00000000..46734dbe --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerType + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs new file mode 100644 index 00000000..98ee3b97 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum InterpolationMode + { + Pass, + Default, + Constant, + Sc + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs new file mode 100644 index 00000000..52214425 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum LogicalOperation + { + And = 0, + Or = 1, + ExclusiveOr = 2, + Passthrough = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs b/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs new file mode 100644 index 00000000..88bd1f5c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum MufuOperation + { + Cosine = 0, + Sine = 1, + ExponentB2 = 2, + LogarithmB2 = 3, + Reciprocal = 4, + ReciprocalSquareRoot = 5, + Reciprocal64H = 6, + ReciprocalSquareRoot64H = 7, + SquareRoot = 8 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCode.cs b/Ryujinx.Graphics.Shader/Decoders/OpCode.cs new file mode 100644 index 00000000..94af49e0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCode.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCode + { + public InstEmitter Emitter { get; } + + public ulong Address { get; } + public long RawOpCode { get; } + + public Register Predicate { get; protected set; } + + public bool InvertPredicate { get; protected set; } + + // When inverted, the always true predicate == always false. + public bool NeverExecute => Predicate.Index == RegisterConsts.PredicateTrueIndex && InvertPredicate; + + public OpCode(InstEmitter emitter, ulong address, long opCode) + { + Emitter = emitter; + Address = address; + RawOpCode = opCode; + + Predicate = new Register(opCode.Extract(16, 3), RegisterType.Predicate); + + InvertPredicate = opCode.Extract(19); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs new file mode 100644 index 00000000..15fbb9af --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + public Register Predicate39 { get; } + + public int ByteSelection { get; } + + public bool InvertP { get; } + public bool Extended { get; protected set; } + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeAlu(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + Predicate39 = new Register(opCode.Extract(39, 3), RegisterType.Predicate); + + ByteSelection = opCode.Extract(41, 2); + + InvertP = opCode.Extract(42); + Extended = opCode.Extract(43); + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(50); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs new file mode 100644 index 00000000..9c127989 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluCbuf : OpCodeAlu, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs new file mode 100644 index 00000000..a407fc6b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs new file mode 100644 index 00000000..9aeb32bd --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm2x10 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs new file mode 100644 index 00000000..5941e0b9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm32 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + SetCondCode = opCode.Extract(52); + Extended = opCode.Extract(53); + Saturate = opCode.Extract(54); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs new file mode 100644 index 00000000..13b96a3a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluReg : OpCodeAlu, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeAluReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs new file mode 100644 index 00000000..6cf6bd2e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluRegCbuf : OpCodeAluReg, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + Rb = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs new file mode 100644 index 00000000..fd8e63fc --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAttribute : OpCodeAluReg + { + public int AttributeOffset { get; } + public int Count { get; } + + public OpCodeAttribute(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(20, 10); + Count = opCode.Extract(47, 2) + 1; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs new file mode 100644 index 00000000..25941b39 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranch : OpCode + { + public int Offset { get; } + + public OpCodeBranch(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = ((int)(opCode >> 20) << 8) >> 8; + } + + public ulong GetAbsoluteAddress() + { + return (ulong)((long)Address + (long)Offset + 8); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs new file mode 100644 index 00000000..d50903eb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeExit : OpCode + { + public Condition Condition { get; } + + public OpCodeExit(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Condition = (Condition)opCode.Extract(0, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs new file mode 100644 index 00000000..c88f7f0e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArith : OpCodeAlu, IOpCodeFArith + { + public RoundingMode RoundingMode { get; } + + public FmulScale Scale { get; } + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public OpCodeFArith(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + RoundingMode = (RoundingMode)opCode.Extract(39, 2); + + Scale = (FmulScale)opCode.Extract(41, 3); + + FlushToZero = opCode.Extract(44); + AbsoluteA = opCode.Extract(46); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs new file mode 100644 index 00000000..5486bb0b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithCbuf : OpCodeFArith, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs new file mode 100644 index 00000000..1bb6f425 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm : OpCodeFArith, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFArithImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs new file mode 100644 index 00000000..ec9da6f3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm32 : OpCodeAlu, IOpCodeFArith, IOpCodeImmF + { + public RoundingMode RoundingMode => RoundingMode.ToNearest; + + public FmulScale Scale => FmulScale.None; + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public float Immediate { get; } + + public OpCodeFArithImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + int imm = opCode.Extract(20, 32); + + Immediate = BitConverter.Int32BitsToSingle(imm); + + SetCondCode = opCode.Extract(52); + AbsoluteA = opCode.Extract(54); + FlushToZero = opCode.Extract(55); + + Saturate = false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs new file mode 100644 index 00000000..55cf4485 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithReg : OpCodeFArith, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeFArithReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs new file mode 100644 index 00000000..315c2c8b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithRegCbuf : OpCodeFArith, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs new file mode 100644 index 00000000..cb5f155e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFsetImm : OpCodeSet, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFsetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs new file mode 100644 index 00000000..32f3cd7a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfma : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; protected set; } + + public FPHalfSwizzle SwizzleA { get; } + + public OpCodeHfma(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SwizzleA = (FPHalfSwizzle)opCode.Extract(47, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs new file mode 100644 index 00000000..33768c7d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP32; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs new file mode 100644 index 00000000..80a5a140 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm2x10 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs new file mode 100644 index 00000000..05eb9ffe --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm32 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate => false; + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP16; + + public OpCodeHfmaImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + NegateC = opCode.Extract(52); + + Rc = Rd; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs new file mode 100644 index 00000000..714c89de --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaReg : OpCodeHfma, IOpCodeHfma, IOpCodeReg + { + public Register Rb { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(28, 2); + + NegateC = opCode.Extract(30); + NegateB = opCode.Extract(31); + Saturate = opCode.Extract(32); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(35, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs new file mode 100644 index 00000000..c0001908 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaRegCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP32; + + public OpCodeHfmaRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs new file mode 100644 index 00000000..03e1e44c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHsetImm2x10 : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeHsetImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs new file mode 100644 index 00000000..b475b6a1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeIpa : OpCodeAluReg + { + public int AttributeOffset { get; } + + public InterpolationMode Mode { get; } + + public OpCodeIpa(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(28, 10); + + Saturate = opCode.Extract(51); + + Mode = (InterpolationMode)opCode.Extract(54, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs new file mode 100644 index 00000000..cc9f0658 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLdc : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeCbuf + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + public int Slot { get; } + + public IntegerSize Size { get; } + + public OpCodeLdc(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(22, 14); + Slot = opCode.Extract(36, 5); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs new file mode 100644 index 00000000..c5f90345 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLop : OpCodeAlu, IOpCodeLop + { + public bool InvertA { get; protected set; } + public bool InvertB { get; protected set; } + + public LogicalOperation LogicalOp { get; } + + public ConditionalOperation CondOp { get; } + + public Register Predicate48 { get; } + + public OpCodeLop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + InvertA = opCode.Extract(39); + InvertB = opCode.Extract(40); + + LogicalOp = (LogicalOperation)opCode.Extract(41, 2); + + CondOp = (ConditionalOperation)opCode.Extract(44, 2); + + Predicate48 = new Register(opCode.Extract(48, 3), RegisterType.Predicate); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs new file mode 100644 index 00000000..f174733c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopCbuf : OpCodeLop, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeLopCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs new file mode 100644 index 00000000..a2f091a2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm : OpCodeLop, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeLopImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs new file mode 100644 index 00000000..cb48f3a6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm32 : OpCodeAluImm32, IOpCodeLop, IOpCodeImm + { + public LogicalOperation LogicalOp { get; } + + public bool InvertA { get; } + public bool InvertB { get; } + + public OpCodeLopImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + LogicalOp = (LogicalOperation)opCode.Extract(53, 2); + + InvertA = opCode.Extract(55); + InvertB = opCode.Extract(56); + + Extended = opCode.Extract(57); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs new file mode 100644 index 00000000..5f43db72 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopReg : OpCodeLop, IOpCodeReg + { + public Register Rb { get; } + + public OpCodeLopReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs new file mode 100644 index 00000000..bece4562 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeMemory : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public IntegerSize Size { get; } + + public OpCodeMemory(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(20, 24); + + Extended = opCode.Extract(45); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodePsetp.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodePsetp.cs new file mode 100644 index 00000000..729e3207 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodePsetp.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodePsetp : OpCodeSet + { + public Register Predicate12 { get; } + public Register Predicate29 { get; } + + public LogicalOperation LogicalOpAB { get; } + + public OpCodePsetp(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate12 = new Register(opCode.Extract(12, 3), RegisterType.Predicate); + Predicate29 = new Register(opCode.Extract(29, 3), RegisterType.Predicate); + + LogicalOpAB = (LogicalOperation)opCode.Extract(24, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs new file mode 100644 index 00000000..b4ee10fb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSet : OpCodeAlu + { + public Register Predicate0 { get; } + public Register Predicate3 { get; } + + public bool NegateP { get; } + + public LogicalOperation LogicalOp { get; } + + public bool FlushToZero { get; } + + public OpCodeSet(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate0 = new Register(opCode.Extract(0, 3), RegisterType.Predicate); + Predicate3 = new Register(opCode.Extract(3, 3), RegisterType.Predicate); + + LogicalOp = (LogicalOperation)opCode.Extract(45, 2); + + FlushToZero = opCode.Extract(47); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs new file mode 100644 index 00000000..4f3dbd74 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetCbuf : OpCodeSet, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeSetCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs new file mode 100644 index 00000000..bc63b9f4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetImm : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeSetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs new file mode 100644 index 00000000..bbdee196 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetReg : OpCodeSet, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeSetReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSsy.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSsy.cs new file mode 100644 index 00000000..499c0706 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSsy.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSsy : OpCodeBranch + { + public Dictionary<OpCodeSync, Operand> Syncs { get; } + + public OpCodeSsy(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Syncs = new Dictionary<OpCodeSync, Operand>(); + + Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate); + + InvertPredicate = false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSync.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSync.cs new file mode 100644 index 00000000..081d08a0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSync.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSync : OpCode + { + public Dictionary<OpCodeSsy, int> Targets { get; } + + public OpCodeSync(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Targets = new Dictionary<OpCodeSsy, int>(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs new file mode 100644 index 00000000..3fd1de86 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs @@ -0,0 +1,227 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class OpCodeTable + { + private const int EncodingBits = 14; + + private class TableEntry + { + public InstEmitter Emitter { get; } + + public Type OpCodeType { get; } + + public int XBits { get; } + + public TableEntry(InstEmitter emitter, Type opCodeType, int xBits) + { + Emitter = emitter; + OpCodeType = opCodeType; + XBits = xBits; + } + } + + private static TableEntry[] _opCodes; + + static OpCodeTable() + { + _opCodes = new TableEntry[1 << EncodingBits]; + +#region Instructions + Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute)); + Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute)); + Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf)); + Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm)); + Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg)); + Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); + Set("0101000010100x", InstEmit.Csetp, typeof(OpCodePsetp)); + Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); + Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); + Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm)); + Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg)); + Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf)); + Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm)); + Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg)); + Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf)); + Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm)); + Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32)); + Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg)); + Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf)); + Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm)); + Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf)); + Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg)); + Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf)); + Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm)); + Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg)); + Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf)); + Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm)); + Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32)); + Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg)); + Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf)); + Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm)); + Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg)); + Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); + Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); + Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); + Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); + Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); + Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); + Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); + Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf)); + Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10)); + Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32)); + Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg)); + Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf)); + Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); + Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); + Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); + Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); + Set("0111111x1xxxxx", InstEmit.Hsetp2, typeof(OpCodeSetCbuf)); + Set("0111111x0xxxxx", InstEmit.Hsetp2, typeof(OpCodeHsetImm2x10)); + Set("0101110100100x", InstEmit.Hsetp2, typeof(OpCodeSetReg)); + Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); + Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); + Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); + Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf)); + Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm)); + Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg)); + Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf)); + Set("0011100000010x", InstEmit.Iadd, typeof(OpCodeAluImm)); + Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32)); + Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg)); + Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf)); + Set("001110001100xx", InstEmit.Iadd3, typeof(OpCodeAluImm)); + Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg)); + Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf)); + Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm)); + Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg)); + Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa)); + Set("1110111111010x", InstEmit.Isberd, typeof(OpCodeAlu)); + Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf)); + Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm)); + Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32)); + Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg)); + Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf)); + Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm)); + Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg)); + Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf)); + Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm)); + Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg)); + Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit)); + Set("1110111101000x", InstEmit.Ld, typeof(OpCodeMemory)); + Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc)); + Set("1110111011010x", InstEmit.Ldg, typeof(OpCodeMemory)); + Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf)); + Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm)); + Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32)); + Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg)); + Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf)); + Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm)); + Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg)); + Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf)); + Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm)); + Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32)); + Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); + Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); + Set("1111101111100x", InstEmit.Out, typeof(OpCode)); + Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePsetp)); + Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf)); + Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm)); + Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg)); + Set("1111000011001x", InstEmit.S2r, typeof(OpCodeAlu)); + Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf)); + Set("0011100x10100x", InstEmit.Sel, typeof(OpCodeAluImm)); + Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg)); + Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf)); + Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm)); + Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg)); + Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); + Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); + Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); + Set("111000101001xx", InstEmit.Ssy, typeof(OpCodeSsy)); + Set("1110111101010x", InstEmit.St, typeof(OpCodeMemory)); + Set("1110111011011x", InstEmit.Stg, typeof(OpCodeMemory)); + Set("1111000011111x", InstEmit.Sync, typeof(OpCodeSync)); + Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); + Set("1101111010111x", InstEmit.TexB, typeof(OpCodeTexB)); + Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); + Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds)); + Set("1101x11100xxxx", InstEmit.Texs, typeof(OpCodeTld4s)); + Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld)); + Set("11011101xx111x", InstEmit.TldB, typeof(OpCodeTld)); + Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4)); + Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex)); + Set("1101111101010x", InstEmit.TxqB, typeof(OpCodeTex)); + Set("01011111xxxxxx", InstEmit.Vmad, typeof(OpCodeVideo)); + Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf)); + Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm)); + Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf)); + Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg)); +#endregion + } + + private static void Set(string encoding, InstEmitter emitter, Type opCodeType) + { + if (encoding.Length != EncodingBits) + { + throw new ArgumentException(nameof(encoding)); + } + + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + + xPos[xBits++] = bit; + } + } + + xMask = ~xMask; + + TableEntry entry = new TableEntry(emitter, opCodeType, xBits); + + for (int index = 0; index < (1 << xBits); index++) + { + value &= xMask; + + for (int x = 0; x < xBits; x++) + { + value |= ((index >> x) & 1) << xPos[x]; + } + + if (_opCodes[value] == null || _opCodes[value].XBits > xBits) + { + _opCodes[value] = entry; + } + } + } + + public static (InstEmitter emitter, Type opCodeType) GetEmitter(long opCode) + { + TableEntry entry = _opCodes[(ulong)opCode >> (64 - EncodingBits)]; + + if (entry != null) + { + return (entry.Emitter, entry.OpCodeType); + } + + return (null, null); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs new file mode 100644 index 00000000..da8756b9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTex : OpCodeTexture + { + public OpCodeTex(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + HasOffset = opCode.Extract(54); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs new file mode 100644 index 00000000..b18bf3be --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexB : OpCodeTex + { + public OpCodeTexB(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + switch (opCode.Extract(37, 3)) + { + case 0: LodMode = TextureLodMode.None; break; + case 1: LodMode = TextureLodMode.LodZero; break; + case 2: LodMode = TextureLodMode.LodBias; break; + case 3: LodMode = TextureLodMode.LodLevel; break; + case 6: LodMode = TextureLodMode.LodBiasA; break; + case 7: LodMode = TextureLodMode.LodLevelA; break; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs new file mode 100644 index 00000000..fb90ccf6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexs : OpCodeTextureScalar + { + public TextureTarget Target => (TextureTarget)RawType; + + public OpCodeTexs(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs new file mode 100644 index 00000000..7a7e8f46 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexture : OpCode + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + + public bool IsArray { get; } + + public TextureDimensions Dimensions { get; } + + public int ComponentMask { get; } + + public int Immediate { get; } + + public TextureLodMode LodMode { get; protected set; } + + public bool HasOffset { get; protected set; } + public bool HasDepthCompare { get; protected set; } + public bool IsMultisample { get; protected set; } + + public OpCodeTexture(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + IsArray = opCode.Extract(28); + + Dimensions = (TextureDimensions)opCode.Extract(29, 2); + + ComponentMask = opCode.Extract(31, 4); + + Immediate = opCode.Extract(36, 13); + + LodMode = (TextureLodMode)opCode.Extract(55, 3); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs new file mode 100644 index 00000000..1c175e30 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs @@ -0,0 +1,62 @@ +// ReSharper disable InconsistentNaming +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTextureScalar : OpCode + { +#region "Component mask LUT" + private const int ____ = 0x0; + private const int R___ = 0x1; + private const int _G__ = 0x2; + private const int RG__ = 0x3; + private const int __B_ = 0x4; + private const int RGB_ = 0x7; + private const int ___A = 0x8; + private const int R__A = 0x9; + private const int _G_A = 0xa; + private const int RG_A = 0xb; + private const int __BA = 0xc; + private const int R_BA = 0xd; + private const int _GBA = 0xe; + private const int RGBA = 0xf; + + private static int[,] _maskLut = new int[,] + { + { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA }, + { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } + }; +#endregion + + public Register Rd0 { get; } + public Register Ra { get; } + public Register Rb { get; } + public Register Rd1 { get; } + + public int Immediate { get; } + + public int ComponentMask { get; protected set; } + + protected int RawType; + + public bool IsFp16 { get; } + + public OpCodeTextureScalar(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd0 = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + Rd1 = new Register(opCode.Extract(28, 8), RegisterType.Gpr); + + Immediate = opCode.Extract(36, 13); + + int compSel = opCode.Extract(50, 3); + + RawType = opCode.Extract(53, 4); + + IsFp16 = !opCode.Extract(59); + + ComponentMask = _maskLut[Rd1.IsRZ ? 0 : 1, compSel]; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs new file mode 100644 index 00000000..61bd900b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld : OpCodeTexture + { + public OpCodeTld(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasOffset = opCode.Extract(35); + + IsMultisample = opCode.Extract(50); + + bool isLL = opCode.Extract(55); + + LodMode = isLL + ? TextureLodMode.LodLevel + : TextureLodMode.LodZero; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs new file mode 100644 index 00000000..485edf93 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4 : OpCodeTexture + { + public TextureGatherOffset Offset { get; } + + public int GatherCompIndex { get; } + + public OpCodeTld4(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + Offset = (TextureGatherOffset)opCode.Extract(54, 2); + + GatherCompIndex = opCode.Extract(56, 2); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs new file mode 100644 index 00000000..7e51a9e5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4s : OpCodeTextureScalar + { + public bool HasDepthCompare { get; } + public bool HasOffset { get; } + + public int GatherCompIndex { get; } + + public OpCodeTld4s(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + HasOffset = opCode.Extract(51); + + GatherCompIndex = opCode.Extract(52, 2); + + ComponentMask = Rd1.IsRZ ? 3 : 0xf; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs new file mode 100644 index 00000000..1e4e943f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTlds : OpCodeTextureScalar + { + public TexelLoadTarget Target => (TexelLoadTarget)RawType; + + public OpCodeTlds(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs new file mode 100644 index 00000000..15dcfa98 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeVideo : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeVideo(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(55); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Register.cs b/Ryujinx.Graphics.Shader/Decoders/Register.cs new file mode 100644 index 00000000..30840d8c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Register.cs @@ -0,0 +1,36 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + struct Register : IEquatable<Register> + { + public int Index { get; } + + public RegisterType Type { get; } + + public bool IsRZ => Type == RegisterType.Gpr && Index == RegisterConsts.RegisterZeroIndex; + public bool IsPT => Type == RegisterType.Predicate && Index == RegisterConsts.PredicateTrueIndex; + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((ushort)Type << 16); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs new file mode 100644 index 00000000..d381f954 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class RegisterConsts + { + public const int GprsCount = 255; + public const int PredsCount = 7; + public const int FlagsCount = 4; + public const int TotalCount = GprsCount + PredsCount + FlagsCount; + + public const int RegisterZeroIndex = GprsCount; + public const int PredicateTrueIndex = PredsCount; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs new file mode 100644 index 00000000..648f816a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RegisterType + { + Flag, + Gpr, + Predicate, + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs b/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs new file mode 100644 index 00000000..13bb08dc --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RoundingMode + { + ToNearest = 0, + TowardsNegativeInfinity = 1, + TowardsPositiveInfinity = 2, + TowardsZero = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs new file mode 100644 index 00000000..3948c899 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum SystemRegister + { + ThreadIdX = 0x21, + ThreadIdY = 0x22, + ThreadIdZ = 0x23, + CtaIdX = 0x25, + CtaIdY = 0x26, + CtaIdZ = 0x27 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs new file mode 100644 index 00000000..478cac44 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TexelLoadTarget + { + Texture1DLodZero = 0x0, + Texture1DLodLevel = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodZeroOffset = 0x4, + Texture2DLodLevel = 0x5, + Texture2DLodZeroMultisample = 0x6, + Texture3DLodZero = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DLodLevelOffset = 0xc + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs new file mode 100644 index 00000000..dbdf1927 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureDimensions + { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCube = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs b/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs new file mode 100644 index 00000000..4e9ade26 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureGatherOffset + { + None = 0, + Offset = 1, + Offsets = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs b/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs new file mode 100644 index 00000000..0cc6f714 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureLodMode + { + None = 0, + LodZero = 1, + LodBias = 2, + LodLevel = 3, + LodBiasA = 4, //? + LodLevelA = 5 //? + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs b/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs new file mode 100644 index 00000000..ea35b1d1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureProperty + { + Dimensions = 0x1, + Type = 0x2, + SamplePos = 0x5, + Filter = 0xa, + Lod = 0xc, + Wrap = 0xe, + BorderColor = 0x10 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs new file mode 100644 index 00000000..181a0a0d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureTarget + { + Texture1DLodZero = 0x0, + Texture2D = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodLevel = 0x3, + Texture2DDepthCompare = 0x4, + Texture2DLodLevelDepthCompare = 0x5, + Texture2DLodZeroDepthCompare = 0x6, + Texture2DArray = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DArrayLodZeroDepthCompare = 0x9, + Texture3D = 0xa, + Texture3DLodZero = 0xb, + TextureCube = 0xc, + TextureCubeLodLevel = 0xd + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs b/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs new file mode 100644 index 00000000..949a2ef7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum XmadCMode + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + Cbcc = 4 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/DefineNames.cs b/Ryujinx.Graphics.Shader/DefineNames.cs new file mode 100644 index 00000000..f55cbd0c --- /dev/null +++ b/Ryujinx.Graphics.Shader/DefineNames.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader +{ + public static class DefineNames + { + public const string InputTopologyName = "S_INPUT_TOPOLOGY"; + + public const string OutQualifierPrefixName = "S_OUT_QUALIFIER"; + + public const string LocalSizeX = "S_LOCAL_SIZE_X"; + public const string LocalSizeY = "S_LOCAL_SIZE_Y"; + public const string LocalSizeZ = "S_LOCAL_SIZE_Z"; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs new file mode 100644 index 00000000..5cbb3b73 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs @@ -0,0 +1,708 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bfe(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = isSigned + ? context.BitfieldExtractS32(srcA, position, size) + : context.BitfieldExtractU32(srcA, position, size); + + context.Copy(GetDest(context), res); + + // TODO: CC, X, corner cases + } + + public static void Csetp(EmitterContext context) + { + OpCodePsetp op = (OpCodePsetp)context.CurrOp; + + // TODO: Implement that properly + + Operand p0Res = Const(IrConsts.True); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Iadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + else + { + // TODO: Other IADD32I variant without the negate. + negateA = op.RawOpCode.Extract(56); + } + + Operand srcA = context.INegate(GetSrcA(context), negateA); + Operand srcB = context.INegate(GetSrcB(context), negateB); + + Operand res = context.IAdd(srcA, srcB); + + bool isSubtraction = negateA || negateB; + + if (op.Extended) + { + // Add carry, or subtract borrow. + res = context.IAdd(res, isSubtraction + ? context.BitwiseNot(GetCF(context)) + : context.BitwiseAnd(GetCF(context), Const(1))); + } + + SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction); + + context.Copy(GetDest(context), res); + } + + public static void Iadd3(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerHalfPart partC = (IntegerHalfPart)op.RawOpCode.Extract(31, 2); + IntegerHalfPart partB = (IntegerHalfPart)op.RawOpCode.Extract(33, 2); + IntegerHalfPart partA = (IntegerHalfPart)op.RawOpCode.Extract(35, 2); + + IntegerShift mode = (IntegerShift)op.RawOpCode.Extract(37, 2); + + bool negateC = op.RawOpCode.Extract(49); + bool negateB = op.RawOpCode.Extract(50); + bool negateA = op.RawOpCode.Extract(51); + + Operand Extend(Operand src, IntegerHalfPart part) + { + if (!(op is OpCodeAluReg) || part == IntegerHalfPart.B32) + { + return src; + } + + if (part == IntegerHalfPart.H0) + { + return context.BitwiseAnd(src, Const(0xffff)); + } + else if (part == IntegerHalfPart.H1) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + // TODO: Warning. + } + + return src; + } + + Operand srcA = context.INegate(Extend(GetSrcA(context), partA), negateA); + Operand srcB = context.INegate(Extend(GetSrcB(context), partB), negateB); + Operand srcC = context.INegate(Extend(GetSrcC(context), partC), negateC); + + Operand res = context.IAdd(srcA, srcB); + + if (op is OpCodeAluReg && mode != IntegerShift.NoShift) + { + if (mode == IntegerShift.ShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + else if (mode == IntegerShift.ShiftRight) + { + res = context.ShiftRightU32(res, Const(16)); + } + else + { + // TODO: Warning. + } + } + + res = context.IAdd(res, srcC); + + context.Copy(GetDest(context), res); + + // TODO: CC, X, corner cases + } + + public static void Imnmx(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isSignedInt = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand resMin = isSignedInt + ? context.IMinimumS32(srcA, srcB) + : context.IMinimumU32(srcA, srcB); + + Operand resMax = isSignedInt + ? context.IMaximumS32(srcA, srcB) + : context.IMaximumU32(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetZnFlags(context, dest, op.SetCondCode); + + // TODO: X flags. + } + + public static void Iscadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + + int shift = op is OpCodeAluImm32 + ? op.RawOpCode.Extract(53, 5) + : op.RawOpCode.Extract(39, 5); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + srcA = context.ShiftLeft(srcA, Const(shift)); + + srcA = context.INegate(srcA, negateA); + srcB = context.INegate(srcB, negateB); + + Operand res = context.IAdd(srcA, srcB); + + context.Copy(GetDest(context), res); + + // TODO: CC, X + } + + public static void Iset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool boolFloat = op.RawOpCode.Extract(44); + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + + // TODO: CC, X + } + + public static void Isetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Lop(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = context.BitwiseNot(GetSrcA(context), op.InvertA); + Operand srcB = context.BitwiseNot(GetSrcB(context), op.InvertB); + + Operand res = srcB; + + switch (op.LogicalOp) + { + case LogicalOperation.And: res = context.BitwiseAnd (srcA, srcB); break; + case LogicalOperation.Or: res = context.BitwiseOr (srcA, srcB); break; + case LogicalOperation.ExclusiveOr: res = context.BitwiseExclusiveOr(srcA, srcB); break; + } + + EmitLopPredWrite(context, op, res); + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Lop3(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + bool regVariant = op is OpCodeLopReg; + + int truthTable = regVariant + ? op.RawOpCode.Extract(28, 8) + : op.RawOpCode.Extract(48, 8); + + Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable); + + if (regVariant) + { + EmitLopPredWrite(context, op, res); + } + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Psetp(EmitterContext context) + { + OpCodePsetp op = (OpCodePsetp)context.CurrOp; + + bool invertA = op.RawOpCode.Extract(15); + bool invertB = op.RawOpCode.Extract(32); + + Operand srcA = context.BitwiseNot(Register(op.Predicate12), invertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), invertB); + + Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Rro(EmitterContext context) + { + // This is the range reduction operator, + // we translate it as a simple move, as it + // should be always followed by a matching + // MUFU instruction. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + srcB = context.FPAbsNeg(srcB, absoluteB, negateB); + + context.Copy(GetDest(context), srcB); + } + + public static void Shl(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + + Operand srcB = GetSrcB(context); + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = context.ShiftLeft(GetSrcA(context), srcB); + + if (!isMasked) + { + // Clamped shift value. + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, Const(0)); + } + + // TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Shr(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = isSigned + ? context.ShiftRightS32(srcA, srcB) + : context.ShiftRightU32(srcA, srcB); + + if (!isMasked) + { + // Clamped shift value. + Operand resShiftBy32; + + if (isSigned) + { + resShiftBy32 = context.ShiftRightS32(srcA, Const(31)); + } + else + { + resShiftBy32 = Const(0); + } + + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, resShiftBy32); + } + + // TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Xmad(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool signedA = context.CurrOp.RawOpCode.Extract(48); + bool signedB = context.CurrOp.RawOpCode.Extract(49); + bool highA = context.CurrOp.RawOpCode.Extract(53); + bool highB = false; + + XmadCMode mode; + + if (op is OpCodeAluReg) + { + highB = context.CurrOp.RawOpCode.Extract(35); + + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 3); + } + else + { + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 2); + + if (!(op is OpCodeAluImm)) + { + highB = context.CurrOp.RawOpCode.Extract(52); + } + } + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + // XMAD immediates are 16-bits unsigned integers. + if (srcB.Type == OperandType.Constant) + { + srcB = Const(srcB.Value & 0xffff); + } + + Operand Extend16To32(Operand src, bool high, bool signed) + { + if (signed && high) + { + return context.ShiftRightS32(src, Const(16)); + } + else if (signed) + { + return context.BitfieldExtractS32(src, Const(0), Const(16)); + } + else if (high) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + return context.BitwiseAnd(src, Const(0xffff)); + } + } + + srcA = Extend16To32(srcA, highA, signedA); + srcB = Extend16To32(srcB, highB, signedB); + + bool productShiftLeft = false; + bool merge = false; + + if (!(op is OpCodeAluRegCbuf)) + { + productShiftLeft = context.CurrOp.RawOpCode.Extract(36); + merge = context.CurrOp.RawOpCode.Extract(37); + } + + bool extended; + + if ((op is OpCodeAluReg) || (op is OpCodeAluImm)) + { + extended = context.CurrOp.RawOpCode.Extract(38); + } + else + { + extended = context.CurrOp.RawOpCode.Extract(54); + } + + Operand res = context.IMultiply(srcA, srcB); + + if (productShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + + switch (mode) + { + case XmadCMode.Cfull: break; + + case XmadCMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break; + case XmadCMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break; + + case XmadCMode.Cbcc: + { + srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16))); + + break; + } + + case XmadCMode.Csfu: + { + Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16)); + Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16)); + + srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB)); + + break; + } + + default: /* TODO: Warning */ break; + } + + Operand product = res; + + if (extended) + { + // Add with carry. + res = context.IAdd(res, context.BitwiseAnd(GetCF(context), Const(1))); + } + else + { + // Add (no carry in). + res = context.IAdd(res, srcC); + } + + SetIaddFlags(context, res, product, srcC, op.SetCondCode, extended); + + if (merge) + { + res = context.BitwiseAnd(res, Const(0xffff)); + res = context.BitwiseOr(res, context.ShiftLeft(GetSrcB(context), Const(16))); + } + + context.Copy(GetDest(context), res); + } + + private static Operand GetIntComparison( + EmitterContext context, + IntegerCondition cond, + Operand srcA, + Operand srcB, + bool isSigned) + { + Operand res; + + if (cond == IntegerCondition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == IntegerCondition.Never) + { + res = Const(IrConsts.False); + } + else + { + Instruction inst; + + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLessU32; break; + case IntegerCondition.Equal: inst = Instruction.CompareEqual; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqualU32; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreaterU32; break; + case IntegerCondition.NotEqual: inst = Instruction.CompareNotEqual; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqualU32; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + if (isSigned) + { + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLess; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreater; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + } + } + + res = context.Add(inst, Local(), srcA, srcB); + } + + return res; + } + + private static void EmitLopPredWrite(EmitterContext context, IOpCodeLop op, Operand result) + { + if (op is OpCodeLop opLop && !opLop.Predicate48.IsPT) + { + Operand pRes; + + if (opLop.CondOp == ConditionalOperation.False) + { + pRes = Const(IrConsts.False); + } + else if (opLop.CondOp == ConditionalOperation.True) + { + pRes = Const(IrConsts.True); + } + else if (opLop.CondOp == ConditionalOperation.Zero) + { + pRes = context.ICompareEqual(result, Const(0)); + } + else /* if (opLop.CondOp == ConditionalOperation.NotZero) */ + { + pRes = context.ICompareNotEqual(result, Const(0)); + } + + context.Copy(Register(opLop.Predicate48), pRes); + } + } + + private static void SetIaddFlags( + EmitterContext context, + Operand res, + Operand srcA, + Operand srcB, + bool setCC, + bool extended, + bool isSubtraction = false) + { + if (!setCC) + { + return; + } + + if (!extended || isSubtraction) + { + // C = d < a + context.Copy(GetCF(context), context.ICompareLessUnsigned(res, srcA)); + } + else + { + // C = (d == a && CIn) || d < a + Operand tempC0 = context.ICompareEqual (res, srcA); + Operand tempC1 = context.ICompareLessUnsigned(res, srcA); + + tempC0 = context.BitwiseAnd(tempC0, GetCF(context)); + + context.Copy(GetCF(context), context.BitwiseOr(tempC0, tempC1)); + } + + // V = (d ^ a) & ~(a ^ b) < 0 + Operand tempV0 = context.BitwiseExclusiveOr(res, srcA); + Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB); + + tempV1 = context.BitwiseNot(tempV1); + + Operand tempV = context.BitwiseAnd(tempV0, tempV1); + + context.Copy(GetVF(context), context.ICompareLess(tempV, Const(0))); + + SetZnFlags(context, res, setCC: true, extended: extended); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs new file mode 100644 index 00000000..5c4f5398 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,88 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitAluHelper + { + public static int GetIntMin(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MinValue; + case IntegerType.S8: return sbyte.MinValue; + case IntegerType.U16: return ushort.MinValue; + case IntegerType.S16: return short.MinValue; + case IntegerType.U32: return (int)uint.MinValue; + case IntegerType.S32: return int.MinValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static int GetIntMax(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MaxValue; + case IntegerType.S8: return sbyte.MaxValue; + case IntegerType.U16: return ushort.MaxValue; + case IntegerType.S16: return short.MaxValue; + case IntegerType.U32: return unchecked((int)uint.MaxValue); + case IntegerType.S32: return int.MaxValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static Operand GetPredLogicalOp( + EmitterContext context, + LogicalOperation logicalOp, + Operand input, + Operand pred) + { + switch (logicalOp) + { + case LogicalOperation.And: return context.BitwiseAnd (input, pred); + case LogicalOperation.Or: return context.BitwiseOr (input, pred); + case LogicalOperation.ExclusiveOr: return context.BitwiseExclusiveOr(input, pred); + } + + return input; + } + + public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false) + { + if (!setCC) + { + return; + } + + if (extended) + { + // When the operation is extended, it means we are doing + // the operation on a long word with any number of bits, + // so we need to AND the zero flag from result with the + // previous result when extended is specified, to ensure + // we have ZF set only if all words are zero, and not just + // the last one. + Operand oldZF = GetZF(context); + + Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF); + + context.Copy(GetZF(context), res); + } + else + { + context.Copy(GetZF(context), context.ICompareEqual(dest, Const(0))); + } + + context.Copy(GetNF(context), context.ICompareLess(dest, Const(0))); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs new file mode 100644 index 00000000..c4de1750 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs @@ -0,0 +1,213 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void F2F(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + FPType srcType = (FPType)op.RawOpCode.Extract(8, 2); + FPType dstType = (FPType)op.RawOpCode.Extract(10, 2); + + bool pass = op.RawOpCode.Extract(40); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + pass &= op.RoundingMode == RoundingMode.TowardsNegativeInfinity; + + Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB); + + if (!pass) + { + switch (op.RoundingMode) + { + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + } + + srcB = context.FPSaturate(srcB, op.Saturate); + + WriteFP(context, dstType, srcB); + + // TODO: CC. + } + + public static void F2I(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2); + + bool isSmallInt = intType <= IntegerType.U16; + + FPType floatType = (FPType)op.RawOpCode.Extract(10, 2); + + bool isSignedInt = op.RawOpCode.Extract(12); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + if (isSignedInt) + { + intType |= IntegerType.S8; + } + + Operand srcB = context.FPAbsNeg(GetSrcB(context, floatType), absoluteB, negateB); + + switch (op.RoundingMode) + { + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + + srcB = context.FPConvertToS32(srcB); + + // TODO: S/U64, conversion overflow handling. + if (intType != IntegerType.S32) + { + int min = GetIntMin(intType); + int max = GetIntMax(intType); + + srcB = isSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + Operand dest = GetDest(context); + + context.Copy(dest, srcB); + + // TODO: CC. + } + + public static void I2F(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + FPType dstType = (FPType)op.RawOpCode.Extract(8, 2); + + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + bool isSmallInt = srcType <= IntegerType.U16; + + bool isSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = context.IAbsNeg(GetSrcB(context), absoluteB, negateB); + + if (isSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = isSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = isSignedInt + ? context.IConvertS32ToFP(srcB) + : context.IConvertU32ToFP(srcB); + + WriteFP(context, dstType, srcB); + + // TODO: CC. + } + + public static void I2I(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerType dstType = (IntegerType)op.RawOpCode.Extract(8, 2); + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + if (srcType == IntegerType.U64 || dstType == IntegerType.U64) + { + // TODO: Warning. This instruction doesn't support 64-bits integers + } + + bool srcIsSmallInt = srcType <= IntegerType.U16; + + bool dstIsSignedInt = op.RawOpCode.Extract(12); + bool srcIsSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + if (srcIsSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = srcIsSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = context.IAbsNeg(srcB, absoluteB, negateB); + + if (op.Saturate) + { + if (dstIsSignedInt) + { + dstType |= IntegerType.S8; + } + + int min = GetIntMin(dstType); + int max = GetIntMax(dstType); + + srcB = dstIsSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + context.Copy(GetDest(context), srcB); + + // TODO: CC. + } + + private static void WriteFP(EmitterContext context, FPType type, Operand srcB) + { + Operand dest = GetDest(context); + + if (type == FPType.FP32) + { + context.Copy(dest, srcB); + } + else if (type == FPType.FP16) + { + context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0))); + } + else + { + // TODO. + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs new file mode 100644 index 00000000..2ecdee27 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs @@ -0,0 +1,403 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Fadd(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA, absoluteB, negateA, negateB; + + if (op is OpCodeFArithImm32) + { + negateB = op.RawOpCode.Extract(53); + negateA = op.RawOpCode.Extract(56); + absoluteB = op.RawOpCode.Extract(57); + } + else + { + negateB = op.RawOpCode.Extract(45); + negateA = op.RawOpCode.Extract(48); + absoluteB = op.RawOpCode.Extract(49); + } + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPAdd(srcA, srcB), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Ffma(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + bool negateC = op.RawOpCode.Extract(49); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + Operand srcC = context.FPNegate(GetSrcC(context), negateC); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmnmx(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA; + bool negateB = op.RawOpCode.Extract(45); + bool negateA = op.RawOpCode.Extract(48); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand resMin = context.FPMinimum(srcA, srcB); + Operand resMax = context.FPMaximum(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmul(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = !(op is OpCodeFArithImm32) && op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + + switch (op.Scale) + { + case FmulScale.None: break; + + case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; + case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; + case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; + case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; + case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; + case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; + + default: break; //TODO: Warning. + } + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + bool boolFloat = op.RawOpCode.Extract(52); + bool negateB = op.RawOpCode.Extract(53); + bool absoluteA = op.RawOpCode.Extract(54); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + + // TODO: CC, X + } + + public static void Fsetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool negateB = op.RawOpCode.Extract(6); + bool absoluteA = op.RawOpCode.Extract(7); + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Hadd2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: true); + } + + public static void Hfma2(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] srcA = GetHfmaSrcA(context); + Operand[] srcB = GetHfmaSrcB(context); + Operand[] srcC = GetHfmaSrcC(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]); + + res[index] = context.FPSaturate(res[index], op.Saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Hmul2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: false); + } + + private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd) + { + OpCode op = context.CurrOp; + + bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + if (isAdd) + { + res[index] = context.FPAdd(srcA[index], srcB[index]); + } + else + { + res[index] = context.FPMultiply(srcA[index], srcB[index]); + } + + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Hsetp2(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool hAnd = op.RawOpCode.Extract(53); + + Condition cmpOp = op is IOpCodeReg + ? (Condition)op.RawOpCode.Extract(35, 4) + : (Condition)op.RawOpCode.Extract(49, 4); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + Operand pred = GetPredicate39(context); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + Operand p1Res = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + if (hAnd) + { + p0Res = context.BitwiseAnd(p0Res, p1Res); + p1Res = context.BitwiseNot(p0Res); + } + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Mufu(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + + Operand res = context.FPAbsNeg(GetSrcA(context), op.AbsoluteA, negateB); + + MufuOperation subOp = (MufuOperation)context.CurrOp.RawOpCode.Extract(20, 4); + + switch (subOp) + { + case MufuOperation.Cosine: + res = context.FPCosine(res); + break; + + case MufuOperation.Sine: + res = context.FPSine(res); + break; + + case MufuOperation.ExponentB2: + res = context.FPExponentB2(res); + break; + + case MufuOperation.LogarithmB2: + res = context.FPLogarithmB2(res); + break; + + case MufuOperation.Reciprocal: + res = context.FPReciprocal(res); + break; + + case MufuOperation.ReciprocalSquareRoot: + res = context.FPReciprocalSquareRoot(res); + break; + + case MufuOperation.SquareRoot: + res = context.FPSquareRoot(res); + break; + + default: /* TODO */ break; + } + + context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate)); + } + + private static Operand GetFPComparison( + EmitterContext context, + Condition cond, + Operand srcA, + Operand srcB) + { + Operand res; + + if (cond == Condition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == Condition.Never) + { + res = Const(IrConsts.False); + } + else if (cond == Condition.Nan || cond == Condition.Number) + { + res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB)); + + if (cond == Condition.Number) + { + res = context.BitwiseNot(res); + } + } + else + { + Instruction inst; + + switch (cond & ~Condition.Nan) + { + case Condition.Less: inst = Instruction.CompareLess; break; + case Condition.Equal: inst = Instruction.CompareEqual; break; + case Condition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case Condition.Greater: inst = Instruction.CompareGreater; break; + case Condition.NotEqual: inst = Instruction.CompareNotEqual; break; + case Condition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + res = context.Add(inst | Instruction.FP, Local(), srcA, srcB); + + if ((cond & Condition.Nan) != 0) + { + res = context.BitwiseOr(res, context.IsNan(srcA)); + res = context.BitwiseOr(res, context.IsNan(srcB)); + } + } + + return res; + } + + private static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC) + { + if (setCC) + { + context.Copy(GetZF(context), context.FPCompareEqual(dest, ConstF(0))); + context.Copy(GetNF(context), context.FPCompareLess (dest, ConstF(0))); + } + } + + private static Operand[] GetHfmaSrcA(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + return GetHalfUnpacked(context, GetSrcA(context), op.SwizzleA); + } + + private static Operand[] GetHfmaSrcB(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), op.SwizzleB); + + return FPAbsNeg(context, operands, false, op.NegateB); + } + + private static Operand[] GetHfmaSrcC(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfUnpacked(context, GetSrcC(context), op.SwizzleC); + + return FPAbsNeg(context, operands, false, op.NegateC); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs new file mode 100644 index 00000000..fb76e06a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bra(EmitterContext context) + { + EmitBranch(context, context.CurrBlock.Branch.Address); + } + + public static void Exit(EmitterContext context) + { + OpCodeExit op = (OpCodeExit)context.CurrOp; + + // TODO: Figure out how this is supposed to work in the + // presence of other condition codes. + if (op.Condition == Condition.Always) + { + context.Return(); + } + } + + public static void Kil(EmitterContext context) + { + context.Discard(); + } + + public static void Ssy(EmitterContext context) + { + OpCodeSsy op = (OpCodeSsy)context.CurrOp; + + foreach (KeyValuePair<OpCodeSync, Operand> kv in op.Syncs) + { + OpCodeSync opSync = kv.Key; + + Operand local = kv.Value; + + int ssyIndex = opSync.Targets[op]; + + context.Copy(local, Const(ssyIndex)); + } + } + + public static void Sync(EmitterContext context) + { + OpCodeSync op = (OpCodeSync)context.CurrOp; + + if (op.Targets.Count == 1) + { + // If we have only one target, then the SSY is basically + // a branch, we can produce better codegen for this case. + OpCodeSsy opSsy = op.Targets.Keys.First(); + + EmitBranch(context, opSsy.GetAbsoluteAddress()); + } + else + { + foreach (KeyValuePair<OpCodeSsy, int> kv in op.Targets) + { + OpCodeSsy opSsy = kv.Key; + + Operand label = context.GetLabel(opSsy.GetAbsoluteAddress()); + + Operand local = opSsy.Syncs[op]; + + int ssyIndex = kv.Value; + + context.BranchIfTrue(label, context.ICompareEqual(local, Const(ssyIndex))); + } + } + } + + private static void EmitBranch(EmitterContext context, ulong address) + { + // If we're branching to the next instruction, then the branch + // is useless and we can ignore it. + if (address == context.CurrOp.Address + 8) + { + return; + } + + Operand label = context.GetLabel(address); + + Operand pred = Register(context.CurrOp.Predicate); + + if (context.CurrOp.Predicate.IsPT) + { + context.Branch(label); + } + else if (context.CurrOp.InvertPredicate) + { + context.BranchIfFalse(label, pred); + } + else + { + context.BranchIfTrue(label, pred); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs new file mode 100644 index 00000000..c0a3012a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs @@ -0,0 +1,267 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitHelper + { + public static Operand GetZF(EmitterContext context) + { + return Register(0, RegisterType.Flag); + } + + public static Operand GetNF(EmitterContext context) + { + return Register(1, RegisterType.Flag); + } + + public static Operand GetCF(EmitterContext context) + { + return Register(2, RegisterType.Flag); + } + + public static Operand GetVF(EmitterContext context) + { + return Register(3, RegisterType.Flag); + } + + public static Operand GetDest(EmitterContext context) + { + return Register(((IOpCodeRd)context.CurrOp).Rd); + } + + public static Operand GetSrcA(EmitterContext context) + { + return Register(((IOpCodeRa)context.CurrOp).Ra); + } + + public static Operand GetSrcB(EmitterContext context, FPType floatType) + { + if (floatType == FPType.FP32) + { + return GetSrcB(context); + } + else if (floatType == FPType.FP16) + { + int h = context.CurrOp.RawOpCode.Extract(41, 1); + + return GetHalfUnpacked(context, GetSrcB(context), FPHalfSwizzle.FP16)[h]; + } + else if (floatType == FPType.FP64) + { + // TODO. + } + + throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + } + + public static Operand GetSrcB(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeImm op: + return Const(op.Immediate); + + case IOpCodeImmF op: + return ConstF(op.Immediate); + + case IOpCodeReg op: + return Register(op.Rb); + + case IOpCodeRegCbuf op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand GetSrcC(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeRegCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeRc op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand[] GetHalfSrcA(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool absoluteA = false, negateA = false; + + if (op is IOpCodeCbuf || op is IOpCodeImm) + { + negateA = op.RawOpCode.Extract(43); + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is IOpCodeReg) + { + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is OpCodeAluImm32 && op.Emitter == InstEmit.Hadd2) + { + negateA = op.RawOpCode.Extract(56); + } + + FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2); + + Operand[] operands = GetHalfUnpacked(context, GetSrcA(context), swizzle); + + return FPAbsNeg(context, operands, absoluteA, negateA); + } + + public static Operand[] GetHalfSrcB(EmitterContext context) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + bool absoluteB = false, negateB = false; + + if (op is IOpCodeReg) + { + swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2); + + absoluteB = op.RawOpCode.Extract(30); + negateB = op.RawOpCode.Extract(31); + } + else if (op is IOpCodeCbuf) + { + swizzle = FPHalfSwizzle.FP32; + + absoluteB = op.RawOpCode.Extract(54); + } + + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), swizzle); + + return FPAbsNeg(context, operands, absoluteB, negateB); + } + + public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg) + { + for (int index = 0; index < operands.Length; index++) + { + operands[index] = context.FPAbsNeg(operands[index], abs, neg); + } + + return operands; + } + + public static Operand[] GetHalfUnpacked(EmitterContext context, Operand src, FPHalfSwizzle swizzle) + { + switch (swizzle) + { + case FPHalfSwizzle.FP16: + return new Operand[] + { + context.UnpackHalf2x16Low (src), + context.UnpackHalf2x16High(src) + }; + + case FPHalfSwizzle.FP32: return new Operand[] { src, src }; + + case FPHalfSwizzle.DupH0: + return new Operand[] + { + context.UnpackHalf2x16Low(src), + context.UnpackHalf2x16Low(src) + }; + + case FPHalfSwizzle.DupH1: + return new Operand[] + { + context.UnpackHalf2x16High(src), + context.UnpackHalf2x16High(src) + }; + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfPacked(EmitterContext context, Operand[] results) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + if (!(op is OpCodeAluImm32)) + { + swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2); + } + + switch (swizzle) + { + case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]); + + case FPHalfSwizzle.FP32: return results[0]; + + case FPHalfSwizzle.DupH0: + { + Operand h1 = GetHalfDest(context, isHigh: true); + + return context.PackHalf2x16(results[0], h1); + } + + case FPHalfSwizzle.DupH1: + { + Operand h0 = GetHalfDest(context, isHigh: false); + + return context.PackHalf2x16(h0, results[1]); + } + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfDest(EmitterContext context, bool isHigh) + { + if (isHigh) + { + return context.UnpackHalf2x16High(GetDest(context)); + } + else + { + return context.UnpackHalf2x16Low(GetDest(context)); + } + } + + public static Operand GetPredicate39(EmitterContext context) + { + IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + + Operand local = Register(op.Predicate39); + + if (op.InvertP) + { + local = context.BitwiseNot(local); + } + + return local; + } + + public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits) + { + return context.BitfieldExtractS32(src, Const(0), Const(srcBits)); + } + + public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits) + { + int mask = (int)(0xffffffffu >> (32 - srcBits)); + + return context.BitwiseAnd(src, Const(mask)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs new file mode 100644 index 00000000..ee210f22 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,325 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Ald(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + Operand primVertex = context.Copy(GetSrcC(context)); + + for (int index = 0; index < op.Count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand src = Attribute(op.AttributeOffset + index * 4); + + context.Copy(Register(rd), context.LoadAttribute(src, primVertex)); + } + } + + public static void Ast(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + for (int index = 0; index < op.Count; index++) + { + if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex) + { + break; + } + + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand dest = Attribute(op.AttributeOffset + index * 4); + + context.Copy(dest, Register(rd)); + } + } + + public static void Ipa(EmitterContext context) + { + OpCodeIpa op = (OpCodeIpa)context.CurrOp; + + InterpolationQualifier iq = InterpolationQualifier.None; + + switch (op.Mode) + { + case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break; + } + + Operand srcA = Attribute(op.AttributeOffset, iq); + + Operand srcB = GetSrcB(context); + + Operand res = context.FPSaturate(srcA, op.Saturate); + + context.Copy(GetDest(context), res); + } + + public static void Isberd(EmitterContext context) + { + // This instruction performs a load from ISBE memory, + // however it seems to be only used to get some vertex + // input data, so we instead propagate the offset so that + // it can be used on the attribute load. + context.Copy(GetDest(context), GetSrcA(context)); + } + + public static void Ld(EmitterContext context) + { + LoadLocalOrGlobal(context, isGlobal: false); + } + + public static void Ldc(EmitterContext context) + { + OpCodeLdc op = (OpCodeLdc)context.CurrOp; + + if (op.Size > IntegerSize.B64) + { + // TODO: Warning. + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = op.Size == IntegerSize.B64 ? 2 : 1; + + Operand wordOffset = context.ShiftRightU32(GetSrcA(context), Const(2)); + + wordOffset = context.IAdd(wordOffset, Const(op.Offset)); + + Operand bitOffset = GetBitOffset(context, GetSrcA(context)); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = context.LoadConstant(Const(op.Slot), offset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, wordOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + public static void Ldg(EmitterContext context) + { + LoadLocalOrGlobal(context, isGlobal: true); + } + + public static void Out(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool emit = op.RawOpCode.Extract(39); + bool cut = op.RawOpCode.Extract(40); + + if (!(emit || cut)) + { + // TODO: Warning. + } + + if (emit) + { + context.EmitVertex(); + } + + if (cut) + { + context.EndPrimitive(); + } + } + + public static void St(EmitterContext context) + { + StoreLocalOrGlobal(context, isGlobal: false); + } + + public static void Stg(EmitterContext context) + { + StoreLocalOrGlobal(context, isGlobal: true); + } + + private static void LoadLocalOrGlobal(EmitterContext context, bool isGlobal) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + // TODO: Warning. + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + // Word offset = byte offset / 4 (one word = 4 bytes). + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = isGlobal + ? context.LoadGlobal(offset) + : context.LoadLocal (offset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + private static void StoreLocalOrGlobal(EmitterContext context, bool isGlobal) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + // TODO: Warning. + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand value = Register(rd); + + Operand offset = context.IAdd(wordOffset, Const(index)); + + if (isSmallInt) + { + Operand word = isGlobal + ? context.LoadGlobal(offset) + : context.LoadLocal (offset); + + value = InsertSmallInt(context, op.Size, bitOffset, word, value); + } + + if (isGlobal) + { + context.StoreGlobal(offset, value); + } + else + { + context.StoreLocal(offset, value); + } + } + } + + private static Operand GetBitOffset(EmitterContext context, Operand baseOffset) + { + // Note: byte offset = (baseOffset & 0b11) * 8. + // Addresses should be always aligned to the integer type, + // so we don't need to take unaligned addresses into account. + return context.ShiftLeft(context.BitwiseAnd(baseOffset, Const(3)), Const(3)); + } + + private static Operand ExtractSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand value) + { + value = context.ShiftRightU32(value, bitOffset); + + switch (size) + { + case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break; + case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break; + case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break; + case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break; + } + + return value; + } + + private static Operand InsertSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand word, + Operand value) + { + switch (size) + { + case IntegerSize.U8: + case IntegerSize.S8: + value = context.BitwiseAnd(value, Const(0xff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(8)); + break; + + case IntegerSize.U16: + case IntegerSize.S16: + value = context.BitwiseAnd(value, Const(0xffff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(16)); + break; + } + + return value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs new file mode 100644 index 00000000..f66ebc9f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Mov(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + context.Copy(GetDest(context), GetSrcB(context)); + } + + public static void S2r(EmitterContext context) + { + // TODO: Better impl. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SystemRegister sysReg = (SystemRegister)op.RawOpCode.Extract(20, 8); + + Operand src; + + switch (sysReg) + { + case SystemRegister.ThreadIdX: src = Attribute(AttributeConsts.ThreadIdX); break; + case SystemRegister.ThreadIdY: src = Attribute(AttributeConsts.ThreadIdY); break; + case SystemRegister.ThreadIdZ: src = Attribute(AttributeConsts.ThreadIdZ); break; + case SystemRegister.CtaIdX: src = Attribute(AttributeConsts.CtaIdX); break; + case SystemRegister.CtaIdY: src = Attribute(AttributeConsts.CtaIdY); break; + case SystemRegister.CtaIdZ: src = Attribute(AttributeConsts.CtaIdZ); break; + + default: src = Const(0); break; + } + + context.Copy(GetDest(context), src); + } + + public static void Sel(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = context.ConditionalSelect(pred, srcA, srcB); + + context.Copy(GetDest(context), res); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs new file mode 100644 index 00000000..554deb7b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -0,0 +1,776 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Tex(EmitterContext context) + { + Tex(context, TextureFlags.None); + } + + public static void TexB(EmitterContext context) + { + Tex(context, TextureFlags.Bindless); + } + + public static void Tld(EmitterContext context) + { + Tex(context, TextureFlags.IntCoords); + } + + public static void TldB(EmitterContext context) + { + Tex(context, TextureFlags.IntCoords | TextureFlags.Bindless); + } + + public static void Texs(EmitterContext context) + { + OpCodeTextureScalar op = (OpCodeTextureScalar)context.CurrOp; + + if (op.Rd0.IsRZ && op.Rd1.IsRZ) + { + return; + } + + List<Operand> sourcesList = new List<Operand>(); + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + void AddTextureOffset(int coordsCount, int stride, int size) + { + Operand packedOffs = Rb(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size))); + } + } + + TextureTarget type; + TextureFlags flags; + + if (op is OpCodeTexs texsOp) + { + type = GetTextureType (texsOp.Target); + flags = GetTextureFlags(texsOp.Target); + + if ((type & TextureTarget.Array) != 0) + { + Operand arrayIndex = Ra(); + + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + + sourcesList.Add(arrayIndex); + + if ((type & TextureTarget.Shadow) != 0) + { + sourcesList.Add(Rb()); + } + + if ((flags & TextureFlags.LodLevel) != 0) + { + sourcesList.Add(ConstF(0)); + } + } + else + { + switch (texsOp.Target) + { + case Decoders.TextureTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + break; + + case Decoders.TextureTarget.Texture2D: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case Decoders.TextureTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case Decoders.TextureTarget.Texture2DLodLevel: + case Decoders.TextureTarget.Texture2DDepthCompare: + case Decoders.TextureTarget.Texture3D: + case Decoders.TextureTarget.TextureCube: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case Decoders.TextureTarget.Texture2DLodZeroDepthCompare: + case Decoders.TextureTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case Decoders.TextureTarget.Texture2DLodLevelDepthCompare: + case Decoders.TextureTarget.TextureCubeLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + break; + } + } + } + else if (op is OpCodeTlds tldsOp) + { + type = GetTextureType (tldsOp.Target); + flags = GetTextureFlags(tldsOp.Target) | TextureFlags.IntCoords; + + switch (tldsOp.Target) + { + case TexelLoadTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture1DLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DLodZeroOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DArrayLodZero: + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + } + + if ((flags & TextureFlags.Offset) != 0) + { + AddTextureOffset(type.GetDimensions(), 4, 4); + } + } + else if (op is OpCodeTld4s tld4sOp) + { + if (!(tld4sOp.HasDepthCompare || tld4sOp.HasOffset)) + { + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + } + else + { + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + } + + type = TextureTarget.Texture2D; + flags = TextureFlags.Gather; + + if (tld4sOp.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureTarget.Shadow; + } + + if (tld4sOp.HasOffset) + { + AddTextureOffset(type.GetDimensions(), 8, 6); + + flags |= TextureFlags.Offset; + } + + sourcesList.Add(Const(tld4sOp.GatherCompIndex)); + } + else + { + throw new InvalidOperationException($"Invalid opcode type \"{op.GetType().Name}\"."); + } + + Operand[] sources = sourcesList.ToArray(); + + Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) }; + Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) }; + + int destIncrement = 0; + + Operand GetDest() + { + int high = destIncrement >> 1; + int low = destIncrement & 1; + + destIncrement++; + + if (op.IsFp16) + { + return high != 0 + ? (rd1[low] = Local()) + : (rd0[low] = Local()); + } + else + { + int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index; + + if (rdIndex < RegisterConsts.RegisterZeroIndex) + { + rdIndex += low; + } + + return Register(rdIndex, RegisterType.Gpr); + } + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + + if (op.IsFp16) + { + context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1])); + context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1])); + } + } + + public static void Tld4(EmitterContext context) + { + OpCodeTld4 op = (OpCodeTld4)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List<Operand> sourcesList = new List<Operand>(); + + TextureTarget type = GetTextureType(op.Dimensions); + + TextureFlags flags = TextureFlags.Gather; + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= TextureTarget.Array; + } + + Operand[] packedOffs = new Operand[2]; + + packedOffs[0] = op.Offset != TextureGatherOffset.None ? Rb() : null; + packedOffs[1] = op.Offset == TextureGatherOffset.Offsets ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureTarget.Shadow; + } + + if (op.Offset != TextureGatherOffset.None) + { + int offsetTexelsCount = op.Offset == TextureGatherOffset.Offsets ? 4 : 1; + + for (int index = 0; index < coordsCount * offsetTexelsCount; index++) + { + Operand packed = packedOffs[(index >> 2) & 1]; + + sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6))); + } + + flags |= op.Offset == TextureGatherOffset.Offsets + ? TextureFlags.Offsets + : TextureFlags.Offset; + } + + sourcesList.Add(Const(op.GatherCompIndex)); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + public static void Txq(EmitterContext context) + { + Txq(context, bindless: false); + } + + public static void TxqB(EmitterContext context) + { + Txq(context, bindless: true); + } + + private static void Txq(EmitterContext context, bool bindless) + { + OpCodeTex op = (OpCodeTex)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + TextureProperty property = (TextureProperty)op.RawOpCode.Extract(22, 6); + + // TODO: Validate and use property. + Instruction inst = Instruction.TextureSize; + + TextureTarget type = TextureTarget.Texture2D; + + TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None; + + int raIndex = op.Ra.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + List<Operand> sourcesList = new List<Operand>(); + + if (bindless) + { + sourcesList.Add(Ra()); + } + + sourcesList.Add(Ra()); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !bindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + inst, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static void Tex(EmitterContext context, TextureFlags flags) + { + OpCodeTexture op = (OpCodeTexture)context.CurrOp; + + bool isBindless = (flags & TextureFlags.Bindless) != 0; + bool intCoords = (flags & TextureFlags.IntCoords) != 0; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List<Operand> sourcesList = new List<Operand>(); + + if (isBindless) + { + sourcesList.Add(Rb()); + } + + TextureTarget type = GetTextureType(op.Dimensions); + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= TextureTarget.Array; + } + + bool hasLod = op.LodMode > TextureLodMode.LodZero; + + Operand lodValue = hasLod ? Rb() : ConstF(0); + + Operand packedOffs = op.HasOffset ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= TextureTarget.Shadow; + } + + if ((op.LodMode == TextureLodMode.LodZero || + op.LodMode == TextureLodMode.LodLevel || + op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodLevel; + } + + if (op.HasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4))); + } + + flags |= TextureFlags.Offset; + } + + if (op.LodMode == TextureLodMode.LodBias || + op.LodMode == TextureLodMode.LodBiasA) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodBias; + } + + if (op.IsMultisample) + { + sourcesList.Add(Rb()); + + type |= TextureTarget.Multisample; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !isBindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static TextureTarget GetTextureType(TextureDimensions dimensions) + { + switch (dimensions) + { + case TextureDimensions.Texture1D: return TextureTarget.Texture1D; + case TextureDimensions.Texture2D: return TextureTarget.Texture2D; + case TextureDimensions.Texture3D: return TextureTarget.Texture3D; + case TextureDimensions.TextureCube: return TextureTarget.TextureCube; + } + + throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\"."); + } + + private static TextureTarget GetTextureType(Decoders.TextureTarget type) + { + switch (type) + { + case Decoders.TextureTarget.Texture1DLodZero: + return TextureTarget.Texture1D; + + case Decoders.TextureTarget.Texture2D: + case Decoders.TextureTarget.Texture2DLodZero: + case Decoders.TextureTarget.Texture2DLodLevel: + return TextureTarget.Texture2D; + + case Decoders.TextureTarget.Texture2DDepthCompare: + case Decoders.TextureTarget.Texture2DLodLevelDepthCompare: + case Decoders.TextureTarget.Texture2DLodZeroDepthCompare: + return TextureTarget.Texture2D | TextureTarget.Shadow; + + case Decoders.TextureTarget.Texture2DArray: + case Decoders.TextureTarget.Texture2DArrayLodZero: + return TextureTarget.Texture2D | TextureTarget.Array; + + case Decoders.TextureTarget.Texture2DArrayLodZeroDepthCompare: + return TextureTarget.Texture2D | TextureTarget.Array | TextureTarget.Shadow; + + case Decoders.TextureTarget.Texture3D: + case Decoders.TextureTarget.Texture3DLodZero: + return TextureTarget.Texture3D; + + case Decoders.TextureTarget.TextureCube: + case Decoders.TextureTarget.TextureCubeLodLevel: + return TextureTarget.TextureCube; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureTarget GetTextureType(TexelLoadTarget type) + { + switch (type) + { + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + return TextureTarget.Texture1D; + + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: + return TextureTarget.Texture2D; + + case TexelLoadTarget.Texture2DLodZeroMultisample: + return TextureTarget.Texture2D | TextureTarget.Multisample; + + case TexelLoadTarget.Texture3DLodZero: + return TextureTarget.Texture3D; + + case TexelLoadTarget.Texture2DArrayLodZero: + return TextureTarget.Texture2D | TextureTarget.Array; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureFlags GetTextureFlags(Decoders.TextureTarget type) + { + switch (type) + { + case Decoders.TextureTarget.Texture1DLodZero: + case Decoders.TextureTarget.Texture2DLodZero: + case Decoders.TextureTarget.Texture2DLodLevel: + case Decoders.TextureTarget.Texture2DLodLevelDepthCompare: + case Decoders.TextureTarget.Texture2DLodZeroDepthCompare: + case Decoders.TextureTarget.Texture2DArrayLodZero: + case Decoders.TextureTarget.Texture2DArrayLodZeroDepthCompare: + case Decoders.TextureTarget.Texture3DLodZero: + case Decoders.TextureTarget.TextureCubeLodLevel: + return TextureFlags.LodLevel; + + case Decoders.TextureTarget.Texture2D: + case Decoders.TextureTarget.Texture2DDepthCompare: + case Decoders.TextureTarget.Texture2DArray: + case Decoders.TextureTarget.Texture3D: + case Decoders.TextureTarget.TextureCube: + return TextureFlags.None; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + + private static TextureFlags GetTextureFlags(TexelLoadTarget type) + { + switch (type) + { + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture3DLodZero: + case TexelLoadTarget.Texture2DArrayLodZero: + return TextureFlags.LodLevel; + + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevelOffset: + return TextureFlags.LodLevel | TextureFlags.Offset; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs new file mode 100644 index 00000000..b19e9fa1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vmad(EmitterContext context) + { + OpCodeVideo op = (OpCodeVideo)context.CurrOp; + + context.Copy(GetDest(context), GetSrcC(context)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs new file mode 100644 index 00000000..91c740b6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs @@ -0,0 +1,6 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + delegate void InstEmitter(EmitterContext context); +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs new file mode 100644 index 00000000..67e24957 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs @@ -0,0 +1,149 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class Lop3Expression + { + public static Operand GetFromTruthTable( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcC, + int imm) + { + Operand expr = null; + + // Handle some simple cases, or cases where + // the KMap would yield poor results (like XORs). + if (imm == 0x96 || imm == 0x69) + { + // XOR (0x96) and XNOR (0x69). + if (imm == 0x69) + { + srcA = context.BitwiseNot(srcA); + } + + expr = context.BitwiseExclusiveOr(srcA, srcB); + expr = context.BitwiseExclusiveOr(expr, srcC); + + return expr; + } + else if (imm == 0) + { + // Always false. + return Const(IrConsts.False); + } + else if (imm == 0xff) + { + // Always true. + return Const(IrConsts.True); + } + + int map; + + // Encode into gray code. + map = ((imm >> 0) & 1) << 0; + map |= ((imm >> 1) & 1) << 4; + map |= ((imm >> 2) & 1) << 1; + map |= ((imm >> 3) & 1) << 5; + map |= ((imm >> 4) & 1) << 3; + map |= ((imm >> 5) & 1) << 7; + map |= ((imm >> 6) & 1) << 2; + map |= ((imm >> 7) & 1) << 6; + + // Solve KMap, get sum of products. + int visited = 0; + + for (int index = 0; index < 8 && visited != 0xff; index++) + { + if ((map & (1 << index)) == 0) + { + continue; + } + + int mask = 0; + + for (int mSize = 4; mSize != 0; mSize >>= 1) + { + mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4); + + if ((map & mask) == mask) + { + break; + } + } + + // The mask should wrap, if we are on the high row, shift to low etc. + int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4; + + if ((map & mask2) == mask2) + { + mask |= mask2; + } + + if ((mask & visited) == mask) + { + continue; + } + + bool notA = (mask & 0x33) != 0; + bool notB = (mask & 0x99) != 0; + bool notC = (mask & 0x0f) != 0; + + bool aChanges = (mask & 0xcc) != 0 && notA; + bool bChanges = (mask & 0x66) != 0 && notB; + bool cChanges = (mask & 0xf0) != 0 && notC; + + Operand localExpr = null; + + void And(Operand source) + { + if (localExpr != null) + { + localExpr = context.BitwiseAnd(localExpr, source); + } + else + { + localExpr = source; + } + } + + if (!aChanges) + { + And(context.BitwiseNot(srcA, notA)); + } + + if (!bChanges) + { + And(context.BitwiseNot(srcB, notB)); + } + + if (!cChanges) + { + And(context.BitwiseNot(srcC, notC)); + } + + if (expr != null) + { + expr = context.BitwiseOr(expr, localExpr); + } + else + { + expr = localExpr; + } + + visited |= mask; + } + + return expr; + } + + private static int RotateLeft4(int value, int shift) + { + return ((value << shift) | (value >> (4 - shift))) & 0xf; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 00000000..94975337 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedList<INode> Operations { get; } + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public bool HasBranch => _branch != null; + + public List<BasicBlock> Predecessors { get; } + + public HashSet<BasicBlock> DominanceFrontiers { get; } + + public BasicBlock ImmediateDominator { get; set; } + + public BasicBlock() + { + Operations = new LinkedList<INode>(); + + Predecessors = new List<BasicBlock>(); + + DominanceFrontiers = new HashSet<BasicBlock>(); + } + + public BasicBlock(int index) : this() + { + Index = index; + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public INode GetLastOp() + { + return Operations.Last?.Value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs new file mode 100644 index 00000000..d4d87b06 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class CommentNode : Operation + { + public string Comment { get; } + + public CommentNode(string comment) : base(Instruction.Comment, null) + { + Comment = comment; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs new file mode 100644 index 00000000..48dda24b --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + interface INode + { + Operand Dest { get; set; } + + int SourcesCount { get; } + + Operand GetSource(int index); + + void SetSource(int index, Operand operand); + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs new file mode 100644 index 00000000..e580965e --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,91 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum Instruction + { + Absolute = 1, + Add, + BitfieldExtractS32, + BitfieldExtractU32, + BitfieldInsert, + BitfieldReverse, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + Branch, + BranchIfFalse, + BranchIfTrue, + Ceiling, + Clamp, + ClampU32, + Comment, + CompareEqual, + CompareGreater, + CompareGreaterOrEqual, + CompareGreaterOrEqualU32, + CompareGreaterU32, + CompareLess, + CompareLessOrEqual, + CompareLessOrEqualU32, + CompareLessU32, + CompareNotEqual, + ConditionalSelect, + ConvertFPToS32, + ConvertS32ToFP, + ConvertU32ToFP, + Copy, + Cosine, + Discard, + Divide, + EmitVertex, + EndPrimitive, + ExponentB2, + Floor, + FusedMultiplyAdd, + IsNan, + LoadAttribute, + LoadConstant, + LoadGlobal, + LoadLocal, + LoadStorage, + LogarithmB2, + LogicalAnd, + LogicalExclusiveOr, + LogicalNot, + LogicalOr, + LoopBreak, + LoopContinue, + MarkLabel, + Maximum, + MaximumU32, + Minimum, + MinimumU32, + Multiply, + Negate, + PackDouble2x32, + PackHalf2x16, + ReciprocalSquareRoot, + Return, + ShiftLeft, + ShiftRightS32, + ShiftRightU32, + Sine, + SquareRoot, + StoreGlobal, + StoreLocal, + StoreStorage, + Subtract, + TextureSample, + TextureSize, + Truncate, + UnpackDouble2x32, + UnpackHalf2x16, + + Count, + FP = 1 << 16, + Mask = 0xffff + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs new file mode 100644 index 00000000..c264e47d --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class IrConsts + { + public const int False = 0; + public const int True = -1; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs new file mode 100644 index 00000000..567277a7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs @@ -0,0 +1,82 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operand + { + private const int CbufSlotBits = 5; + private const int CbufSlotLsb = 32 - CbufSlotBits; + private const int CbufSlotMask = (1 << CbufSlotBits) - 1; + + public OperandType Type { get; } + + public int Value { get; } + + public InterpolationQualifier Interpolation { get; } + + public INode AsgOp { get; set; } + + public HashSet<INode> UseOps { get; } + + private Operand() + { + UseOps = new HashSet<INode>(); + } + + public Operand(OperandType type) : this() + { + Type = type; + } + + public Operand(OperandType type, int value, InterpolationQualifier iq = InterpolationQualifier.None) : this() + { + Type = type; + Value = value; + Interpolation = iq; + } + + public Operand(Register reg) : this() + { + Type = OperandType.Register; + Value = PackRegInfo(reg.Index, reg.Type); + } + + public Operand(int slot, int offset) : this() + { + Type = OperandType.ConstantBuffer; + Value = PackCbufInfo(slot, offset); + } + + private static int PackCbufInfo(int slot, int offset) + { + return (slot << CbufSlotLsb) | offset; + } + + private static int PackRegInfo(int index, RegisterType type) + { + return ((int)type << 24) | index; + } + + public int GetCbufSlot() + { + return (Value >> CbufSlotLsb) & CbufSlotMask; + } + + public int GetCbufOffset() + { + return Value & ~(CbufSlotMask << CbufSlotLsb); + } + + public Register GetRegister() + { + return new Register(Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + public float AsFloat() + { + return BitConverter.Int32BitsToSingle(Value); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs new file mode 100644 index 00000000..45c9ba1e --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs @@ -0,0 +1,62 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class OperandHelper + { + public static Operand Attribute(int value, InterpolationQualifier iq = InterpolationQualifier.None) + { + return new Operand(OperandType.Attribute, value, iq); + } + + public static Operand Cbuf(int slot, int offset) + { + return new Operand(slot, offset); + } + + public static Operand Const(int value) + { + return new Operand(OperandType.Constant, value); + } + + public static Operand ConstF(float value) + { + return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand Label() + { + return new Operand(OperandType.Label); + } + + public static Operand Local() + { + return new Operand(OperandType.LocalVariable); + } + + public static Operand Register(int index, RegisterType type) + { + return Register(new Register(index, type)); + } + + public static Operand Register(Register reg) + { + if (reg.IsRZ) + { + return Const(0); + } + else if (reg.IsPT) + { + return Const(IrConsts.True); + } + + return new Operand(reg); + } + + public static Operand Undef() + { + return new Operand(OperandType.Undefined); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs new file mode 100644 index 00000000..8f8df9e4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum OperandType + { + Attribute, + Constant, + ConstantBuffer, + Label, + LocalVariable, + Register, + Undefined + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs new file mode 100644 index 00000000..fc01d47e --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -0,0 +1,101 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operation : INode + { + public Instruction Inst { get; private set; } + + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private Operand[] _sources; + + public int SourcesCount => _sources.Length; + + public int ComponentIndex { get; } + + public Operation(Instruction inst, Operand dest, params Operand[] sources) + { + Inst = inst; + Dest = dest; + + // The array may be modified externally, so we store a copy. + _sources = (Operand[])sources.Clone(); + + for (int index = 0; index < _sources.Length; index++) + { + Operand source = _sources[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + } + } + + public Operation( + Instruction inst, + int compIndex, + Operand dest, + params Operand[] sources) : this(inst, dest, sources) + { + ComponentIndex = compIndex; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public Operand GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index]; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source != null && source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index] = source; + } + + public void TurnIntoCopy(Operand source) + { + Inst = Instruction.Copy; + + foreach (Operand oldSrc in _sources) + { + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources = new Operand[] { source }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs new file mode 100644 index 00000000..13ff41bd --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class PhiNode : INode + { + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private HashSet<BasicBlock> _blocks; + + private class PhiSource + { + public BasicBlock Block { get; } + public Operand Operand { get; set; } + + public PhiSource(BasicBlock block, Operand operand) + { + Block = block; + Operand = operand; + } + } + + private List<PhiSource> _sources; + + public int SourcesCount => _sources.Count; + + public PhiNode(Operand dest) + { + _blocks = new HashSet<BasicBlock>(); + + _sources = new List<PhiSource>(); + + dest.AsgOp = this; + + Dest = dest; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public void AddSource(BasicBlock block, Operand operand) + { + if (_blocks.Add(block)) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources.Add(new PhiSource(block, operand)); + } + } + + public Operand GetSource(int index) + { + return _sources[index].Operand; + } + + public BasicBlock GetBlock(int index) + { + return _sources[index].Block; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index].Operand; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index].Operand = source; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs new file mode 100644 index 00000000..5f0a8427 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureFlags + { + None = 0, + Bindless = 1 << 0, + Gather = 1 << 1, + IntCoords = 1 << 2, + LodBias = 1 << 3, + LodLevel = 1 << 4, + Offset = 1 << 5, + Offsets = 1 << 6 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs new file mode 100644 index 00000000..0c768345 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class TextureOperation : Operation + { + public TextureTarget Target { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + + public TextureOperation( + Instruction inst, + TextureTarget target, + TextureFlags flags, + int handle, + int compIndex, + Operand dest, + params Operand[] sources) : base(inst, compIndex, dest, sources) + { + Target = target; + Flags = flags; + Handle = handle; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/InterpolationQualifier.cs b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs new file mode 100644 index 00000000..e710427d --- /dev/null +++ b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum InterpolationQualifier + { + None = 0, + + Flat = 1, + NoPerspective = 2, + Smooth = 3, + + Centroid = 1 << 16, + Sample = 1 << 17, + + FlagsMask = Centroid | Sample + } + + public static class InterpolationQualifierExtensions + { + public static string ToGlslQualifier(this InterpolationQualifier iq) + { + string output = string.Empty; + + switch (iq & ~InterpolationQualifier.FlagsMask) + { + case InterpolationQualifier.Flat: output = "flat"; break; + case InterpolationQualifier.NoPerspective: output = "noperspective"; break; + case InterpolationQualifier.Smooth: output = "smooth"; break; + } + + if ((iq & InterpolationQualifier.Centroid) != 0) + { + output = "centroid " + output; + } + else if ((iq & InterpolationQualifier.Sample) != 0) + { + output = "sample " + output; + } + + return output; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/OutputTopology.cs b/Ryujinx.Graphics.Shader/OutputTopology.cs new file mode 100644 index 00000000..e8336aa3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/OutputTopology.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader +{ + enum OutputTopology + { + PointList = 1, + LineStrip = 6, + TriangleStrip = 7 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj new file mode 100644 index 00000000..ea83d296 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -0,0 +1,7 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.0</TargetFramework> + </PropertyGroup> + +</Project> diff --git a/Ryujinx.Graphics.Shader/ShaderConfig.cs b/Ryujinx.Graphics.Shader/ShaderConfig.cs new file mode 100644 index 00000000..6ab4689a --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderConfig.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader +{ + struct ShaderConfig + { + public ShaderStage Stage { get; } + + public TranslationFlags Flags { get; } + + public int MaxCBufferSize { get; } + public int MaxOutputVertices { get; } + + public OutputTopology OutputTopology { get; } + + public ShaderConfig( + ShaderStage stage, + TranslationFlags flags, + int maxCBufferSize, + int maxOutputVertices, + OutputTopology outputTopology) + { + Stage = stage; + Flags = flags; + MaxCBufferSize = maxCBufferSize; + MaxOutputVertices = maxOutputVertices; + OutputTopology = outputTopology; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderHeader.cs b/Ryujinx.Graphics.Shader/ShaderHeader.cs new file mode 100644 index 00000000..94c57435 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderHeader.cs @@ -0,0 +1,170 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Shader +{ + struct OutputMapTarget + { + public bool Red { get; } + public bool Green { get; } + public bool Blue { get; } + public bool Alpha { get; } + + public bool Enabled => Red || Green || Blue || Alpha; + + public OutputMapTarget(bool red, bool green, bool blue, bool alpha) + { + Red = red; + Green = green; + Blue = blue; + Alpha = alpha; + } + + public bool ComponentEnabled(int component) + { + switch (component) + { + case 0: return Red; + case 1: return Green; + case 2: return Blue; + case 3: return Alpha; + } + + throw new ArgumentOutOfRangeException(nameof(component)); + } + } + + class ShaderHeader + { + public int SphType { get; } + public int Version { get; } + + public ShaderStage Stage { get; } + + public bool MrtEnable { get; } + + public bool KillsPixels { get; } + + public bool DoesGlobalStore { get; } + + public int SassVersion { get; } + + public bool DoesLoadOrStore { get; } + public bool DoesFp64 { get; } + + public int StreamOutMask { get; } + + public int ShaderLocalMemoryLowSize { get; } + + public int PerPatchAttributeCount { get; } + + public int ShaderLocalMemoryHighSize { get; } + + public int ThreadsPerInputPrimitive { get; } + + public int ShaderLocalMemoryCrsSize { get; } + + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertexCount { get; } + + public int StoreReqStart { get; } + public int StoreReqEnd { get; } + + public OutputMapTarget[] OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public int DepthRegister + { + get + { + int count = 0; + + for (int index = 0; index < OmapTargets.Length; index++) + { + for (int component = 0; component < 4; component++) + { + if (OmapTargets[index].ComponentEnabled(component)) + { + count++; + } + } + } + + // Depth register is always two registers after the last color output. + return count + 1; + } + } + + public ShaderHeader(Span<byte> code) + { + Span<int> header = MemoryMarshal.Cast<byte, int>(code); + + int commonWord0 = header[0]; + int commonWord1 = header[1]; + int commonWord2 = header[2]; + int commonWord3 = header[3]; + int commonWord4 = header[4]; + + SphType = commonWord0.Extract(0, 5); + Version = commonWord0.Extract(5, 5); + + Stage = (ShaderStage)commonWord0.Extract(10, 4); + + // Invalid. + if (Stage == ShaderStage.Compute) + { + Stage = ShaderStage.Vertex; + } + + MrtEnable = commonWord0.Extract(14); + + KillsPixels = commonWord0.Extract(15); + + DoesGlobalStore = commonWord0.Extract(16); + + SassVersion = commonWord0.Extract(17, 4); + + DoesLoadOrStore = commonWord0.Extract(26); + DoesFp64 = commonWord0.Extract(27); + + StreamOutMask = commonWord0.Extract(28, 4); + + ShaderLocalMemoryLowSize = commonWord1.Extract(0, 24); + + PerPatchAttributeCount = commonWord1.Extract(24, 8); + + ShaderLocalMemoryHighSize = commonWord2.Extract(0, 24); + + ThreadsPerInputPrimitive = commonWord2.Extract(24, 8); + + ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24); + + OutputTopology = (OutputTopology)commonWord3.Extract(24, 4); + + MaxOutputVertexCount = commonWord4.Extract(0, 12); + + StoreReqStart = commonWord4.Extract(12, 8); + StoreReqEnd = commonWord4.Extract(24, 8); + + int type2OmapTarget = header[18]; + int type2Omap = header[19]; + + OmapTargets = new OutputMapTarget[8]; + + for (int offset = 0; offset < OmapTargets.Length * 4; offset += 4) + { + OmapTargets[offset >> 2] = new OutputMapTarget( + type2OmapTarget.Extract(offset + 0), + type2OmapTarget.Extract(offset + 1), + type2OmapTarget.Extract(offset + 2), + type2OmapTarget.Extract(offset + 3)); + } + + OmapSampleMask = type2Omap.Extract(0); + OmapDepth = type2Omap.Extract(1); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs new file mode 100644 index 00000000..52c2d55b --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgram + { + public ShaderProgramInfo Info { get; } + + public ShaderStage Stage { get; } + + public string Code { get; private set; } + + internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code) + { + Info = info; + Stage = stage; + Code = code; + } + + public void Replace(string name, string value) + { + Code = Code.Replace(name, value); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs new file mode 100644 index 00000000..841e636d --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgramInfo + { + public ReadOnlyCollection<BufferDescriptor> CBuffers { get; } + public ReadOnlyCollection<BufferDescriptor> SBuffers { get; } + public ReadOnlyCollection<TextureDescriptor> Textures { get; } + + public ReadOnlyCollection<InterpolationQualifier> InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; } + + internal ShaderProgramInfo( + BufferDescriptor[] cBuffers, + BufferDescriptor[] sBuffers, + TextureDescriptor[] textures, + InterpolationQualifier[] interpolationQualifiers, + bool usesInstanceId) + { + CBuffers = Array.AsReadOnly(cBuffers); + SBuffers = Array.AsReadOnly(sBuffers); + Textures = Array.AsReadOnly(textures); + + InterpolationQualifiers = Array.AsReadOnly(interpolationQualifiers); + + UsesInstanceId = usesInstanceId; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderStage.cs b/Ryujinx.Graphics.Shader/ShaderStage.cs new file mode 100644 index 00000000..30b65348 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderStage.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum ShaderStage + { + Compute, + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs new file mode 100644 index 00000000..bb3fe7af --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs @@ -0,0 +1,35 @@ +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstAssignment : AstNode + { + public IAstNode Destination { get; } + + private IAstNode _source; + + public IAstNode Source + { + get + { + return _source; + } + set + { + RemoveUse(_source, this); + + AddUse(value, this); + + _source = value; + } + } + + public AstAssignment(IAstNode destination, IAstNode source) + { + Destination = destination; + Source = source; + + AddDef(destination, this); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs new file mode 100644 index 00000000..fdef87de --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlock : AstNode, IEnumerable<IAstNode> + { + public AstBlockType Type { get; private set; } + + private IAstNode _condition; + + public IAstNode Condition + { + get + { + return _condition; + } + set + { + RemoveUse(_condition, this); + + AddUse(value, this); + + _condition = value; + } + } + + private LinkedList<IAstNode> _nodes; + + public IAstNode First => _nodes.First?.Value; + + public int Count => _nodes.Count; + + public AstBlock(AstBlockType type, IAstNode condition = null) + { + Type = type; + Condition = condition; + + _nodes = new LinkedList<IAstNode>(); + } + + public void Add(IAstNode node) + { + Add(node, _nodes.AddLast(node)); + } + + public void AddFirst(IAstNode node) + { + Add(node, _nodes.AddFirst(node)); + } + + public void AddBefore(IAstNode next, IAstNode node) + { + Add(node, _nodes.AddBefore(next.LLNode, node)); + } + + public void AddAfter(IAstNode prev, IAstNode node) + { + Add(node, _nodes.AddAfter(prev.LLNode, node)); + } + + private void Add(IAstNode node, LinkedListNode<IAstNode> newNode) + { + if (node.Parent != null) + { + throw new ArgumentException("Node already belongs to a block."); + } + + node.Parent = this; + node.LLNode = newNode; + } + + public void Remove(IAstNode node) + { + _nodes.Remove(node.LLNode); + + node.Parent = null; + node.LLNode = null; + } + + public void AndCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond); + } + + public void OrCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalOr, Condition, cond); + } + public void TurnIntoIf(IAstNode cond) + { + Condition = cond; + + Type = AstBlockType.If; + } + + public void TurnIntoElseIf() + { + Type = AstBlockType.ElseIf; + } + + public IEnumerator<IAstNode> GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs new file mode 100644 index 00000000..c12efda9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum AstBlockType + { + DoWhile, + If, + Else, + ElseIf, + Main, + While + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs new file mode 100644 index 00000000..10d5dce0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlockVisitor + { + public AstBlock Block { get; private set; } + + public class BlockVisitationEventArgs : EventArgs + { + public AstBlock Block { get; } + + public BlockVisitationEventArgs(AstBlock block) + { + Block = block; + } + } + + public event EventHandler<BlockVisitationEventArgs> BlockEntered; + public event EventHandler<BlockVisitationEventArgs> BlockLeft; + + public AstBlockVisitor(AstBlock mainBlock) + { + Block = mainBlock; + } + + public IEnumerable<IAstNode> Visit() + { + IAstNode node = Block.First; + + while (node != null) + { + // We reached a child block, visit the nodes inside. + while (node is AstBlock childBlock) + { + Block = childBlock; + + node = childBlock.First; + + BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block)); + } + + // Node may be null, if the block is empty. + if (node != null) + { + IAstNode next = Next(node); + + yield return node; + + node = next; + } + + // We reached the end of the list, go up on tree to the parent blocks. + while (node == null && Block.Type != AstBlockType.Main) + { + BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block)); + + node = Next(Block); + + Block = Block.Parent; + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs new file mode 100644 index 00000000..dabe623f --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstComment : AstNode + { + public string Comment { get; } + + public AstComment(string comment) + { + Comment = comment; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs new file mode 100644 index 00000000..9d3148e1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstHelper + { + public static void AddUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Add(parent); + } + } + + public static void AddDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Add(parent); + } + } + + public static void RemoveUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Remove(parent); + } + } + + public static void RemoveDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Remove(parent); + } + } + + public static AstAssignment Assign(IAstNode destination, IAstNode source) + { + return new AstAssignment(destination, source); + } + + public static AstOperand Const(int value) + { + return new AstOperand(OperandType.Constant, value); + } + + public static AstOperand Local(VariableType type) + { + AstOperand local = new AstOperand(OperandType.LocalVariable); + + local.VarType = type; + + return local; + } + + public static IAstNode InverseCond(IAstNode cond) + { + return new AstOperation(Instruction.LogicalNot, cond); + } + + public static IAstNode Next(IAstNode node) + { + return node.LLNode.Next?.Value; + } + + public static IAstNode Previous(IAstNode node) + { + return node.LLNode.Previous?.Value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs new file mode 100644 index 00000000..c667aac9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstNode : IAstNode + { + public AstBlock Parent { get; set; } + + public LinkedListNode<IAstNode> LLNode { get; set; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs new file mode 100644 index 00000000..25b09636 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperand : AstNode + { + public HashSet<IAstNode> Defs { get; } + public HashSet<IAstNode> Uses { get; } + + public OperandType Type { get; } + + public VariableType VarType { get; set; } + + public InterpolationQualifier Interpolation { get; } + + public int Value { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + private AstOperand() + { + Defs = new HashSet<IAstNode>(); + Uses = new HashSet<IAstNode>(); + + VarType = VariableType.S32; + } + + public AstOperand(Operand operand) : this() + { + Type = operand.Type; + Interpolation = operand.Interpolation; + + if (Type == OperandType.ConstantBuffer) + { + CbufSlot = operand.GetCbufSlot(); + CbufOffset = operand.GetCbufOffset(); + } + else + { + Value = operand.Value; + } + } + + public AstOperand(OperandType type, int value = 0) : this() + { + Type = type; + Value = value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs new file mode 100644 index 00000000..1607ffec --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperation : AstNode + { + public Instruction Inst { get; } + + public int ComponentMask { get; } + + private IAstNode[] _sources; + + public int SourcesCount => _sources.Length; + + public AstOperation(Instruction inst, params IAstNode[] sources) + { + Inst = inst; + _sources = sources; + + foreach (IAstNode source in sources) + { + AddUse(source, this); + } + + ComponentMask = 1; + } + + public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources) + { + ComponentMask = compMask; + } + + public IAstNode GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, IAstNode source) + { + RemoveUse(_sources[index], this); + + AddUse(source, this); + + _sources[index] = source; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs new file mode 100644 index 00000000..a37e1a3e --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs @@ -0,0 +1,155 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstOptimizer + { + public static void Optimize(StructuredProgramContext context) + { + AstBlock mainBlock = context.Info.MainBlock; + + // When debug mode is enabled, we disable expression propagation + // (this makes comparison with the disassembly easier). + if ((context.Config.Flags & TranslationFlags.DebugMode) == 0) + { + AstBlockVisitor visitor = new AstBlockVisitor(mainBlock); + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) + { + bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); + + if (propVar.Defs.Count == 1 && isWorthPropagating) + { + PropagateExpression(propVar, assignment.Source); + } + + if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) + { + visitor.Block.Remove(assignment); + + context.Info.Locals.Remove(propVar); + } + } + } + } + + RemoveEmptyBlocks(mainBlock); + } + + private static bool IsWorthPropagating(IAstNode source) + { + if (!(source is AstOperation srcOp)) + { + return false; + } + + if (!InstructionInfo.IsUnary(srcOp.Inst)) + { + return false; + } + + return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy; + } + + private static void PropagateExpression(AstOperand propVar, IAstNode source) + { + IAstNode[] uses = propVar.Uses.ToArray(); + + foreach (IAstNode useNode in uses) + { + if (useNode is AstBlock useBlock) + { + useBlock.Condition = source; + } + else if (useNode is AstOperation useOperation) + { + for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++) + { + if (useOperation.GetSource(srcIndex) == propVar) + { + useOperation.SetSource(srcIndex, source); + } + } + } + else if (useNode is AstAssignment useAssignment) + { + useAssignment.Source = source; + } + } + } + + private static void RemoveEmptyBlocks(AstBlock mainBlock) + { + Queue<AstBlock> pending = new Queue<AstBlock>(); + + pending.Enqueue(mainBlock); + + while (pending.TryDequeue(out AstBlock block)) + { + foreach (IAstNode node in block) + { + if (node is AstBlock childBlock) + { + pending.Enqueue(childBlock); + } + } + + AstBlock parent = block.Parent; + + if (parent == null) + { + continue; + } + + AstBlock nextBlock = Next(block) as AstBlock; + + bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else; + + bool isIf = block.Type == AstBlockType.If; + + if (block.Count == 0) + { + if (isIf) + { + if (hasElse) + { + nextBlock.TurnIntoIf(InverseCond(block.Condition)); + } + + parent.Remove(block); + } + else if (block.Type == AstBlockType.Else) + { + parent.Remove(block); + } + } + else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1)) + { + AstBlock parentOfParent = parent.Parent; + + parent.Remove(block); + + parentOfParent.AddAfter(parent, block); + + if (hasElse) + { + parent.Remove(nextBlock); + + parentOfParent.AddAfter(block, nextBlock); + } + + parentOfParent.Remove(parent); + + block.TurnIntoElseIf(); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs new file mode 100644 index 00000000..c9bd5750 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public TextureTarget Target { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + + public AstTextureOperation( + Instruction inst, + TextureTarget target, + TextureFlags flags, + int handle, + int compMask, + params IAstNode[] sources) : base(inst, compMask, sources) + { + Target = target; + Flags = flags; + Handle = handle; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs new file mode 100644 index 00000000..8bcf9d9c --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs @@ -0,0 +1,459 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class GotoElimination + { + // This is a modified version of the algorithm presented on the paper + // "Taming Control Flow: A Structured Approach to Eliminating Goto Statements". + public static void Eliminate(GotoStatement[] gotos) + { + for (int index = gotos.Length - 1; index >= 0; index--) + { + GotoStatement stmt = gotos[index]; + + AstBlock gBlock = ParentBlock(stmt.Goto); + AstBlock lBlock = ParentBlock(stmt.Label); + + int gLevel = Level(gBlock); + int lLevel = Level(lBlock); + + if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + AstBlock drBlock = gBlock; + + int drLevel = gLevel; + + do + { + drBlock = drBlock.Parent; + + drLevel--; + } + while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel)); + + MoveOutward(stmt, gLevel, drLevel); + + gBlock = drBlock; + gLevel = drLevel; + + if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + // It's possible that the label was enclosed inside an else block, + // in this case we need to update the block and level. + // We also need to set the IsLoop for the case when the label is + // now before the goto, due to the newly introduced else block. + lBlock = ParentBlock(stmt.Label); + + lLevel = Level(lBlock); + + if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel)) + { + stmt.IsLoop = true; + } + } + } + + if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + if (gLevel > lLevel) + { + MoveOutward(stmt, gLevel, lLevel); + } + else + { + if (stmt.IsLoop) + { + Lift(stmt); + } + + MoveInward(stmt); + } + } + + gBlock = ParentBlock(stmt.Goto); + + if (stmt.IsLoop) + { + EncloseDoWhile(stmt, gBlock, stmt.Label); + } + else + { + Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label); + } + + gBlock.Remove(stmt.Goto); + } + } + + private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel) + { + return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel)); + } + + private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel) + { + // If the levels are equal, they can be either siblings or indirectly related. + if (lLevel == rLevel) + { + return false; + } + + IAstNode block; + IAstNode other; + + int blockLvl, otherLvl; + + if (lLevel > rLevel) + { + block = lBlock; + blockLvl = lLevel; + other = rBlock; + otherLvl = rLevel; + } + else /* if (rLevel > lLevel) */ + { + block = rBlock; + blockLvl = rLevel; + other = lBlock; + otherLvl = lLevel; + } + + while (blockLvl >= otherLvl) + { + if (block == other) + { + return true; + } + + block = block.Parent; + + blockLvl--; + } + + return false; + } + + private static void Lift(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + AstBlock loopFirstStmt = path[path.Length - 1]; + + if (loopFirstStmt.Type == AstBlockType.Else) + { + loopFirstStmt = Previous(loopFirstStmt) as AstBlock; + + if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + } + + AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt); + + block.Remove(stmt.Goto); + + newBlock.AddFirst(stmt.Goto); + + stmt.IsLoop = false; + } + + private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel) + { + AstBlock origin = ParentBlock(stmt.Goto); + + AstBlock block = origin; + + // Check if a loop is enclosing the goto, and the block that is + // directly related to the label is above the loop block. + // In that case, we need to introduce a break to get out of the loop. + AstBlock loopBlock = origin; + + int loopLevel = gLevel; + + while (loopLevel > lLevel) + { + AstBlock child = loopBlock; + + loopBlock = loopBlock.Parent; + + loopLevel--; + + if (child.Type == AstBlockType.DoWhile) + { + EncloseSingleInst(stmt, Instruction.LoopBreak); + + block.Remove(stmt.Goto); + + loopBlock.AddAfter(child, stmt.Goto); + + block = loopBlock; + gLevel = loopLevel; + } + } + + // Insert ifs to skip the parts that shouldn't be executed due to the goto. + bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If; + + while (gLevel > lLevel) + { + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto)); + + block.Remove(stmt.Goto); + + AstBlock child = block; + + // We can't move the goto in the middle of a if and a else block, in + // this case we need to move it after the else. + // IsLoop may need to be updated if the label is inside the else, as + // introducing a loop is the only way to ensure the else will be executed. + if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + child = elseBlock; + } + + block = block.Parent; + + block.AddAfter(child, stmt.Goto); + + gLevel--; + + if (tryInsertElse && child == origin) + { + AstBlock lBlock = ParentBlock(stmt.Label); + + IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null; + + AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last); + + if (newBlock != null) + { + block.Remove(stmt.Goto); + + block.AddAfter(newBlock, stmt.Goto); + } + } + } + } + + private static void MoveInward(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + for (int index = path.Length - 1; index >= 0; index--) + { + AstBlock child = path[index]; + AstBlock last = child; + + if (child.Type == AstBlockType.If) + { + // Modify the if condition to allow it to be entered by the goto. + if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition)) + { + child.OrCondition(stmt.Condition); + } + } + else if (child.Type == AstBlockType.Else) + { + // Modify the matching if condition to force the else to be entered by the goto. + if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + + IAstNode cond = InverseCond(stmt.Condition); + + if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond)) + { + ifBlock.AndCondition(cond); + } + + last = ifBlock; + } + + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last); + + block.Remove(stmt.Goto); + + child.AddFirst(stmt.Goto); + + block = child; + } + } + + private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond) + { + while (node is AstOperation operation && operation.SourcesCount == 2) + { + if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond)) + { + return true; + } + + node = operation.GetSource(0); + } + + return false; + } + + private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first) + { + if (block.Type == AstBlockType.DoWhile && first == block.First) + { + // We only need to insert the continue if we're not at the end of the loop, + // or if our condition is different from the loop condition. + if (Next(stmt.Goto) != null || block.Condition != stmt.Condition) + { + EncloseSingleInst(stmt, Instruction.LoopContinue); + } + + // Modify the do-while condition to allow it to continue. + if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition)) + { + block.OrCondition(stmt.Condition); + } + + return block; + } + + return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto); + } + + private static void EncloseSingleInst(GotoStatement stmt, Instruction inst) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition); + + block.AddAfter(stmt.Goto, newBlock); + + newBlock.AddFirst(new AstOperation(inst)); + } + + private static AstBlock Enclose( + AstBlock block, + AstBlockType type, + IAstNode cond, + IAstNode first, + IAstNode last = null) + { + if (first == last) + { + return null; + } + + if (type == AstBlockType.If) + { + cond = InverseCond(cond); + } + + // Do a quick check, if we are enclosing a single block, + // and the block type/condition matches the one we're going + // to create, then we don't need a new block, we can just + // return the old one. + bool hasSingleNode = Next(first) == last; + + if (hasSingleNode && BlockMatches(first, type, cond)) + { + return first as AstBlock; + } + + AstBlock newBlock = new AstBlock(type, cond); + + block.AddBefore(first, newBlock); + + while (first != last) + { + IAstNode next = Next(first); + + block.Remove(first); + + newBlock.Add(first); + + first = next; + } + + return newBlock; + } + + private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond) + { + if (!(node is AstBlock block)) + { + return false; + } + + return block.Type == type && IsSameCond(block.Condition, cond); + } + + private static bool IsSameCond(IAstNode lCond, IAstNode rCond) + { + if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot) + { + if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst) + { + return false; + } + + lCond = lCondOp.GetSource(0); + rCond = rCondOp.GetSource(0); + } + + return lCond == rCond; + } + + private static AstBlock ParentBlock(IAstNode node) + { + if (node is AstBlock block) + { + return block.Parent; + } + + while (!(node is AstBlock)) + { + node = node.Parent; + } + + return node as AstBlock; + } + + private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom) + { + AstBlock block = bottom; + + List<AstBlock> path = new List<AstBlock>(); + + while (block != top) + { + path.Add(block); + + block = block.Parent; + } + + return path.ToArray(); + } + + private static int Level(IAstNode node) + { + int level = 0; + + while (node != null) + { + level++; + + node = node.Parent; + } + + return level; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs new file mode 100644 index 00000000..25216e55 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class GotoStatement + { + public AstOperation Goto { get; } + public AstAssignment Label { get; } + + public IAstNode Condition => Label.Destination; + + public bool IsLoop { get; set; } + + public bool IsUnconditional => Goto.Inst == Instruction.Branch; + + public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop) + { + Goto = branch; + Label = label; + IsLoop = isLoop; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs new file mode 100644 index 00000000..5ececbb5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + interface IAstNode + { + AstBlock Parent { get; set; } + + LinkedListNode<IAstNode> LLNode { get; set; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs new file mode 100644 index 00000000..cb08a213 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -0,0 +1,150 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class InstructionInfo + { + private struct InstInfo + { + public VariableType DestType { get; } + + public VariableType[] SrcTypes { get; } + + public InstInfo(VariableType destType, params VariableType[] srcTypes) + { + DestType = destType; + SrcTypes = srcTypes; + } + } + + private static InstInfo[] _infoTbl; + + static InstructionInfo() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + // Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type + Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool); + Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool); + Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32); + Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32); + Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32); + Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Floor, VariableType.F32, VariableType.F32); + Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.IsNan, VariableType.Bool, VariableType.F32); + Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadGlobal, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadLocal, VariableType.F32, VariableType.S32); + Add(Instruction.LoadStorage, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int); + Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int); + Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32); + Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.StoreGlobal, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32); + Add(Instruction.StoreLocal, VariableType.None, VariableType.S32, VariableType.F32); + Add(Instruction.StoreStorage, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32); + Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.TextureSample, VariableType.F32); + Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.Truncate, VariableType.F32, VariableType.F32); + Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32); + } + + private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) + { + _infoTbl[(int)inst] = new InstInfo(destType, srcTypes); + } + + public static VariableType GetDestVarType(Instruction inst) + { + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst); + } + + public static VariableType GetSrcVarType(Instruction inst, int index) + { + if (inst == Instruction.TextureSample) + { + return VariableType.F32; + } + + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst); + } + + private static VariableType GetFinalVarType(VariableType type, Instruction inst) + { + if (type == VariableType.Scalar) + { + return (inst & Instruction.FP) != 0 + ? VariableType.F32 + : VariableType.S32; + } + else if (type == VariableType.Int) + { + return VariableType.S32; + } + else if (type == VariableType.None) + { + throw new ArgumentException($"Invalid operand for instruction \"{inst}\"."); + } + + return type; + } + + public static bool IsUnary(Instruction inst) + { + if (inst == Instruction.Copy) + { + return true; + } + else if (inst == Instruction.TextureSample) + { + return false; + } + + return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs new file mode 100644 index 00000000..95c5731a --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs @@ -0,0 +1,33 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class OperandInfo + { + public static VariableType GetVarType(AstOperand operand) + { + if (operand.Type == OperandType.LocalVariable) + { + return operand.VarType; + } + else + { + return GetVarType(operand.Type); + } + } + + public static VariableType GetVarType(OperandType type) + { + switch (type) + { + case OperandType.Attribute: return VariableType.F32; + case OperandType.Constant: return VariableType.S32; + case OperandType.ConstantBuffer: return VariableType.F32; + case OperandType.Undefined: return VariableType.S32; + } + + throw new ArgumentException($"Invalid operand type \"{type}\"."); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs new file mode 100644 index 00000000..53391b62 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs @@ -0,0 +1,74 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class PhiFunctions + { + public static void Remove(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode<INode> node = block.Operations.First; + + while (node != null) + { + LinkedListNode<INode> nextNode = node.Next; + + if (!(node.Value is PhiNode phi)) + { + node = nextNode; + + continue; + } + + for (int index = 0; index < phi.SourcesCount; index++) + { + Operand src = phi.GetSource(index); + + BasicBlock srcBlock = phi.GetBlock(index); + + Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src); + + AddBeforeBranch(srcBlock, copyOp); + } + + block.Operations.Remove(node); + + node = nextNode; + } + } + } + + private static void AddBeforeBranch(BasicBlock block, INode node) + { + INode lastOp = block.GetLastOp(); + + if (lastOp is Operation operation && IsControlFlowInst(operation.Inst)) + { + block.Operations.AddBefore(block.Operations.Last, node); + } + else + { + block.Operations.AddLast(node); + } + } + + private static bool IsControlFlowInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + case Instruction.Discard: + case Instruction.Return: + return true; + } + + return false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs new file mode 100644 index 00000000..dc822621 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -0,0 +1,281 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class StructuredProgram + { + public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks, ShaderConfig config) + { + PhiFunctions.Remove(blocks); + + StructuredProgramContext context = new StructuredProgramContext(blocks.Length, config); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + context.EnterBlock(block); + + foreach (INode node in block.Operations) + { + Operation operation = (Operation)node; + + if (IsBranchInst(operation.Inst)) + { + context.LeaveBlock(block, operation); + } + else + { + AddOperation(context, operation); + } + } + } + + GotoElimination.Eliminate(context.GetGotos()); + + AstOptimizer.Optimize(context); + + return context.Info; + } + + private static void AddOperation(StructuredProgramContext context, Operation operation) + { + Instruction inst = operation.Inst; + + IAstNode[] sources = new IAstNode[operation.SourcesCount]; + + for (int index = 0; index < sources.Length; index++) + { + sources[index] = context.GetOperandUse(operation.GetSource(index)); + } + + if (operation.Dest != null) + { + AstOperand dest = context.GetOperandDef(operation.Dest); + + if (inst == Instruction.LoadConstant) + { + Operand slot = operation.GetSource(0); + + if (slot.Type != OperandType.Constant) + { + throw new InvalidOperationException("Found load with non-constant constant buffer slot."); + } + + context.Info.CBuffers.Add(slot.Value); + } + else if (inst == Instruction.LoadStorage) + { + Operand slot = operation.GetSource(0); + + if (slot.Type != OperandType.Constant) + { + throw new InvalidOperationException("Found load or store with non-constant storage buffer slot."); + } + + context.Info.SBuffers.Add(slot.Value); + } + + AstAssignment assignment; + + // If all the sources are bool, it's better to use short-circuiting + // logical operations, rather than forcing a cast to int and doing + // a bitwise operation with the value, as it is likely to be used as + // a bool in the end. + if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool)) + { + inst = GetLogicalFromBitwiseInst(inst); + } + + bool isCondSel = inst == Instruction.ConditionalSelect; + bool isCopy = inst == Instruction.Copy; + + if (isCondSel || isCopy) + { + VariableType type = GetVarTypeFromUses(operation.Dest); + + if (isCondSel && type == VariableType.F32) + { + inst |= Instruction.FP; + } + + dest.VarType = type; + } + else + { + dest.VarType = InstructionInfo.GetDestVarType(inst); + } + + int componentMask = 1 << operation.ComponentIndex; + + IAstNode source; + + if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = new AstTextureOperation( + inst, + texOp.Target, + texOp.Flags, + texOp.Handle, + componentMask, + sources); + + context.Info.Samplers.Add(astTexOp); + + source = astTexOp; + } + else if (!isCopy) + { + source = new AstOperation(inst, componentMask, sources); + } + else + { + source = sources[0]; + } + + assignment = new AstAssignment(dest, source); + + context.AddNode(assignment); + } + else if (operation.Inst == Instruction.Comment) + { + context.AddNode(new AstComment(((CommentNode)operation).Comment)); + } + else + { + if (inst == Instruction.StoreStorage) + { + Operand slot = operation.GetSource(0); + + if (slot.Type != OperandType.Constant) + { + throw new InvalidOperationException("Found load or store with non-constant storage buffer slot."); + } + + context.Info.SBuffers.Add(slot.Value); + } + + context.AddNode(new AstOperation(inst, sources)); + } + } + + private static VariableType GetVarTypeFromUses(Operand dest) + { + HashSet<Operand> visited = new HashSet<Operand>(); + + Queue<Operand> pending = new Queue<Operand>(); + + bool Enqueue(Operand operand) + { + if (visited.Add(operand)) + { + pending.Enqueue(operand); + + return true; + } + + return false; + } + + Enqueue(dest); + + while (pending.TryDequeue(out Operand operand)) + { + foreach (INode useNode in operand.UseOps) + { + if (!(useNode is Operation operation)) + { + continue; + } + + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + if (Enqueue(operation.Dest)) + { + break; + } + } + else + { + return OperandInfo.GetVarType(operation.Dest.Type); + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == operand) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + } + + return VariableType.S32; + } + + private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type) + { + foreach (IAstNode node in sources) + { + if (!(node is AstOperand operand)) + { + return false; + } + + if (operand.VarType != type) + { + return false; + } + } + + return true; + } + + private static bool IsBranchInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + return true; + } + + return false; + } + + private static bool IsBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + return true; + } + + return false; + } + + private static Instruction GetLogicalFromBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: return Instruction.LogicalAnd; + case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr; + case Instruction.BitwiseNot: return Instruction.LogicalNot; + case Instruction.BitwiseOr: return Instruction.LogicalOr; + } + + throw new ArgumentException($"Unexpected instruction \"{inst}\"."); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs new file mode 100644 index 00000000..03ff8818 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -0,0 +1,303 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramContext + { + private HashSet<BasicBlock> _loopTails; + + private Stack<(AstBlock Block, int EndIndex)> _blockStack; + + private Dictionary<Operand, AstOperand> _localsMap; + + private Dictionary<int, AstAssignment> _gotoTempAsgs; + + private List<GotoStatement> _gotos; + + private AstBlock _currBlock; + + private int _currEndIndex; + + public StructuredProgramInfo Info { get; } + + public ShaderConfig Config { get; } + + public StructuredProgramContext(int blocksCount, ShaderConfig config) + { + _loopTails = new HashSet<BasicBlock>(); + + _blockStack = new Stack<(AstBlock, int)>(); + + _localsMap = new Dictionary<Operand, AstOperand>(); + + _gotoTempAsgs = new Dictionary<int, AstAssignment>(); + + _gotos = new List<GotoStatement>(); + + _currBlock = new AstBlock(AstBlockType.Main); + + _currEndIndex = blocksCount; + + Info = new StructuredProgramInfo(_currBlock); + + Config = config; + } + + public void EnterBlock(BasicBlock block) + { + while (_currEndIndex == block.Index) + { + (_currBlock, _currEndIndex) = _blockStack.Pop(); + } + + if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) + { + AddGotoTempReset(block, gotoTempAsg); + } + + LookForDoWhileStatements(block); + } + + public void LeaveBlock(BasicBlock block, Operation branchOp) + { + LookForIfStatements(block, branchOp); + } + + private void LookForDoWhileStatements(BasicBlock block) + { + // Check if we have any predecessor whose index is greater than the + // current block, this indicates a loop. + bool done = false; + + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) + { + if (predecessor.Index < block.Index) + { + break; + } + + if (predecessor.Index < _currEndIndex && !done) + { + Operation branchOp = (Operation)predecessor.GetLastOp(); + + NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + + _loopTails.Add(predecessor); + + done = true; + } + else + { + AddGotoTempReset(block, GetGotoTempAsg(block.Index)); + + break; + } + } + } + + private void LookForIfStatements(BasicBlock block, Operation branchOp) + { + if (block.Branch == null) + { + return; + } + + bool isLoop = block.Branch.Index <= block.Index; + + if (block.Branch.Index <= _currEndIndex && !isLoop) + { + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); + } + else if (!_loopTails.Contains(block)) + { + AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + + IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); + + AddNode(Assign(gotoTempAsg.Destination, cond)); + + AstOperation branch = new AstOperation(branchOp.Inst); + + AddNode(branch); + + GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop); + + _gotos.Add(gotoStmt); + } + } + + private AstAssignment GetGotoTempAsg(int index) + { + if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg)) + { + return gotoTempAsg; + } + + AstOperand gotoTemp = NewTemp(VariableType.Bool); + + gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False)); + + _gotoTempAsgs.Add(index, gotoTempAsg); + + return gotoTempAsg; + } + + private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) + { + AddNode(gotoTempAsg); + + // For block 0, we don't need to add the extra "reset" at the beginning, + // because it is already the first node to be executed on the shader, + // so it is reset to false by the "local" assignment anyway. + if (block.Index != 0) + { + Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False))); + } + } + + private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) + { + NewBlock(type, GetBranchCond(type, branchOp), endIndex); + } + + private void NewBlock(AstBlockType type, IAstNode cond, int endIndex) + { + AstBlock childBlock = new AstBlock(type, cond); + + AddNode(childBlock); + + _blockStack.Push((_currBlock, _currEndIndex)); + + _currBlock = childBlock; + _currEndIndex = endIndex; + } + + private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) + { + IAstNode cond; + + if (branchOp.Inst == Instruction.Branch) + { + cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); + } + else + { + cond = GetOperandUse(branchOp.GetSource(0)); + + Instruction invInst = type == AstBlockType.If + ? Instruction.BranchIfTrue + : Instruction.BranchIfFalse; + + if (branchOp.Inst == invInst) + { + cond = new AstOperation(Instruction.LogicalNot, cond); + } + } + + return cond; + } + + public void AddNode(IAstNode node) + { + _currBlock.Add(node); + } + + public GotoStatement[] GetGotos() + { + return _gotos.ToArray(); + } + + private AstOperand NewTemp(VariableType type) + { + AstOperand newTemp = Local(type); + + Info.Locals.Add(newTemp); + + return newTemp; + } + + public AstOperand GetOperandDef(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.OAttributes.Add(attrIndex); + } + + return GetOperand(operand); + } + + public AstOperand GetOperandUse(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.IAttributes.Add(attrIndex); + + Info.InterpolationQualifiers[attrIndex] = operand.Interpolation; + } + else if (operand.Type == OperandType.Attribute && operand.Value == AttributeConsts.InstanceId) + { + Info.UsesInstanceId = true; + } + else if (operand.Type == OperandType.ConstantBuffer) + { + Info.CBuffers.Add(operand.GetCbufSlot()); + } + + return GetOperand(operand); + } + + private AstOperand GetOperand(Operand operand) + { + if (operand == null) + { + return null; + } + + if (operand.Type != OperandType.LocalVariable) + { + return new AstOperand(operand); + } + + if (!_localsMap.TryGetValue(operand, out AstOperand astOperand)) + { + astOperand = new AstOperand(operand); + + _localsMap.Add(operand, astOperand); + + Info.Locals.Add(astOperand); + } + + return astOperand; + } + + private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex) + { + if (operand.Type == OperandType.Attribute) + { + if (operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd) + { + attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4; + + return true; + } + else if (operand.Value >= AttributeConsts.FragmentOutputColorBase && + operand.Value < AttributeConsts.FragmentOutputColorEnd) + { + attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4; + + return true; + } + } + + attrIndex = 0; + + return false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs new file mode 100644 index 00000000..27fd1487 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramInfo + { + public AstBlock MainBlock { get; } + + public HashSet<AstOperand> Locals { get; } + + public HashSet<int> CBuffers { get; } + public HashSet<int> SBuffers { get; } + + public HashSet<int> IAttributes { get; } + public HashSet<int> OAttributes { get; } + + public InterpolationQualifier[] InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; set; } + + public HashSet<AstTextureOperation> Samplers { get; } + + public StructuredProgramInfo(AstBlock mainBlock) + { + MainBlock = mainBlock; + + Locals = new HashSet<AstOperand>(); + + CBuffers = new HashSet<int>(); + SBuffers = new HashSet<int>(); + + IAttributes = new HashSet<int>(); + OAttributes = new HashSet<int>(); + + InterpolationQualifiers = new InterpolationQualifier[32]; + + Samplers = new HashSet<AstTextureOperation>(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs new file mode 100644 index 00000000..4c7f3849 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum VariableType + { + None, + Bool, + Scalar, + Int, + F32, + S32, + U32 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs new file mode 100644 index 00000000..484fa1bc --- /dev/null +++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct TextureDescriptor + { + public string Name { get; } + + public TextureTarget Target { get; } + + public int HandleIndex { get; } + + public bool IsBindless { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + public TextureDescriptor(string name, TextureTarget target, int hIndex) + { + Name = name; + Target = target; + HandleIndex = hIndex; + + IsBindless = false; + + CbufSlot = 0; + CbufOffset = 0; + } + + public TextureDescriptor(string name, TextureTarget target, int cbufSlot, int cbufOffset) + { + Name = name; + Target = target; + HandleIndex = 0; + + IsBindless = true; + + CbufSlot = cbufSlot; + CbufOffset = cbufOffset; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/TextureTarget.cs b/Ryujinx.Graphics.Shader/TextureTarget.cs new file mode 100644 index 00000000..3642ef23 --- /dev/null +++ b/Ryujinx.Graphics.Shader/TextureTarget.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum TextureTarget + { + Texture1D, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + Multisample = 1 << 9, + Shadow = 1 << 10 + } + + static class TextureTargetExtensions + { + public static int GetDimensions(this TextureTarget type) + { + switch (type & TextureTarget.Mask) + { + case TextureTarget.Texture1D: return 1; + case TextureTarget.Texture2D: return 2; + case TextureTarget.Texture3D: return 3; + case TextureTarget.TextureCube: return 3; + } + + throw new ArgumentException($"Invalid texture type \"{type}\"."); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs new file mode 100644 index 00000000..08aac1ca --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -0,0 +1,46 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + static class AttributeConsts + { + public const int Layer = 0x064; + public const int PointSize = 0x06c; + public const int PositionX = 0x070; + public const int PositionY = 0x074; + public const int PositionZ = 0x078; + public const int PositionW = 0x07c; + public const int ClipDistance0 = 0x2c0; + public const int ClipDistance1 = 0x2c4; + public const int ClipDistance2 = 0x2c8; + public const int ClipDistance3 = 0x2cc; + public const int ClipDistance4 = 0x2d0; + public const int ClipDistance5 = 0x2d4; + public const int ClipDistance6 = 0x2d8; + public const int ClipDistance7 = 0x2dc; + public const int PointCoordX = 0x2e0; + public const int PointCoordY = 0x2e4; + public const int TessCoordX = 0x2f0; + public const int TessCoordY = 0x2f4; + public const int InstanceId = 0x2f8; + public const int VertexId = 0x2fc; + public const int FrontFacing = 0x3fc; + + public const int UserAttributesCount = 32; + public const int UserAttributeBase = 0x80; + public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16; + + + // Note: Those attributes are used internally by the translator + // only, they don't exist on Maxwell. + public const int FragmentOutputDepth = 0x1000000; + public const int FragmentOutputColorBase = 0x1000010; + public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; + + 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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs b/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs new file mode 100644 index 00000000..e2ca74a4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs @@ -0,0 +1,108 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class ControlFlowGraph + { + public static BasicBlock[] MakeCfg(Operation[] operations) + { + Dictionary<Operand, BasicBlock> labels = new Dictionary<Operand, BasicBlock>(); + + List<BasicBlock> blocks = new List<BasicBlock>(); + + BasicBlock currentBlock = null; + + void NextBlock(BasicBlock nextBlock) + { + if (currentBlock != null && !EndsWithUnconditionalInst(currentBlock.GetLastOp())) + { + currentBlock.Next = nextBlock; + } + + currentBlock = nextBlock; + } + + void NewNextBlock() + { + BasicBlock block = new BasicBlock(blocks.Count); + + blocks.Add(block); + + NextBlock(block); + } + + bool needsNewBlock = true; + + for (int index = 0; index < operations.Length; index++) + { + Operation operation = operations[index]; + + if (operation.Inst == Instruction.MarkLabel) + { + Operand label = operation.Dest; + + if (labels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = blocks.Count; + + blocks.Add(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + labels.Add(label, currentBlock); + } + } + else + { + if (needsNewBlock) + { + NewNextBlock(); + } + + currentBlock.Operations.AddLast(operation); + } + + needsNewBlock = operation.Inst == Instruction.Branch || + operation.Inst == Instruction.BranchIfTrue || + operation.Inst == Instruction.BranchIfFalse; + + if (needsNewBlock) + { + Operand label = operation.Dest; + + if (!labels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + labels.Add(label, branchBlock); + } + + currentBlock.Branch = branchBlock; + } + } + + return blocks.ToArray(); + } + + private static bool EndsWithUnconditionalInst(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst) + { + case Instruction.Branch: + case Instruction.Discard: + case Instruction.Return: + return true; + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Dominance.cs b/Ryujinx.Graphics.Shader/Translation/Dominance.cs new file mode 100644 index 00000000..6a3ff35f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Dominance.cs @@ -0,0 +1,127 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Dominance + { + // Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + // https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(BasicBlock entry, int blocksCount) + { + HashSet<BasicBlock> visited = new HashSet<BasicBlock>(); + + Stack<BasicBlock> blockStack = new Stack<BasicBlock>(); + + List<BasicBlock> postOrderBlocks = new List<BasicBlock>(blocksCount); + + int[] postOrderMap = new int[blocksCount]; + + visited.Add(entry); + + blockStack.Push(entry); + + while (blockStack.TryPop(out BasicBlock block)) + { + if (block.Next != null && visited.Add(block.Next)) + { + blockStack.Push(block); + blockStack.Push(block.Next); + } + else if (block.Branch != null && visited.Add(block.Branch)) + { + blockStack.Push(block); + blockStack.Push(block.Branch); + } + else + { + postOrderMap[block.Index] = postOrderBlocks.Count; + + postOrderBlocks.Add(block); + } + } + + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (postOrderMap[block1.Index] < postOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (postOrderMap[block2.Index] < postOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + entry.ImmediateDominator = entry; + + bool modified; + + do + { + modified = false; + + for (int blkIndex = postOrderBlocks.Count - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = postOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs new file mode 100644 index 00000000..3995d430 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class EmitterContext + { + public Block CurrBlock { get; set; } + public OpCode CurrOp { get; set; } + + private ShaderStage _stage; + + private ShaderHeader _header; + + private List<Operation> _operations; + + private Dictionary<ulong, Operand> _labels; + + public EmitterContext(ShaderStage stage, ShaderHeader header) + { + _stage = stage; + _header = header; + + _operations = new List<Operation>(); + + _labels = new Dictionary<ulong, Operand>(); + } + + public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources) + { + Operation operation = new Operation(inst, dest, sources); + + Add(operation); + + return dest; + } + + public void Add(Operation operation) + { + _operations.Add(operation); + } + + public void MarkLabel(Operand label) + { + Add(Instruction.MarkLabel, label); + } + + public Operand GetLabel(ulong address) + { + if (!_labels.TryGetValue(address, out Operand label)) + { + label = Label(); + + _labels.Add(address, label); + } + + return label; + } + + public void PrepareForReturn() + { + if (_stage == ShaderStage.Fragment) + { + if (_header.OmapDepth) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); + + Operand src = Register(_header.DepthRegister, RegisterType.Gpr); + + this.Copy(dest, src); + } + + int regIndex = 0; + + for (int attachment = 0; attachment < 8; attachment++) + { + OutputMapTarget target = _header.OmapTargets[attachment]; + + for (int component = 0; component < 4; component++) + { + if (target.ComponentEnabled(component)) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4); + + Operand src = Register(regIndex, RegisterType.Gpr); + + this.Copy(dest, src); + + regIndex++; + } + } + } + } + } + + public Operation[] GetOperations() + { + return _operations.ToArray(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs new file mode 100644 index 00000000..f0bede18 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -0,0 +1,445 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class EmitterContextInsts + { + public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c); + } + + public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c); + } + + public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d); + } + + public static Operand BitfieldReverse(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitfieldReverse, Local(), a); + } + + public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseAnd, Local(), a, b); + } + + public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b); + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert) + { + if (invert) + { + a = context.BitwiseNot(a); + } + + return a; + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitwiseNot, Local(), a); + } + + public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseOr, Local(), a, b); + } + + public static Operand Branch(this EmitterContext context, Operand d) + { + return context.Add(Instruction.Branch, d); + } + + public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfFalse, d, a); + } + + public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfTrue, d, a); + } + + public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ConditionalSelect, Local(), a, b, c); + } + + public static Operand Copy(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Copy, Local(), a); + } + + public static void Copy(this EmitterContext context, Operand d, Operand a) + { + if (d.Type == OperandType.Constant) + { + return; + } + + context.Add(Instruction.Copy, d, a); + } + + public static Operand Discard(this EmitterContext context) + { + return context.Add(Instruction.Discard); + } + + public static Operand EmitVertex(this EmitterContext context) + { + return context.Add(Instruction.EmitVertex); + } + + public static Operand EndPrimitive(this EmitterContext context) + { + return context.Add(Instruction.EndPrimitive); + } + + public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.FPNegate(context.FPAbsolute(a, abs), neg); + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.FPAbsolute(a); + } + + return a; + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Absolute, Local(), a); + } + + public static Operand FPAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Add, Local(), a, b); + } + + public static Operand FPCeiling(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Ceiling, Local(), a); + } + + public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareEqual, Local(), a, b); + } + + public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareLess, Local(), a, b); + } + + public static Operand FPConvertToS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFPToS32, Local(), a); + } + + public static Operand FPCosine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Cosine, Local(), a); + } + + public static Operand FPDivide(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Divide, Local(), a, b); + } + + public static Operand FPExponentB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ExponentB2, Local(), a); + } + + public static Operand FPFloor(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Floor, Local(), a); + } + + public static Operand FPLogarithmB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a); + } + + public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Maximum, Local(), a, b); + } + + public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Minimum, Local(), a, b); + } + + public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b); + } + + public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c); + } + + public static Operand FPNegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.FPNegate(a); + } + + return a; + } + + public static Operand FPNegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Negate, Local(), a); + } + + public static Operand FPReciprocal(this EmitterContext context, Operand a) + { + return context.FPDivide(ConstF(1), a); + } + + public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a); + } + + public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat) + { + if (sat) + { + a = context.FPSaturate(a); + } + + return a; + } + + public static Operand FPSaturate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1)); + } + + public static Operand FPSine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Sine, Local(), a); + } + + public static Operand FPSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.SquareRoot, Local(), a); + } + + public static Operand FPTruncate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Truncate, Local(), a); + } + + public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.INegate(context.IAbsolute(a, abs), neg); + } + + public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.IAbsolute(a); + } + + return a; + } + + public static Operand IAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Absolute, Local(), a); + } + + public static Operand IAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Add, Local(), a, b); + } + + public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Clamp, Local(), a, b, c); + } + + public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ClampU32, Local(), a, b, c); + } + + public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareEqual, Local(), a, b); + } + + public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLess, Local(), a, b); + } + + public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessU32, Local(), a, b); + } + + public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareNotEqual, Local(), a, b); + } + + public static Operand IConvertS32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertS32ToFP, Local(), a); + } + + public static Operand IConvertU32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertU32ToFP, Local(), a); + } + + public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Maximum, Local(), a, b); + } + + public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MaximumU32, Local(), a, b); + } + + public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Minimum, Local(), a, b); + } + + public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MinimumU32, Local(), a, b); + } + + public static Operand IMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Multiply, Local(), a, b); + } + + public static Operand INegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.INegate(a); + } + + return a; + } + + public static Operand INegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Negate, Local(), a); + } + + public static Operand ISubtract(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Subtract, Local(), a, b); + } + + public static Operand IsNan(this EmitterContext context, Operand a) + { + return context.Add(Instruction.IsNan, Local(), a); + } + + public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadAttribute, Local(), a, b); + } + + public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadConstant, Local(), a, b); + } + + public static Operand LoadGlobal(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadGlobal, Local(), a); + } + + public static Operand LoadLocal(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadLocal, Local(), a); + } + + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackHalf2x16, Local(), a, b); + } + + public static Operand Return(this EmitterContext context) + { + context.PrepareForReturn(); + + return context.Add(Instruction.Return); + } + + public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftLeft, Local(), a, b); + } + + public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightS32, Local(), a, b); + } + + public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightU32, Local(), a, b); + } + + public static Operand StoreGlobal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreGlobal, null, a, b); + } + + public static Operand StoreLocal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreLocal, null, a, b); + } + + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 1); + } + + public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 0); + } + + private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a)); + + return dest; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs new file mode 100644 index 00000000..c87d1474 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs @@ -0,0 +1,64 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BranchElimination + { + public static bool RunPass(BasicBlock block) + { + if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block))) + { + block.Branch = null; + + return true; + } + + return false; + } + + private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock) + { + // Here we check that: + // - The current block ends with a branch. + // - The next block only contains a branch. + // - The branch on the next block is unconditional. + // - Both branches are jumping to the same location. + // In this case, the branch on the current block can be removed, + // as the next block is going to jump to the same place anyway. + if (nextBlock == null) + { + return false; + } + + if (!(nextBlock.Operations.First?.Value is Operation next)) + { + return false; + } + + if (next.Inst != Instruction.Branch) + { + return false; + } + + return current.Dest == next.Dest; + } + + private static BasicBlock Next(BasicBlock block) + { + block = block.Next; + + while (block != null && block.Operations.Count == 0) + { + if (block.HasBranch) + { + throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\"."); + } + + block = block.Next; + } + + return block; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs new file mode 100644 index 00000000..d64579b7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs @@ -0,0 +1,323 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(Operation operation) + { + if (!AreAllSourcesConstant(operation)) + { + return; + } + + switch (operation.Inst) + { + case Instruction.Add: + EvaluateBinary(operation, (x, y) => x + y); + break; + + case Instruction.BitwiseAnd: + EvaluateBinary(operation, (x, y) => x & y); + break; + + case Instruction.BitwiseExclusiveOr: + EvaluateBinary(operation, (x, y) => x ^ y); + break; + + case Instruction.BitwiseNot: + EvaluateUnary(operation, (x) => ~x); + break; + + case Instruction.BitwiseOr: + EvaluateBinary(operation, (x, y) => x | y); + break; + + case Instruction.BitfieldExtractS32: + BitfieldExtractS32(operation); + break; + + case Instruction.BitfieldExtractU32: + BitfieldExtractU32(operation); + break; + + case Instruction.Clamp: + EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.ClampU32: + EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z)); + break; + + case Instruction.CompareEqual: + EvaluateBinary(operation, (x, y) => x == y); + break; + + case Instruction.CompareGreater: + EvaluateBinary(operation, (x, y) => x > y); + break; + + case Instruction.CompareGreaterOrEqual: + EvaluateBinary(operation, (x, y) => x >= y); + break; + + case Instruction.CompareGreaterOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y); + break; + + case Instruction.CompareGreaterU32: + EvaluateBinary(operation, (x, y) => (uint)x > (uint)y); + break; + + case Instruction.CompareLess: + EvaluateBinary(operation, (x, y) => x < y); + break; + + case Instruction.CompareLessOrEqual: + EvaluateBinary(operation, (x, y) => x <= y); + break; + + case Instruction.CompareLessOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y); + break; + + case Instruction.CompareLessU32: + EvaluateBinary(operation, (x, y) => (uint)x < (uint)y); + break; + + case Instruction.CompareNotEqual: + EvaluateBinary(operation, (x, y) => x != y); + break; + + case Instruction.Divide: + EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0); + break; + + case Instruction.FP | Instruction.Add: + EvaluateFPBinary(operation, (x, y) => x + y); + break; + + case Instruction.FP | Instruction.Clamp: + EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.FP | Instruction.CompareEqual: + EvaluateFPBinary(operation, (x, y) => x == y); + break; + + case Instruction.FP | Instruction.CompareGreater: + EvaluateFPBinary(operation, (x, y) => x > y); + break; + + case Instruction.FP | Instruction.CompareGreaterOrEqual: + EvaluateFPBinary(operation, (x, y) => x >= y); + break; + + case Instruction.FP | Instruction.CompareLess: + EvaluateFPBinary(operation, (x, y) => x < y); + break; + + case Instruction.FP | Instruction.CompareLessOrEqual: + EvaluateFPBinary(operation, (x, y) => x <= y); + break; + + case Instruction.FP | Instruction.CompareNotEqual: + EvaluateFPBinary(operation, (x, y) => x != y); + break; + + case Instruction.FP | Instruction.Divide: + EvaluateFPBinary(operation, (x, y) => x / y); + break; + + case Instruction.FP | Instruction.Multiply: + EvaluateFPBinary(operation, (x, y) => x * y); + break; + + case Instruction.FP | Instruction.Negate: + EvaluateFPUnary(operation, (x) => -x); + break; + + case Instruction.FP | Instruction.Subtract: + EvaluateFPBinary(operation, (x, y) => x - y); + break; + + case Instruction.IsNan: + EvaluateFPUnary(operation, (x) => float.IsNaN(x)); + break; + + case Instruction.Maximum: + EvaluateBinary(operation, (x, y) => Math.Max(x, y)); + break; + + case Instruction.MaximumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y)); + break; + + case Instruction.Minimum: + EvaluateBinary(operation, (x, y) => Math.Min(x, y)); + break; + + case Instruction.MinimumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y)); + break; + + case Instruction.Multiply: + EvaluateBinary(operation, (x, y) => x * y); + break; + + case Instruction.Negate: + EvaluateUnary(operation, (x) => -x); + break; + + case Instruction.ShiftLeft: + EvaluateBinary(operation, (x, y) => x << y); + break; + + case Instruction.ShiftRightS32: + EvaluateBinary(operation, (x, y) => x >> y); + break; + + case Instruction.ShiftRightU32: + EvaluateBinary(operation, (x, y) => (int)((uint)x >> y)); + break; + + case Instruction.Subtract: + EvaluateBinary(operation, (x, y) => x - y); + break; + + case Instruction.UnpackHalf2x16: + UnpackHalf2x16(operation); + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index).Type != OperandType.Constant) + { + return false; + } + } + + return true; + } + + private static void BitfieldExtractS32(Operation operation) + { + int value = GetBitfieldExtractValue(operation); + + int shift = 32 - operation.GetSource(2).Value; + + value = (value << shift) >> shift; + + operation.TurnIntoCopy(Const(value)); + } + + private static void BitfieldExtractU32(Operation operation) + { + operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation))); + } + + private static int GetBitfieldExtractValue(Operation operation) + { + int value = operation.GetSource(0).Value; + int lsb = operation.GetSource(1).Value; + int length = operation.GetSource(2).Value; + + return value.Extract(lsb, length); + } + + private static void UnpackHalf2x16(Operation operation) + { + int value = operation.GetSource(0).Value; + + value = (value >> operation.ComponentIndex * 16) & 0xffff; + + operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value))); + } + + private static void FPNegate(Operation operation) + { + float value = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(-value)); + } + + private static void EvaluateUnary(Operation operation, Func<int, int> op) + { + int x = operation.GetSource(0).Value; + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func<float, float> op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func<float, bool> op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateBinary(Operation operation, Func<int, int, int> op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinary(Operation operation, Func<int, int, bool> op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateFPBinary(Operation operation, Func<float, float, float> op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y))); + } + + private static void EvaluateFPBinary(Operation operation, Func<float, float, bool> op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateTernary(Operation operation, Func<int, int, int, int> op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + int z = operation.GetSource(2).Value; + + operation.TurnIntoCopy(Const(op(x, y, z))); + } + + private static void EvaluateFPTernary(Operation operation, Func<float, float, float, float> op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + float z = operation.GetSource(2).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y, z))); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs new file mode 100644 index 00000000..06db2a80 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -0,0 +1,145 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class GlobalToStorage + { + private const int StorageDescsBaseOffset = 0x44; // In words. + + private const int UbeStorageDescsBaseOffset = 0x84; // In words. + private const int UbeStorageMaxCount = 14; + + private const int StorageDescSize = 4; // In words. + private const int StorageMaxCount = 16; + + private const int StorageDescsSize = StorageDescSize * StorageMaxCount; + + public static void RunPass(BasicBlock block, ShaderStage stage) + { + int sbStart = GetStorageBaseCbOffset(stage); + + int sbEnd = sbStart + StorageDescsSize; + + // This one is only used on compute shaders. + // Compute shaders uses two separate sets of storage. + int ubeSbStart = UbeStorageDescsBaseOffset; + int ubeSbEnd = UbeStorageDescsBaseOffset + StorageDescSize * UbeStorageMaxCount; + + for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is Operation operation)) + { + continue; + } + + if (operation.Inst == Instruction.LoadGlobal || + operation.Inst == Instruction.StoreGlobal) + { + Operand source = operation.GetSource(0); + + if (source.AsgOp is Operation asgOperation) + { + int storageIndex = SearchForStorageBase(asgOperation, sbStart, sbEnd); + + /*if (storageIndex < 0 && stage == ShaderStage.Compute) + { + storageIndex = SearchForStorageBase(asgOperation, ubeSbStart, ubeSbEnd); + }*/ + + if (storageIndex >= 0) + { + node = ReplaceGlobalWithStorage(node, storageIndex); + } + } + } + } + } + + private static LinkedListNode<INode> ReplaceGlobalWithStorage(LinkedListNode<INode> node, int storageIndex) + { + Operation operation = (Operation)node.Value; + + Operation storageOp; + + if (operation.Inst == Instruction.LoadGlobal) + { + Operand source = operation.GetSource(0); + + storageOp = new Operation(Instruction.LoadStorage, operation.Dest, Const(storageIndex), source); + } + else + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + storageOp = new Operation(Instruction.StoreStorage, null, Const(storageIndex), src1, src2); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode<INode> oldNode = node; + + node = node.List.AddAfter(node, storageOp); + + node.List.Remove(oldNode); + + return node; + } + + private static int SearchForStorageBase(Operation operation, int sbStart, int sbEnd) + { + Queue<Operation> assignments = new Queue<Operation>(); + + assignments.Enqueue(operation); + + while (assignments.TryDequeue(out operation)) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source.Type == OperandType.ConstantBuffer) + { + int slot = source.GetCbufSlot(); + int offset = source.GetCbufOffset(); + + if (slot == 0 && offset >= sbStart && offset < sbEnd) + { + int storageIndex = (offset - sbStart) / StorageDescSize; + + return storageIndex; + } + } + + if (source.AsgOp is Operation asgOperation) + { + assignments.Enqueue(asgOperation); + } + } + } + + return -1; + } + + private static int GetStorageBaseCbOffset(ShaderStage stage) + { + switch (stage) + { + case ShaderStage.Compute: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Vertex: return StorageDescsBaseOffset; + case ShaderStage.TessellationControl: return StorageDescsBaseOffset + 1 * StorageDescsSize; + case ShaderStage.TessellationEvaluation: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Geometry: return StorageDescsBaseOffset + 3 * StorageDescsSize; + case ShaderStage.Fragment: return StorageDescsBaseOffset + 4 * StorageDescsSize; + } + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs new file mode 100644 index 00000000..96060272 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs @@ -0,0 +1,47 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class HalfConversion + { + public static float HalfToSingle(int value) + { + int mantissa = (value >> 0) & 0x3ff; + int exponent = (value >> 10) & 0x1f; + int sign = (value >> 15) & 0x1; + + if (exponent == 0x1f) + { + // NaN or Infinity. + mantissa <<= 13; + exponent = 0xff; + } + else if (exponent != 0 || mantissa != 0 ) + { + if (exponent == 0) + { + // Denormal. + int e = -1; + int m = mantissa; + + do + { + e++; + m <<= 1; + } + while ((m & 0x400) == 0); + + mantissa = m & 0x3ff; + exponent = e; + } + + mantissa <<= 13; + exponent = 127 - 15 + exponent; + } + + int output = (sign << 31) | (exponent << 23) | mantissa; + + return BitConverter.Int32BitsToSingle(output); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs new file mode 100644 index 00000000..d5e57546 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -0,0 +1,177 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Optimizer + { + public static void Optimize(BasicBlock[] blocks, ShaderStage stage) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + GlobalToStorage.RunPass(blocks[blkIndex], stage); + } + + bool modified; + + do + { + modified = false; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode<INode> node = block.Operations.First; + + while (node != null) + { + LinkedListNode<INode> nextNode = node.Next; + + bool isUnused = IsUnused(node.Value); + + if (!(node.Value is Operation operation) || isUnused) + { + if (isUnused) + { + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + + continue; + } + + ConstantFolding.RunPass(operation); + + Simplification.RunPass(operation); + + if (DestIsLocalVar(operation)) + { + if (operation.Inst == Instruction.Copy) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + else if (operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) + { + if (operation.Dest.UseOps.Count == 0) + { + RemoveNode(block, node); + } + + modified = true; + } + } + + node = nextNode; + } + + if (BranchElimination.RunPass(block)) + { + RemoveNode(block, block.Operations.Last); + + modified = true; + } + } + } + while (modified); + } + + private static void PropagateCopy(Operation copyOp) + { + // Propagate copy source operand to all uses of + // the destination operand. + Operand dest = copyOp.Dest; + Operand src = copyOp.GetSource(0); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, src); + } + } + } + } + + private static bool PropagatePack(Operation packOp) + { + // Propagate pack source operands to uses by unpack + // instruction. The source depends on the unpack instruction. + bool modified = false; + + Operand dest = packOp.Dest; + Operand src0 = packOp.GetSource(0); + Operand src1 = packOp.GetSource(1); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16) + { + continue; + } + + if (operation.GetSource(0) == dest) + { + operation.TurnIntoCopy(operation.ComponentIndex == 1 ? src1 : src0); + + modified = true; + } + } + + return modified; + } + + private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode) + { + // Remove a node from the nodes list, and also remove itself + // from all the use lists on the operands that this node uses. + block.Operations.Remove(llNode); + + Queue<INode> nodes = new Queue<INode>(); + + nodes.Enqueue(llNode.Value); + + while (nodes.TryDequeue(out INode node)) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (src.Type != OperandType.LocalVariable) + { + continue; + } + + if (src.UseOps.Remove(node) && src.UseOps.Count == 0) + { + nodes.Enqueue(src.AsgOp); + } + } + } + } + + private static bool IsUnused(INode node) + { + return DestIsLocalVar(node) && node.Dest.UseOps.Count == 0; + } + + private static bool DestIsLocalVar(INode node) + { + return node.Dest != null && node.Dest.Type == OperandType.LocalVariable; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs new file mode 100644 index 00000000..8d05f99a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs @@ -0,0 +1,147 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Simplification + { + private const int AllOnes = ~0; + + public static void RunPass(Operation operation) + { + switch (operation.Inst) + { + case Instruction.Add: + case Instruction.BitwiseExclusiveOr: + TryEliminateBinaryOpCommutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpCommutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightS32: + case Instruction.ShiftRightU32: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + // x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(0)); + } + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x | 0x00000000 == x, 0x00000000 | y == y, + // x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes) || IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(Const(AllOnes)); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpCommutative(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Type != OperandType.Constant) + { + return; + } + + // The condition is constant, we can turn it into a copy, and select + // the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, int comparand) + { + if (operand.Type != OperandType.Constant) + { + return false; + } + + return operand.Value == comparand; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Ssa.cs b/Ryujinx.Graphics.Shader/Translation/Ssa.cs new file mode 100644 index 00000000..a4d763be --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Ssa.cs @@ -0,0 +1,330 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Ssa + { + private const int GprsAndPredsCount = RegisterConsts.GprsCount + RegisterConsts.PredsCount; + + private class DefMap + { + private Dictionary<Register, Operand> _map; + + private long[] _phiMasks; + + public DefMap() + { + _map = new Dictionary<Register, Operand>(); + + _phiMasks = new long[(RegisterConsts.TotalCount + 63) / 64]; + } + + public bool TryAddOperand(Register reg, Operand operand) + { + return _map.TryAdd(reg, operand); + } + + public bool TryGetOperand(Register reg, out Operand operand) + { + return _map.TryGetValue(reg, out operand); + } + + public bool AddPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + long mask = 1L << bit; + + if ((_phiMasks[index] & mask) != 0) + { + return false; + } + + _phiMasks[index] |= mask; + + return true; + } + + public bool HasPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + return (_phiMasks[index] & (1L << bit)) != 0; + } + } + + private struct Definition + { + public BasicBlock Block { get; } + public Operand Local { get; } + + public Definition(BasicBlock block, Operand local) + { + Block = block; + Local = local; + } + } + + public static void Rename(BasicBlock[] blocks) + { + DefMap[] globalDefs = new DefMap[blocks.Length]; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + globalDefs[blkIndex] = new DefMap(); + } + + Queue<BasicBlock> dfPhiBlocks = new Queue<BasicBlock>(); + + // First pass, get all defs and locals uses. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + Operand RenameLocal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + Operand local = localDefs[GetKeyFromRegister(operand.GetRegister())]; + + operand = local ?? operand; + } + + return operand; + } + + BasicBlock block = blocks[blkIndex]; + + LinkedListNode<INode> node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameLocal(operation.GetSource(index))); + } + + if (operation.Dest != null && operation.Dest.Type == OperandType.Register) + { + Operand local = Local(); + + localDefs[GetKeyFromRegister(operation.Dest.GetRegister())] = local; + + operation.Dest = local; + } + } + + node = node.Next; + } + + for (int index = 0; index < RegisterConsts.TotalCount; index++) + { + Operand local = localDefs[index]; + + if (local == null) + { + continue; + } + + Register reg = GetRegisterFromKey(index); + + globalDefs[block.Index].TryAddOperand(reg, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(reg)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + } + + // Second pass, rename variables with definitions on different blocks. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + BasicBlock block = blocks[blkIndex]; + + Operand RenameGlobal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + int key = GetKeyFromRegister(operand.GetRegister()); + + Operand local = localDefs[key]; + + if (local != null) + { + return local; + } + + operand = FindDefinitionForCurr(globalDefs, block, operand.GetRegister()); + + localDefs[key] = operand; + } + + return operand; + } + + LinkedListNode<INode> node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameGlobal(operation.GetSource(index))); + } + } + + node = node.Next; + } + } + } + + private static Operand FindDefinitionForCurr(DefMap[] globalDefs, BasicBlock current, Register reg) + { + if (globalDefs[current.Index].HasPhi(reg)) + { + return InsertPhi(globalDefs, current, reg); + } + + if (current != current.ImmediateDominator) + { + return FindDefinition(globalDefs, current.ImmediateDominator, reg).Local; + } + + return Undef(); + } + + private static Definition FindDefinition(DefMap[] globalDefs, BasicBlock current, Register reg) + { + foreach (BasicBlock block in SelfAndImmediateDominators(current)) + { + DefMap defMap = globalDefs[block.Index]; + + if (defMap.TryGetOperand(reg, out Operand lastDef)) + { + return new Definition(block, lastDef); + } + + if (defMap.HasPhi(reg)) + { + return new Definition(block, InsertPhi(globalDefs, block, reg)); + } + } + + return new Definition(current, Undef()); + } + + private static IEnumerable<BasicBlock> SelfAndImmediateDominators(BasicBlock block) + { + while (block != block.ImmediateDominator) + { + yield return block; + + block = block.ImmediateDominator; + } + + yield return block; + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Register reg) + { + // This block has a Phi that has not been materialized yet, but that + // would define a new version of the variable we're looking for. We need + // to materialize the Phi, add all the block/operand pairs into the Phi, and + // then use the definition from that Phi. + Operand local = Local(); + + PhiNode phi = new PhiNode(local); + + AddPhi(block, phi); + + globalDefs[block.Index].TryAddOperand(reg, local); + + foreach (BasicBlock predecessor in block.Predecessors) + { + Definition def = FindDefinition(globalDefs, predecessor, reg); + + phi.AddSource(def.Block, def.Local); + } + + return local; + } + + private static void AddPhi(BasicBlock block, PhiNode phi) + { + LinkedListNode<INode> node = block.Operations.First; + + if (node != null) + { + while (node.Next?.Value is PhiNode) + { + node = node.Next; + } + } + + if (node?.Value is PhiNode) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static int GetKeyFromRegister(Register reg) + { + if (reg.Type == RegisterType.Gpr) + { + return reg.Index; + } + else if (reg.Type == RegisterType.Predicate) + { + return RegisterConsts.GprsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + return GprsAndPredsCount + reg.Index; + } + } + + private static Register GetRegisterFromKey(int key) + { + if (key < RegisterConsts.GprsCount) + { + return new Register(key, RegisterType.Gpr); + } + else if (key < GprsAndPredsCount) + { + return new Register(key - RegisterConsts.GprsCount, RegisterType.Predicate); + } + else /* if (key < RegisterConsts.TotalCount) */ + { + return new Register(key - GprsAndPredsCount, RegisterType.Flag); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationConfig.cs b/Ryujinx.Graphics.Shader/Translation/TranslationConfig.cs new file mode 100644 index 00000000..e5fa6d14 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslationConfig.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public struct TranslationConfig + { + public int MaxCBufferSize { get; } + + public int Version { get; } + + public TranslationFlags Flags { get; } + + public TranslationConfig(int maxCBufferSize, int version, TranslationFlags flags) + { + if (maxCBufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxCBufferSize)); + } + + MaxCBufferSize = maxCBufferSize; + Version = version; + Flags = flags; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs new file mode 100644 index 00000000..99b6107a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + public enum TranslationFlags + { + None = 0, + + Compute = 1 << 0, + DebugMode = 1 << 1, + Unspecialized = 1 << 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs new file mode 100644 index 00000000..4838b1e2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -0,0 +1,300 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl; +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public static class Translator + { + private const int HeaderSize = 0x50; + + public static ShaderProgram Translate(Span<byte> code, TranslationConfig translationConfig) + { + return Translate(code, Span<byte>.Empty, translationConfig); + } + + public static ShaderProgram Translate(Span<byte> code, Span<byte> code2, TranslationConfig translationConfig) + { + bool compute = (translationConfig.Flags & TranslationFlags.Compute) != 0; + bool debugMode = (translationConfig.Flags & TranslationFlags.DebugMode) != 0; + + Operation[] shaderOps = DecodeShader(code, compute, debugMode, out ShaderHeader header); + + if (code2 != Span<byte>.Empty) + { + // Dual vertex shader. + Operation[] shaderOpsB = DecodeShader(code2, compute, debugMode, out header); + + shaderOps = Combine(shaderOps, shaderOpsB); + } + + ShaderStage stage; + + if (compute) + { + stage = ShaderStage.Compute; + } + else + { + stage = header.Stage; + } + + int maxOutputVertexCount = 0; + + OutputTopology outputTopology = OutputTopology.LineStrip; + + if (!compute) + { + maxOutputVertexCount = header.MaxOutputVertexCount; + outputTopology = header.OutputTopology; + } + + ShaderConfig config = new ShaderConfig( + stage, + translationConfig.Flags, + translationConfig.MaxCBufferSize, + maxOutputVertexCount, + outputTopology); + + BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(shaderOps); + + Dominance.FindDominators(irBlocks[0], irBlocks.Length); + + Dominance.FindDominanceFrontiers(irBlocks); + + Ssa.Rename(irBlocks); + + Optimizer.Optimize(irBlocks, stage); + + StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(irBlocks, config); + + GlslProgram program = GlslGenerator.Generate(sInfo, config); + + ShaderProgramInfo spInfo = new ShaderProgramInfo( + program.CBufferDescriptors, + program.SBufferDescriptors, + program.TextureDescriptors, + sInfo.InterpolationQualifiers, + sInfo.UsesInstanceId); + + string glslCode = program.Code; + + if (translationConfig.Version != 0) + { + glslCode = "// " + translationConfig.Version + Environment.NewLine + glslCode; + } + + return new ShaderProgram(spInfo, stage, glslCode); + } + + private static Operation[] DecodeShader(Span<byte> code, bool compute, bool debugMode, out ShaderHeader header) + { + Block[] cfg; + + EmitterContext context; + + ulong headerSize; + + if (compute) + { + header = null; + + cfg = Decoder.Decode(code, 0); + + context = new EmitterContext(ShaderStage.Compute, header); + + headerSize = 0; + } + else + { + header = new ShaderHeader(code); + + cfg = Decoder.Decode(code, HeaderSize); + + context = new EmitterContext(header.Stage, header); + + headerSize = HeaderSize; + } + + for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++) + { + Block block = cfg[blkIndex]; + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) + { + OpCode op = block.OpCodes[opIndex]; + + if (debugMode) + { + string instName; + + if (op.Emitter != null) + { + instName = op.Emitter.Method.Name; + } + else + { + instName = "???"; + } + + string dbgComment = $"0x{(op.Address - headerSize):X6}: 0x{op.RawOpCode:X16} {instName}"; + + context.Add(new CommentNode(dbgComment)); + } + + if (op.NeverExecute) + { + continue; + } + + Operand predSkipLbl = null; + + bool skipPredicateCheck = op.Emitter == InstEmit.Bra; + + if (op is OpCodeSync opSync) + { + // If the instruction is a SYNC instruction with only one + // possible target address, then the instruction is basically + // just a simple branch, we can generate code similar to branch + // instructions, with the condition check on the branch itself. + skipPredicateCheck |= opSync.Targets.Count < 2; + } + + if (!(op.Predicate.IsPT || skipPredicateCheck)) + { + Operand label; + + if (opIndex == block.OpCodes.Count - 1 && block.Next != null) + { + label = context.GetLabel(block.Next.Address); + } + else + { + label = Label(); + + predSkipLbl = label; + } + + Operand pred = Register(op.Predicate); + + if (op.InvertPredicate) + { + context.BranchIfTrue(label, pred); + } + else + { + context.BranchIfFalse(label, pred); + } + } + + context.CurrOp = op; + + if (op.Emitter != null) + { + op.Emitter(context); + } + + if (predSkipLbl != null) + { + context.MarkLabel(predSkipLbl); + } + } + } + + return context.GetOperations(); + } + + private static Operation[] Combine(Operation[] a, Operation[] b) + { + // Here we combine two shaders. + // For shader A: + // - All user attribute stores on shader A are turned into copies to a + // temporary variable. It's assumed that shader B will consume them. + // - All return instructions are turned into branch instructions, the + // branch target being the start of the shader B code. + // For shader B: + // - All user attribute loads on shader B are turned into copies from a + // temporary variable, as long that attribute is written by shader A. + List<Operation> output = new List<Operation>(a.Length + b.Length); + + Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4]; + + Operand lblB = Label(); + + for (int index = 0; index < a.Length; index++) + { + Operation operation = a[index]; + + if (IsUserAttribute(operation.Dest)) + { + int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4; + + Operand temp = temps[tIndex]; + + if (temp == null) + { + temp = Local(); + + temps[tIndex] = temp; + } + + operation.Dest = temp; + } + + if (operation.Inst == Instruction.Return) + { + output.Add(new Operation(Instruction.Branch, lblB)); + } + else + { + output.Add(operation); + } + } + + output.Add(new Operation(Instruction.MarkLabel, lblB)); + + for (int index = 0; index < b.Length; index++) + { + Operation operation = b[index]; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand src = operation.GetSource(srcIndex); + + if (IsUserAttribute(src)) + { + Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4]; + + if (temp != null) + { + operation.SetSource(srcIndex, temp); + } + } + } + + output.Add(operation); + } + + return output.ToArray(); + } + + private static bool IsUserAttribute(Operand operand) + { + return operand != null && + operand.Type == OperandType.Attribute && + operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd; + } + } +}
\ No newline at end of file |
