aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics/Shader/CodeGen
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2019-04-17 20:57:08 -0300
committerjduncanator <1518948+jduncanator@users.noreply.github.com>2019-04-18 09:57:08 +1000
commit6b23a2c125b9c48b5ebea92716004ef68698bb0f (patch)
tree69332df6fbbd8e2bddc522ba682fcc5c7a69e101 /Ryujinx.Graphics/Shader/CodeGen
parentb2e88b04a85b41cc60af3485d88c90429e84a218 (diff)
New shader translator implementation (#654)
* Start implementing a new shader translator * Fix shift instructions and a typo * Small refactoring on StructuredProgram, move RemovePhis method to a separate class * Initial geometry shader support * Implement TLD4 * Fix -- There's no negation on FMUL32I * Add constant folding and algebraic simplification optimizations, nits * Some leftovers from constant folding * Avoid cast for constant assignments * Add a branch elimination pass, and misc small fixes * Remove redundant branches, add expression propagation and other improvements on the code * Small leftovers -- add missing break and continue, remove unused properties, other improvements * Add null check to handle empty block cases on block visitor * Add HADD2 and HMUL2 half float shader instructions * Optimize pack/unpack sequences, some fixes related to half float instructions * Add TXQ, TLD, TLDS and TLD4S shader texture instructions, and some support for bindless textures, some refactoring on codegen * Fix copy paste mistake that caused RZ to be ignored on the AST instruction * Add workaround for conditional exit, and fix half float instruction with constant buffer * Add missing 0.0 source for TLDS.LZ variants * Simplify the switch for TLDS.LZ * Texture instructions related fixes * Implement the HFMA instruction, and some misc. fixes * Enable constant folding on UnpackHalf2x16 instructions * Refactor HFMA to use OpCode* for opcode decoding rather than on the helper methods * Remove the old shader translator * Remove ShaderDeclInfo and other unused things * Add dual vertex shader support * Add ShaderConfig, used to pass shader type and maximum cbuffer size * Move and rename some instruction enums * Move texture instructions into a separate file * Move operand GetExpression and locals management to OperandManager * Optimize opcode decoding using a simple list and binary search * Add missing condition for do-while on goto elimination * Misc. fixes on texture instructions * Simplify TLDS switch * Address PR feedback, and a nit
Diffstat (limited to 'Ryujinx.Graphics/Shader/CodeGen')
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs90
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs206
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs17
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs133
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs20
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs110
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs170
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs244
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs45
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs18
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs27
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs104
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs239
-rw-r--r--Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs85
14 files changed, 1508 insertions, 0 deletions
diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs
new file mode 100644
index 00000000..ce5d7b94
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs
@@ -0,0 +1,90 @@
+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<CBufferDescriptor> CBufferDescriptors { get; }
+ public List<TextureDescriptor> TextureDescriptors { get; }
+
+ public OperandManager OperandManager { get; }
+
+ private StringBuilder _sb;
+
+ private int _level;
+
+ private string _identation;
+
+ public CodeGenContext(ShaderConfig config)
+ {
+ Config = config;
+
+ CBufferDescriptors = new List<CBufferDescriptor>();
+ TextureDescriptors = new List<TextureDescriptor>();
+
+ OperandManager = new OperandManager();
+
+ _sb = new StringBuilder();
+ }
+
+ public void AppendLine()
+ {
+ _sb.AppendLine();
+ }
+
+ public void AppendLine(string str)
+ {
+ _sb.AppendLine(_identation + str);
+ }
+
+ public string GetCode()
+ {
+ return _sb.ToString();
+ }
+
+ public void EnterScope()
+ {
+ AppendLine("{");
+
+ _level++;
+
+ UpdateIdentation();
+ }
+
+ public void LeaveScope(string suffix = "")
+ {
+ if (_level == 0)
+ {
+ return;
+ }
+
+ _level--;
+
+ UpdateIdentation();
+
+ AppendLine("}" + suffix);
+ }
+
+ private void UpdateIdentation()
+ {
+ _identation = GetIdentation(_level);
+ }
+
+ private static string GetIdentation(int level)
+ {
+ string identation = string.Empty;
+
+ for (int index = 0; index < level; index++)
+ {
+ identation += Tab;
+ }
+
+ return identation;
+ }
+ }
+} \ 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..5412d872
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ static class Declarations
+ {
+ public static void Declare(CodeGenContext context, StructuredProgramInfo info)
+ {
+ context.AppendLine("#version 420 core");
+
+ context.AppendLine();
+
+ context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
+
+ context.AppendLine();
+
+ if (context.Config.Type == GalShaderType.Geometry)
+ {
+ context.AppendLine("layout (points) in;");
+ context.AppendLine("layout (triangle_strip, max_vertices = 4) out;");
+
+ context.AppendLine();
+ }
+
+ context.AppendLine("layout (std140) uniform Extra");
+
+ context.EnterScope();
+
+ context.AppendLine("vec2 flip;");
+ context.AppendLine("int instance;");
+
+ context.LeaveScope(";");
+
+ context.AppendLine();
+
+ if (info.CBuffers.Count != 0)
+ {
+ DeclareUniforms(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.Samplers.Count != 0)
+ {
+ DeclareSamplers(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.IAttributes.Count != 0)
+ {
+ DeclareInputAttributes(context, info);
+
+ context.AppendLine();
+ }
+
+ if (info.OAttributes.Count != 0)
+ {
+ DeclareOutputAttributes(context, info);
+
+ 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 "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.Type);
+
+ ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot;
+
+ context.CBufferDescriptors.Add(new CBufferDescriptor(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.Type, cbufSlot) + ubSize + ";");
+
+ 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.Type, texOp);
+
+ if (!samplers.TryAdd(samplerName, texOp))
+ {
+ continue;
+ }
+
+ string samplerTypeName = GetSamplerTypeName(texOp.Type);
+
+ 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, operand.CbufSlot, operand.CbufOffset);
+ }
+ else
+ {
+ desc = new TextureDescriptor(samplerName, texOp.Handle);
+ }
+
+ context.TextureDescriptors.Add(desc);
+ }
+ }
+
+ private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
+ {
+ string suffix = context.Config.Type == GalShaderType.Geometry ? "[]" : string.Empty;
+
+ foreach (int attr in info.IAttributes.OrderBy(x => x))
+ {
+ context.AppendLine($"layout (location = {attr}) in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};");
+ }
+ }
+
+ private static void DeclareOutputAttributes(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 string GetSamplerTypeName(TextureType type)
+ {
+ string typeName;
+
+ switch (type & TextureType.Mask)
+ {
+ case TextureType.Texture1D: typeName = "sampler1D"; break;
+ case TextureType.Texture2D: typeName = "sampler2D"; break;
+ case TextureType.Texture3D: typeName = "sampler3D"; break;
+ case TextureType.TextureCube: typeName = "samplerCube"; break;
+
+ default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
+ }
+
+ if ((type & TextureType.Multisample) != 0)
+ {
+ typeName += "MS";
+ }
+
+ if ((type & TextureType.Array) != 0)
+ {
+ typeName += "Array";
+ }
+
+ if ((type & TextureType.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..1d3939fb
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs
@@ -0,0 +1,17 @@
+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 UniformNamePrefix = "c";
+ public const string UniformNameSuffix = "data";
+
+ 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..4edbda8b
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs
@@ -0,0 +1,133 @@
+using Ryujinx.Graphics.Gal;
+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.TextureDescriptors.ToArray(),
+ context.GetCode());
+ }
+
+ private static void PrintMainBlock(CodeGenContext context, StructuredProgramInfo info)
+ {
+ context.AppendLine("void main()");
+
+ context.EnterScope();
+
+ Declarations.DeclareLocals(context, info);
+
+ 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)
+ {
+ if (operation.Inst == Instruction.Return)
+ {
+ PrepareForReturn(context);
+ }
+
+ 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.Type);
+ }
+ else
+ {
+ dest = InstGen.GetExpression(context, assignment.Destination);
+ }
+
+ string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
+
+ context.AppendLine(dest + " = " + src + ";");
+ }
+ 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);
+ }
+
+ private static void PrepareForReturn(CodeGenContext context)
+ {
+ if (context.Config.Type == GalShaderType.Vertex)
+ {
+ context.AppendLine("gl_Position.xy *= flip;");
+ }
+ }
+ }
+} \ 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..e616aa1f
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
+{
+ class GlslProgram
+ {
+ public CBufferDescriptor[] CBufferDescriptors { get; }
+ public TextureDescriptor[] TextureDescriptors { get; }
+
+ public string Code { get; }
+
+ public GlslProgram(
+ CBufferDescriptor[] cBufferDescs,
+ TextureDescriptor[] textureDescs,
+ string code)
+ {
+ CBufferDescriptors = cBufferDescs;
+ TextureDescriptors = textureDescs;
+ 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..b0b2ec1a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs
@@ -0,0 +1,110 @@
+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.Type);
+ }
+
+ 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.LoadConstant:
+ return InstGenMemory.LoadConstant(context, operation);
+
+ case Instruction.PackHalf2x16:
+ return InstGenPacking.PackHalf2x16(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);
+ }
+ }
+
+ 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..0b860072
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -0,0 +1,170 @@
+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.LoadConstant, 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.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..79f80057
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -0,0 +1,244 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+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 InstGenMemory
+ {
+ public static string LoadConstant(CodeGenContext context, AstOperation operation)
+ {
+ IAstNode src1 = operation.GetSource(1);
+
+ string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1));
+
+ offsetExpr = Enclose(offsetExpr, src1, Instruction.ShiftRightS32, isLhs: true);
+
+ return OperandManager.GetConstantBufferName(operation.GetSource(0), offsetExpr, context.Config.Type);
+ }
+
+ 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.Type & TextureType.Array) != 0;
+ bool isMultisample = (texOp.Type & TextureType.Multisample) != 0;
+ bool isShadow = (texOp.Type & TextureType.Shadow) != 0;
+
+ 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.Type, texOp);
+
+ texCall += "(" + samplerName;
+
+ int coordsCount = texOp.Type.GetCoordsCount();
+
+ 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.Type == (TextureType.Texture1D | TextureType.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.Type, 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..7d38a9d2
--- /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 | Comutative,
+
+ CallNullary = Call | 0,
+ CallUnary = Call | 1,
+ CallBinary = Call | 2,
+ CallTernary = Call | 3,
+ CallQuaternary = Call | 4,
+
+ Comutative = 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..debba428
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs
@@ -0,0 +1,239 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+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[] { "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.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("instance", VariableType.S32) },
+ { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) },
+ { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) },
+ { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }
+ };
+
+ 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, GalShaderType shaderType)
+ {
+ switch (operand.Type)
+ {
+ case OperandType.Attribute:
+ return GetAttributeName(operand, shaderType);
+
+ case OperandType.Constant:
+ return NumberFormatter.FormatInt(operand.Value);
+
+ case OperandType.ConstantBuffer:
+ return GetConstantBufferName(operand, shaderType);
+
+ 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, GalShaderType shaderType)
+ {
+ string ubName = GetUbName(shaderType, cbuf.CbufSlot);
+
+ ubName += "[" + (cbuf.CbufOffset >> 2) + "]";
+
+ return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3);
+ }
+
+ public static string GetConstantBufferName(IAstNode slot, string offsetExpr, GalShaderType shaderType)
+ {
+ //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(shaderType, operand.Value);
+
+ string index0 = "[" + offsetExpr + " >> 4]";
+ string index1 = "[" + offsetExpr + " >> 2 & 3]";
+
+ return ubName + index0 + index1;
+ }
+
+ public static string GetOutAttributeName(AstOperand attr, GalShaderType shaderType)
+ {
+ return GetAttributeName(attr, shaderType, isOutAttr: true);
+ }
+
+ private static string GetAttributeName(AstOperand attr, GalShaderType shaderType, bool isOutAttr = false)
+ {
+ 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 (shaderType == GalShaderType.Geometry && !isOutAttr)
+ {
+ name += "[0]";
+ }
+
+ 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 (shaderType == GalShaderType.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 (shaderType == GalShaderType.Geometry && !isOutAttr)
+ {
+ name = "gl_in[0]." + name;
+ }
+
+ return name;
+ }
+ }
+
+ return DefaultNames.UndefinedName;
+ }
+
+ public static string GetUbName(GalShaderType shaderType, int slot)
+ {
+ string ubName = OperandManager.GetShaderStagePrefix(shaderType);
+
+ ubName += "_" + DefaultNames.UniformNamePrefix + slot;
+
+ return ubName + "_" + DefaultNames.UniformNameSuffix;
+ }
+
+ public static string GetSamplerName(GalShaderType shaderType, 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(shaderType) + "_" + DefaultNames.SamplerNamePrefix + suffix;
+ }
+
+ public static string GetShaderStagePrefix(GalShaderType shaderType)
+ {
+ return _stagePrefixes[(int)shaderType];
+ }
+
+ private static string GetSwizzleMask(int value)
+ {
+ return "xyzw".Substring(value, 1);
+ }
+
+ public static VariableType GetNodeDestType(IAstNode node)
+ {
+ if (node is AstOperation operation)
+ {
+ return GetDestVarType(operation.Inst);
+ }
+ else if (node is AstOperand operand)
+ {
+ if (operand.Type == OperandType.Attribute)
+ {
+ if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
+ {
+ return builtInAttr.Type;
+ }
+ }
+
+ return OperandInfo.GetVarType(operand);
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
+ }
+ }
+ }
+} \ 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