Initial work
This commit is contained in:
parent
f617fb542a
commit
1876b346fe
518 changed files with 15170 additions and 12486 deletions
708
Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs
Normal file
708
Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
88
Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs
Normal file
88
Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
213
Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs
Normal file
213
Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
403
Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs
Normal file
403
Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
107
Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs
Normal file
107
Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
267
Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs
Normal file
267
Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
325
Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
Normal file
325
Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
57
Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
Normal file
57
Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
776
Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
Normal file
776
Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
Normal 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}\".");
|
||||
}
|
||||
}
|
||||
}
|
19
Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs
Normal file
19
Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
6
Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs
Normal file
6
Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Instructions
|
||||
{
|
||||
delegate void InstEmitter(EmitterContext context);
|
||||
}
|
149
Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs
Normal file
149
Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue