aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Shader/CodeGen
diff options
context:
space:
mode:
authorgdk <gab.dark.100@gmail.com>2019-10-13 03:02:07 -0300
committerThog <thog@protonmail.com>2020-01-09 02:13:00 +0100
commit1876b346fea647e8284a66bb6d62c38801035cff (patch)
tree6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Shader/CodeGen
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Shader/CodeGen')
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs92
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs319
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs22
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs141
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs23
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs127
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs175
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs320
-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.cs298
-rw-r--r--Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs85
14 files changed, 1796 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..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