aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs
blob: a0e9fb38415abf547cdb15fd2b0e52f502a29ae0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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)
        {
            InstVmad op = context.GetOp<InstVmad>();

            bool aSigned = (op.ASelect & VectorSelect.S8B0) != 0;
            bool bSigned = (op.BSelect & VectorSelect.S8B0) != 0;

            Operand srcA = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcA), op.ASelect);
            Operand srcC = context.INegate(GetSrcReg(context, op.SrcC), op.AvgMode == AvgMode.NegB);
            Operand srcB;

            if (op.BVideo)
            {
                srcB = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcB), op.BSelect);
            }
            else
            {
                int imm = op.Imm16;

                if (bSigned)
                {
                    imm = (imm << 16) >> 16;
                }

                srcB = Const(imm);
            }

            Operand productLow = context.IMultiply(srcA, srcB);
            Operand productHigh;

            if (aSigned == bSigned)
            {
                productHigh = aSigned
                    ? context.MultiplyHighS32(srcA, srcB)
                    : context.MultiplyHighU32(srcA, srcB);
            }
            else
            {
                Operand temp = aSigned
                    ? context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31)))
                    : context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31)));

                productHigh = context.IAdd(temp, context.MultiplyHighU32(srcA, srcB));
            }

            if (op.AvgMode == AvgMode.NegA)
            {
                (productLow, productHigh) = InstEmitAluHelper.NegateLong(context, productLow, productHigh);
            }

            Operand resLow = InstEmitAluHelper.AddWithCarry(context, productLow, srcC, out Operand sumCarry);
            Operand resHigh = context.IAdd(productHigh, sumCarry);

            if (op.AvgMode == AvgMode.PlusOne)
            {
                resLow = InstEmitAluHelper.AddWithCarry(context, resLow, Const(1), out Operand poCarry);
                resHigh = context.IAdd(resHigh, poCarry);
            }

            bool resSigned = op.ASelect == VectorSelect.S32 ||
                             op.BSelect == VectorSelect.S32 ||
                             op.AvgMode == AvgMode.NegB ||
                             op.AvgMode == AvgMode.NegA;

            int shift = op.VideoScale switch
            {
                VideoScale.Shr7 => 7,
                VideoScale.Shr15 => 15,
                _ => 0,
            };

            if (shift != 0)
            {
                // Low = (Low >> Shift) | (High << (32 - Shift))
                // High >>= Shift
                resLow = context.ShiftRightU32(resLow, Const(shift));
                resLow = context.BitwiseOr(resLow, context.ShiftLeft(resHigh, Const(32 - shift)));
                resHigh = resSigned
                    ? context.ShiftRightS32(resHigh, Const(shift))
                    : context.ShiftRightU32(resHigh, Const(shift));
            }

            Operand res = resLow;

            if (op.Sat)
            {
                Operand sign = context.ShiftRightS32(resHigh, Const(31));

                if (resSigned)
                {
                    Operand overflow = context.ICompareNotEqual(resHigh, context.ShiftRightS32(resLow, Const(31)));
                    Operand clampValue = context.ConditionalSelect(sign, Const(int.MinValue), Const(int.MaxValue));
                    res = context.ConditionalSelect(overflow, clampValue, resLow);
                }
                else
                {
                    Operand overflow = context.ICompareNotEqual(resHigh, Const(0));
                    res = context.ConditionalSelect(overflow, context.BitwiseNot(sign), resLow);
                }
            }

            context.Copy(GetDest(op.Dest), res);

            // TODO: CC.
        }
    }
}