diff options
| author | gdk <gab.dark.100@gmail.com> | 2019-10-13 03:02:07 -0300 |
|---|---|---|
| committer | Thog <thog@protonmail.com> | 2020-01-09 02:13:00 +0100 |
| commit | 1876b346fea647e8284a66bb6d62c38801035cff (patch) | |
| tree | 6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Shader/Instructions | |
| parent | f617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff) | |
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Shader/Instructions')
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs | 708 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs | 88 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs | 213 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs | 403 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs | 107 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs | 267 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs | 325 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs | 57 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs | 776 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs | 19 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs | 6 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs | 149 |
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 |
