aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions
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/Glsl/Instructions
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions')
-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
6 files changed, 712 insertions, 0 deletions
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