Ryujinx/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs
gdkchan cb171f6ebf Support shared color mask, implement more shader instructions
Support shared color masks (used by Nouveau and maybe the NVIDIA
driver).
Support draw buffers (also required by OpenGL).
Support viewport transform disable (disabled for now as it breaks some
games).
Fix instanced rendering draw being ignored for multi draw.
Fix IADD and IADD3 immediate shader encodings, that was not matching
some ops.
Implement FFMA32I shader instruction.
Implement IMAD shader instruction.
2020-01-09 02:13:00 +01:00

829 lines
No EOL
26 KiB
C#

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 Bfi(EmitterContext context)
{
OpCodeAlu op = (OpCodeAlu)context.CurrOp;
Operand srcA = GetSrcA(context);
Operand srcB = GetSrcB(context);
Operand srcC = GetSrcC(context);
Operand position = context.BitwiseAnd(srcB, Const(0xff));
Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8));
Operand res = context.BitfieldInsert(srcC, srcA, position, size);
context.Copy(GetDest(context), res);
}
public static void Csetp(EmitterContext context)
{
OpCodePset op = (OpCodePset)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 Flo(EmitterContext context)
{
OpCodeAlu op = (OpCodeAlu)context.CurrOp;
bool invert = op.RawOpCode.Extract(40);
bool countZeros = op.RawOpCode.Extract(41);
bool isSigned = op.RawOpCode.Extract(48);
Operand srcB = context.BitwiseNot(GetSrcB(context), invert);
Operand res = isSigned
? context.FindFirstSetS32(srcB)
: context.FindFirstSetU32(srcB);
if (countZeros)
{
res = context.BitwiseExclusiveOr(res, Const(31));
}
context.Copy(GetDest(context), res);
}
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 Imad(EmitterContext context)
{
OpCodeAlu op = (OpCodeAlu)context.CurrOp;
bool signedA = context.CurrOp.RawOpCode.Extract(48);
bool signedB = context.CurrOp.RawOpCode.Extract(53);
bool high = context.CurrOp.RawOpCode.Extract(54);
Operand srcA = GetSrcA(context);
Operand srcB = GetSrcB(context);
Operand srcC = GetSrcC(context);
Operand res;
if (high)
{
if (signedA && signedB)
{
res = context.MultiplyHighS32(srcA, srcB);
}
else
{
res = context.MultiplyHighU32(srcA, srcB);
if (signedA)
{
res = context.IAdd(res, context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31))));
}
else if (signedB)
{
res = context.IAdd(res, context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31))));
}
}
}
else
{
res = context.IMultiply(srcA, srcB);
}
res = context.IAdd(res, srcC);
context.Copy(GetDest(context), res);
}
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);
}
SetZnFlags(context, res, op.SetCondCode, op.Extended);
// TODO: 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 Popc(EmitterContext context)
{
OpCodeAlu op = (OpCodeAlu)context.CurrOp;
bool invert = op.RawOpCode.Extract(40);
Operand srcB = context.BitwiseNot(GetSrcB(context), invert);
Operand res = context.BitCount(srcB);
context.Copy(GetDest(context), res);
}
public static void Pset(EmitterContext context)
{
OpCodePset op = (OpCodePset)context.CurrOp;
bool boolFloat = op.RawOpCode.Extract(44);
Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA);
Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.InvertB);
Operand srcC = context.BitwiseNot(Register(op.Predicate39), op.InvertP);
Operand res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB);
res = GetPredLogicalOp(context, op.LogicalOp, res, srcC);
Operand dest = GetDest(context);
if (boolFloat)
{
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
}
else
{
context.Copy(dest, res);
}
}
public static void Psetp(EmitterContext context)
{
OpCodePset op = (OpCodePset)context.CurrOp;
Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA);
Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.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);
}
}
}