aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Shader/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/Instructions
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Shader/Instructions')
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs708
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs88
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs213
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs403
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs107
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs267
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs325
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs57
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs776
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs19
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs6
-rw-r--r--Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs149
12 files changed, 3118 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs
new file mode 100644
index 00000000..5cbb3b73
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs
@@ -0,0 +1,708 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Bfe(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isReverse = op.RawOpCode.Extract(40);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ if (isReverse)
+ {
+ srcA = context.BitfieldReverse(srcA);
+ }
+
+ Operand position = context.BitwiseAnd(srcB, Const(0xff));
+
+ Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8));
+
+ Operand res = isSigned
+ ? context.BitfieldExtractS32(srcA, position, size)
+ : context.BitfieldExtractU32(srcA, position, size);
+
+ context.Copy(GetDest(context), res);
+
+ // TODO: CC, X, corner cases
+ }
+
+ public static void Csetp(EmitterContext context)
+ {
+ OpCodePsetp op = (OpCodePsetp)context.CurrOp;
+
+ // TODO: Implement that properly
+
+ Operand p0Res = Const(IrConsts.True);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Iadd(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateA = false, negateB = false;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ negateB = op.RawOpCode.Extract(48);
+ negateA = op.RawOpCode.Extract(49);
+ }
+ else
+ {
+ // TODO: Other IADD32I variant without the negate.
+ negateA = op.RawOpCode.Extract(56);
+ }
+
+ Operand srcA = context.INegate(GetSrcA(context), negateA);
+ Operand srcB = context.INegate(GetSrcB(context), negateB);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ bool isSubtraction = negateA || negateB;
+
+ if (op.Extended)
+ {
+ // Add carry, or subtract borrow.
+ res = context.IAdd(res, isSubtraction
+ ? context.BitwiseNot(GetCF(context))
+ : context.BitwiseAnd(GetCF(context), Const(1)));
+ }
+
+ SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction);
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Iadd3(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ IntegerHalfPart partC = (IntegerHalfPart)op.RawOpCode.Extract(31, 2);
+ IntegerHalfPart partB = (IntegerHalfPart)op.RawOpCode.Extract(33, 2);
+ IntegerHalfPart partA = (IntegerHalfPart)op.RawOpCode.Extract(35, 2);
+
+ IntegerShift mode = (IntegerShift)op.RawOpCode.Extract(37, 2);
+
+ bool negateC = op.RawOpCode.Extract(49);
+ bool negateB = op.RawOpCode.Extract(50);
+ bool negateA = op.RawOpCode.Extract(51);
+
+ Operand Extend(Operand src, IntegerHalfPart part)
+ {
+ if (!(op is OpCodeAluReg) || part == IntegerHalfPart.B32)
+ {
+ return src;
+ }
+
+ if (part == IntegerHalfPart.H0)
+ {
+ return context.BitwiseAnd(src, Const(0xffff));
+ }
+ else if (part == IntegerHalfPart.H1)
+ {
+ return context.ShiftRightU32(src, Const(16));
+ }
+ else
+ {
+ // TODO: Warning.
+ }
+
+ return src;
+ }
+
+ Operand srcA = context.INegate(Extend(GetSrcA(context), partA), negateA);
+ Operand srcB = context.INegate(Extend(GetSrcB(context), partB), negateB);
+ Operand srcC = context.INegate(Extend(GetSrcC(context), partC), negateC);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ if (op is OpCodeAluReg && mode != IntegerShift.NoShift)
+ {
+ if (mode == IntegerShift.ShiftLeft)
+ {
+ res = context.ShiftLeft(res, Const(16));
+ }
+ else if (mode == IntegerShift.ShiftRight)
+ {
+ res = context.ShiftRightU32(res, Const(16));
+ }
+ else
+ {
+ // TODO: Warning.
+ }
+ }
+
+ res = context.IAdd(res, srcC);
+
+ context.Copy(GetDest(context), res);
+
+ // TODO: CC, X, corner cases
+ }
+
+ public static void Imnmx(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isSignedInt = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand resMin = isSignedInt
+ ? context.IMinimumS32(srcA, srcB)
+ : context.IMinimumU32(srcA, srcB);
+
+ Operand resMax = isSignedInt
+ ? context.IMaximumS32(srcA, srcB)
+ : context.IMaximumU32(srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax));
+
+ SetZnFlags(context, dest, op.SetCondCode);
+
+ // TODO: X flags.
+ }
+
+ public static void Iscadd(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateA = false, negateB = false;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ negateB = op.RawOpCode.Extract(48);
+ negateA = op.RawOpCode.Extract(49);
+ }
+
+ int shift = op is OpCodeAluImm32
+ ? op.RawOpCode.Extract(53, 5)
+ : op.RawOpCode.Extract(39, 5);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ srcA = context.ShiftLeft(srcA, Const(shift));
+
+ srcA = context.INegate(srcA, negateA);
+ srcB = context.INegate(srcB, negateB);
+
+ Operand res = context.IAdd(srcA, srcB);
+
+ context.Copy(GetDest(context), res);
+
+ // TODO: CC, X
+ }
+
+ public static void Iset(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ bool boolFloat = op.RawOpCode.Extract(44);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned);
+
+ Operand pred = GetPredicate39(context);
+
+ res = GetPredLogicalOp(context, op.LogicalOp, res, pred);
+
+ Operand dest = GetDest(context);
+
+ if (boolFloat)
+ {
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ }
+ else
+ {
+ context.Copy(dest, res);
+ }
+
+ // TODO: CC, X
+ }
+
+ public static void Isetp(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Lop(EmitterContext context)
+ {
+ IOpCodeLop op = (IOpCodeLop)context.CurrOp;
+
+ Operand srcA = context.BitwiseNot(GetSrcA(context), op.InvertA);
+ Operand srcB = context.BitwiseNot(GetSrcB(context), op.InvertB);
+
+ Operand res = srcB;
+
+ switch (op.LogicalOp)
+ {
+ case LogicalOperation.And: res = context.BitwiseAnd (srcA, srcB); break;
+ case LogicalOperation.Or: res = context.BitwiseOr (srcA, srcB); break;
+ case LogicalOperation.ExclusiveOr: res = context.BitwiseExclusiveOr(srcA, srcB); break;
+ }
+
+ EmitLopPredWrite(context, op, res);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, res);
+
+ SetZnFlags(context, dest, op.SetCondCode, op.Extended);
+ }
+
+ public static void Lop3(EmitterContext context)
+ {
+ IOpCodeLop op = (IOpCodeLop)context.CurrOp;
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+ Operand srcC = GetSrcC(context);
+
+ bool regVariant = op is OpCodeLopReg;
+
+ int truthTable = regVariant
+ ? op.RawOpCode.Extract(28, 8)
+ : op.RawOpCode.Extract(48, 8);
+
+ Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable);
+
+ if (regVariant)
+ {
+ EmitLopPredWrite(context, op, res);
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, res);
+
+ SetZnFlags(context, dest, op.SetCondCode, op.Extended);
+ }
+
+ public static void Psetp(EmitterContext context)
+ {
+ OpCodePsetp op = (OpCodePsetp)context.CurrOp;
+
+ bool invertA = op.RawOpCode.Extract(15);
+ bool invertB = op.RawOpCode.Extract(32);
+
+ Operand srcA = context.BitwiseNot(Register(op.Predicate12), invertA);
+ Operand srcB = context.BitwiseNot(Register(op.Predicate29), invertB);
+
+ Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Rro(EmitterContext context)
+ {
+ // This is the range reduction operator,
+ // we translate it as a simple move, as it
+ // should be always followed by a matching
+ // MUFU instruction.
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = GetSrcB(context);
+
+ srcB = context.FPAbsNeg(srcB, absoluteB, negateB);
+
+ context.Copy(GetDest(context), srcB);
+ }
+
+ public static void Shl(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isMasked = op.RawOpCode.Extract(39);
+
+ Operand srcB = GetSrcB(context);
+
+ if (isMasked)
+ {
+ srcB = context.BitwiseAnd(srcB, Const(0x1f));
+ }
+
+ Operand res = context.ShiftLeft(GetSrcA(context), srcB);
+
+ if (!isMasked)
+ {
+ // Clamped shift value.
+ Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32));
+
+ res = context.ConditionalSelect(isLessThan32, res, Const(0));
+ }
+
+ // TODO: X, CC
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Shr(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool isMasked = op.RawOpCode.Extract(39);
+ bool isReverse = op.RawOpCode.Extract(40);
+ bool isSigned = op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ if (isReverse)
+ {
+ srcA = context.BitfieldReverse(srcA);
+ }
+
+ if (isMasked)
+ {
+ srcB = context.BitwiseAnd(srcB, Const(0x1f));
+ }
+
+ Operand res = isSigned
+ ? context.ShiftRightS32(srcA, srcB)
+ : context.ShiftRightU32(srcA, srcB);
+
+ if (!isMasked)
+ {
+ // Clamped shift value.
+ Operand resShiftBy32;
+
+ if (isSigned)
+ {
+ resShiftBy32 = context.ShiftRightS32(srcA, Const(31));
+ }
+ else
+ {
+ resShiftBy32 = Const(0);
+ }
+
+ Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32));
+
+ res = context.ConditionalSelect(isLessThan32, res, resShiftBy32);
+ }
+
+ // TODO: X, CC
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Xmad(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ bool signedA = context.CurrOp.RawOpCode.Extract(48);
+ bool signedB = context.CurrOp.RawOpCode.Extract(49);
+ bool highA = context.CurrOp.RawOpCode.Extract(53);
+ bool highB = false;
+
+ XmadCMode mode;
+
+ if (op is OpCodeAluReg)
+ {
+ highB = context.CurrOp.RawOpCode.Extract(35);
+
+ mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 3);
+ }
+ else
+ {
+ mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 2);
+
+ if (!(op is OpCodeAluImm))
+ {
+ highB = context.CurrOp.RawOpCode.Extract(52);
+ }
+ }
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+ Operand srcC = GetSrcC(context);
+
+ // XMAD immediates are 16-bits unsigned integers.
+ if (srcB.Type == OperandType.Constant)
+ {
+ srcB = Const(srcB.Value & 0xffff);
+ }
+
+ Operand Extend16To32(Operand src, bool high, bool signed)
+ {
+ if (signed && high)
+ {
+ return context.ShiftRightS32(src, Const(16));
+ }
+ else if (signed)
+ {
+ return context.BitfieldExtractS32(src, Const(0), Const(16));
+ }
+ else if (high)
+ {
+ return context.ShiftRightU32(src, Const(16));
+ }
+ else
+ {
+ return context.BitwiseAnd(src, Const(0xffff));
+ }
+ }
+
+ srcA = Extend16To32(srcA, highA, signedA);
+ srcB = Extend16To32(srcB, highB, signedB);
+
+ bool productShiftLeft = false;
+ bool merge = false;
+
+ if (!(op is OpCodeAluRegCbuf))
+ {
+ productShiftLeft = context.CurrOp.RawOpCode.Extract(36);
+ merge = context.CurrOp.RawOpCode.Extract(37);
+ }
+
+ bool extended;
+
+ if ((op is OpCodeAluReg) || (op is OpCodeAluImm))
+ {
+ extended = context.CurrOp.RawOpCode.Extract(38);
+ }
+ else
+ {
+ extended = context.CurrOp.RawOpCode.Extract(54);
+ }
+
+ Operand res = context.IMultiply(srcA, srcB);
+
+ if (productShiftLeft)
+ {
+ res = context.ShiftLeft(res, Const(16));
+ }
+
+ switch (mode)
+ {
+ case XmadCMode.Cfull: break;
+
+ case XmadCMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break;
+ case XmadCMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break;
+
+ case XmadCMode.Cbcc:
+ {
+ srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16)));
+
+ break;
+ }
+
+ case XmadCMode.Csfu:
+ {
+ Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16));
+ Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16));
+
+ srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB));
+
+ break;
+ }
+
+ default: /* TODO: Warning */ break;
+ }
+
+ Operand product = res;
+
+ if (extended)
+ {
+ // Add with carry.
+ res = context.IAdd(res, context.BitwiseAnd(GetCF(context), Const(1)));
+ }
+ else
+ {
+ // Add (no carry in).
+ res = context.IAdd(res, srcC);
+ }
+
+ SetIaddFlags(context, res, product, srcC, op.SetCondCode, extended);
+
+ if (merge)
+ {
+ res = context.BitwiseAnd(res, Const(0xffff));
+ res = context.BitwiseOr(res, context.ShiftLeft(GetSrcB(context), Const(16)));
+ }
+
+ context.Copy(GetDest(context), res);
+ }
+
+ private static Operand GetIntComparison(
+ EmitterContext context,
+ IntegerCondition cond,
+ Operand srcA,
+ Operand srcB,
+ bool isSigned)
+ {
+ Operand res;
+
+ if (cond == IntegerCondition.Always)
+ {
+ res = Const(IrConsts.True);
+ }
+ else if (cond == IntegerCondition.Never)
+ {
+ res = Const(IrConsts.False);
+ }
+ else
+ {
+ Instruction inst;
+
+ switch (cond)
+ {
+ case IntegerCondition.Less: inst = Instruction.CompareLessU32; break;
+ case IntegerCondition.Equal: inst = Instruction.CompareEqual; break;
+ case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqualU32; break;
+ case IntegerCondition.Greater: inst = Instruction.CompareGreaterU32; break;
+ case IntegerCondition.NotEqual: inst = Instruction.CompareNotEqual; break;
+ case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqualU32; break;
+
+ default: throw new InvalidOperationException($"Unexpected condition \"{cond}\".");
+ }
+
+ if (isSigned)
+ {
+ switch (cond)
+ {
+ case IntegerCondition.Less: inst = Instruction.CompareLess; break;
+ case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break;
+ case IntegerCondition.Greater: inst = Instruction.CompareGreater; break;
+ case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break;
+ }
+ }
+
+ res = context.Add(inst, Local(), srcA, srcB);
+ }
+
+ return res;
+ }
+
+ private static void EmitLopPredWrite(EmitterContext context, IOpCodeLop op, Operand result)
+ {
+ if (op is OpCodeLop opLop && !opLop.Predicate48.IsPT)
+ {
+ Operand pRes;
+
+ if (opLop.CondOp == ConditionalOperation.False)
+ {
+ pRes = Const(IrConsts.False);
+ }
+ else if (opLop.CondOp == ConditionalOperation.True)
+ {
+ pRes = Const(IrConsts.True);
+ }
+ else if (opLop.CondOp == ConditionalOperation.Zero)
+ {
+ pRes = context.ICompareEqual(result, Const(0));
+ }
+ else /* if (opLop.CondOp == ConditionalOperation.NotZero) */
+ {
+ pRes = context.ICompareNotEqual(result, Const(0));
+ }
+
+ context.Copy(Register(opLop.Predicate48), pRes);
+ }
+ }
+
+ private static void SetIaddFlags(
+ EmitterContext context,
+ Operand res,
+ Operand srcA,
+ Operand srcB,
+ bool setCC,
+ bool extended,
+ bool isSubtraction = false)
+ {
+ if (!setCC)
+ {
+ return;
+ }
+
+ if (!extended || isSubtraction)
+ {
+ // C = d < a
+ context.Copy(GetCF(context), context.ICompareLessUnsigned(res, srcA));
+ }
+ else
+ {
+ // C = (d == a && CIn) || d < a
+ Operand tempC0 = context.ICompareEqual (res, srcA);
+ Operand tempC1 = context.ICompareLessUnsigned(res, srcA);
+
+ tempC0 = context.BitwiseAnd(tempC0, GetCF(context));
+
+ context.Copy(GetCF(context), context.BitwiseOr(tempC0, tempC1));
+ }
+
+ // V = (d ^ a) & ~(a ^ b) < 0
+ Operand tempV0 = context.BitwiseExclusiveOr(res, srcA);
+ Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB);
+
+ tempV1 = context.BitwiseNot(tempV1);
+
+ Operand tempV = context.BitwiseAnd(tempV0, tempV1);
+
+ context.Copy(GetVF(context), context.ICompareLess(tempV, Const(0)));
+
+ SetZnFlags(context, res, setCC: true, extended: extended);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs
new file mode 100644
index 00000000..5c4f5398
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs
@@ -0,0 +1,88 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static class InstEmitAluHelper
+ {
+ public static int GetIntMin(IntegerType type)
+ {
+ switch (type)
+ {
+ case IntegerType.U8: return byte.MinValue;
+ case IntegerType.S8: return sbyte.MinValue;
+ case IntegerType.U16: return ushort.MinValue;
+ case IntegerType.S16: return short.MinValue;
+ case IntegerType.U32: return (int)uint.MinValue;
+ case IntegerType.S32: return int.MinValue;
+ }
+
+ throw new ArgumentException($"The type \"{type}\" is not a supported int type.");
+ }
+
+ public static int GetIntMax(IntegerType type)
+ {
+ switch (type)
+ {
+ case IntegerType.U8: return byte.MaxValue;
+ case IntegerType.S8: return sbyte.MaxValue;
+ case IntegerType.U16: return ushort.MaxValue;
+ case IntegerType.S16: return short.MaxValue;
+ case IntegerType.U32: return unchecked((int)uint.MaxValue);
+ case IntegerType.S32: return int.MaxValue;
+ }
+
+ throw new ArgumentException($"The type \"{type}\" is not a supported int type.");
+ }
+
+ public static Operand GetPredLogicalOp(
+ EmitterContext context,
+ LogicalOperation logicalOp,
+ Operand input,
+ Operand pred)
+ {
+ switch (logicalOp)
+ {
+ case LogicalOperation.And: return context.BitwiseAnd (input, pred);
+ case LogicalOperation.Or: return context.BitwiseOr (input, pred);
+ case LogicalOperation.ExclusiveOr: return context.BitwiseExclusiveOr(input, pred);
+ }
+
+ return input;
+ }
+
+ public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false)
+ {
+ if (!setCC)
+ {
+ return;
+ }
+
+ if (extended)
+ {
+ // When the operation is extended, it means we are doing
+ // the operation on a long word with any number of bits,
+ // so we need to AND the zero flag from result with the
+ // previous result when extended is specified, to ensure
+ // we have ZF set only if all words are zero, and not just
+ // the last one.
+ Operand oldZF = GetZF(context);
+
+ Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF);
+
+ context.Copy(GetZF(context), res);
+ }
+ else
+ {
+ context.Copy(GetZF(context), context.ICompareEqual(dest, Const(0)));
+ }
+
+ context.Copy(GetNF(context), context.ICompareLess(dest, Const(0)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs
new file mode 100644
index 00000000..c4de1750
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs
@@ -0,0 +1,213 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void F2F(EmitterContext context)
+ {
+ OpCodeFArith op = (OpCodeFArith)context.CurrOp;
+
+ FPType srcType = (FPType)op.RawOpCode.Extract(8, 2);
+ FPType dstType = (FPType)op.RawOpCode.Extract(10, 2);
+
+ bool pass = op.RawOpCode.Extract(40);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ pass &= op.RoundingMode == RoundingMode.TowardsNegativeInfinity;
+
+ Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB);
+
+ if (!pass)
+ {
+ switch (op.RoundingMode)
+ {
+ case RoundingMode.TowardsNegativeInfinity:
+ srcB = context.FPFloor(srcB);
+ break;
+
+ case RoundingMode.TowardsPositiveInfinity:
+ srcB = context.FPCeiling(srcB);
+ break;
+
+ case RoundingMode.TowardsZero:
+ srcB = context.FPTruncate(srcB);
+ break;
+ }
+ }
+
+ srcB = context.FPSaturate(srcB, op.Saturate);
+
+ WriteFP(context, dstType, srcB);
+
+ // TODO: CC.
+ }
+
+ public static void F2I(EmitterContext context)
+ {
+ OpCodeFArith op = (OpCodeFArith)context.CurrOp;
+
+ IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2);
+
+ bool isSmallInt = intType <= IntegerType.U16;
+
+ FPType floatType = (FPType)op.RawOpCode.Extract(10, 2);
+
+ bool isSignedInt = op.RawOpCode.Extract(12);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ if (isSignedInt)
+ {
+ intType |= IntegerType.S8;
+ }
+
+ Operand srcB = context.FPAbsNeg(GetSrcB(context, floatType), absoluteB, negateB);
+
+ switch (op.RoundingMode)
+ {
+ case RoundingMode.TowardsNegativeInfinity:
+ srcB = context.FPFloor(srcB);
+ break;
+
+ case RoundingMode.TowardsPositiveInfinity:
+ srcB = context.FPCeiling(srcB);
+ break;
+
+ case RoundingMode.TowardsZero:
+ srcB = context.FPTruncate(srcB);
+ break;
+ }
+
+ srcB = context.FPConvertToS32(srcB);
+
+ // TODO: S/U64, conversion overflow handling.
+ if (intType != IntegerType.S32)
+ {
+ int min = GetIntMin(intType);
+ int max = GetIntMax(intType);
+
+ srcB = isSignedInt
+ ? context.IClampS32(srcB, Const(min), Const(max))
+ : context.IClampU32(srcB, Const(min), Const(max));
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, srcB);
+
+ // TODO: CC.
+ }
+
+ public static void I2F(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ FPType dstType = (FPType)op.RawOpCode.Extract(8, 2);
+
+ IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2);
+
+ bool isSmallInt = srcType <= IntegerType.U16;
+
+ bool isSignedInt = op.RawOpCode.Extract(13);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = context.IAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ if (isSmallInt)
+ {
+ int size = srcType == IntegerType.U16 ? 16 : 8;
+
+ srcB = isSignedInt
+ ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size))
+ : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size));
+ }
+
+ srcB = isSignedInt
+ ? context.IConvertS32ToFP(srcB)
+ : context.IConvertU32ToFP(srcB);
+
+ WriteFP(context, dstType, srcB);
+
+ // TODO: CC.
+ }
+
+ public static void I2I(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ IntegerType dstType = (IntegerType)op.RawOpCode.Extract(8, 2);
+ IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2);
+
+ if (srcType == IntegerType.U64 || dstType == IntegerType.U64)
+ {
+ // TODO: Warning. This instruction doesn't support 64-bits integers
+ }
+
+ bool srcIsSmallInt = srcType <= IntegerType.U16;
+
+ bool dstIsSignedInt = op.RawOpCode.Extract(12);
+ bool srcIsSignedInt = op.RawOpCode.Extract(13);
+ bool negateB = op.RawOpCode.Extract(45);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcB = GetSrcB(context);
+
+ if (srcIsSmallInt)
+ {
+ int size = srcType == IntegerType.U16 ? 16 : 8;
+
+ srcB = srcIsSignedInt
+ ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size))
+ : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size));
+ }
+
+ srcB = context.IAbsNeg(srcB, absoluteB, negateB);
+
+ if (op.Saturate)
+ {
+ if (dstIsSignedInt)
+ {
+ dstType |= IntegerType.S8;
+ }
+
+ int min = GetIntMin(dstType);
+ int max = GetIntMax(dstType);
+
+ srcB = dstIsSignedInt
+ ? context.IClampS32(srcB, Const(min), Const(max))
+ : context.IClampU32(srcB, Const(min), Const(max));
+ }
+
+ context.Copy(GetDest(context), srcB);
+
+ // TODO: CC.
+ }
+
+ private static void WriteFP(EmitterContext context, FPType type, Operand srcB)
+ {
+ Operand dest = GetDest(context);
+
+ if (type == FPType.FP32)
+ {
+ context.Copy(dest, srcB);
+ }
+ else if (type == FPType.FP16)
+ {
+ context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0)));
+ }
+ else
+ {
+ // TODO.
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs
new file mode 100644
index 00000000..2ecdee27
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs
@@ -0,0 +1,403 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Fadd(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool absoluteA = op.AbsoluteA, absoluteB, negateA, negateB;
+
+ if (op is OpCodeFArithImm32)
+ {
+ negateB = op.RawOpCode.Extract(53);
+ negateA = op.RawOpCode.Extract(56);
+ absoluteB = op.RawOpCode.Extract(57);
+ }
+ else
+ {
+ negateB = op.RawOpCode.Extract(45);
+ negateA = op.RawOpCode.Extract(48);
+ absoluteB = op.RawOpCode.Extract(49);
+ }
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPAdd(srcA, srcB), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Ffma(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(48);
+ bool negateC = op.RawOpCode.Extract(49);
+
+ Operand srcA = GetSrcA(context);
+
+ Operand srcB = context.FPNegate(GetSrcB(context), negateB);
+ Operand srcC = context.FPNegate(GetSrcC(context), negateC);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fmnmx(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool absoluteA = op.AbsoluteA;
+ bool negateB = op.RawOpCode.Extract(45);
+ bool negateA = op.RawOpCode.Extract(48);
+ bool absoluteB = op.RawOpCode.Extract(49);
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand resMin = context.FPMinimum(srcA, srcB);
+ Operand resMax = context.FPMaximum(srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fmul(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = !(op is OpCodeFArithImm32) && op.RawOpCode.Extract(48);
+
+ Operand srcA = GetSrcA(context);
+
+ Operand srcB = context.FPNegate(GetSrcB(context), negateB);
+
+ switch (op.Scale)
+ {
+ case FmulScale.None: break;
+
+ case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break;
+ case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break;
+ case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break;
+ case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break;
+ case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break;
+ case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break;
+
+ default: break; //TODO: Warning.
+ }
+
+ Operand dest = GetDest(context);
+
+ context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), op.Saturate));
+
+ SetFPZnFlags(context, dest, op.SetCondCode);
+ }
+
+ public static void Fset(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4);
+
+ bool negateA = op.RawOpCode.Extract(43);
+ bool absoluteB = op.RawOpCode.Extract(44);
+ bool boolFloat = op.RawOpCode.Extract(52);
+ bool negateB = op.RawOpCode.Extract(53);
+ bool absoluteA = op.RawOpCode.Extract(54);
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand res = GetFPComparison(context, cmpOp, srcA, srcB);
+
+ Operand pred = GetPredicate39(context);
+
+ res = GetPredLogicalOp(context, op.LogicalOp, res, pred);
+
+ Operand dest = GetDest(context);
+
+ if (boolFloat)
+ {
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ }
+ else
+ {
+ context.Copy(dest, res);
+ }
+
+ // TODO: CC, X
+ }
+
+ public static void Fsetp(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4);
+
+ bool negateB = op.RawOpCode.Extract(6);
+ bool absoluteA = op.RawOpCode.Extract(7);
+ bool negateA = op.RawOpCode.Extract(43);
+ bool absoluteB = op.RawOpCode.Extract(44);
+
+ Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA);
+ Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB);
+
+ Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB);
+
+ Operand p1Res = context.BitwiseNot(p0Res);
+
+ Operand pred = GetPredicate39(context);
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Hadd2(EmitterContext context)
+ {
+ Hadd2Hmul2Impl(context, isAdd: true);
+ }
+
+ public static void Hfma2(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] srcA = GetHfmaSrcA(context);
+ Operand[] srcB = GetHfmaSrcB(context);
+ Operand[] srcC = GetHfmaSrcC(context);
+
+ Operand[] res = new Operand[2];
+
+ for (int index = 0; index < res.Length; index++)
+ {
+ res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]);
+
+ res[index] = context.FPSaturate(res[index], op.Saturate);
+ }
+
+ context.Copy(GetDest(context), GetHalfPacked(context, res));
+ }
+
+ public static void Hmul2(EmitterContext context)
+ {
+ Hadd2Hmul2Impl(context, isAdd: false);
+ }
+
+ private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd)
+ {
+ OpCode op = context.CurrOp;
+
+ bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32);
+
+ Operand[] srcA = GetHalfSrcA(context);
+ Operand[] srcB = GetHalfSrcB(context);
+
+ Operand[] res = new Operand[2];
+
+ for (int index = 0; index < res.Length; index++)
+ {
+ if (isAdd)
+ {
+ res[index] = context.FPAdd(srcA[index], srcB[index]);
+ }
+ else
+ {
+ res[index] = context.FPMultiply(srcA[index], srcB[index]);
+ }
+
+ res[index] = context.FPSaturate(res[index], saturate);
+ }
+
+ context.Copy(GetDest(context), GetHalfPacked(context, res));
+ }
+
+ public static void Hsetp2(EmitterContext context)
+ {
+ OpCodeSet op = (OpCodeSet)context.CurrOp;
+
+ bool hAnd = op.RawOpCode.Extract(53);
+
+ Condition cmpOp = op is IOpCodeReg
+ ? (Condition)op.RawOpCode.Extract(35, 4)
+ : (Condition)op.RawOpCode.Extract(49, 4);
+
+ Operand[] srcA = GetHalfSrcA(context);
+ Operand[] srcB = GetHalfSrcB(context);
+
+ Operand[] res = new Operand[2];
+
+ Operand pred = GetPredicate39(context);
+
+ Operand p0Res = GetFPComparison(context, cmpOp, srcA[0], srcB[0]);
+ Operand p1Res = GetFPComparison(context, cmpOp, srcA[1], srcB[1]);
+
+ if (hAnd)
+ {
+ p0Res = context.BitwiseAnd(p0Res, p1Res);
+ p1Res = context.BitwiseNot(p0Res);
+ }
+
+ p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred);
+ p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred);
+
+ context.Copy(Register(op.Predicate3), p0Res);
+ context.Copy(Register(op.Predicate0), p1Res);
+ }
+
+ public static void Mufu(EmitterContext context)
+ {
+ IOpCodeFArith op = (IOpCodeFArith)context.CurrOp;
+
+ bool negateB = op.RawOpCode.Extract(48);
+
+ Operand res = context.FPAbsNeg(GetSrcA(context), op.AbsoluteA, negateB);
+
+ MufuOperation subOp = (MufuOperation)context.CurrOp.RawOpCode.Extract(20, 4);
+
+ switch (subOp)
+ {
+ case MufuOperation.Cosine:
+ res = context.FPCosine(res);
+ break;
+
+ case MufuOperation.Sine:
+ res = context.FPSine(res);
+ break;
+
+ case MufuOperation.ExponentB2:
+ res = context.FPExponentB2(res);
+ break;
+
+ case MufuOperation.LogarithmB2:
+ res = context.FPLogarithmB2(res);
+ break;
+
+ case MufuOperation.Reciprocal:
+ res = context.FPReciprocal(res);
+ break;
+
+ case MufuOperation.ReciprocalSquareRoot:
+ res = context.FPReciprocalSquareRoot(res);
+ break;
+
+ case MufuOperation.SquareRoot:
+ res = context.FPSquareRoot(res);
+ break;
+
+ default: /* TODO */ break;
+ }
+
+ context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate));
+ }
+
+ private static Operand GetFPComparison(
+ EmitterContext context,
+ Condition cond,
+ Operand srcA,
+ Operand srcB)
+ {
+ Operand res;
+
+ if (cond == Condition.Always)
+ {
+ res = Const(IrConsts.True);
+ }
+ else if (cond == Condition.Never)
+ {
+ res = Const(IrConsts.False);
+ }
+ else if (cond == Condition.Nan || cond == Condition.Number)
+ {
+ res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB));
+
+ if (cond == Condition.Number)
+ {
+ res = context.BitwiseNot(res);
+ }
+ }
+ else
+ {
+ Instruction inst;
+
+ switch (cond & ~Condition.Nan)
+ {
+ case Condition.Less: inst = Instruction.CompareLess; break;
+ case Condition.Equal: inst = Instruction.CompareEqual; break;
+ case Condition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break;
+ case Condition.Greater: inst = Instruction.CompareGreater; break;
+ case Condition.NotEqual: inst = Instruction.CompareNotEqual; break;
+ case Condition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break;
+
+ default: throw new InvalidOperationException($"Unexpected condition \"{cond}\".");
+ }
+
+ res = context.Add(inst | Instruction.FP, Local(), srcA, srcB);
+
+ if ((cond & Condition.Nan) != 0)
+ {
+ res = context.BitwiseOr(res, context.IsNan(srcA));
+ res = context.BitwiseOr(res, context.IsNan(srcB));
+ }
+ }
+
+ return res;
+ }
+
+ private static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC)
+ {
+ if (setCC)
+ {
+ context.Copy(GetZF(context), context.FPCompareEqual(dest, ConstF(0)));
+ context.Copy(GetNF(context), context.FPCompareLess (dest, ConstF(0)));
+ }
+ }
+
+ private static Operand[] GetHfmaSrcA(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ return GetHalfUnpacked(context, GetSrcA(context), op.SwizzleA);
+ }
+
+ private static Operand[] GetHfmaSrcB(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), op.SwizzleB);
+
+ return FPAbsNeg(context, operands, false, op.NegateB);
+ }
+
+ private static Operand[] GetHfmaSrcC(EmitterContext context)
+ {
+ IOpCodeHfma op = (IOpCodeHfma)context.CurrOp;
+
+ Operand[] operands = GetHalfUnpacked(context, GetSrcC(context), op.SwizzleC);
+
+ return FPAbsNeg(context, operands, false, op.NegateC);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs
new file mode 100644
index 00000000..fb76e06a
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs
@@ -0,0 +1,107 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System.Collections.Generic;
+using System.Linq;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Bra(EmitterContext context)
+ {
+ EmitBranch(context, context.CurrBlock.Branch.Address);
+ }
+
+ public static void Exit(EmitterContext context)
+ {
+ OpCodeExit op = (OpCodeExit)context.CurrOp;
+
+ // TODO: Figure out how this is supposed to work in the
+ // presence of other condition codes.
+ if (op.Condition == Condition.Always)
+ {
+ context.Return();
+ }
+ }
+
+ public static void Kil(EmitterContext context)
+ {
+ context.Discard();
+ }
+
+ public static void Ssy(EmitterContext context)
+ {
+ OpCodeSsy op = (OpCodeSsy)context.CurrOp;
+
+ foreach (KeyValuePair<OpCodeSync, Operand> kv in op.Syncs)
+ {
+ OpCodeSync opSync = kv.Key;
+
+ Operand local = kv.Value;
+
+ int ssyIndex = opSync.Targets[op];
+
+ context.Copy(local, Const(ssyIndex));
+ }
+ }
+
+ public static void Sync(EmitterContext context)
+ {
+ OpCodeSync op = (OpCodeSync)context.CurrOp;
+
+ if (op.Targets.Count == 1)
+ {
+ // If we have only one target, then the SSY is basically
+ // a branch, we can produce better codegen for this case.
+ OpCodeSsy opSsy = op.Targets.Keys.First();
+
+ EmitBranch(context, opSsy.GetAbsoluteAddress());
+ }
+ else
+ {
+ foreach (KeyValuePair<OpCodeSsy, int> kv in op.Targets)
+ {
+ OpCodeSsy opSsy = kv.Key;
+
+ Operand label = context.GetLabel(opSsy.GetAbsoluteAddress());
+
+ Operand local = opSsy.Syncs[op];
+
+ int ssyIndex = kv.Value;
+
+ context.BranchIfTrue(label, context.ICompareEqual(local, Const(ssyIndex)));
+ }
+ }
+ }
+
+ private static void EmitBranch(EmitterContext context, ulong address)
+ {
+ // If we're branching to the next instruction, then the branch
+ // is useless and we can ignore it.
+ if (address == context.CurrOp.Address + 8)
+ {
+ return;
+ }
+
+ Operand label = context.GetLabel(address);
+
+ Operand pred = Register(context.CurrOp.Predicate);
+
+ if (context.CurrOp.Predicate.IsPT)
+ {
+ context.Branch(label);
+ }
+ else if (context.CurrOp.InvertPredicate)
+ {
+ context.BranchIfFalse(label, pred);
+ }
+ else
+ {
+ context.BranchIfTrue(label, pred);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs
new file mode 100644
index 00000000..c0a3012a
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs
@@ -0,0 +1,267 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static class InstEmitHelper
+ {
+ public static Operand GetZF(EmitterContext context)
+ {
+ return Register(0, RegisterType.Flag);
+ }
+
+ public static Operand GetNF(EmitterContext context)
+ {
+ return Register(1, RegisterType.Flag);
+ }
+
+ public static Operand GetCF(EmitterContext context)
+ {
+ return Register(2, RegisterType.Flag);
+ }
+
+ public static Operand GetVF(EmitterContext context)
+ {
+ return Register(3, RegisterType.Flag);
+ }
+
+ public static Operand GetDest(EmitterContext context)
+ {
+ return Register(((IOpCodeRd)context.CurrOp).Rd);
+ }
+
+ public static Operand GetSrcA(EmitterContext context)
+ {
+ return Register(((IOpCodeRa)context.CurrOp).Ra);
+ }
+
+ public static Operand GetSrcB(EmitterContext context, FPType floatType)
+ {
+ if (floatType == FPType.FP32)
+ {
+ return GetSrcB(context);
+ }
+ else if (floatType == FPType.FP16)
+ {
+ int h = context.CurrOp.RawOpCode.Extract(41, 1);
+
+ return GetHalfUnpacked(context, GetSrcB(context), FPHalfSwizzle.FP16)[h];
+ }
+ else if (floatType == FPType.FP64)
+ {
+ // TODO.
+ }
+
+ throw new ArgumentException($"Invalid floating point type \"{floatType}\".");
+ }
+
+ public static Operand GetSrcB(EmitterContext context)
+ {
+ switch (context.CurrOp)
+ {
+ case IOpCodeCbuf op:
+ return Cbuf(op.Slot, op.Offset);
+
+ case IOpCodeImm op:
+ return Const(op.Immediate);
+
+ case IOpCodeImmF op:
+ return ConstF(op.Immediate);
+
+ case IOpCodeReg op:
+ return Register(op.Rb);
+
+ case IOpCodeRegCbuf op:
+ return Register(op.Rc);
+ }
+
+ throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\".");
+ }
+
+ public static Operand GetSrcC(EmitterContext context)
+ {
+ switch (context.CurrOp)
+ {
+ case IOpCodeRegCbuf op:
+ return Cbuf(op.Slot, op.Offset);
+
+ case IOpCodeRc op:
+ return Register(op.Rc);
+ }
+
+ throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\".");
+ }
+
+ public static Operand[] GetHalfSrcA(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ bool absoluteA = false, negateA = false;
+
+ if (op is IOpCodeCbuf || op is IOpCodeImm)
+ {
+ negateA = op.RawOpCode.Extract(43);
+ absoluteA = op.RawOpCode.Extract(44);
+ }
+ else if (op is IOpCodeReg)
+ {
+ absoluteA = op.RawOpCode.Extract(44);
+ }
+ else if (op is OpCodeAluImm32 && op.Emitter == InstEmit.Hadd2)
+ {
+ negateA = op.RawOpCode.Extract(56);
+ }
+
+ FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2);
+
+ Operand[] operands = GetHalfUnpacked(context, GetSrcA(context), swizzle);
+
+ return FPAbsNeg(context, operands, absoluteA, negateA);
+ }
+
+ public static Operand[] GetHalfSrcB(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ FPHalfSwizzle swizzle = FPHalfSwizzle.FP16;
+
+ bool absoluteB = false, negateB = false;
+
+ if (op is IOpCodeReg)
+ {
+ swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2);
+
+ absoluteB = op.RawOpCode.Extract(30);
+ negateB = op.RawOpCode.Extract(31);
+ }
+ else if (op is IOpCodeCbuf)
+ {
+ swizzle = FPHalfSwizzle.FP32;
+
+ absoluteB = op.RawOpCode.Extract(54);
+ }
+
+ Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), swizzle);
+
+ return FPAbsNeg(context, operands, absoluteB, negateB);
+ }
+
+ public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg)
+ {
+ for (int index = 0; index < operands.Length; index++)
+ {
+ operands[index] = context.FPAbsNeg(operands[index], abs, neg);
+ }
+
+ return operands;
+ }
+
+ public static Operand[] GetHalfUnpacked(EmitterContext context, Operand src, FPHalfSwizzle swizzle)
+ {
+ switch (swizzle)
+ {
+ case FPHalfSwizzle.FP16:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16Low (src),
+ context.UnpackHalf2x16High(src)
+ };
+
+ case FPHalfSwizzle.FP32: return new Operand[] { src, src };
+
+ case FPHalfSwizzle.DupH0:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16Low(src),
+ context.UnpackHalf2x16Low(src)
+ };
+
+ case FPHalfSwizzle.DupH1:
+ return new Operand[]
+ {
+ context.UnpackHalf2x16High(src),
+ context.UnpackHalf2x16High(src)
+ };
+ }
+
+ throw new ArgumentException($"Invalid swizzle \"{swizzle}\".");
+ }
+
+ public static Operand GetHalfPacked(EmitterContext context, Operand[] results)
+ {
+ OpCode op = context.CurrOp;
+
+ FPHalfSwizzle swizzle = FPHalfSwizzle.FP16;
+
+ if (!(op is OpCodeAluImm32))
+ {
+ swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2);
+ }
+
+ switch (swizzle)
+ {
+ case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]);
+
+ case FPHalfSwizzle.FP32: return results[0];
+
+ case FPHalfSwizzle.DupH0:
+ {
+ Operand h1 = GetHalfDest(context, isHigh: true);
+
+ return context.PackHalf2x16(results[0], h1);
+ }
+
+ case FPHalfSwizzle.DupH1:
+ {
+ Operand h0 = GetHalfDest(context, isHigh: false);
+
+ return context.PackHalf2x16(h0, results[1]);
+ }
+ }
+
+ throw new ArgumentException($"Invalid swizzle \"{swizzle}\".");
+ }
+
+ public static Operand GetHalfDest(EmitterContext context, bool isHigh)
+ {
+ if (isHigh)
+ {
+ return context.UnpackHalf2x16High(GetDest(context));
+ }
+ else
+ {
+ return context.UnpackHalf2x16Low(GetDest(context));
+ }
+ }
+
+ public static Operand GetPredicate39(EmitterContext context)
+ {
+ IOpCodeAlu op = (IOpCodeAlu)context.CurrOp;
+
+ Operand local = Register(op.Predicate39);
+
+ if (op.InvertP)
+ {
+ local = context.BitwiseNot(local);
+ }
+
+ return local;
+ }
+
+ public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits)
+ {
+ return context.BitfieldExtractS32(src, Const(0), Const(srcBits));
+ }
+
+ public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits)
+ {
+ int mask = (int)(0xffffffffu >> (32 - srcBits));
+
+ return context.BitwiseAnd(src, Const(mask));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
new file mode 100644
index 00000000..ee210f22
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
@@ -0,0 +1,325 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Ald(EmitterContext context)
+ {
+ OpCodeAttribute op = (OpCodeAttribute)context.CurrOp;
+
+ Operand primVertex = context.Copy(GetSrcC(context));
+
+ for (int index = 0; index < op.Count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ Operand src = Attribute(op.AttributeOffset + index * 4);
+
+ context.Copy(Register(rd), context.LoadAttribute(src, primVertex));
+ }
+ }
+
+ public static void Ast(EmitterContext context)
+ {
+ OpCodeAttribute op = (OpCodeAttribute)context.CurrOp;
+
+ for (int index = 0; index < op.Count; index++)
+ {
+ if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex)
+ {
+ break;
+ }
+
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ Operand dest = Attribute(op.AttributeOffset + index * 4);
+
+ context.Copy(dest, Register(rd));
+ }
+ }
+
+ public static void Ipa(EmitterContext context)
+ {
+ OpCodeIpa op = (OpCodeIpa)context.CurrOp;
+
+ InterpolationQualifier iq = InterpolationQualifier.None;
+
+ switch (op.Mode)
+ {
+ case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break;
+ }
+
+ Operand srcA = Attribute(op.AttributeOffset, iq);
+
+ Operand srcB = GetSrcB(context);
+
+ Operand res = context.FPSaturate(srcA, op.Saturate);
+
+ context.Copy(GetDest(context), res);
+ }
+
+ public static void Isberd(EmitterContext context)
+ {
+ // This instruction performs a load from ISBE memory,
+ // however it seems to be only used to get some vertex
+ // input data, so we instead propagate the offset so that
+ // it can be used on the attribute load.
+ context.Copy(GetDest(context), GetSrcA(context));
+ }
+
+ public static void Ld(EmitterContext context)
+ {
+ LoadLocalOrGlobal(context, isGlobal: false);
+ }
+
+ public static void Ldc(EmitterContext context)
+ {
+ OpCodeLdc op = (OpCodeLdc)context.CurrOp;
+
+ if (op.Size > IntegerSize.B64)
+ {
+ // TODO: Warning.
+ }
+
+ bool isSmallInt = op.Size < IntegerSize.B32;
+
+ int count = op.Size == IntegerSize.B64 ? 2 : 1;
+
+ Operand wordOffset = context.ShiftRightU32(GetSrcA(context), Const(2));
+
+ wordOffset = context.IAdd(wordOffset, Const(op.Offset));
+
+ Operand bitOffset = GetBitOffset(context, GetSrcA(context));
+
+ for (int index = 0; index < count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ Operand offset = context.IAdd(wordOffset, Const(index));
+
+ Operand value = context.LoadConstant(Const(op.Slot), offset);
+
+ if (isSmallInt)
+ {
+ value = ExtractSmallInt(context, op.Size, wordOffset, value);
+ }
+
+ context.Copy(Register(rd), value);
+ }
+ }
+
+ public static void Ldg(EmitterContext context)
+ {
+ LoadLocalOrGlobal(context, isGlobal: true);
+ }
+
+ public static void Out(EmitterContext context)
+ {
+ OpCode op = context.CurrOp;
+
+ bool emit = op.RawOpCode.Extract(39);
+ bool cut = op.RawOpCode.Extract(40);
+
+ if (!(emit || cut))
+ {
+ // TODO: Warning.
+ }
+
+ if (emit)
+ {
+ context.EmitVertex();
+ }
+
+ if (cut)
+ {
+ context.EndPrimitive();
+ }
+ }
+
+ public static void St(EmitterContext context)
+ {
+ StoreLocalOrGlobal(context, isGlobal: false);
+ }
+
+ public static void Stg(EmitterContext context)
+ {
+ StoreLocalOrGlobal(context, isGlobal: true);
+ }
+
+ private static void LoadLocalOrGlobal(EmitterContext context, bool isGlobal)
+ {
+ OpCodeMemory op = (OpCodeMemory)context.CurrOp;
+
+ if (op.Size > IntegerSize.B128)
+ {
+ // TODO: Warning.
+ }
+
+ bool isSmallInt = op.Size < IntegerSize.B32;
+
+ int count = 1;
+
+ switch (op.Size)
+ {
+ case IntegerSize.B64: count = 2; break;
+ case IntegerSize.B128: count = 4; break;
+ }
+
+ Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset));
+
+ // Word offset = byte offset / 4 (one word = 4 bytes).
+ Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2));
+
+ Operand bitOffset = GetBitOffset(context, baseOffset);
+
+ for (int index = 0; index < count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ Operand offset = context.IAdd(wordOffset, Const(index));
+
+ Operand value = isGlobal
+ ? context.LoadGlobal(offset)
+ : context.LoadLocal (offset);
+
+ if (isSmallInt)
+ {
+ value = ExtractSmallInt(context, op.Size, bitOffset, value);
+ }
+
+ context.Copy(Register(rd), value);
+ }
+ }
+
+ private static void StoreLocalOrGlobal(EmitterContext context, bool isGlobal)
+ {
+ OpCodeMemory op = (OpCodeMemory)context.CurrOp;
+
+ if (op.Size > IntegerSize.B128)
+ {
+ // TODO: Warning.
+ }
+
+ bool isSmallInt = op.Size < IntegerSize.B32;
+
+ int count = 1;
+
+ switch (op.Size)
+ {
+ case IntegerSize.B64: count = 2; break;
+ case IntegerSize.B128: count = 4; break;
+ }
+
+ Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset));
+
+ Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2));
+
+ Operand bitOffset = GetBitOffset(context, baseOffset);
+
+ for (int index = 0; index < count; index++)
+ {
+ Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr);
+
+ if (rd.IsRZ)
+ {
+ break;
+ }
+
+ Operand value = Register(rd);
+
+ Operand offset = context.IAdd(wordOffset, Const(index));
+
+ if (isSmallInt)
+ {
+ Operand word = isGlobal
+ ? context.LoadGlobal(offset)
+ : context.LoadLocal (offset);
+
+ value = InsertSmallInt(context, op.Size, bitOffset, word, value);
+ }
+
+ if (isGlobal)
+ {
+ context.StoreGlobal(offset, value);
+ }
+ else
+ {
+ context.StoreLocal(offset, value);
+ }
+ }
+ }
+
+ private static Operand GetBitOffset(EmitterContext context, Operand baseOffset)
+ {
+ // Note: byte offset = (baseOffset & 0b11) * 8.
+ // Addresses should be always aligned to the integer type,
+ // so we don't need to take unaligned addresses into account.
+ return context.ShiftLeft(context.BitwiseAnd(baseOffset, Const(3)), Const(3));
+ }
+
+ private static Operand ExtractSmallInt(
+ EmitterContext context,
+ IntegerSize size,
+ Operand bitOffset,
+ Operand value)
+ {
+ value = context.ShiftRightU32(value, bitOffset);
+
+ switch (size)
+ {
+ case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break;
+ case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break;
+ case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break;
+ case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break;
+ }
+
+ return value;
+ }
+
+ private static Operand InsertSmallInt(
+ EmitterContext context,
+ IntegerSize size,
+ Operand bitOffset,
+ Operand word,
+ Operand value)
+ {
+ switch (size)
+ {
+ case IntegerSize.U8:
+ case IntegerSize.S8:
+ value = context.BitwiseAnd(value, Const(0xff));
+ value = context.BitfieldInsert(word, value, bitOffset, Const(8));
+ break;
+
+ case IntegerSize.U16:
+ case IntegerSize.S16:
+ value = context.BitwiseAnd(value, Const(0xffff));
+ value = context.BitfieldInsert(word, value, bitOffset, Const(16));
+ break;
+ }
+
+ return value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
new file mode 100644
index 00000000..f66ebc9f
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Mov(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ context.Copy(GetDest(context), GetSrcB(context));
+ }
+
+ public static void S2r(EmitterContext context)
+ {
+ // TODO: Better impl.
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ SystemRegister sysReg = (SystemRegister)op.RawOpCode.Extract(20, 8);
+
+ Operand src;
+
+ switch (sysReg)
+ {
+ case SystemRegister.ThreadIdX: src = Attribute(AttributeConsts.ThreadIdX); break;
+ case SystemRegister.ThreadIdY: src = Attribute(AttributeConsts.ThreadIdY); break;
+ case SystemRegister.ThreadIdZ: src = Attribute(AttributeConsts.ThreadIdZ); break;
+ case SystemRegister.CtaIdX: src = Attribute(AttributeConsts.CtaIdX); break;
+ case SystemRegister.CtaIdY: src = Attribute(AttributeConsts.CtaIdY); break;
+ case SystemRegister.CtaIdZ: src = Attribute(AttributeConsts.CtaIdZ); break;
+
+ default: src = Const(0); break;
+ }
+
+ context.Copy(GetDest(context), src);
+ }
+
+ public static void Sel(EmitterContext context)
+ {
+ OpCodeAlu op = (OpCodeAlu)context.CurrOp;
+
+ Operand pred = GetPredicate39(context);
+
+ Operand srcA = GetSrcA(context);
+ Operand srcB = GetSrcB(context);
+
+ Operand res = context.ConditionalSelect(pred, srcA, srcB);
+
+ context.Copy(GetDest(context), res);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
new file mode 100644
index 00000000..554deb7b
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -0,0 +1,776 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Tex(EmitterContext context)
+ {
+ Tex(context, TextureFlags.None);
+ }
+
+ public static void TexB(EmitterContext context)
+ {
+ Tex(context, TextureFlags.Bindless);
+ }
+
+ public static void Tld(EmitterContext context)
+ {
+ Tex(context, TextureFlags.IntCoords);
+ }
+
+ public static void TldB(EmitterContext context)
+ {
+ Tex(context, TextureFlags.IntCoords | TextureFlags.Bindless);
+ }
+
+ public static void Texs(EmitterContext context)
+ {
+ OpCodeTextureScalar op = (OpCodeTextureScalar)context.CurrOp;
+
+ if (op.Rd0.IsRZ && op.Rd1.IsRZ)
+ {
+ return;
+ }
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ void AddTextureOffset(int coordsCount, int stride, int size)
+ {
+ Operand packedOffs = Rb();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size)));
+ }
+ }
+
+ TextureTarget type;
+ TextureFlags flags;
+
+ if (op is OpCodeTexs texsOp)
+ {
+ type = GetTextureType (texsOp.Target);
+ flags = GetTextureFlags(texsOp.Target);
+
+ if ((type & TextureTarget.Array) != 0)
+ {
+ Operand arrayIndex = Ra();
+
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+
+ sourcesList.Add(arrayIndex);
+
+ if ((type & TextureTarget.Shadow) != 0)
+ {
+ sourcesList.Add(Rb());
+ }
+
+ if ((flags & TextureFlags.LodLevel) != 0)
+ {
+ sourcesList.Add(ConstF(0));
+ }
+ }
+ else
+ {
+ switch (texsOp.Target)
+ {
+ case Decoders.TextureTarget.Texture1DLodZero:
+ sourcesList.Add(Ra());
+ break;
+
+ case Decoders.TextureTarget.Texture2D:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case Decoders.TextureTarget.Texture2DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(ConstF(0));
+ break;
+
+ case Decoders.TextureTarget.Texture2DLodLevel:
+ case Decoders.TextureTarget.Texture2DDepthCompare:
+ case Decoders.TextureTarget.Texture3D:
+ case Decoders.TextureTarget.TextureCube:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case Decoders.TextureTarget.Texture2DLodZeroDepthCompare:
+ case Decoders.TextureTarget.Texture3DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(ConstF(0));
+ break;
+
+ case Decoders.TextureTarget.Texture2DLodLevelDepthCompare:
+ case Decoders.TextureTarget.TextureCubeLodLevel:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Rb());
+ break;
+ }
+ }
+ }
+ else if (op is OpCodeTlds tldsOp)
+ {
+ type = GetTextureType (tldsOp.Target);
+ flags = GetTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
+
+ switch (tldsOp.Target)
+ {
+ case TexelLoadTarget.Texture1DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadTarget.Texture1DLodLevel:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TexelLoadTarget.Texture2DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadTarget.Texture2DLodZeroOffset:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadTarget.Texture2DLodZeroMultisample:
+ case TexelLoadTarget.Texture2DLodLevel:
+ case TexelLoadTarget.Texture2DLodLevelOffset:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ break;
+
+ case TexelLoadTarget.Texture3DLodZero:
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Const(0));
+ break;
+
+ case TexelLoadTarget.Texture2DArrayLodZero:
+ sourcesList.Add(Rb());
+ sourcesList.Add(Rb());
+ sourcesList.Add(Ra());
+ sourcesList.Add(Const(0));
+ break;
+ }
+
+ if ((flags & TextureFlags.Offset) != 0)
+ {
+ AddTextureOffset(type.GetDimensions(), 4, 4);
+ }
+ }
+ else if (op is OpCodeTld4s tld4sOp)
+ {
+ if (!(tld4sOp.HasDepthCompare || tld4sOp.HasOffset))
+ {
+ sourcesList.Add(Ra());
+ sourcesList.Add(Rb());
+ }
+ else
+ {
+ sourcesList.Add(Ra());
+ sourcesList.Add(Ra());
+ }
+
+ type = TextureTarget.Texture2D;
+ flags = TextureFlags.Gather;
+
+ if (tld4sOp.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureTarget.Shadow;
+ }
+
+ if (tld4sOp.HasOffset)
+ {
+ AddTextureOffset(type.GetDimensions(), 8, 6);
+
+ flags |= TextureFlags.Offset;
+ }
+
+ sourcesList.Add(Const(tld4sOp.GatherCompIndex));
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid opcode type \"{op.GetType().Name}\".");
+ }
+
+ Operand[] sources = sourcesList.ToArray();
+
+ Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) };
+ Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) };
+
+ int destIncrement = 0;
+
+ Operand GetDest()
+ {
+ int high = destIncrement >> 1;
+ int low = destIncrement & 1;
+
+ destIncrement++;
+
+ if (op.IsFp16)
+ {
+ return high != 0
+ ? (rd1[low] = Local())
+ : (rd0[low] = Local());
+ }
+ else
+ {
+ int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index;
+
+ if (rdIndex < RegisterConsts.RegisterZeroIndex)
+ {
+ rdIndex += low;
+ }
+
+ return Register(rdIndex, RegisterType.Gpr);
+ }
+ }
+
+ int handle = op.Immediate;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+
+ if (op.IsFp16)
+ {
+ context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1]));
+ context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1]));
+ }
+ }
+
+ public static void Tld4(EmitterContext context)
+ {
+ OpCodeTld4 op = (OpCodeTld4)context.CurrOp;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ Operand arrayIndex = op.IsArray ? Ra() : null;
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ TextureTarget type = GetTextureType(op.Dimensions);
+
+ TextureFlags flags = TextureFlags.Gather;
+
+ int coordsCount = type.GetDimensions();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ if (op.IsArray)
+ {
+ sourcesList.Add(arrayIndex);
+
+ type |= TextureTarget.Array;
+ }
+
+ Operand[] packedOffs = new Operand[2];
+
+ packedOffs[0] = op.Offset != TextureGatherOffset.None ? Rb() : null;
+ packedOffs[1] = op.Offset == TextureGatherOffset.Offsets ? Rb() : null;
+
+ if (op.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureTarget.Shadow;
+ }
+
+ if (op.Offset != TextureGatherOffset.None)
+ {
+ int offsetTexelsCount = op.Offset == TextureGatherOffset.Offsets ? 4 : 1;
+
+ for (int index = 0; index < coordsCount * offsetTexelsCount; index++)
+ {
+ Operand packed = packedOffs[(index >> 2) & 1];
+
+ sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6)));
+ }
+
+ flags |= op.Offset == TextureGatherOffset.Offsets
+ ? TextureFlags.Offsets
+ : TextureFlags.Offset;
+ }
+
+ sourcesList.Add(Const(op.GatherCompIndex));
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = op.Immediate;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ public static void Txq(EmitterContext context)
+ {
+ Txq(context, bindless: false);
+ }
+
+ public static void TxqB(EmitterContext context)
+ {
+ Txq(context, bindless: true);
+ }
+
+ private static void Txq(EmitterContext context, bool bindless)
+ {
+ OpCodeTex op = (OpCodeTex)context.CurrOp;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ TextureProperty property = (TextureProperty)op.RawOpCode.Extract(22, 6);
+
+ // TODO: Validate and use property.
+ Instruction inst = Instruction.TextureSize;
+
+ TextureTarget type = TextureTarget.Texture2D;
+
+ TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None;
+
+ int raIndex = op.Ra.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ if (bindless)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ sourcesList.Add(Ra());
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = !bindless ? op.Immediate : 0;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ inst,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ private static void Tex(EmitterContext context, TextureFlags flags)
+ {
+ OpCodeTexture op = (OpCodeTexture)context.CurrOp;
+
+ bool isBindless = (flags & TextureFlags.Bindless) != 0;
+ bool intCoords = (flags & TextureFlags.IntCoords) != 0;
+
+ if (op.Rd.IsRZ)
+ {
+ return;
+ }
+
+ int raIndex = op.Ra.Index;
+ int rbIndex = op.Rb.Index;
+
+ Operand Ra()
+ {
+ if (raIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(raIndex++, RegisterType.Gpr));
+ }
+
+ Operand Rb()
+ {
+ if (rbIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return context.Copy(Register(rbIndex++, RegisterType.Gpr));
+ }
+
+ Operand arrayIndex = op.IsArray ? Ra() : null;
+
+ List<Operand> sourcesList = new List<Operand>();
+
+ if (isBindless)
+ {
+ sourcesList.Add(Rb());
+ }
+
+ TextureTarget type = GetTextureType(op.Dimensions);
+
+ int coordsCount = type.GetDimensions();
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(Ra());
+ }
+
+ if (op.IsArray)
+ {
+ sourcesList.Add(arrayIndex);
+
+ type |= TextureTarget.Array;
+ }
+
+ bool hasLod = op.LodMode > TextureLodMode.LodZero;
+
+ Operand lodValue = hasLod ? Rb() : ConstF(0);
+
+ Operand packedOffs = op.HasOffset ? Rb() : null;
+
+ if (op.HasDepthCompare)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureTarget.Shadow;
+ }
+
+ if ((op.LodMode == TextureLodMode.LodZero ||
+ op.LodMode == TextureLodMode.LodLevel ||
+ op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample)
+ {
+ sourcesList.Add(lodValue);
+
+ flags |= TextureFlags.LodLevel;
+ }
+
+ if (op.HasOffset)
+ {
+ for (int index = 0; index < coordsCount; index++)
+ {
+ sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4)));
+ }
+
+ flags |= TextureFlags.Offset;
+ }
+
+ if (op.LodMode == TextureLodMode.LodBias ||
+ op.LodMode == TextureLodMode.LodBiasA)
+ {
+ sourcesList.Add(lodValue);
+
+ flags |= TextureFlags.LodBias;
+ }
+
+ if (op.IsMultisample)
+ {
+ sourcesList.Add(Rb());
+
+ type |= TextureTarget.Multisample;
+ }
+
+ Operand[] sources = sourcesList.ToArray();
+
+ int rdIndex = op.Rd.Index;
+
+ Operand GetDest()
+ {
+ if (rdIndex > RegisterConsts.RegisterZeroIndex)
+ {
+ return Const(0);
+ }
+
+ return Register(rdIndex++, RegisterType.Gpr);
+ }
+
+ int handle = !isBindless ? op.Immediate : 0;
+
+ for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+ {
+ if ((compMask & 1) != 0)
+ {
+ Operand dest = GetDest();
+
+ TextureOperation operation = new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ flags,
+ handle,
+ compIndex,
+ dest,
+ sources);
+
+ context.Add(operation);
+ }
+ }
+ }
+
+ private static TextureTarget GetTextureType(TextureDimensions dimensions)
+ {
+ switch (dimensions)
+ {
+ case TextureDimensions.Texture1D: return TextureTarget.Texture1D;
+ case TextureDimensions.Texture2D: return TextureTarget.Texture2D;
+ case TextureDimensions.Texture3D: return TextureTarget.Texture3D;
+ case TextureDimensions.TextureCube: return TextureTarget.TextureCube;
+ }
+
+ throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\".");
+ }
+
+ private static TextureTarget GetTextureType(Decoders.TextureTarget type)
+ {
+ switch (type)
+ {
+ case Decoders.TextureTarget.Texture1DLodZero:
+ return TextureTarget.Texture1D;
+
+ case Decoders.TextureTarget.Texture2D:
+ case Decoders.TextureTarget.Texture2DLodZero:
+ case Decoders.TextureTarget.Texture2DLodLevel:
+ return TextureTarget.Texture2D;
+
+ case Decoders.TextureTarget.Texture2DDepthCompare:
+ case Decoders.TextureTarget.Texture2DLodLevelDepthCompare:
+ case Decoders.TextureTarget.Texture2DLodZeroDepthCompare:
+ return TextureTarget.Texture2D | TextureTarget.Shadow;
+
+ case Decoders.TextureTarget.Texture2DArray:
+ case Decoders.TextureTarget.Texture2DArrayLodZero:
+ return TextureTarget.Texture2D | TextureTarget.Array;
+
+ case Decoders.TextureTarget.Texture2DArrayLodZeroDepthCompare:
+ return TextureTarget.Texture2D | TextureTarget.Array | TextureTarget.Shadow;
+
+ case Decoders.TextureTarget.Texture3D:
+ case Decoders.TextureTarget.Texture3DLodZero:
+ return TextureTarget.Texture3D;
+
+ case Decoders.TextureTarget.TextureCube:
+ case Decoders.TextureTarget.TextureCubeLodLevel:
+ return TextureTarget.TextureCube;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureTarget GetTextureType(TexelLoadTarget type)
+ {
+ switch (type)
+ {
+ case TexelLoadTarget.Texture1DLodZero:
+ case TexelLoadTarget.Texture1DLodLevel:
+ return TextureTarget.Texture1D;
+
+ case TexelLoadTarget.Texture2DLodZero:
+ case TexelLoadTarget.Texture2DLodZeroOffset:
+ case TexelLoadTarget.Texture2DLodLevel:
+ case TexelLoadTarget.Texture2DLodLevelOffset:
+ return TextureTarget.Texture2D;
+
+ case TexelLoadTarget.Texture2DLodZeroMultisample:
+ return TextureTarget.Texture2D | TextureTarget.Multisample;
+
+ case TexelLoadTarget.Texture3DLodZero:
+ return TextureTarget.Texture3D;
+
+ case TexelLoadTarget.Texture2DArrayLodZero:
+ return TextureTarget.Texture2D | TextureTarget.Array;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureFlags GetTextureFlags(Decoders.TextureTarget type)
+ {
+ switch (type)
+ {
+ case Decoders.TextureTarget.Texture1DLodZero:
+ case Decoders.TextureTarget.Texture2DLodZero:
+ case Decoders.TextureTarget.Texture2DLodLevel:
+ case Decoders.TextureTarget.Texture2DLodLevelDepthCompare:
+ case Decoders.TextureTarget.Texture2DLodZeroDepthCompare:
+ case Decoders.TextureTarget.Texture2DArrayLodZero:
+ case Decoders.TextureTarget.Texture2DArrayLodZeroDepthCompare:
+ case Decoders.TextureTarget.Texture3DLodZero:
+ case Decoders.TextureTarget.TextureCubeLodLevel:
+ return TextureFlags.LodLevel;
+
+ case Decoders.TextureTarget.Texture2D:
+ case Decoders.TextureTarget.Texture2DDepthCompare:
+ case Decoders.TextureTarget.Texture2DArray:
+ case Decoders.TextureTarget.Texture3D:
+ case Decoders.TextureTarget.TextureCube:
+ return TextureFlags.None;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+
+ private static TextureFlags GetTextureFlags(TexelLoadTarget type)
+ {
+ switch (type)
+ {
+ case TexelLoadTarget.Texture1DLodZero:
+ case TexelLoadTarget.Texture1DLodLevel:
+ case TexelLoadTarget.Texture2DLodZero:
+ case TexelLoadTarget.Texture2DLodLevel:
+ case TexelLoadTarget.Texture2DLodZeroMultisample:
+ case TexelLoadTarget.Texture3DLodZero:
+ case TexelLoadTarget.Texture2DArrayLodZero:
+ return TextureFlags.LodLevel;
+
+ case TexelLoadTarget.Texture2DLodZeroOffset:
+ case TexelLoadTarget.Texture2DLodLevelOffset:
+ return TextureFlags.LodLevel | TextureFlags.Offset;
+ }
+
+ throw new ArgumentException($"Invalid texture type \"{type}\".");
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs
new file mode 100644
index 00000000..b19e9fa1
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static partial class InstEmit
+ {
+ public static void Vmad(EmitterContext context)
+ {
+ OpCodeVideo op = (OpCodeVideo)context.CurrOp;
+
+ context.Copy(GetDest(context), GetSrcC(context));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs
new file mode 100644
index 00000000..91c740b6
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs
@@ -0,0 +1,6 @@
+using Ryujinx.Graphics.Shader.Translation;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ delegate void InstEmitter(EmitterContext context);
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs
new file mode 100644
index 00000000..67e24957
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs
@@ -0,0 +1,149 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.Translation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Instructions
+{
+ static class Lop3Expression
+ {
+ public static Operand GetFromTruthTable(
+ EmitterContext context,
+ Operand srcA,
+ Operand srcB,
+ Operand srcC,
+ int imm)
+ {
+ Operand expr = null;
+
+ // Handle some simple cases, or cases where
+ // the KMap would yield poor results (like XORs).
+ if (imm == 0x96 || imm == 0x69)
+ {
+ // XOR (0x96) and XNOR (0x69).
+ if (imm == 0x69)
+ {
+ srcA = context.BitwiseNot(srcA);
+ }
+
+ expr = context.BitwiseExclusiveOr(srcA, srcB);
+ expr = context.BitwiseExclusiveOr(expr, srcC);
+
+ return expr;
+ }
+ else if (imm == 0)
+ {
+ // Always false.
+ return Const(IrConsts.False);
+ }
+ else if (imm == 0xff)
+ {
+ // Always true.
+ return Const(IrConsts.True);
+ }
+
+ int map;
+
+ // Encode into gray code.
+ map = ((imm >> 0) & 1) << 0;
+ map |= ((imm >> 1) & 1) << 4;
+ map |= ((imm >> 2) & 1) << 1;
+ map |= ((imm >> 3) & 1) << 5;
+ map |= ((imm >> 4) & 1) << 3;
+ map |= ((imm >> 5) & 1) << 7;
+ map |= ((imm >> 6) & 1) << 2;
+ map |= ((imm >> 7) & 1) << 6;
+
+ // Solve KMap, get sum of products.
+ int visited = 0;
+
+ for (int index = 0; index < 8 && visited != 0xff; index++)
+ {
+ if ((map & (1 << index)) == 0)
+ {
+ continue;
+ }
+
+ int mask = 0;
+
+ for (int mSize = 4; mSize != 0; mSize >>= 1)
+ {
+ mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4);
+
+ if ((map & mask) == mask)
+ {
+ break;
+ }
+ }
+
+ // The mask should wrap, if we are on the high row, shift to low etc.
+ int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4;
+
+ if ((map & mask2) == mask2)
+ {
+ mask |= mask2;
+ }
+
+ if ((mask & visited) == mask)
+ {
+ continue;
+ }
+
+ bool notA = (mask & 0x33) != 0;
+ bool notB = (mask & 0x99) != 0;
+ bool notC = (mask & 0x0f) != 0;
+
+ bool aChanges = (mask & 0xcc) != 0 && notA;
+ bool bChanges = (mask & 0x66) != 0 && notB;
+ bool cChanges = (mask & 0xf0) != 0 && notC;
+
+ Operand localExpr = null;
+
+ void And(Operand source)
+ {
+ if (localExpr != null)
+ {
+ localExpr = context.BitwiseAnd(localExpr, source);
+ }
+ else
+ {
+ localExpr = source;
+ }
+ }
+
+ if (!aChanges)
+ {
+ And(context.BitwiseNot(srcA, notA));
+ }
+
+ if (!bChanges)
+ {
+ And(context.BitwiseNot(srcB, notB));
+ }
+
+ if (!cChanges)
+ {
+ And(context.BitwiseNot(srcC, notC));
+ }
+
+ if (expr != null)
+ {
+ expr = context.BitwiseOr(expr, localExpr);
+ }
+ else
+ {
+ expr = localExpr;
+ }
+
+ visited |= mask;
+ }
+
+ return expr;
+ }
+
+ private static int RotateLeft4(int value, int shift)
+ {
+ return ((value << shift) | (value >> (4 - shift))) & 0xf;
+ }
+ }
+} \ No newline at end of file