From 3980043cf2f1cf796e0519328c058fd4c069f5f1 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 6 Apr 2019 00:34:43 -0300 Subject: [PATCH] Add HADD2 and HMUL2 half float shader instructions --- .../Shader/CodeGen/Glsl/Instructions.cs | 37 ++++ .../Shader/Decoders/FPHalfSwizzle.cs | 10 ++ .../Shader/Decoders/OpCodeAluImm2x10.cs | 30 ++++ .../Shader/Decoders/OpCodeTable.cs | 8 + .../Shader/Instructions/InstEmitFArith.cs | 158 +++++++++++++++++- .../Shader/Instructions/InstEmitHelper.cs | 8 +- .../Shader/Instructions/InstEmitMemory.cs | 51 +++--- .../IntermediateRepresentation/Instruction.cs | 6 +- .../OperandHelper.cs | 4 +- .../Shader/StructuredIr/InstructionInfo.cs | 3 + .../Shader/Translation/EmitterContext.cs | 4 +- .../Shader/Translation/EmitterContextInsts.cs | 15 ++ 12 files changed, 304 insertions(+), 30 deletions(-) create mode 100644 Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs create mode 100644 Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs index 78dd25b9f6..5da6c37225 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs @@ -103,6 +103,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl Add(Instruction.MinimumU32, InstFlags.CallBinary, "min"); Add(Instruction.Multiply, InstFlags.OpBinary, "*", 1); Add(Instruction.Negate, InstFlags.OpUnary, "-", 0); + Add(Instruction.PackHalf2x16, InstFlags.Call); Add(Instruction.ReciprocalSquareRoot, InstFlags.CallUnary, "inversesqrt"); Add(Instruction.Return, InstFlags.OpNullary, "return"); Add(Instruction.Sine, InstFlags.CallUnary, "sin"); @@ -110,6 +111,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl Add(Instruction.Subtract, InstFlags.OpBinary, "-", 2); Add(Instruction.TextureSample, InstFlags.Call); Add(Instruction.Truncate, InstFlags.CallUnary, "trunc"); + Add(Instruction.UnpackHalf2x16High, InstFlags.Call); + Add(Instruction.UnpackHalf2x16Low, InstFlags.Call); } private static void Add(Instruction inst, InstFlags flags, string opName = null, int precedence = 0) @@ -158,12 +161,26 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl case Instruction.LoadConstant: return GetLoadConstantExpr(context, operation); + case Instruction.PackHalf2x16: + return GetPackHalf2x16Expr(context, operation); + case Instruction.TextureSample: return GetTextureSampleExpr(context, operation); + + case Instruction.UnpackHalf2x16High: + return GetUnpackHalf2x16Expr(context, operation, 1); + + case Instruction.UnpackHalf2x16Low: + return GetUnpackHalf2x16Expr(context, operation, 0); } InstInfo info = _infoTbl[(int)inst]; + if (info.OpName == null) + { + throw new InvalidOperationException($"Unknown instruction \"{inst}\"."); + } + if ((info.Flags & InstFlags.Call) != 0) { int arity = (int)(info.Flags & InstFlags.ArityMask); @@ -248,6 +265,26 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return OperandManager.GetConstantBufferName(context, operation.GetSource(0), offsetExpr); } + private static string GetPackHalf2x16Expr(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}))"; + } + + private static string GetUnpackHalf2x16Expr(CodeGenContext context, AstOperation operation, int elem) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackHalf2x16({srcExpr}).{"xy".Substring(elem, 1)}"; + } + private static string GetTextureSampleExpr(CodeGenContext context, AstOperation operation) { AstTextureOperation texOp = (AstTextureOperation)operation; diff --git a/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs new file mode 100644 index 0000000000..3ddf17cfcc --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPHalfSwizzle + { + FP16 = 0, + FP32 = 1, + DupH0 = 2, + DupH1 = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs new file mode 100644 index 0000000000..cd6a84bf92 --- /dev/null +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm2x10 : OpCode, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + int immH0 = opCode.Extract(20, 9); + int immH1 = opCode.Extract(30, 9); + + bool negateH0 = opCode.Extract(29); + bool negateH1 = opCode.Extract(56); + + if (negateH0) + { + immH0 |= 1 << 10; + } + + if (negateH1) + { + immH1 |= 1 << 10; + } + + Immediate = immH1 << 16 | immH0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs index 520bd0ce4a..6ce654cb4c 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs +++ b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs @@ -64,6 +64,14 @@ namespace Ryujinx.Graphics.Shader.Decoders Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); + Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); + Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); + Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); + Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); + Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); + Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); + Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); + Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs index 410b86761d..34ba287453 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs @@ -3,8 +3,8 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.Translation; using System; -using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; namespace Ryujinx.Graphics.Shader.Instructions @@ -175,6 +175,44 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(Register(op.Predicate0), p1Res); } + public static void Hadd2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: true); + } + + public static void Hmul2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: false); + } + + private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd) + { + OpCode op = context.CurrOp; + + bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + if (isAdd) + { + res[index] = context.FPAdd(srcA[index], srcB[index]); + } + else + { + res[index] = context.FPMultiply(srcA[index], srcB[index]); + } + + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + public static void Mufu(EmitterContext context) { IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; @@ -221,6 +259,124 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate)); } + private static Operand[] GetHalfSrcA(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool absoluteA = false, negateA = false; + + if (op is IOpCodeCbuf || op is IOpCodeImm) + { + negateA = op.RawOpCode.Extract(43); + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is IOpCodeReg) + { + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is OpCodeAluImm32 && op.Emitter == Hadd2) + { + negateA = op.RawOpCode.Extract(56); + } + + FPHalfSwizzle swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(47, 2); + + Operand[] operands = GetHalfSources(context, GetSrcA(context), swizzle); + + return FPAbsNeg(context, operands, absoluteA, negateA); + } + + private static Operand[] GetHalfSrcB(EmitterContext context) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + if (!(op is OpCodeAluImm32)) + { + swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2); + } + + bool absoluteB = false, negateB = false; + + if (op is IOpCodeReg) + { + absoluteB = op.RawOpCode.Extract(30); + negateB = op.RawOpCode.Extract(31); + } + else if (op is IOpCodeCbuf) + { + absoluteB = op.RawOpCode.Extract(54); + } + + Operand[] operands = GetHalfSources(context, GetSrcB(context), swizzle); + + return FPAbsNeg(context, operands, absoluteB, negateB); + } + + private static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg) + { + for (int index = 0; index < operands.Length; index++) + { + operands[index] = context.FPAbsNeg(operands[index], abs, neg); + } + + return operands; + } + + private static Operand[] GetHalfSources(EmitterContext context, Operand src, FPHalfSwizzle swizzle) + { + switch (swizzle) + { + case FPHalfSwizzle.FP16: + return new Operand[] + { + context.UnpackHalf2x16Low (src), + context.UnpackHalf2x16High(src) + }; + + case FPHalfSwizzle.FP32: return new Operand[] { src, src }; + + case FPHalfSwizzle.DupH0: + return new Operand[] + { + context.UnpackHalf2x16Low(src), + context.UnpackHalf2x16Low(src) + }; + + case FPHalfSwizzle.DupH1: + return new Operand[] + { + context.UnpackHalf2x16High(src), + context.UnpackHalf2x16High(src) + }; + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + private static Operand GetHalfPacked(EmitterContext context, Operand[] results) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + if (!(op is OpCodeAluImm32)) + { + swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2); + } + + switch (swizzle) + { + case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]); + case FPHalfSwizzle.FP32: return results[0]; + case FPHalfSwizzle.DupH0: return context.PackHalf2x16(results[0], results[0]); + case FPHalfSwizzle.DupH1: return context.PackHalf2x16(results[1], results[1]); + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + private static Operand GetFPComparison( EmitterContext context, Condition cond, diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs index cfc2e94b70..22747a85d0 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs @@ -11,22 +11,22 @@ namespace Ryujinx.Graphics.Shader.Instructions { public static Operand GetZF(EmitterContext context) { - return Register(new Register(0, RegisterType.Flag)); + return Register(0, RegisterType.Flag); } public static Operand GetNF(EmitterContext context) { - return Register(new Register(1, RegisterType.Flag)); + return Register(1, RegisterType.Flag); } public static Operand GetCF(EmitterContext context) { - return Register(new Register(2, RegisterType.Flag)); + return Register(2, RegisterType.Flag); } public static Operand GetVF(EmitterContext context) { - return Register(new Register(3, RegisterType.Flag)); + return Register(3, RegisterType.Flag); } public static Operand GetDest(EmitterContext context) diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs index 389130ace9..a8ad282f41 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs +++ b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs @@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return context.Copy(Register(new Register(raIndex++, RegisterType.Gpr))); + return context.Copy(Register(raIndex++, RegisterType.Gpr)); } Operand Rb() @@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return context.Copy(Register(new Register(rbIndex++, RegisterType.Gpr))); + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); } Operand arrayIndex = op.IsArray ? Ra() : null; @@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return Register(new Register(rdIndex++, RegisterType.Gpr)); + return Register(rdIndex++, RegisterType.Gpr); } int textureHandle = op.Immediate; @@ -291,7 +291,7 @@ namespace Ryujinx.Graphics.Shader.Instructions regIndex += index & 1; } - return context.Copy(Register(new Register(regIndex, RegisterType.Gpr))); + return context.Copy(Register(regIndex, RegisterType.Gpr)); } switch (op.Type) @@ -363,28 +363,35 @@ namespace Ryujinx.Graphics.Shader.Instructions TextureType type = GetTextureType (op.Type); TextureFlags flags = GetTextureFlags(op.Type); + Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) }; + Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) }; + int destIncrement = 0; Operand GetDest() { - int rdIndex; + int high = destIncrement >> 1; + int low = destIncrement & 1; - if (op.Rd1.IsRZ) + destIncrement++; + + if (op.IsFp16) { - rdIndex = op.Rd0.Index; - } - else if (op.Rd0.IsRZ) - { - rdIndex = op.Rd1.Index; + return high != 0 + ? (rd1[low] = Local()) + : (rd0[low] = Local()); } else { - rdIndex = (destIncrement >> 1) != 0 ? op.Rd1.Index : op.Rd0.Index; + int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index; + + if (rdIndex < RegisterConsts.RegisterZeroIndex) + { + rdIndex += low; + } + + return Register(rdIndex, RegisterType.Gpr); } - - rdIndex += destIncrement++ & 1; - - return Register(new Register(rdIndex, RegisterType.Gpr)); } int textureHandle = op.Immediate; @@ -407,6 +414,12 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Add(operation); } } + + if (op.IsFp16) + { + context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1])); + context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1])); + } } public static void Tld4(EmitterContext context) @@ -432,7 +445,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return context.Copy(Register(new Register(raIndex++, RegisterType.Gpr))); + return context.Copy(Register(raIndex++, RegisterType.Gpr)); } Operand Rb() @@ -442,7 +455,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return context.Copy(Register(new Register(rbIndex++, RegisterType.Gpr))); + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); } Operand arrayIndex = op.IsArray ? Ra() : null; @@ -506,7 +519,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return Const(0); } - return Register(new Register(rdIndex++, RegisterType.Gpr)); + return Register(rdIndex++, RegisterType.Gpr); } int textureHandle = op.Immediate; diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs index 6965f4516f..f5219d8be5 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs @@ -76,8 +76,10 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Subtract, TextureSample, Truncate, - UnpackDouble2x32, - UnpackHalf2x16, + UnpackDouble2x32High, + UnpackDouble2x32Low, + UnpackHalf2x16High, + UnpackHalf2x16Low, Count, FP = 1 << 16, diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs index 51e42b1f25..6765f8a44f 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs @@ -35,9 +35,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation return new Operand(OperandType.LocalVariable); } - public static Operand Register(Register reg, int increment) + public static Operand Register(int index, RegisterType type) { - return new Operand(new Register(reg.Index + increment, reg.Type)); + return Register(new Register(index, type)); } public static Operand Register(Register reg) diff --git a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs index 3130200eeb..278701f930 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs @@ -74,11 +74,14 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32); Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32); Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); Add(Instruction.Truncate, VariableType.F32, VariableType.F32); + Add(Instruction.UnpackHalf2x16High, VariableType.F32, VariableType.U32); + Add(Instruction.UnpackHalf2x16Low, VariableType.F32, VariableType.U32); } private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs index cb3caa6f88..6c2bf6e478 100644 --- a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs @@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Shader.Translation { Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); - Operand src = Register(new Register(_header.DepthRegister, RegisterType.Gpr)); + Operand src = Register(_header.DepthRegister, RegisterType.Gpr); this.Copy(dest, src); } @@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Shader.Translation { Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4); - Operand src = Register(new Register(regIndex, RegisterType.Gpr)); + Operand src = Register(regIndex, RegisterType.Gpr); this.Copy(dest, src); diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs index e26112e69a..d0b54961a4 100644 --- a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs +++ b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs @@ -371,6 +371,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.LoadConstant, Local(), a, b); } + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackHalf2x16, Local(), a, b); + } + public static Operand Return(this EmitterContext context) { context.PrepareForReturn(); @@ -392,5 +397,15 @@ namespace Ryujinx.Graphics.Shader.Translation { return context.Add(Instruction.ShiftRightU32, Local(), a, b); } + + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) + { + return context.Add(Instruction.UnpackHalf2x16High, Local(), a); + } + + public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a) + { + return context.Add(Instruction.UnpackHalf2x16Low, Local(), a); + } } } \ No newline at end of file