Initial work

This commit is contained in:
gdk 2019-10-13 03:02:07 -03:00 committed by Thog
parent f617fb542a
commit 1876b346fe
518 changed files with 15170 additions and 12486 deletions

View file

@ -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);
}
}
}

View file

@ -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)));
}
}
}

View file

@ -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.
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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}\".");
}
}
}

View file

@ -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));
}
}
}

View file

@ -0,0 +1,6 @@
using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Shader.Instructions
{
delegate void InstEmitter(EmitterContext context);
}

View file

@ -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;
}
}
}