diff --git a/.gitignore b/.gitignore index 82d9719b59..123f46184d 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store + +# VS Launch Settings +launchSettings.json diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs index b752d37d7a..11df153f4a 100644 --- a/ChocolArm64/AOpCodeTable.cs +++ b/ChocolArm64/AOpCodeTable.cs @@ -4,6 +4,7 @@ using ChocolArm64.Instruction; using ChocolArm64.Instruction32; using ChocolArm64.State; using System; +using System.Collections.Generic; namespace ChocolArm64 { @@ -44,7 +45,7 @@ namespace ChocolArm64 SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluRs)); SetA64("x0011010110xxxxx001010xxxxxxxxxx", AInstEmit.Asrv, typeof(AOpCodeAluRs)); SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B, typeof(AOpCodeBImmAl)); - SetA64("01010100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B_Cond, typeof(AOpCodeBImmCond)); + SetA64("01010100xxxxxxxxxxxxxxxxxxx0xxxx", AInstEmit.B_Cond, typeof(AOpCodeBImmCond)); SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm)); SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm)); SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bic, typeof(AOpCodeAluRs)); @@ -52,8 +53,8 @@ namespace ChocolArm64 SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs)); SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs)); SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bl, typeof(AOpCodeBImmAl)); - SetA64("11010110001xxxxx000000xxxxxxxxxx", AInstEmit.Blr, typeof(AOpCodeBReg)); - SetA64("11010110000xxxxx000000xxxxxxxxxx", AInstEmit.Br, typeof(AOpCodeBReg)); + SetA64("1101011000111111000000xxxxx00000", AInstEmit.Blr, typeof(AOpCodeBReg)); + SetA64("1101011000011111000000xxxxx00000", AInstEmit.Br, typeof(AOpCodeBReg)); SetA64("11010100001xxxxxxxxxxxxxxxx00000", AInstEmit.Brk, typeof(AOpCodeException)); SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbnz, typeof(AOpCodeBImmCmp)); SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbz, typeof(AOpCodeBImmCmp)); @@ -126,7 +127,7 @@ namespace ChocolArm64 SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemLit)); SetA64("x101101011000000000000xxxxxxxxxx", AInstEmit.Rbit, typeof(AOpCodeAlu)); - SetA64("11010110010xxxxx000000xxxxxxxxxx", AInstEmit.Ret, typeof(AOpCodeBReg)); + SetA64("1101011001011111000000xxxxx00000", AInstEmit.Ret, typeof(AOpCodeBReg)); SetA64("x101101011000000000001xxxxxxxxxx", AInstEmit.Rev16, typeof(AOpCodeAlu)); SetA64("x101101011000000000010xxxxxxxxxx", AInstEmit.Rev32, typeof(AOpCodeAlu)); SetA64("1101101011000000000011xxxxxxxxxx", AInstEmit.Rev64, typeof(AOpCodeAlu)); @@ -266,11 +267,13 @@ namespace ChocolArm64 SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Fdiv_V, typeof(AOpCodeSimdReg)); SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fmadd_S, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx010010xxxxxxxxxx", AInstEmit.Fmax_S, typeof(AOpCodeSimdReg)); - SetA64("0x0011100x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmax_V, typeof(AOpCodeSimdReg)); + SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", AInstEmit.Fmax_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg)); + SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", AInstEmit.Fmaxnm_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg)); - SetA64("0x0011101x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmin_V, typeof(AOpCodeSimdReg)); + SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", AInstEmit.Fmin_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg)); + SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", AInstEmit.Fminnm_V, typeof(AOpCodeSimdReg)); SetA64("010111111<0011100<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg)); SetA64("0x0011111<001110<<100000011110xxxxxxxxxx", AInstEmit.Sqabs_V, typeof(AOpCodeSimd)); + SetA64("01011110xx1xxxxx000011xxxxxxxxxx", AInstEmit.Sqadd_S, typeof(AOpCodeSimdReg)); + SetA64("0>001110<<1xxxxx000011xxxxxxxxxx", AInstEmit.Sqadd_V, typeof(AOpCodeSimdReg)); + SetA64("01111110xx100000011110xxxxxxxxxx", AInstEmit.Sqneg_S, typeof(AOpCodeSimd)); + SetA64("0>101110<<100000011110xxxxxxxxxx", AInstEmit.Sqneg_V, typeof(AOpCodeSimd)); SetA64("0x00111100>>>xxx100111xxxxxxxxxx", AInstEmit.Sqrshrn_V, typeof(AOpCodeSimdShImm)); SetA64("0111111100>>>xxx100001xxxxxxxxxx", AInstEmit.Sqshrun_S, typeof(AOpCodeSimdShImm)); SetA64("0x10111100>>>xxx100001xxxxxxxxxx", AInstEmit.Sqshrun_V, typeof(AOpCodeSimdShImm)); + SetA64("01011110xx1xxxxx001011xxxxxxxxxx", AInstEmit.Sqsub_S, typeof(AOpCodeSimdReg)); + SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", AInstEmit.Sqsub_V, typeof(AOpCodeSimdReg)); SetA64("01011110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_S, typeof(AOpCodeSimd)); SetA64("0x001110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_V, typeof(AOpCodeSimd)); SetA64("01111110<<100001001010xxxxxxxxxx", AInstEmit.Sqxtun_S, typeof(AOpCodeSimd)); @@ -389,6 +400,7 @@ namespace ChocolArm64 SetA64("0100111101xxxxxx000001xxxxxxxxxx", AInstEmit.Sshr_V, typeof(AOpCodeSimdShImm)); SetA64("0x00111100>>>xxx000101xxxxxxxxxx", AInstEmit.Ssra_V, typeof(AOpCodeSimdShImm)); SetA64("0100111101xxxxxx000101xxxxxxxxxx", AInstEmit.Ssra_V, typeof(AOpCodeSimdShImm)); + SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", AInstEmit.Ssubw_V, typeof(AOpCodeSimdReg)); SetA64("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); @@ -402,6 +414,8 @@ namespace ChocolArm64 SetA64("01111110111xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg)); SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", AInstEmit.Subhn_V, typeof(AOpCodeSimdReg)); + SetA64("01011110xx100000001110xxxxxxxxxx", AInstEmit.Suqadd_S, typeof(AOpCodeSimd)); + SetA64("0>001110<<100000001110xxxxxxxxxx", AInstEmit.Suqadd_V, typeof(AOpCodeSimd)); SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", AInstEmit.Tbl_V, typeof(AOpCodeSimdTbl)); SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", AInstEmit.Trn1_V, typeof(AOpCodeSimdReg)); SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", AInstEmit.Trn2_V, typeof(AOpCodeSimdReg)); @@ -423,6 +437,10 @@ namespace ChocolArm64 SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", AInstEmit.Uminp_V, typeof(AOpCodeSimdReg)); SetA64("0x001110000xxxxx001111xxxxxxxxxx", AInstEmit.Umov_S, typeof(AOpCodeSimdIns)); SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Umull_V, typeof(AOpCodeSimdReg)); + SetA64("01111110xx1xxxxx000011xxxxxxxxxx", AInstEmit.Uqadd_S, typeof(AOpCodeSimdReg)); + SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", AInstEmit.Uqadd_V, typeof(AOpCodeSimdReg)); + SetA64("01111110xx1xxxxx001011xxxxxxxxxx", AInstEmit.Uqsub_S, typeof(AOpCodeSimdReg)); + SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", AInstEmit.Uqsub_V, typeof(AOpCodeSimdReg)); SetA64("01111110<<100001010010xxxxxxxxxx", AInstEmit.Uqxtn_S, typeof(AOpCodeSimd)); SetA64("0x101110<<100001010010xxxxxxxxxx", AInstEmit.Uqxtn_V, typeof(AOpCodeSimd)); SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg)); @@ -430,26 +448,54 @@ namespace ChocolArm64 SetA64("0111111101xxxxxx000001xxxxxxxxxx", AInstEmit.Ushr_S, typeof(AOpCodeSimdShImm)); SetA64("0x10111100>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm)); SetA64("0110111101xxxxxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm)); + SetA64("01111110xx100000001110xxxxxxxxxx", AInstEmit.Usqadd_S, typeof(AOpCodeSimd)); + SetA64("0>101110<<100000001110xxxxxxxxxx", AInstEmit.Usqadd_V, typeof(AOpCodeSimd)); SetA64("0x10111100>>>xxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm)); SetA64("0110111101xxxxxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm)); + SetA64("0x101110<<1xxxxx001100xxxxxxxxxx", AInstEmit.Usubw_V, typeof(AOpCodeSimdReg)); SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg)); SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", AInstEmit.Uzp2_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<100001001010xxxxxxxxxx", AInstEmit.Xtn_V, typeof(AOpCodeSimd)); SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", AInstEmit.Zip1_V, typeof(AOpCodeSimdReg)); SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", AInstEmit.Zip2_V, typeof(AOpCodeSimdReg)); #endregion + +#region "Generate InstA64FastLookup Table (AArch64)" + var Tmp = new List[FastLookupSize]; + for (int i = 0; i < FastLookupSize; i++) + { + Tmp[i] = new List(); + } + + foreach (var Inst in AllInstA64) + { + int Mask = ToFastLookupIndex(Inst.Mask); + int Value = ToFastLookupIndex(Inst.Value); + + for (int i = 0; i < FastLookupSize; i++) + { + if ((i & Mask) == Value) + { + Tmp[i].Add(Inst); + } + } + } + + for (int i = 0; i < FastLookupSize; i++) + { + InstA64FastLookup[i] = Tmp[i].ToArray(); + } +#endregion } - private class TreeNode + private class InstInfo { public int Mask; public int Value; - public TreeNode Next; - public AInst Inst; - public TreeNode(int Mask, int Value, AInst Inst) + public InstInfo(int Mask, int Value, AInst Inst) { this.Mask = Mask; this.Value = Value; @@ -457,8 +503,11 @@ namespace ChocolArm64 } } - private static TreeNode InstHeadA32; - private static TreeNode InstHeadA64; + private static List AllInstA32 = new List(); + private static List AllInstA64 = new List(); + + private static int FastLookupSize = 0x1000; + private static InstInfo[][] InstA64FastLookup = new InstInfo[FastLookupSize][]; private static void SetA32(string Encoding, AInstInterpreter Interpreter, Type Type) { @@ -519,7 +568,7 @@ namespace ChocolArm64 if (XBits == 0) { - InsertTop(XMask, Value, Inst, Mode); + InsertInst(XMask, Value, Inst, Mode); return; } @@ -535,55 +584,53 @@ namespace ChocolArm64 if (Mask != Blacklisted) { - InsertTop(XMask, Value | Mask, Inst, Mode); + InsertInst(XMask, Value | Mask, Inst, Mode); } } } - private static void InsertTop( + private static void InsertInst( int XMask, int Value, AInst Inst, AExecutionMode Mode) { - TreeNode Node = new TreeNode(XMask, Value, Inst); + InstInfo Info = new InstInfo(XMask, Value, Inst); if (Mode == AExecutionMode.AArch64) { - Node.Next = InstHeadA64; - - InstHeadA64 = Node; + AllInstA64.Add(Info); } else { - Node.Next = InstHeadA32; - - InstHeadA32 = Node; + AllInstA32.Add(Info); } } public static AInst GetInstA32(int OpCode) { - return GetInst(InstHeadA32, OpCode); + return GetInstFromList(AllInstA32, OpCode); } public static AInst GetInstA64(int OpCode) { - return GetInst(InstHeadA64, OpCode); + return GetInstFromList(InstA64FastLookup[ToFastLookupIndex(OpCode)], OpCode); } - private static AInst GetInst(TreeNode Head, int OpCode) + private static int ToFastLookupIndex(int Value) { - TreeNode Node = Head; + return ((Value >> 10) & 0x00F) | ((Value >> 18) & 0xFF0); + } - do + private static AInst GetInstFromList(IEnumerable InstList, int OpCode) + { + foreach (var Node in InstList) { if ((OpCode & Node.Mask) == Node.Value) { return Node.Inst; } } - while ((Node = Node.Next) != null); return AInst.Undefined; } diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs index 36bb1cbf16..559811d93f 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs @@ -335,98 +335,66 @@ namespace ChocolArm64.Instruction public static void Fmax_S(AILEmitterCtx Context) { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - EmitScalarBinaryOpF(Context, () => { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MaxF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Max)); - } - else - { - throw new InvalidOperationException(); - } + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Max)); }); } public static void Fmax_V(AILEmitterCtx Context) { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - EmitVectorBinaryOpF(Context, () => { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MaxF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Max)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmin_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitScalarBinaryOpF(Context, () => - { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MinF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Min)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmin_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorBinaryOpF(Context, () => - { - if (SizeF == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MinF)); - } - else if (SizeF == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Min)); - } - else - { - throw new InvalidOperationException(); - } + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Max)); }); } public static void Fmaxnm_S(AILEmitterCtx Context) { - Fmax_S(Context); + EmitScalarBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MaxNum)); + }); + } + + public static void Fmaxnm_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MaxNum)); + }); + } + + public static void Fmin_S(AILEmitterCtx Context) + { + EmitScalarBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Min)); + }); + } + + public static void Fmin_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Min)); + }); } public static void Fminnm_S(AILEmitterCtx Context) { - Fmin_S(Context); + EmitScalarBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MinNum)); + }); + } + + public static void Fminnm_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpF(Context, () => + { + EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MinNum)); + }); } public static void Fmla_Se(AILEmitterCtx Context) @@ -1052,6 +1020,46 @@ namespace ChocolArm64.Instruction EmitVectorWidenRnRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Mul)); } + public static void Sqabs_S(AILEmitterCtx Context) + { + EmitScalarSaturatingUnaryOpSx(Context, () => EmitAbs(Context)); + } + + public static void Sqabs_V(AILEmitterCtx Context) + { + EmitVectorSaturatingUnaryOpSx(Context, () => EmitAbs(Context)); + } + + public static void Sqadd_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpSx(Context, SaturatingFlags.Add); + } + + public static void Sqadd_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpSx(Context, SaturatingFlags.Add); + } + + public static void Sqneg_S(AILEmitterCtx Context) + { + EmitScalarSaturatingUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); + } + + public static void Sqneg_V(AILEmitterCtx Context) + { + EmitVectorSaturatingUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); + } + + public static void Sqsub_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpSx(Context, SaturatingFlags.Sub); + } + + public static void Sqsub_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpSx(Context, SaturatingFlags.Sub); + } + public static void Sqxtn_S(AILEmitterCtx Context) { EmitScalarSaturatingNarrowOpSxSx(Context, () => { }); @@ -1072,6 +1080,11 @@ namespace ChocolArm64.Instruction EmitVectorSaturatingNarrowOpSxZx(Context, () => { }); } + public static void Ssubw_V(AILEmitterCtx Context) + { + EmitVectorWidenRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Sub)); + } + public static void Sub_S(AILEmitterCtx Context) { EmitScalarBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); @@ -1094,6 +1107,16 @@ namespace ChocolArm64.Instruction EmitHighNarrow(Context, () => Context.Emit(OpCodes.Sub), Round: false); } + public static void Suqadd_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpSx(Context, SaturatingFlags.Accumulate); + } + + public static void Suqadd_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpSx(Context, SaturatingFlags.Accumulate); + } + public static void Uaba_V(AILEmitterCtx Context) { EmitVectorTernaryOpZx(Context, () => @@ -1216,6 +1239,26 @@ namespace ChocolArm64.Instruction EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Mul)); } + public static void Uqadd_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpZx(Context, SaturatingFlags.Add); + } + + public static void Uqadd_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpZx(Context, SaturatingFlags.Add); + } + + public static void Uqsub_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpZx(Context, SaturatingFlags.Sub); + } + + public static void Uqsub_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpZx(Context, SaturatingFlags.Sub); + } + public static void Uqxtn_S(AILEmitterCtx Context) { EmitScalarSaturatingNarrowOpZxZx(Context, () => { }); @@ -1225,5 +1268,20 @@ namespace ChocolArm64.Instruction { EmitVectorSaturatingNarrowOpZxZx(Context, () => { }); } + + public static void Usqadd_S(AILEmitterCtx Context) + { + EmitScalarSaturatingBinaryOpZx(Context, SaturatingFlags.Accumulate); + } + + public static void Usqadd_V(AILEmitterCtx Context) + { + EmitVectorSaturatingBinaryOpZx(Context, SaturatingFlags.Accumulate); + } + + public static void Usubw_V(AILEmitterCtx Context) + { + EmitVectorWidenRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); + } } } diff --git a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs index 773d989447..c2d47747ed 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs @@ -364,7 +364,7 @@ namespace ChocolArm64.Instruction AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; int Bytes = Op.GetBitsCount() >> 3; - int Elems = (!Scalar ? Bytes >> Op.Size : 1); + int Elems = !Scalar ? Bytes >> Op.Size : 1; ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size)); @@ -408,7 +408,7 @@ namespace ChocolArm64.Instruction AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; int Bytes = Op.GetBitsCount() >> 3; - int Elems = (!Scalar ? Bytes >> Op.Size : 1); + int Elems = !Scalar ? Bytes >> Op.Size : 1; ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size)); @@ -522,4 +522,4 @@ namespace ChocolArm64.Instruction Context.MarkLabel(LblEnd); } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs index 7716e2987a..161c44ea26 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs @@ -336,17 +336,21 @@ namespace ChocolArm64.Instruction { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - if (Opers.HasFlag(OperFlags.Rd)) + bool Rd = (Opers & OperFlags.Rd) != 0; + bool Rn = (Opers & OperFlags.Rn) != 0; + bool Rm = (Opers & OperFlags.Rm) != 0; + + if (Rd) { EmitVectorExtract(Context, Op.Rd, 0, Op.Size, Signed); } - if (Opers.HasFlag(OperFlags.Rn)) + if (Rn) { EmitVectorExtract(Context, Op.Rn, 0, Op.Size, Signed); } - if (Opers.HasFlag(OperFlags.Rm)) + if (Rm) { EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, 0, Op.Size, Signed); } @@ -377,17 +381,21 @@ namespace ChocolArm64.Instruction int SizeF = Op.Size & 1; - if (Opers.HasFlag(OperFlags.Ra)) + bool Ra = (Opers & OperFlags.Ra) != 0; + bool Rn = (Opers & OperFlags.Rn) != 0; + bool Rm = (Opers & OperFlags.Rm) != 0; + + if (Ra) { EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Ra, 0, SizeF); } - if (Opers.HasFlag(OperFlags.Rn)) + if (Rn) { EmitVectorExtractF(Context, Op.Rn, 0, SizeF); } - if (Opers.HasFlag(OperFlags.Rm)) + if (Rm) { EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Rm, 0, SizeF); } @@ -419,20 +427,25 @@ namespace ChocolArm64.Instruction int SizeF = Op.Size & 1; int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> SizeF + 2; - for (int Index = 0; Index < (Bytes >> SizeF + 2); Index++) + bool Rd = (Opers & OperFlags.Rd) != 0; + bool Rn = (Opers & OperFlags.Rn) != 0; + bool Rm = (Opers & OperFlags.Rm) != 0; + + for (int Index = 0; Index < Elems; Index++) { - if (Opers.HasFlag(OperFlags.Rd)) + if (Rd) { EmitVectorExtractF(Context, Op.Rd, Index, SizeF); } - if (Opers.HasFlag(OperFlags.Rn)) + if (Rn) { EmitVectorExtractF(Context, Op.Rn, Index, SizeF); } - if (Opers.HasFlag(OperFlags.Rm)) + if (Rm) { EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Rm, Index, SizeF); } @@ -469,8 +482,9 @@ namespace ChocolArm64.Instruction int SizeF = Op.Size & 1; int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> SizeF + 2; - for (int Index = 0; Index < (Bytes >> SizeF + 2); Index++) + for (int Index = 0; Index < Elems; Index++) { if (Ternary) { @@ -531,19 +545,23 @@ namespace ChocolArm64.Instruction int Bytes = Op.GetBitsCount() >> 3; int Elems = Bytes >> Op.Size; + bool Rd = (Opers & OperFlags.Rd) != 0; + bool Rn = (Opers & OperFlags.Rn) != 0; + bool Rm = (Opers & OperFlags.Rm) != 0; + for (int Index = 0; Index < Elems; Index++) { - if (Opers.HasFlag(OperFlags.Rd)) + if (Rd) { EmitVectorExtract(Context, Op.Rd, Index, Op.Size, Signed); } - if (Opers.HasFlag(OperFlags.Rn)) + if (Rn) { EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); } - if (Opers.HasFlag(OperFlags.Rm)) + if (Rm) { EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, Index, Op.Size, Signed); } @@ -662,9 +680,6 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - int Elems = 8 >> Op.Size; int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; @@ -707,9 +722,6 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - int Elems = 8 >> Op.Size; int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; @@ -747,21 +759,25 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - int Bytes = Op.GetBitsCount() >> 3; + int Words = Op.GetBitsCount() >> 4; + int Pairs = Words >> Op.Size; - int Elems = Bytes >> Op.Size; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) + for (int Index = 0; Index < Pairs; Index++) { - int Elem = (Index & (Half - 1)) << 1; + int Idx = Index << 1; - EmitVectorExtract(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 0, Op.Size, Signed); - EmitVectorExtract(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 1, Op.Size, Signed); + EmitVectorExtract(Context, Op.Rn, Idx, Op.Size, Signed); + EmitVectorExtract(Context, Op.Rn, Idx + 1, Op.Size, Signed); Emit(); - EmitVectorInsertTmp(Context, Index, Op.Size); + EmitVectorExtract(Context, Op.Rm, Idx, Op.Size, Signed); + EmitVectorExtract(Context, Op.Rm, Idx + 1, Op.Size, Signed); + + Emit(); + + EmitVectorInsertTmp(Context, Pairs + Index, Op.Size); + EmitVectorInsertTmp(Context, Index, Op.Size); } Context.EmitLdvectmp(); @@ -773,56 +789,241 @@ namespace ChocolArm64.Instruction } } + [Flags] + public enum SaturatingFlags + { + Scalar = 1 << 0, + Signed = 1 << 1, + + Add = 1 << 2, + Sub = 1 << 3, + + Accumulate = 1 << 4, + + ScalarSx = Scalar | Signed, + ScalarZx = Scalar, + + VectorSx = Signed, + VectorZx = 0, + } + + public static void EmitScalarSaturatingUnaryOpSx(AILEmitterCtx Context, Action Emit) + { + EmitSaturatingUnaryOpSx(Context, Emit, SaturatingFlags.ScalarSx); + } + + public static void EmitVectorSaturatingUnaryOpSx(AILEmitterCtx Context, Action Emit) + { + EmitSaturatingUnaryOpSx(Context, Emit, SaturatingFlags.VectorSx); + } + + public static void EmitSaturatingUnaryOpSx(AILEmitterCtx Context, Action Emit, SaturatingFlags Flags) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + bool Scalar = (Flags & SaturatingFlags.Scalar) != 0; + + int Bytes = Op.GetBitsCount() >> 3; + int Elems = !Scalar ? Bytes >> Op.Size : 1; + + if (Scalar) + { + EmitVectorZeroLowerTmp(Context); + } + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractSx(Context, Op.Rn, Index, Op.Size); + + Emit(); + + EmitUnarySignedSatQAbsOrNeg(Context, Op.Size); + + EmitVectorInsertTmp(Context, Index, Op.Size); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if ((Op.RegisterSize == ARegisterSize.SIMD64) || Scalar) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + public static void EmitScalarSaturatingBinaryOpSx(AILEmitterCtx Context, SaturatingFlags Flags) + { + EmitSaturatingBinaryOp(Context, SaturatingFlags.ScalarSx | Flags); + } + + public static void EmitScalarSaturatingBinaryOpZx(AILEmitterCtx Context, SaturatingFlags Flags) + { + EmitSaturatingBinaryOp(Context, SaturatingFlags.ScalarZx | Flags); + } + + public static void EmitVectorSaturatingBinaryOpSx(AILEmitterCtx Context, SaturatingFlags Flags) + { + EmitSaturatingBinaryOp(Context, SaturatingFlags.VectorSx | Flags); + } + + public static void EmitVectorSaturatingBinaryOpZx(AILEmitterCtx Context, SaturatingFlags Flags) + { + EmitSaturatingBinaryOp(Context, SaturatingFlags.VectorZx | Flags); + } + + public static void EmitSaturatingBinaryOp(AILEmitterCtx Context, SaturatingFlags Flags) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + bool Scalar = (Flags & SaturatingFlags.Scalar) != 0; + bool Signed = (Flags & SaturatingFlags.Signed) != 0; + + bool Add = (Flags & SaturatingFlags.Add) != 0; + bool Sub = (Flags & SaturatingFlags.Sub) != 0; + + bool Accumulate = (Flags & SaturatingFlags.Accumulate) != 0; + + int Bytes = Op.GetBitsCount() >> 3; + int Elems = !Scalar ? Bytes >> Op.Size : 1; + + if (Scalar) + { + EmitVectorZeroLowerTmp(Context); + } + + if (Add || Sub) + { + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); + EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, Index, Op.Size, Signed); + + if (Op.Size <= 2) + { + Context.Emit(Add ? OpCodes.Add : OpCodes.Sub); + + EmitSatQ(Context, Op.Size, true, Signed); + } + else /* if (Op.Size == 3) */ + { + if (Add) + { + EmitBinarySatQAdd(Context, Signed); + } + else /* if (Sub) */ + { + EmitBinarySatQSub(Context, Signed); + } + } + + EmitVectorInsertTmp(Context, Index, Op.Size); + } + } + else if (Accumulate) + { + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtract(Context, Op.Rn, Index, Op.Size, !Signed); + EmitVectorExtract(Context, Op.Rd, Index, Op.Size, Signed); + + if (Op.Size <= 2) + { + Context.Emit(OpCodes.Add); + + EmitSatQ(Context, Op.Size, true, Signed); + } + else /* if (Op.Size == 3) */ + { + EmitBinarySatQAccumulate(Context, Signed); + } + + EmitVectorInsertTmp(Context, Index, Op.Size); + } + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if ((Op.RegisterSize == ARegisterSize.SIMD64) || Scalar) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + [Flags] + public enum SaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxSx = Scalar | SignedDst, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxSx = SignedDst, + VectorZxZx = 0 + } + public static void EmitScalarSaturatingNarrowOpSxSx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, true, true, true); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarSxSx); } public static void EmitScalarSaturatingNarrowOpSxZx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, true, false, true); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarSxZx); + } + + public static void EmitScalarSaturatingNarrowOpZxSx(AILEmitterCtx Context, Action Emit) + { + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarZxSx); } public static void EmitScalarSaturatingNarrowOpZxZx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, false, false, true); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarZxZx); } public static void EmitVectorSaturatingNarrowOpSxSx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, true, true, false); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorSxSx); } public static void EmitVectorSaturatingNarrowOpSxZx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, true, false, false); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorSxZx); + } + + public static void EmitVectorSaturatingNarrowOpZxSx(AILEmitterCtx Context, Action Emit) + { + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorZxSx); } public static void EmitVectorSaturatingNarrowOpZxZx(AILEmitterCtx Context, Action Emit) { - EmitSaturatingNarrowOp(Context, Emit, false, false, false); + EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorZxZx); } - public static void EmitSaturatingNarrowOp( - AILEmitterCtx Context, - Action Emit, - bool SignedSrc, - bool SignedDst, - bool Scalar) + public static void EmitSaturatingNarrowOp(AILEmitterCtx Context, Action Emit, SaturatingNarrowFlags Flags) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - int Elems = !Scalar ? 8 >> Op.Size : 1; + bool Scalar = (Flags & SaturatingNarrowFlags.Scalar) != 0; + bool SignedSrc = (Flags & SaturatingNarrowFlags.SignedSrc) != 0; + bool SignedDst = (Flags & SaturatingNarrowFlags.SignedDst) != 0; - int ESize = 8 << Op.Size; + int Elems = !Scalar ? 8 >> Op.Size : 1; int Part = !Scalar && (Op.RegisterSize == ARegisterSize.SIMD128) ? Elems : 0; - long TMaxValue = SignedDst ? (1 << (ESize - 1)) - 1 : (1L << ESize) - 1L; - long TMinValue = SignedDst ? -((1 << (ESize - 1))) : 0; - - Context.EmitLdc_I8(0L); - Context.EmitSttmp(); + if (Scalar) + { + EmitVectorZeroLowerTmp(Context); + } if (Part != 0) { @@ -832,47 +1033,11 @@ namespace ChocolArm64.Instruction for (int Index = 0; Index < Elems; Index++) { - AILLabel LblLe = new AILLabel(); - AILLabel LblGeEnd = new AILLabel(); - EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, SignedSrc); Emit(); - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I8(TMaxValue); - - Context.Emit(SignedSrc ? OpCodes.Ble_S : OpCodes.Ble_Un_S, LblLe); - - Context.Emit(OpCodes.Pop); - - Context.EmitLdc_I8(TMaxValue); - Context.EmitLdc_I8(0x8000000L); - Context.EmitSttmp(); - - Context.Emit(OpCodes.Br_S, LblGeEnd); - - Context.MarkLabel(LblLe); - - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I8(TMinValue); - - Context.Emit(SignedSrc ? OpCodes.Bge_S : OpCodes.Bge_Un_S, LblGeEnd); - - Context.Emit(OpCodes.Pop); - - Context.EmitLdc_I8(TMinValue); - Context.EmitLdc_I8(0x8000000L); - Context.EmitSttmp(); - - Context.MarkLabel(LblGeEnd); - - if (Scalar) - { - EmitVectorZeroLower(Context, Op.Rd); - } + EmitSatQ(Context, Op.Size, SignedSrc, SignedDst); EmitVectorInsertTmp(Context, Part + Index, Op.Size); } @@ -884,13 +1049,120 @@ namespace ChocolArm64.Instruction { EmitVectorZeroUpper(Context, Op.Rd); } + } + + // TSrc (16bit, 32bit, 64bit; signed, unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). + public static void EmitSatQ( + AILEmitterCtx Context, + int SizeDst, + bool SignedSrc, + bool SignedDst) + { + if (SizeDst > 2) + { + throw new ArgumentOutOfRangeException(nameof(SizeDst)); + } + + Context.EmitLdc_I4(SizeDst); + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + if (SignedSrc) + { + ASoftFallback.EmitCall(Context, SignedDst + ? nameof(ASoftFallback.SignedSrcSignedDstSatQ) + : nameof(ASoftFallback.SignedSrcUnsignedDstSatQ)); + } + else + { + ASoftFallback.EmitCall(Context, SignedDst + ? nameof(ASoftFallback.UnsignedSrcSignedDstSatQ) + : nameof(ASoftFallback.UnsignedSrcUnsignedDstSatQ)); + } + } + + // TSrc (8bit, 16bit, 32bit, 64bit) == TDst (8bit, 16bit, 32bit, 64bit); signed. + public static void EmitUnarySignedSatQAbsOrNeg(AILEmitterCtx Context, int Size) + { + int ESize = 8 << Size; + + long TMaxValue = (1L << (ESize - 1)) - 1L; + long TMinValue = -(1L << (ESize - 1)); + + AILLabel LblFalse = new AILLabel(); + + Context.Emit(OpCodes.Dup); + Context.Emit(OpCodes.Neg); + Context.EmitLdc_I8(TMinValue); + Context.Emit(OpCodes.Ceq); + Context.Emit(OpCodes.Brfalse_S, LblFalse); + + Context.Emit(OpCodes.Pop); + + EmitSetFpsrQCFlag(Context); + + Context.EmitLdc_I8(TMaxValue); + + Context.MarkLabel(LblFalse); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static void EmitBinarySatQAdd(AILEmitterCtx Context, bool Signed) + { + if (((AOpCodeSimdReg)Context.CurrOp).Size < 3) + { + throw new InvalidOperationException(); + } Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + ASoftFallback.EmitCall(Context, Signed + ? nameof(ASoftFallback.BinarySignedSatQAdd) + : nameof(ASoftFallback.BinaryUnsignedSatQAdd)); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static void EmitBinarySatQSub(AILEmitterCtx Context, bool Signed) + { + if (((AOpCodeSimdReg)Context.CurrOp).Size < 3) + { + throw new InvalidOperationException(); + } + + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + ASoftFallback.EmitCall(Context, Signed + ? nameof(ASoftFallback.BinarySignedSatQSub) + : nameof(ASoftFallback.BinaryUnsignedSatQSub)); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static void EmitBinarySatQAccumulate(AILEmitterCtx Context, bool Signed) + { + if (((AOpCodeSimd)Context.CurrOp).Size < 3) + { + throw new InvalidOperationException(); + } + + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + ASoftFallback.EmitCall(Context, Signed + ? nameof(ASoftFallback.BinarySignedSatQAcc) + : nameof(ASoftFallback.BinaryUnsignedSatQAcc)); + } + + public static void EmitSetFpsrQCFlag(AILEmitterCtx Context) + { + const int QCFlagBit = 27; + + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdarg(ATranslatedSub.StateArgIdx); Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpsr)); - Context.EmitLdtmp(); - Context.Emit(OpCodes.Conv_I4); + + Context.EmitLdc_I4(1 << QCFlagBit); + Context.Emit(OpCodes.Or); + Context.EmitCallPropSet(typeof(AThreadState), nameof(AThreadState.Fpsr)); } @@ -963,6 +1235,11 @@ namespace ChocolArm64.Instruction EmitVectorInsert(Context, Rd, 0, 3, 0); } + public static void EmitVectorZeroLowerTmp(AILEmitterCtx Context) + { + EmitVectorInsertTmp(Context, 0, 3, 0); + } + public static void EmitVectorZeroUpper(AILEmitterCtx Context, int Rd) { EmitVectorInsert(Context, Rd, 1, 3, 0); @@ -1008,6 +1285,20 @@ namespace ChocolArm64.Instruction Context.EmitStvec(Reg); } + public static void EmitVectorInsertTmp(AILEmitterCtx Context, int Index, int Size, long Value) + { + ThrowIfInvalid(Index, Size); + + Context.EmitLdc_I8(Value); + Context.EmitLdvectmp(); + Context.EmitLdc_I4(Index); + Context.EmitLdc_I4(Size); + + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertInt)); + + Context.EmitStvectmp(); + } + public static void EmitVectorInsertF(AILEmitterCtx Context, int Reg, int Index, int Size) { ThrowIfInvalidF(Index, Size); diff --git a/ChocolArm64/Instruction/AInstEmitSimdMove.cs b/ChocolArm64/Instruction/AInstEmitSimdMove.cs index 592cab733e..3bf1e4635b 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdMove.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdMove.cs @@ -295,13 +295,22 @@ namespace ChocolArm64.Instruction int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } + for (int Index = 0; Index < Elems; Index++) { EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); + EmitVectorInsertTmp(Context, Part + Index, Op.Size); } + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + if (Part == 0) { EmitVectorZeroUpper(Context, Op.Rd); @@ -342,7 +351,7 @@ namespace ChocolArm64.Instruction EmitVectorExtractZx(Context, Op.Rm, Idx + Part, Op.Size); EmitVectorInsertTmp(Context, Idx + 1, Op.Size); - EmitVectorInsertTmp(Context, Idx , Op.Size); + EmitVectorInsertTmp(Context, Idx, Op.Size); } Context.EmitLdvectmp(); @@ -398,7 +407,7 @@ namespace ChocolArm64.Instruction EmitVectorExtractZx(Context, Op.Rm, Base + Index, Op.Size); EmitVectorInsertTmp(Context, Idx + 1, Op.Size); - EmitVectorInsertTmp(Context, Idx , Op.Size); + EmitVectorInsertTmp(Context, Idx, Op.Size); } Context.EmitLdvectmp(); diff --git a/ChocolArm64/Instruction/ASoftFallback.cs b/ChocolArm64/Instruction/ASoftFallback.cs index ae3994b029..a4d12dd613 100644 --- a/ChocolArm64/Instruction/ASoftFallback.cs +++ b/ChocolArm64/Instruction/ASoftFallback.cs @@ -1,3 +1,4 @@ +using ChocolArm64.State; using ChocolArm64.Translation; using System; @@ -10,6 +11,273 @@ namespace ChocolArm64.Instruction Context.EmitCall(typeof(ASoftFallback), MthdName); } + public static long BinarySignedSatQAdd(long op1, long op2, AThreadState State) + { + long Add = op1 + op2; + + if ((~(op1 ^ op2) & (op1 ^ Add)) < 0L) + { + SetFpsrQCFlag(State); + + if (op1 < 0L) + { + return long.MinValue; + } + else + { + return long.MaxValue; + } + } + else + { + return Add; + } + } + + public static ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2, AThreadState State) + { + ulong Add = op1 + op2; + + if ((Add < op1) && (Add < op2)) + { + SetFpsrQCFlag(State); + + return ulong.MaxValue; + } + else + { + return Add; + } + } + + public static long BinarySignedSatQSub(long op1, long op2, AThreadState State) + { + long Sub = op1 - op2; + + if (((op1 ^ op2) & (op1 ^ Sub)) < 0L) + { + SetFpsrQCFlag(State); + + if (op1 < 0L) + { + return long.MinValue; + } + else + { + return long.MaxValue; + } + } + else + { + return Sub; + } + } + + public static ulong BinaryUnsignedSatQSub(ulong op1, ulong op2, AThreadState State) + { + ulong Sub = op1 - op2; + + if (op1 < op2) + { + SetFpsrQCFlag(State); + + return ulong.MinValue; + } + else + { + return Sub; + } + } + + public static long BinarySignedSatQAcc(ulong op1, long op2, AThreadState State) + { + if (op1 <= (ulong)long.MaxValue) + { + // op1 from ulong.MinValue to (ulong)long.MaxValue + // op2 from long.MinValue to long.MaxValue + + long Add = (long)op1 + op2; + + if ((~op2 & Add) < 0L) + { + SetFpsrQCFlag(State); + + return long.MaxValue; + } + else + { + return Add; + } + } + else if (op2 >= 0L) + { + // op1 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + // op2 from (long)ulong.MinValue to long.MaxValue + + SetFpsrQCFlag(State); + + return long.MaxValue; + } + else + { + // op1 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + // op2 from long.MinValue to (long)ulong.MinValue - 1L + + ulong Add = op1 + (ulong)op2; + + if (Add > (ulong)long.MaxValue) + { + SetFpsrQCFlag(State); + + return long.MaxValue; + } + else + { + return (long)Add; + } + } + } + + public static ulong BinaryUnsignedSatQAcc(long op1, ulong op2, AThreadState State) + { + if (op1 >= 0L) + { + // op1 from (long)ulong.MinValue to long.MaxValue + // op2 from ulong.MinValue to ulong.MaxValue + + ulong Add = (ulong)op1 + op2; + + if ((Add < (ulong)op1) && (Add < op2)) + { + SetFpsrQCFlag(State); + + return ulong.MaxValue; + } + else + { + return Add; + } + } + else if (op2 > (ulong)long.MaxValue) + { + // op1 from long.MinValue to (long)ulong.MinValue - 1L + // op2 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + + return (ulong)op1 + op2; + } + else + { + // op1 from long.MinValue to (long)ulong.MinValue - 1L + // op2 from ulong.MinValue to (ulong)long.MaxValue + + long Add = op1 + (long)op2; + + if (Add < (long)ulong.MinValue) + { + SetFpsrQCFlag(State); + + return ulong.MinValue; + } + else + { + return (ulong)Add; + } + } + } + + public static long SignedSrcSignedDstSatQ(long op, int Size, AThreadState State) + { + int ESize = 8 << Size; + + long TMaxValue = (1L << (ESize - 1)) - 1L; + long TMinValue = -(1L << (ESize - 1)); + + if (op > TMaxValue) + { + SetFpsrQCFlag(State); + + return TMaxValue; + } + else if (op < TMinValue) + { + SetFpsrQCFlag(State); + + return TMinValue; + } + else + { + return op; + } + } + + public static ulong SignedSrcUnsignedDstSatQ(long op, int Size, AThreadState State) + { + int ESize = 8 << Size; + + ulong TMaxValue = (1UL << ESize) - 1UL; + ulong TMinValue = 0UL; + + if (op > (long)TMaxValue) + { + SetFpsrQCFlag(State); + + return TMaxValue; + } + else if (op < (long)TMinValue) + { + SetFpsrQCFlag(State); + + return TMinValue; + } + else + { + return (ulong)op; + } + } + + public static long UnsignedSrcSignedDstSatQ(ulong op, int Size, AThreadState State) + { + int ESize = 8 << Size; + + long TMaxValue = (1L << (ESize - 1)) - 1L; + + if (op > (ulong)TMaxValue) + { + SetFpsrQCFlag(State); + + return TMaxValue; + } + else + { + return (long)op; + } + } + + public static ulong UnsignedSrcUnsignedDstSatQ(ulong op, int Size, AThreadState State) + { + int ESize = 8 << Size; + + ulong TMaxValue = (1UL << ESize) - 1UL; + + if (op > TMaxValue) + { + SetFpsrQCFlag(State); + + return TMaxValue; + } + else + { + return op; + } + } + + private static void SetFpsrQCFlag(AThreadState State) + { + const int QCFlagBit = 27; + + State.Fpsr |= 1 << QCFlagBit; + } + public static ulong CountLeadingSigns(ulong Value, int Size) { Value ^= Value >> 1; diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs index 27f4f7fb4f..8afa4002a8 100644 --- a/ChocolArm64/Instruction/ASoftFloat.cs +++ b/ChocolArm64/Instruction/ASoftFloat.cs @@ -79,7 +79,7 @@ namespace ChocolArm64.Instruction if (scaled == 0) { // Zero -> Infinity - return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7ff0000000000000)); + return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7FF0000000000000)); } // Denormal @@ -94,7 +94,7 @@ namespace ChocolArm64.Instruction if (x_sign != 0) { // Negative -> NaN - return BitConverter.Int64BitsToDouble((long)0x7ff8000000000000); + return BitConverter.Int64BitsToDouble((long)0x7FF8000000000000); } if (x_exp == 0x7ff && scaled == 0) @@ -153,7 +153,7 @@ namespace ChocolArm64.Instruction if (scaled == 0) { // Zero -> Infinity - return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7ff0000000000000)); + return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7FF0000000000000)); } // Denormal @@ -208,8 +208,8 @@ namespace ChocolArm64.Instruction ulong op1_other = op1_bits & 0x7FFFFFFFFFFFFFFF; ulong op2_other = op2_bits & 0x7FFFFFFFFFFFFFFF; - bool inf1 = op1_other == 0x7ff0000000000000; - bool inf2 = op2_other == 0x7ff0000000000000; + bool inf1 = op1_other == 0x7FF0000000000000; + bool inf2 = op2_other == 0x7FF0000000000000; bool zero1 = op1_other == 0; bool zero2 = op2_other == 0; @@ -220,7 +220,7 @@ namespace ChocolArm64.Instruction else if (inf1 || inf2) { // Infinity - return BitConverter.Int64BitsToDouble((long)(0x7ff0000000000000 | (op1_sign ^ op2_sign))); + return BitConverter.Int64BitsToDouble((long)(0x7FF0000000000000 | (op1_sign ^ op2_sign))); } return 2.0 + op1 * op2; @@ -261,5 +261,277 @@ namespace ChocolArm64.Instruction uint new_exp = (uint)((exponent + 127) & 0xFF) << 23; return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | new_exp | (x_mantissa << 13))); } + + public static float MaxNum(float op1, float op2) + { + uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); + uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + + if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + { + op1 = float.NegativeInfinity; + } + else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) + { + op2 = float.NegativeInfinity; + } + + return Max(op1, op2); + } + + public static double MaxNum(double op1, double op2) + { + ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); + ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + + if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + { + op1 = double.NegativeInfinity; + } + else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) + { + op2 = double.NegativeInfinity; + } + + return Max(op1, op2); + } + + public static float Max(float op1, float op2) + { + // Fast path + if (op1 > op2) + { + return op1; + } + + if (op1 < op2 || (op1 == op2 && op2 != 0)) + { + return op2; + } + + uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); + uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + + // Handle NaN cases + if (ProcessNaNs(op1_bits, op2_bits, out uint op_bits)) + { + return BitConverter.Int32BitsToSingle((int)op_bits); + } + + // Return the most positive zero + if ((op1_bits & op2_bits) == 0x80000000u) + { + return BitConverter.Int32BitsToSingle(int.MinValue); + } + + return 0; + } + + public static double Max(double op1, double op2) + { + // Fast path + if (op1 > op2) + { + return op1; + } + + if (op1 < op2 || (op1 == op2 && op2 != 0)) + { + return op2; + } + + ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); + ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + + // Handle NaN cases + if (ProcessNaNs(op1_bits, op2_bits, out ulong op_bits)) + { + return BitConverter.Int64BitsToDouble((long)op_bits); + } + + // Return the most positive zero + if ((op1_bits & op2_bits) == 0x8000000000000000ul) + { + return BitConverter.Int64BitsToDouble(long.MinValue); + } + + return 0; + } + + public static float MinNum(float op1, float op2) + { + uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); + uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + + if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + { + op1 = float.PositiveInfinity; + } + else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) + { + op2 = float.PositiveInfinity; + } + + return Max(op1, op2); + } + + public static double MinNum(double op1, double op2) + { + ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); + ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + + if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + { + op1 = double.PositiveInfinity; + } + else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) + { + op2 = double.PositiveInfinity; + } + + return Min(op1, op2); + } + + public static float Min(float op1, float op2) + { + // Fast path + if (op1 < op2) + { + return op1; + } + + if (op1 > op2 || (op1 == op2 && op2 != 0)) + { + return op2; + } + + uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); + uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + + // Handle NaN cases + if (ProcessNaNs(op1_bits, op2_bits, out uint op_bits)) + { + return BitConverter.Int32BitsToSingle((int)op_bits); + } + + // Return the most negative zero + if ((op1_bits | op2_bits) == 0x80000000u) + { + return BitConverter.Int32BitsToSingle(int.MinValue); + } + + return 0; + } + + public static double Min(double op1, double op2) + { + // Fast path + if (op1 < op2) + { + return op1; + } + + if (op1 > op2 || (op1 == op2 && op2 != 0)) + { + return op2; + } + + ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); + ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + + // Handle NaN cases + if (ProcessNaNs(op1_bits, op2_bits, out ulong op_bits)) + { + return BitConverter.Int64BitsToDouble((long)op_bits); + } + + // Return the most negative zero + if ((op1_bits | op2_bits) == 0x8000000000000000ul) + { + return BitConverter.Int64BitsToDouble(long.MinValue); + } + + return 0; + } + + private static bool ProcessNaNs(uint op1_bits, uint op2_bits, out uint op_bits) + { + if (IsSNaN(op1_bits)) + { + op_bits = op1_bits | (1u << 22); // op1 is SNaN, return QNaN op1 + } + else if (IsSNaN(op2_bits)) + { + op_bits = op2_bits | (1u << 22); // op2 is SNaN, return QNaN op2 + } + else if (IsQNaN(op1_bits)) + { + op_bits = op1_bits; // op1 is QNaN, return QNaN op1 + } + else if (IsQNaN(op2_bits)) + { + op_bits = op2_bits; // op2 is QNaN, return QNaN op2 + } + else + { + op_bits = 0; + + return false; + } + + return true; + } + + private static bool ProcessNaNs(ulong op1_bits, ulong op2_bits, out ulong op_bits) + { + if (IsSNaN(op1_bits)) + { + op_bits = op1_bits | (1ul << 51); // op1 is SNaN, return QNaN op1 + } + else if (IsSNaN(op2_bits)) + { + op_bits = op2_bits | (1ul << 51); // op2 is SNaN, return QNaN op2 + } + else if (IsQNaN(op1_bits)) + { + op_bits = op1_bits; // op1 is QNaN, return QNaN op1 + } + else if (IsQNaN(op2_bits)) + { + op_bits = op2_bits; // op2 is QNaN, return QNaN op2 + } + else + { + op_bits = 0; + + return false; + } + + return true; + } + + private static bool IsQNaN(uint op_bits) + { + return (op_bits & 0x007FFFFF) != 0 && + (op_bits & 0x7FC00000) == 0x7FC00000; + } + + private static bool IsQNaN(ulong op_bits) + { + return (op_bits & 0x000FFFFFFFFFFFFF) != 0 && + (op_bits & 0x7FF8000000000000) == 0x7FF8000000000000; + } + + private static bool IsSNaN(uint op_bits) + { + return (op_bits & 0x007FFFFF) != 0 && + (op_bits & 0x7FC00000) == 0x7F800000; + } + + private static bool IsSNaN(ulong op_bits) + { + return (op_bits & 0x000FFFFFFFFFFFFF) != 0 && + (op_bits & 0x7FF8000000000000) == 0x7FF0000000000000; + } } } \ No newline at end of file diff --git a/ChocolArm64/Instruction/AVectorHelper.cs b/ChocolArm64/Instruction/AVectorHelper.cs index a0f887b043..b2d53740eb 100644 --- a/ChocolArm64/Instruction/AVectorHelper.cs +++ b/ChocolArm64/Instruction/AVectorHelper.cs @@ -93,86 +93,6 @@ namespace ChocolArm64.Instruction Value < ulong.MinValue ? ulong.MinValue : (ulong)Value; } - public static double Max(double LHS, double RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.DoubleToInt64Bits(LHS) < 0 && - BitConverter.DoubleToInt64Bits(RHS) < 0) - return -0.0; - - return 0.0; - } - - if (LHS > RHS) - return LHS; - - if (double.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static float MaxF(float LHS, float RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.SingleToInt32Bits(LHS) < 0 && - BitConverter.SingleToInt32Bits(RHS) < 0) - return -0.0f; - - return 0.0f; - } - - if (LHS > RHS) - return LHS; - - if (float.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static double Min(double LHS, double RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.DoubleToInt64Bits(LHS) < 0 || - BitConverter.DoubleToInt64Bits(RHS) < 0) - return -0.0; - - return 0.0; - } - - if (LHS < RHS) - return LHS; - - if (double.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static float MinF(float LHS, float RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.SingleToInt32Bits(LHS) < 0 || - BitConverter.SingleToInt32Bits(RHS) < 0) - return -0.0f; - - return 0.0f; - } - - if (LHS < RHS) - return LHS; - - if (float.IsNaN(LHS)) - return LHS; - - return RHS; - } - public static double Round(double Value, int Fpcr) { switch ((ARoundMode)((Fpcr >> 22) & 3)) diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs index da5cf00749..e969cca5fe 100644 --- a/ChocolArm64/Memory/AMemory.cs +++ b/ChocolArm64/Memory/AMemory.cs @@ -160,23 +160,23 @@ namespace ChocolArm64.Memory return HostPageSize; } - public bool[] IsRegionModified(long Position, long Size) + public (bool[], long) IsRegionModified(long Position, long Size) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return null; + return (null, 0); } long EndPos = Position + Size; if ((ulong)EndPos < (ulong)Position) { - return null; + return (null, 0); } if ((ulong)EndPos > AMemoryMgr.RamSize) { - return null; + return (null, 0); } IntPtr MemAddress = new IntPtr(RamPtr + Position); @@ -201,7 +201,14 @@ namespace ChocolArm64.Memory Modified[(VA - Position) / HostPageSize] = true; } - return Modified; + return (Modified, Count); + } + + public IntPtr GetHostAddress(long Position, long Size) + { + EnsureRangeIsValid(Position, Size, AMemoryPerm.Read); + + return (IntPtr)(RamPtr + (ulong)Position); } public sbyte ReadSByte(long Position) diff --git a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs index 1dd63202ba..85e2d803e9 100644 --- a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs @@ -222,7 +222,8 @@ namespace Ryujinx.Audio.OpenAL Td.CallReleaseCallbackIfNeeded(); } - Thread.Yield(); + //If it's not slept it will waste cycles + Thread.Sleep(10); } while (KeepPolling); } diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs index 30f5c22974..7e3e65e82f 100644 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Gal R32G32B32A32 = 0x1, R16G16B16A16 = 0x3, A8B8G8R8 = 0x8, + A2B10G10R10 = 0x9, R32 = 0xf, BC6H_SF16 = 0x10, BC6H_UF16 = 0x11, @@ -14,11 +15,13 @@ namespace Ryujinx.Graphics.Gal G8R8 = 0x18, R16 = 0x1b, R8 = 0x1d, + BF10GF11RF11 = 0x21, BC1 = 0x24, BC2 = 0x25, BC3 = 0x26, BC4 = 0x27, BC5 = 0x28, + Z24S8 = 0x29, ZF32 = 0x2f, Astc2D4x4 = 0x40, Astc2D5x5 = 0x41, diff --git a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs index eaae0a492e..c0287ef8be 100644 --- a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs +++ b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gal void Set(byte[] Data, int Width, int Height); - void SetTransform(float SX, float SY, float Rotate, float TX, float TY); + void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom); void SetWindowSize(int Width, int Height); @@ -22,6 +22,25 @@ namespace Ryujinx.Graphics.Gal void Render(); + void Copy( + long SrcKey, + long DstKey, + int SrcX0, + int SrcY0, + int SrcX1, + int SrcY1, + int DstX0, + int DstY0, + int DstX1, + int DstY1); + void GetBufferData(long Key, Action Callback); + + void SetBufferData( + long Key, + int Width, + int Height, + GalTextureFormat Format, + byte[] Buffer); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs index 0c5d37e40e..a87d36c38f 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -1,3 +1,5 @@ +using System; + namespace Ryujinx.Graphics.Gal { public interface IGalRasterizer @@ -45,9 +47,9 @@ namespace Ryujinx.Graphics.Gal void SetPrimitiveRestartIndex(uint Index); - void CreateVbo(long Key, byte[] Buffer); + void CreateVbo(long Key, int DataSize, IntPtr HostAddress); - void CreateIbo(long Key, byte[] Buffer); + void CreateIbo(long Key, int DataSize, IntPtr HostAddress); void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs); diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs index 9adaceaf50..56235a0709 100644 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ b/Ryujinx.Graphics/Gal/IGalShader.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gal @@ -10,7 +11,7 @@ namespace Ryujinx.Graphics.Gal IEnumerable GetTextureUsage(long Key); - void SetConstBuffer(long Key, int Cbuf, byte[] Data); + void SetConstBuffer(long Key, int Cbuf, int DataSize, IntPtr HostAddress); void EnsureTextureBinding(string UniformName, int Value); diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl deleted file mode 100644 index 74e33bd7c0..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl +++ /dev/null @@ -1,13 +0,0 @@ -#version 330 core - -precision highp float; - -uniform sampler2D tex; - -in vec2 tex_coord; - -out vec4 out_frag_color; - -void main(void) { - out_frag_color = texture(tex, tex_coord); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl deleted file mode 100644 index 35d560c09b..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl +++ /dev/null @@ -1,28 +0,0 @@ -#version 330 core - -precision highp float; - -uniform mat2 transform; -uniform vec2 window_size; -uniform vec2 offset; - -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_tex_coord; - -out vec2 tex_coord; - -// Have a fixed aspect ratio, fit the image within the available space. -vec2 get_scale_ratio(void) { - vec2 native_size = vec2(1280, 720); - vec2 ratio = vec2( - (window_size.y * native_size.x) / (native_size.y * window_size.x), - (window_size.x * native_size.y) / (native_size.x * window_size.y) - ); - return min(ratio, 1); -} - -void main(void) { - tex_coord = in_tex_coord; - vec2 t_pos = (transform * in_position) + offset; - gl_Position = vec4(t_pos * get_scale_ratio(), 0, 1); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index 5d20c931ef..3c42e5d387 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -132,6 +132,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba, PixelType.Float); case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba, PixelType.HalfFloat); case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte); + case GalTextureFormat.A2B10G10R10: return (PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed); case GalTextureFormat.R32: return (PixelFormat.Red, PixelType.Float); case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551); case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565); @@ -139,6 +140,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureFormat.R16: return (PixelFormat.Red, PixelType.HalfFloat); case GalTextureFormat.R8: return (PixelFormat.Red, PixelType.UnsignedByte); case GalTextureFormat.ZF32: return (PixelFormat.DepthComponent, PixelType.Float); + case GalTextureFormat.BF10GF11RF11: return (PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev); + case GalTextureFormat.Z24S8: return (PixelFormat.DepthStencil, PixelType.UnsignedInt248); } throw new NotImplementedException(Format.ToString()); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs new file mode 100644 index 0000000000..69fce6d31d --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs @@ -0,0 +1,43 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + static class OGLExtension + { + private static bool Initialized = false; + + private static bool EnhancedLayouts; + + public static bool HasEnhancedLayouts() + { + EnsureInitialized(); + + return EnhancedLayouts; + } + + private static void EnsureInitialized() + { + if (Initialized) + { + return; + } + + EnhancedLayouts = HasExtension("GL_ARB_enhanced_layouts"); + } + + private static bool HasExtension(string Name) + { + int NumExtensions = GL.GetInteger(GetPName.NumExtensions); + + for (int Extension = 0; Extension < NumExtensions; Extension++) + { + if (GL.GetString(StringNameIndexed.Extensions, Extension) == Name) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs index 305fa37d8f..30a3de64a0 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs @@ -32,57 +32,49 @@ namespace Ryujinx.Graphics.Gal.OpenGL public int RbHandle { get; private set; } public int TexHandle { get; private set; } - public FrameBuffer(int Width, int Height) + public FrameBuffer(int Width, int Height, bool HasRenderBuffer) { this.Width = Width; this.Height = Height; Handle = GL.GenFramebuffer(); - RbHandle = GL.GenRenderbuffer(); TexHandle = GL.GenTexture(); + + if (HasRenderBuffer) + { + RbHandle = GL.GenRenderbuffer(); + } } } - private struct ShaderProgram - { - public int Handle; - public int VpHandle; - public int FpHandle; - } + private const int NativeWidth = 1280; + private const int NativeHeight = 720; private Dictionary Fbs; - private ShaderProgram Shader; - private Rect Viewport; private Rect Window; - private bool IsInitialized; + private FrameBuffer CurrFb; + private FrameBuffer CurrReadFb; - private int RawFbTexWidth; - private int RawFbTexHeight; - private int RawFbTexHandle; + private FrameBuffer RawFb; - private int CurrFbHandle; - private int CurrTexHandle; + private bool FlipX; + private bool FlipY; - private int VaoHandle; - private int VboHandle; + private int CropTop; + private int CropLeft; + private int CropRight; + private int CropBottom; public OGLFrameBuffer() { Fbs = new Dictionary(); - - Shader = new ShaderProgram(); } public void Create(long Key, int Width, int Height) { - //TODO: We should either use the original frame buffer size, - //or just remove the Width/Height arguments. - Width = Window.Width; - Height = Window.Height; - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) { if (Fb.Width != Width || @@ -97,7 +89,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL return; } - Fb = new FrameBuffer(Width, Height); + Fb = new FrameBuffer(Width, Height, true); SetupTexture(Fb.TexHandle, Width, Height); @@ -125,8 +117,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.DrawBuffer(DrawBufferMode.ColorAttachment0); - GL.Viewport(0, 0, Width, Height); - Fbs.Add(Key, Fb); } @@ -136,7 +126,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); - CurrFbHandle = Fb.Handle; + CurrFb = Fb; } } @@ -154,75 +144,50 @@ namespace Ryujinx.Graphics.Gal.OpenGL { if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) { - CurrTexHandle = Fb.TexHandle; + CurrReadFb = Fb; } } public void Set(byte[] Data, int Width, int Height) { - if (RawFbTexHandle == 0) + if (RawFb == null) { - RawFbTexHandle = GL.GenTexture(); + CreateRawFb(Width, Height); } - if (RawFbTexWidth != Width || - RawFbTexHeight != Height) + if (RawFb.Width != Width || + RawFb.Height != Height) { - SetupTexture(RawFbTexHandle, Width, Height); + SetupTexture(RawFb.TexHandle, Width, Height); - RawFbTexWidth = Width; - RawFbTexHeight = Height; + RawFb.Width = Width; + RawFb.Height = Height; } GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, RawFbTexHandle); + GL.BindTexture(TextureTarget.Texture2D, RawFb.TexHandle); (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data); - CurrTexHandle = RawFbTexHandle; + CurrReadFb = RawFb; } - public void SetTransform(float SX, float SY, float Rotate, float TX, float TY) + public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom) { - EnsureInitialized(); + this.FlipX = FlipX; + this.FlipY = FlipY; - Matrix2 Transform; - - Transform = Matrix2.CreateScale(SX, SY); - Transform *= Matrix2.CreateRotation(Rotate); - - Vector2 Offs = new Vector2(TX, TY); - - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); - - GL.UseProgram(Shader.Handle); - - int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); - - GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); - - int OffsetUniformLocation = GL.GetUniformLocation(Shader.Handle, "offset"); - - GL.Uniform2(OffsetUniformLocation, ref Offs); - - GL.UseProgram(CurrentProgram); + CropTop = Top; + CropLeft = Left; + CropRight = Right; + CropBottom = Bottom; } public void SetWindowSize(int Width, int Height) { - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); - - GL.UseProgram(Shader.Handle); - - int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(Width, Height)); - - GL.UseProgram(CurrentProgram); - Window = new Rect(0, 0, Width, Height); } @@ -230,80 +195,104 @@ namespace Ryujinx.Graphics.Gal.OpenGL { Viewport = new Rect(X, Y, Width, Height); - //TODO + SetViewport(Viewport); + } + + private void SetViewport(Rect Viewport) + { + GL.Viewport( + Viewport.X, + Viewport.Y, + Viewport.Width, + Viewport.Height); } public void Render() { - if (CurrTexHandle != 0) + if (CurrReadFb != null) { - EnsureInitialized(); + int SrcX0, SrcX1, SrcY0, SrcY1; - //bool CullFaceEnable = GL.IsEnabled(EnableCap.CullFace); + if (CropLeft == 0 && CropRight == 0) + { + SrcX0 = 0; + SrcX1 = CurrReadFb.Width; + } + else + { + SrcX0 = CropLeft; + SrcX1 = CropRight; + } - bool DepthTestEnable = GL.IsEnabled(EnableCap.DepthTest); + if (CropTop == 0 && CropBottom == 0) + { + SrcY0 = 0; + SrcY1 = CurrReadFb.Height; + } + else + { + SrcY0 = CropTop; + SrcY1 = CropBottom; + } - bool StencilTestEnable = GL.IsEnabled(EnableCap.StencilTest); + float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth) / ((float)NativeHeight * Window.Width)); + float RatioY = MathF.Min(1f, (Window.Width * (float)NativeHeight) / ((float)NativeWidth * Window.Height)); - bool AlphaBlendEnable = GL.IsEnabled(EnableCap.Blend); + int DstWidth = (int)(Window.Width * RatioX); + int DstHeight = (int)(Window.Height * RatioY); - //GL.Disable(EnableCap.CullFace); + int DstPaddingX = (Window.Width - DstWidth) / 2; + int DstPaddingY = (Window.Height - DstHeight) / 2; - GL.Disable(EnableCap.DepthTest); + int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX; + int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX; - GL.Disable(EnableCap.StencilTest); - - GL.Disable(EnableCap.Blend); - - GL.ActiveTexture(TextureUnit.Texture0); - - GL.BindTexture(TextureTarget.Texture2D, CurrTexHandle); - - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); + int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY; + int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY; GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - SetViewport(Window); + GL.Viewport(0, 0, Window.Width, Window.Height); - GL.Clear( - ClearBufferMask.ColorBufferBit | - ClearBufferMask.DepthBufferBit); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrReadFb.Handle); - GL.BindVertexArray(VaoHandle); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - GL.UseProgram(Shader.Handle); - - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); - - //Restore the original state. - GL.BindFramebuffer(FramebufferTarget.Framebuffer, CurrFbHandle); - - GL.UseProgram(CurrentProgram); - - //if (CullFaceEnable) - //{ - // GL.Enable(EnableCap.CullFace); - //} - - if (DepthTestEnable) - { - GL.Enable(EnableCap.DepthTest); - } - - if (StencilTestEnable) - { - GL.Enable(EnableCap.StencilTest); - } - - if (AlphaBlendEnable) - { - GL.Enable(EnableCap.Blend); - } - - //GL.Viewport(0, 0, 1280, 720); + GL.BlitFramebuffer( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear); } } + public void Copy( + long SrcKey, + long DstKey, + int SrcX0, + int SrcY0, + int SrcX1, + int SrcY1, + int DstX0, + int DstY0, + int DstX1, + int DstY1) + { + if (Fbs.TryGetValue(SrcKey, out FrameBuffer SrcFb) && + Fbs.TryGetValue(DstKey, out FrameBuffer DstFb)) + { + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb.Handle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb.Handle); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + GL.BlitFramebuffer( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + ClearBufferMask.ColorBufferBit, + BlitFramebufferFilter.Linear); + } +} + public void GetBufferData(long Key, Action Callback) { if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) @@ -324,98 +313,61 @@ namespace Ryujinx.Graphics.Gal.OpenGL Data); Callback(Data); - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrFbHandle); } } - private void SetViewport(Rect Viewport) + public void SetBufferData( + long Key, + int Width, + int Height, + GalTextureFormat Format, + byte[] Buffer) { - GL.Viewport( - Viewport.X, - Viewport.Y, - Viewport.Width, - Viewport.Height); - } - - private void EnsureInitialized() - { - if (!IsInitialized) + if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) { - IsInitialized = true; + GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle); - SetupShader(); - SetupVertex(); + const int Level = 0; + const int Border = 0; + + const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; + + (PixelFormat GlFormat, PixelType Type) = OGLEnumConverter.GetTextureFormat(Format); + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Width, + Height, + Border, + GlFormat, + Type, + Buffer); } } - private void SetupShader() + private void CreateRawFb(int Width, int Height) { - Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader); - Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader); - - string VpSource = EmbeddedResource.GetString("GlFbVtxShader"); - string FpSource = EmbeddedResource.GetString("GlFbFragShader"); - - GL.ShaderSource(Shader.VpHandle, VpSource); - GL.ShaderSource(Shader.FpHandle, FpSource); - GL.CompileShader(Shader.VpHandle); - GL.CompileShader(Shader.FpHandle); - - Shader.Handle = GL.CreateProgram(); - - GL.AttachShader(Shader.Handle, Shader.VpHandle); - GL.AttachShader(Shader.Handle, Shader.FpHandle); - GL.LinkProgram(Shader.Handle); - GL.UseProgram(Shader.Handle); - - Matrix2 Transform = Matrix2.Identity; - - int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex"); - - GL.Uniform1(TexUniformLocation, 0); - - int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f)); - - int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); - - GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); - } - - private void SetupVertex() - { - VaoHandle = GL.GenVertexArray(); - VboHandle = GL.GenBuffer(); - - float[] Buffer = new float[] + if (RawFb == null) { - -1, 1, 0, 0, - 1, 1, 1, 0, - -1, -1, 0, 1, - 1, -1, 1, 1 - }; + RawFb = new FrameBuffer(Width, Height, false); - IntPtr Length = new IntPtr(Buffer.Length * 4); + SetupTexture(RawFb.TexHandle, Width, Height); - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + RawFb.Width = Width; + RawFb.Height = Height; - GL.BindVertexArray(VaoHandle); + GL.BindFramebuffer(FramebufferTarget.Framebuffer, RawFb.Handle); - GL.EnableVertexAttribArray(0); + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0, + RawFb.TexHandle, + 0); - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0); - - GL.EnableVertexAttribArray(1); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8); + GL.Viewport(0, 0, Width, Height); + } } private void SetupTexture(int Handle, int Width, int Height) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index 0dc56966b3..c5166b51bf 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -211,28 +211,28 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.PrimitiveRestartIndex(Index); } - public void CreateVbo(long Key, byte[] Buffer) + public void CreateVbo(long Key, int DataSize, IntPtr HostAddress) { int Handle = GL.GenBuffer(); - VboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length); + VboCache.AddOrUpdate(Key, Handle, (uint)DataSize); - IntPtr Length = new IntPtr(Buffer.Length); + IntPtr Length = new IntPtr(DataSize); GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); } - public void CreateIbo(long Key, byte[] Buffer) + public void CreateIbo(long Key, int DataSize, IntPtr HostAddress) { int Handle = GL.GenBuffer(); - IboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length); + IboCache.AddOrUpdate(Key, Handle, (uint)DataSize); - IntPtr Length = new IntPtr(Buffer.Length); + IntPtr Length = new IntPtr(DataSize); GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); } public void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs) @@ -278,7 +278,19 @@ namespace Ryujinx.Graphics.Gal.OpenGL int Size = AttribElements[Attrib.Size]; int Offset = Attrib.Offset; - GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Stride, Offset); + if (Attrib.Type == GalVertexAttribType.Sint || + Attrib.Type == GalVertexAttribType.Uint) + { + IntPtr Pointer = new IntPtr(Offset); + + VertexAttribIntegerType IType = (VertexAttribIntegerType)Type; + + GL.VertexAttribIPointer(Attrib.Index, Size, IType, Stride, Pointer); + } + else + { + GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Stride, Offset); + } } } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs index ad71775502..3f3f23b8a2 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using Buffer = System.Buffer; + namespace Ryujinx.Graphics.Gal.OpenGL { public class OGLShader : IGalShader @@ -118,20 +120,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL if (IsDualVp) { - ShaderDumper.Dump(Memory, Position + 0x50, Type, "a"); - ShaderDumper.Dump(Memory, PositionB + 0x50, Type, "b"); + ShaderDumper.Dump(Memory, Position, Type, "a"); + ShaderDumper.Dump(Memory, PositionB, Type, "b"); Program = Decompiler.Decompile( Memory, - Position + 0x50, - PositionB + 0x50, + Position, + PositionB, Type); } else { - ShaderDumper.Dump(Memory, Position + 0x50, Type); + ShaderDumper.Dump(Memory, Position, Type); - Program = Decompiler.Decompile(Memory, Position + 0x50, Type); + Program = Decompiler.Decompile(Memory, Position, Type); } return new ShaderStage( @@ -151,7 +153,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL return Enumerable.Empty(); } - public void SetConstBuffer(long Key, int Cbuf, byte[] Data) + public void SetConstBuffer(long Key, int Cbuf, int DataSize, IntPtr HostAddress) { if (Stages.TryGetValue(Key, out ShaderStage Stage)) { @@ -159,13 +161,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL { OGLStreamBuffer Buffer = GetConstBuffer(Stage.Type, Cbuf); - int Size = Math.Min(Data.Length, Buffer.Size); + int Size = Math.Min(DataSize, Buffer.Size); - byte[] Destiny = Buffer.Map(Size); - - Array.Copy(Data, Destiny, Size); - - Buffer.Unmap(Size); + Buffer.SetData(Size, HostAddress); } } } @@ -198,6 +196,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL private void Bind(ShaderStage Stage) { + if (Stage.Type == GalShaderType.Geometry) + { + //Enhanced layouts are required for Geometry shaders + //skip this stage if current driver has no ARB_enhanced_layouts + if (!OGLExtension.HasEnhancedLayouts()) + { + return; + } + } + switch (Stage.Type) { case GalShaderType.Vertex: Current.Vertex = Stage; break; @@ -249,7 +257,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.UseProgram(Handle); - BindUniformBuffers(Handle); + if (CurrentProgramHandle != Handle) + { + BindUniformBuffers(Handle); + } CurrentProgramHandle = Handle; } @@ -268,7 +279,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { int FreeBinding = 0; - int BindUniformBlocksIfNotNull(ShaderStage Stage) + void BindUniformBlocksIfNotNull(ShaderStage Stage) { if (Stage != null) { @@ -287,8 +298,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL FreeBinding++; } } - - return FreeBinding; } BindUniformBlocksIfNotNull(Current.Vertex); @@ -302,7 +311,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { int FreeBinding = 0; - int BindUniformBuffersIfNotNull(ShaderStage Stage) + void BindUniformBuffersIfNotNull(ShaderStage Stage) { if (Stage != null) { @@ -315,8 +324,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL FreeBinding++; } } - - return FreeBinding; } BindUniformBuffersIfNotNull(Current.Vertex); @@ -337,7 +344,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL //Allocate a maximum of 64 KiB int Size = Math.Min(GL.GetInteger(GetPName.MaxUniformBlockSize), 64 * 1024); - Buffer = OGLStreamBuffer.Create(BufferTarget.UniformBuffer, Size); + Buffer = new OGLStreamBuffer(BufferTarget.UniformBuffer, Size); ConstBuffers[StageIndex][Cbuf] = Buffer; } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs index 329c5b5df7..0d5dee93f5 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs @@ -1,9 +1,9 @@ -using System; using OpenTK.Graphics.OpenGL; +using System; namespace Ryujinx.Graphics.Gal.OpenGL { - abstract class OGLStreamBuffer : IDisposable + class OGLStreamBuffer : IDisposable { public int Handle { get; protected set; } @@ -11,53 +11,25 @@ namespace Ryujinx.Graphics.Gal.OpenGL protected BufferTarget Target { get; private set; } - private bool Mapped = false; - - public OGLStreamBuffer(BufferTarget Target, int MaxSize) + public OGLStreamBuffer(BufferTarget Target, int Size) { - Handle = 0; - Mapped = false; - this.Target = Target; - this.Size = MaxSize; + this.Size = Size; + + Handle = GL.GenBuffer(); + + GL.BindBuffer(Target, Handle); + + GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw); } - public static OGLStreamBuffer Create(BufferTarget Target, int MaxSize) + public void SetData(int Size, IntPtr HostAddress) { - //TODO: Query here for ARB_buffer_storage and use when available - return new SubDataBuffer(Target, MaxSize); + GL.BindBuffer(Target, Handle); + + GL.BufferSubData(Target, IntPtr.Zero, Size, HostAddress); } - public byte[] Map(int Size) - { - if (Handle == 0 || Mapped || Size > this.Size) - { - throw new InvalidOperationException(); - } - - byte[] Memory = InternMap(Size); - - Mapped = true; - - return Memory; - } - - public void Unmap(int UsedSize) - { - if (Handle == 0 || !Mapped) - { - throw new InvalidOperationException(); - } - - InternUnmap(UsedSize); - - Mapped = false; - } - - protected abstract byte[] InternMap(int Size); - - protected abstract void InternUnmap(int UsedSize); - public void Dispose() { Dispose(true); @@ -73,41 +45,4 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } } - - class SubDataBuffer : OGLStreamBuffer - { - private byte[] Memory; - - public SubDataBuffer(BufferTarget Target, int MaxSize) - : base(Target, MaxSize) - { - Memory = new byte[MaxSize]; - - GL.GenBuffers(1, out int Handle); - - GL.BindBuffer(Target, Handle); - - GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw); - - this.Handle = Handle; - } - - protected override byte[] InternMap(int Size) - { - return Memory; - } - - protected override void InternUnmap(int UsedSize) - { - GL.BindBuffer(Target, Handle); - - unsafe - { - fixed (byte* MemoryPtr = Memory) - { - GL.BufferSubData(Target, IntPtr.Zero, UsedSize, (IntPtr)MemoryPtr); - } - } - } - } } diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs index d3284f9f55..7688545c02 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs @@ -4,13 +4,13 @@ namespace Ryujinx.Graphics.Gal.Shader { class GlslDecl { + public const int LayerAttr = 0x064; public const int TessCoordAttrX = 0x2f0; public const int TessCoordAttrY = 0x2f4; public const int TessCoordAttrZ = 0x2f8; public const int InstanceIdAttr = 0x2f8; public const int VertexIdAttr = 0x2fc; public const int FaceAttr = 0x3fc; - public const int GlPositionWAttr = 0x7c; public const int MaxUboSize = 1024; @@ -210,7 +210,8 @@ namespace Ryujinx.Graphics.Gal.Shader //This is a built-in input variable. if (Abuf.Offs == VertexIdAttr || Abuf.Offs == InstanceIdAttr || - Abuf.Offs == FaceAttr) + Abuf.Offs == FaceAttr || + Abuf.Offs == LayerAttr) { break; } @@ -254,6 +255,8 @@ namespace Ryujinx.Graphics.Gal.Shader m_Attributes.Add(Index, DeclInfo); } + + Traverse(Abuf, Abuf.Vertex); break; } diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 575fb72f9a..a338f40413 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.Gal.Shader private const string IdentationStr = " "; + private const int MaxVertexInput = 3; + private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" }; private GlslDecl Decl; + private ShaderHeader Header, HeaderB; + private ShaderIrBlock[] Blocks, BlocksB; private StringBuilder SB; @@ -50,6 +54,7 @@ namespace Ryujinx.Graphics.Gal.Shader { ShaderIrInst.Cle, GetCleExpr }, { ShaderIrInst.Clt, GetCltExpr }, { ShaderIrInst.Cne, GetCneExpr }, + { ShaderIrInst.Cut, GetCutExpr }, { ShaderIrInst.Exit, GetExitExpr }, { ShaderIrInst.Fabs, GetAbsExpr }, { ShaderIrInst.Fadd, GetAddExpr }, @@ -110,6 +115,9 @@ namespace Ryujinx.Graphics.Gal.Shader long VpBPosition, GalShaderType ShaderType) { + Header = new ShaderHeader(Memory, VpAPosition); + HeaderB = new ShaderHeader(Memory, VpBPosition); + Blocks = ShaderDecoder.Decode(Memory, VpAPosition); BlocksB = ShaderDecoder.Decode(Memory, VpBPosition); @@ -123,6 +131,9 @@ namespace Ryujinx.Graphics.Gal.Shader public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType) { + Header = new ShaderHeader(Memory, Position); + HeaderB = null; + Blocks = ShaderDecoder.Decode(Memory, Position); BlocksB = null; @@ -137,6 +148,7 @@ namespace Ryujinx.Graphics.Gal.Shader SB.AppendLine("#version 410 core"); + PrintDeclHeader(); PrintDeclTextures(); PrintDeclUniforms(); PrintDeclAttributes(); @@ -170,6 +182,37 @@ namespace Ryujinx.Graphics.Gal.Shader Decl.Uniforms.Values); } + private void PrintDeclHeader() + { + if (Decl.ShaderType == GalShaderType.Geometry) + { + int MaxVertices = Header.MaxOutputVertexCount; + + string OutputTopology; + + switch (Header.OutputTopology) + { + case ShaderHeader.PointList: OutputTopology = "points"; break; + case ShaderHeader.LineStrip: OutputTopology = "line_strip"; break; + case ShaderHeader.TriangleStrip: OutputTopology = "triangle_strip"; break; + + default: throw new InvalidOperationException(); + } + + SB.AppendLine("#extension GL_ARB_enhanced_layouts : require"); + + SB.AppendLine(); + + SB.AppendLine("// Stubbed. Maxwell geometry shaders don't inform input geometry type"); + + SB.AppendLine("layout(triangles) in;" + Environment.NewLine); + + SB.AppendLine($"layout({OutputTopology}, max_vertices = {MaxVertices}) out;"); + + SB.AppendLine(); + } + } + private void PrintDeclTextures() { PrintDecls(Decl.Textures, "uniform sampler2D"); @@ -201,7 +244,9 @@ namespace Ryujinx.Graphics.Gal.Shader private void PrintDeclAttributes() { - PrintDecls(Decl.Attributes); + string GeometryArray = (Decl.ShaderType == GalShaderType.Geometry) ? "[" + MaxVertexInput + "]" : ""; + + PrintDecls(Decl.Attributes, Suffix: GeometryArray); } private void PrintDeclInAttributes() @@ -211,7 +256,27 @@ namespace Ryujinx.Graphics.Gal.Shader SB.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") in vec4 " + GlslDecl.PositionOutAttrName + ";"); } - PrintDeclAttributes(Decl.InAttributes.Values, "in"); + if (Decl.ShaderType == GalShaderType.Geometry) + { + if (Decl.InAttributes.Count > 0) + { + SB.AppendLine("in Vertex {"); + + foreach (ShaderDeclInfo DeclInfo in Decl.InAttributes.Values.OrderBy(DeclKeySelector)) + { + if (DeclInfo.Index >= 0) + { + SB.AppendLine(IdentationStr + "layout (location = " + DeclInfo.Index + ") " + GetDecl(DeclInfo) + "; "); + } + } + + SB.AppendLine("} block_in[];" + Environment.NewLine); + } + } + else + { + PrintDeclAttributes(Decl.InAttributes.Values, "in"); + } } private void PrintDeclOutAttributes() @@ -254,7 +319,7 @@ namespace Ryujinx.Graphics.Gal.Shader PrintDecls(Decl.Preds, "bool"); } - private void PrintDecls(IReadOnlyDictionary Dict, string CustomType = null) + private void PrintDecls(IReadOnlyDictionary Dict, string CustomType = null, string Suffix = "") { foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector)) { @@ -262,15 +327,15 @@ namespace Ryujinx.Graphics.Gal.Shader if (CustomType != null) { - Name = CustomType + " " + DeclInfo.Name + ";"; + Name = CustomType + " " + DeclInfo.Name + Suffix + ";"; } else if (DeclInfo.Name == GlslDecl.FragmentOutputName) { - Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";" + Environment.NewLine; + Name = "layout (location = 0) out " + GetDecl(DeclInfo) + Suffix + ";" + Environment.NewLine; } else { - Name = GetDecl(DeclInfo) + ";"; + Name = GetDecl(DeclInfo) + Suffix + ";"; } SB.AppendLine(Name); @@ -307,7 +372,21 @@ namespace Ryujinx.Graphics.Gal.Shader string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); - SB.AppendLine(IdentationStr + Attr.Name + Swizzle + " = " + DeclInfo.Name + ";"); + if (Decl.ShaderType == GalShaderType.Geometry) + { + for (int Vertex = 0; Vertex < MaxVertexInput; Vertex++) + { + string Dst = Attr.Name + "[" + Vertex + "]" + Swizzle; + + string Src = "block_in[" + Vertex + "]." + DeclInfo.Name; + + SB.AppendLine(IdentationStr + Dst + " = " + Src + ";"); + } + } + else + { + SB.AppendLine(IdentationStr + Attr.Name + Swizzle + " = " + DeclInfo.Name + ";"); + } } if (BlocksB != null) @@ -320,6 +399,16 @@ namespace Ryujinx.Graphics.Gal.Shader SB.AppendLine(IdentationStr + GlslDecl.ProgramName + "();"); } + if (Decl.ShaderType != GalShaderType.Geometry) + { + PrintAttrToOutput(); + } + + SB.AppendLine("}"); + } + + private void PrintAttrToOutput(string Identation = IdentationStr) + { foreach (KeyValuePair KV in Decl.OutAttributes) { if (!Decl.Attributes.TryGetValue(KV.Key, out ShaderDeclInfo Attr)) @@ -331,21 +420,26 @@ namespace Ryujinx.Graphics.Gal.Shader string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); - SB.AppendLine(IdentationStr + DeclInfo.Name + " = " + Attr.Name + Swizzle + ";"); + string Name = Attr.Name; + + if (Decl.ShaderType == GalShaderType.Geometry) + { + Name += "[0]"; + } + + SB.AppendLine(Identation + DeclInfo.Name + " = " + Name + Swizzle + ";"); } if (Decl.ShaderType == GalShaderType.Vertex) { - SB.AppendLine(IdentationStr + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";"); + SB.AppendLine(Identation + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";"); } if (Decl.ShaderType != GalShaderType.Fragment) { - SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + " = gl_Position;"); - SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + ".w = 1;"); + SB.AppendLine(Identation + GlslDecl.PositionOutAttrName + " = gl_Position;"); + SB.AppendLine(Identation + GlslDecl.PositionOutAttrName + ".w = 1;"); } - - SB.AppendLine("}"); } private void PrintBlockScope( @@ -484,11 +578,17 @@ namespace Ryujinx.Graphics.Gal.Shader { SB.AppendLine(Identation + "continue;"); } - - continue; } + else if (Op.Inst == ShaderIrInst.Emit) + { + PrintAttrToOutput(Identation); - SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); + SB.AppendLine(Identation + "EmitVertex();"); + } + else + { + SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); + } } else if (Node is ShaderIrCmnt Cmnt) { @@ -634,6 +734,14 @@ namespace Ryujinx.Graphics.Gal.Shader private string GetOutAbufName(ShaderIrOperAbuf Abuf) { + if (Decl.ShaderType == GalShaderType.Geometry) + { + switch (Abuf.Offs) + { + case GlslDecl.LayerAttr: return "gl_Layer"; + } + } + return GetAttrTempName(Abuf); } @@ -692,7 +800,16 @@ namespace Ryujinx.Graphics.Gal.Shader throw new InvalidOperationException(); } - return DeclInfo.Name + Swizzle; + if (Decl.ShaderType == GalShaderType.Geometry) + { + string Vertex = "floatBitsToInt(" + GetSrcExpr(Abuf.Vertex) + ")"; + + return DeclInfo.Name + "[" + Vertex + "]" + Swizzle; + } + else + { + return DeclInfo.Name + Swizzle; + } } private string GetName(ShaderIrOperGpr Gpr) @@ -805,6 +922,8 @@ namespace Ryujinx.Graphics.Gal.Shader private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!="); + private string GetCutExpr(ShaderIrOp Op) => "EndPrimitive()"; + private string GetCneuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "!="); private string GetCnumExpr(ShaderIrOp Op) => GetUnaryCall(Op, "!isnan"); @@ -1104,8 +1223,9 @@ namespace Ryujinx.Graphics.Gal.Shader switch (Node) { case ShaderIrOperAbuf Abuf: - return Abuf.Offs == GlslDecl.VertexIdAttr || + return Abuf.Offs == GlslDecl.LayerAttr || Abuf.Offs == GlslDecl.InstanceIdAttr || + Abuf.Offs == GlslDecl.VertexIdAttr || Abuf.Offs == GlslDecl.FaceAttr ? OperType.I32 : OperType.F32; diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs index a44073513c..8486d8a7d9 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -442,6 +442,41 @@ namespace Ryujinx.Graphics.Gal.Shader return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; } + public static void Vmad(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperA = GetOperGpr8(OpCode); + + ShaderIrNode OperB; + + if (((OpCode >> 50) & 1) != 0) + { + OperB = GetOperGpr20(OpCode); + } + else + { + OperB = GetOperImm19_20(OpCode); + } + + ShaderIrOperGpr OperC = GetOperGpr39(OpCode); + + ShaderIrNode Tmp = new ShaderIrOp(ShaderIrInst.Mul, OperA, OperB); + + ShaderIrNode Final = new ShaderIrOp(ShaderIrInst.Add, Tmp, OperC); + + int Shr = (int)((OpCode >> 51) & 3); + + if (Shr != 0) + { + int Shift = (Shr == 2) ? 15 : 7; + + Final = new ShaderIrOp(ShaderIrInst.Lsr, Final, new ShaderIrOperImm(Shift)); + } + + Block.AddNode(new ShaderIrCmnt("Stubbed. Instruction is reduced to a * b + c")); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Final), OpCode)); + } + public static void Xmad_CR(ShaderIrBlock Block, long OpCode) { EmitXmad(Block, OpCode, ShaderOper.CR); @@ -819,6 +854,8 @@ namespace Ryujinx.Graphics.Gal.Shader OperA = GetAluFabsFneg(OperA, AbsA, NegA); + Block.AddNode(new ShaderIrCmnt("Stubbed.")); + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); } @@ -834,11 +871,12 @@ namespace Ryujinx.Graphics.Gal.Shader private static void EmitSet(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper) { - bool NegA = ((OpCode >> 43) & 1) != 0; - bool AbsB = ((OpCode >> 44) & 1) != 0; - bool BoolFloat = ((OpCode >> 52) & 1) != 0; - bool NegB = ((OpCode >> 53) & 1) != 0; - bool AbsA = ((OpCode >> 54) & 1) != 0; + bool NegA = ((OpCode >> 43) & 1) != 0; + bool AbsB = ((OpCode >> 44) & 1) != 0; + bool NegB = ((OpCode >> 53) & 1) != 0; + bool AbsA = ((OpCode >> 54) & 1) != 0; + + bool BoolFloat = ((OpCode >> (IsFloat ? 52 : 44)) & 1) != 0; ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs index 1f1b158ef8..7d7b2f6c6d 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -7,14 +7,15 @@ namespace Ryujinx.Graphics.Gal.Shader public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode) { int Abuf = (int)(OpCode >> 20) & 0x3ff; - int Reg = (int)(OpCode >> 39) & 0xff; int Size = (int)(OpCode >> 47) & 3; + ShaderIrOperGpr Vertex = GetOperGpr39(OpCode); + ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1]; for (int Index = 0; Index <= Size; Index++) { - Opers[Index] = new ShaderIrOperAbuf(Abuf + Index * 4, Reg); + Opers[Index] = new ShaderIrOperAbuf(Abuf + Index * 4, Vertex); } return Opers; @@ -23,9 +24,8 @@ namespace Ryujinx.Graphics.Gal.Shader public static ShaderIrOperAbuf GetOperAbuf28(long OpCode) { int Abuf = (int)(OpCode >> 28) & 0x3ff; - int Reg = (int)(OpCode >> 39) & 0xff; - return new ShaderIrOperAbuf(Abuf, Reg); + return new ShaderIrOperAbuf(Abuf, GetOperGpr39(OpCode)); } public static ShaderIrOperCbuf GetOperCbuf34(long OpCode) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs index 083b0c63a1..aea7e744dc 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs @@ -35,6 +35,9 @@ namespace Ryujinx.Graphics.Gal.Shader { ShaderIrNode[] Opers = GetOperAbuf20(OpCode); + //Used by GS + ShaderIrOperGpr Vertex = GetOperGpr39(OpCode); + int Index = 0; foreach (ShaderIrNode OperA in Opers) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs index 4c9e59cf0c..c6b71fb01a 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs @@ -85,6 +85,16 @@ namespace Ryujinx.Graphics.Gal.Shader EmitI2i(Block, OpCode, ShaderOper.RR); } + public static void Isberd(ShaderIrBlock Block, long OpCode) + { + //This instruction seems to be used to translate from an address to a vertex index in a GS + //Stub it as such + + Block.AddNode(new ShaderIrCmnt("Stubbed.")); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), GetOperGpr8(OpCode)), OpCode)); + } + public static void Mov_C(ShaderIrBlock Block, long OpCode) { ShaderIrOperCbuf Cbuf = GetOperCbuf34(OpCode); @@ -128,6 +138,16 @@ namespace Ryujinx.Graphics.Gal.Shader EmitSel(Block, OpCode, ShaderOper.RR); } + public static void Mov_S(ShaderIrBlock Block, long OpCode) + { + Block.AddNode(new ShaderIrCmnt("Stubbed.")); + + //Zero is used as a special number to get a valid "0 * 0 + VertexIndex" in a GS + ShaderIrNode Source = new ShaderIrOperImm(0); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Source), OpCode)); + } + private static void EmitF2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) { bool NegA = ((OpCode >> 45) & 1) != 0; diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs new file mode 100644 index 0000000000..591631ff99 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs @@ -0,0 +1,29 @@ +using System; + +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Out_R(ShaderIrBlock Block, long OpCode) + { + //TODO: Those registers have to be used for something + ShaderIrOperGpr Gpr0 = GetOperGpr0(OpCode); + ShaderIrOperGpr Gpr8 = GetOperGpr8(OpCode); + ShaderIrOperGpr Gpr20 = GetOperGpr20(OpCode); + + int Type = (int)((OpCode >> 39) & 3); + + if ((Type & 1) != 0) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Emit), OpCode)); + } + + if ((Type & 2) != 0) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Cut), OpCode)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs index 85522ff950..98f371b573 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -4,6 +4,8 @@ namespace Ryujinx.Graphics.Gal.Shader { static class ShaderDecoder { + private const long HeaderSize = 0x50; + private const bool AddDbgComments = true; public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start) @@ -32,13 +34,13 @@ namespace Ryujinx.Graphics.Gal.Shader return Output; } - ShaderIrBlock Entry = Enqueue(Start); + ShaderIrBlock Entry = Enqueue(Start + HeaderSize); while (Blocks.Count > 0) { ShaderIrBlock Current = Blocks.Dequeue(); - FillBlock(Memory, Current); + FillBlock(Memory, Current, Start + HeaderSize); //Set child blocks. "Branch" is the block the branch instruction //points to (when taken), "Next" is the block at the next address, @@ -122,14 +124,14 @@ namespace Ryujinx.Graphics.Gal.Shader return Graph; } - private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block) + private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block, long Beginning) { long Position = Block.Position; do { //Ignore scheduling instructions, which are written every 32 bytes. - if ((Position & 0x1f) == 0) + if (((Position - Beginning) & 0x1f) == 0) { Position += 8; @@ -147,7 +149,7 @@ namespace Ryujinx.Graphics.Gal.Shader if (AddDbgComments) { - string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} "; + string DbgOpCode = $"0x{(Position - Beginning - 8):x16}: 0x{OpCode:x16} "; DbgOpCode += (Decode?.Method.Name ?? "???"); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs new file mode 100644 index 0000000000..8e5057ed9e --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs @@ -0,0 +1,73 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderHeader + { + public const int PointList = 1; + public const int LineStrip = 6; + public const int TriangleStrip = 7; + + public int SphType { get; private set; } + public int Version { get; private set; } + public int ShaderType { get; private set; } + public bool MrtEnable { get; private set; } + public bool KillsPixels { get; private set; } + public bool DoesGlobalStore { get; private set; } + public int SassVersion { get; private set; } + public bool DoesLoadOrStore { get; private set; } + public bool DoesFp64 { get; private set; } + public int StreamOutMask { get; private set; } + + public int ShaderLocalMemoryLowSize { get; private set; } + public int PerPatchAttributeCount { get; private set; } + + public int ShaderLocalMemoryHighSize { get; private set; } + public int ThreadsPerInputPrimitive { get; private set; } + + public int ShaderLocalMemoryCrsSize { get; private set; } + public int OutputTopology { get; private set; } + + public int MaxOutputVertexCount { get; private set; } + public int StoreReqStart { get; private set; } + public int StoreReqEnd { get; private set; } + + public ShaderHeader(IGalMemory Memory, long Position) + { + uint CommonWord0 = (uint)Memory.ReadInt32(Position + 0); + uint CommonWord1 = (uint)Memory.ReadInt32(Position + 4); + uint CommonWord2 = (uint)Memory.ReadInt32(Position + 8); + uint CommonWord3 = (uint)Memory.ReadInt32(Position + 12); + uint CommonWord4 = (uint)Memory.ReadInt32(Position + 16); + + SphType = ReadBits(CommonWord0, 0, 5); + Version = ReadBits(CommonWord0, 5, 5); + ShaderType = ReadBits(CommonWord0, 10, 4); + MrtEnable = ReadBits(CommonWord0, 14, 1) != 0; + KillsPixels = ReadBits(CommonWord0, 15, 1) != 0; + DoesGlobalStore = ReadBits(CommonWord0, 16, 1) != 0; + SassVersion = ReadBits(CommonWord0, 17, 4); + DoesLoadOrStore = ReadBits(CommonWord0, 26, 1) != 0; + DoesFp64 = ReadBits(CommonWord0, 27, 1) != 0; + StreamOutMask = ReadBits(CommonWord0, 28, 4); + + ShaderLocalMemoryLowSize = ReadBits(CommonWord1, 0, 24); + PerPatchAttributeCount = ReadBits(CommonWord1, 24, 8); + + ShaderLocalMemoryHighSize = ReadBits(CommonWord2, 0, 24); + ThreadsPerInputPrimitive = ReadBits(CommonWord2, 24, 8); + + ShaderLocalMemoryCrsSize = ReadBits(CommonWord3, 0, 24); + OutputTopology = ReadBits(CommonWord3, 24, 4); + + MaxOutputVertexCount = ReadBits(CommonWord4, 0, 12); + StoreReqStart = ReadBits(CommonWord4, 12, 8); + StoreReqEnd = ReadBits(CommonWord4, 24, 8); + } + + private static int ReadBits(uint Word, int Offset, int BitWidth) + { + uint Mask = (1u << BitWidth) - 1u; + + return (int)((Word >> Offset) & Mask); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs index 9841f58ff0..fd86cadb10 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs @@ -82,6 +82,9 @@ namespace Ryujinx.Graphics.Gal.Shader Bra, Exit, - Kil + Kil, + + Emit, + Cut } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs index fa612de76a..f17d9c0e62 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs @@ -2,13 +2,14 @@ namespace Ryujinx.Graphics.Gal.Shader { class ShaderIrOperAbuf : ShaderIrNode { - public int Offs { get; private set; } - public int GprIndex { get; private set; } + public int Offs { get; private set; } - public ShaderIrOperAbuf(int Offs, int GprIndex) + public ShaderIrNode Vertex { get; private set; } + + public ShaderIrOperAbuf(int Offs, ShaderIrNode Vertex) { - this.Offs = Offs; - this.GprIndex = GprIndex; + this.Offs = Offs; + this.Vertex = Vertex; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs index 1ac1178512..3f20dc4465 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -74,6 +74,7 @@ namespace Ryujinx.Graphics.Gal.Shader Set("0100110000100x", ShaderDecode.Imnmx_C); Set("0011100x00100x", ShaderDecode.Imnmx_I); Set("0101110000100x", ShaderDecode.Imnmx_R); + Set("1110111111010x", ShaderDecode.Isberd); Set("11100000xxxxxx", ShaderDecode.Ipa); Set("0100110000011x", ShaderDecode.Iscadd_C); Set("0011100x00011x", ShaderDecode.Iscadd_I); @@ -95,7 +96,9 @@ namespace Ryujinx.Graphics.Gal.Shader Set("0011100x10011x", ShaderDecode.Mov_I); Set("000000010000xx", ShaderDecode.Mov_I32); Set("0101110010011x", ShaderDecode.Mov_R); + Set("1111000011001x", ShaderDecode.Mov_S); Set("0101000010000x", ShaderDecode.Mufu); + Set("1111101111100x", ShaderDecode.Out_R); Set("0101000010010x", ShaderDecode.Psetp); Set("0100110010010x", ShaderDecode.Rro_C); Set("0011100x10010x", ShaderDecode.Rro_I); @@ -114,6 +117,7 @@ namespace Ryujinx.Graphics.Gal.Shader Set("1101111101001x", ShaderDecode.Texq); Set("1101100xxxxxxx", ShaderDecode.Texs); Set("1101101xxxxxxx", ShaderDecode.Tlds); + Set("01011111xxxxxx", ShaderDecode.Vmad); Set("0100111xxxxxxx", ShaderDecode.Xmad_CR); Set("0011011x00xxxx", ShaderDecode.Xmad_I); Set("010100010xxxxx", ShaderDecode.Xmad_RC); diff --git a/Ryujinx.Graphics/Gal/ShaderDumper.cs b/Ryujinx.Graphics/Gal/ShaderDumper.cs index 7cd56b21ef..541368e89b 100644 --- a/Ryujinx.Graphics/Gal/ShaderDumper.cs +++ b/Ryujinx.Graphics/Gal/ShaderDumper.cs @@ -18,13 +18,21 @@ namespace Ryujinx.Graphics.Gal string FileName = "Shader" + DumpIndex.ToString("d4") + "." + ShaderExtension(Type) + ExtSuffix + ".bin"; - string FilePath = Path.Combine(DumpDir(), FileName); + string FullPath = Path.Combine(FullDir(), FileName); + string CodePath = Path.Combine(CodeDir(), FileName); DumpIndex++; - using (FileStream Output = File.Create(FilePath)) - using (BinaryWriter Writer = new BinaryWriter(Output)) + using (FileStream FullFile = File.Create(FullPath)) + using (FileStream CodeFile = File.Create(CodePath)) + using (BinaryWriter FullWriter = new BinaryWriter(FullFile)) + using (BinaryWriter CodeWriter = new BinaryWriter(CodeFile)) { + for (long i = 0; i < 0x50; i += 4) + { + FullWriter.Write(Memory.ReadInt32(Position + i)); + } + long Offset = 0; ulong Instruction = 0; @@ -32,8 +40,8 @@ namespace Ryujinx.Graphics.Gal //Dump until a NOP instruction is found while ((Instruction >> 52 & 0xfff8) != 0x50b0) { - uint Word0 = (uint)Memory.ReadInt32(Position + Offset + 0); - uint Word1 = (uint)Memory.ReadInt32(Position + Offset + 4); + uint Word0 = (uint)Memory.ReadInt32(Position + 0x50 + Offset + 0); + uint Word1 = (uint)Memory.ReadInt32(Position + 0x50 + Offset + 4); Instruction = Word0 | (ulong)Word1 << 32; @@ -44,7 +52,8 @@ namespace Ryujinx.Graphics.Gal break; } - Writer.Write(Instruction); + FullWriter.Write(Instruction); + CodeWriter.Write(Instruction); Offset += 8; } @@ -52,13 +61,24 @@ namespace Ryujinx.Graphics.Gal //Align to meet nvdisasm requeriments while (Offset % 0x20 != 0) { - Writer.Write(0); + FullWriter.Write(0); + CodeWriter.Write(0); Offset += 4; } } } + private static string FullDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Full")); + } + + private static string CodeDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Code")); + } + private static string DumpDir() { if (string.IsNullOrEmpty(RuntimeDir)) @@ -79,6 +99,16 @@ namespace Ryujinx.Graphics.Gal return RuntimeDir; } + private static string CreateAndReturn(string Dir) + { + if (!Directory.Exists(Dir)) + { + Directory.CreateDirectory(Dir); + } + + return Dir; + } + private static string ShaderExtension(GalShaderType Type) { switch (Type) diff --git a/Ryujinx.Graphics/Ryujinx.Graphics.csproj b/Ryujinx.Graphics/Ryujinx.Graphics.csproj index d0fad10765..7d86cbe134 100644 --- a/Ryujinx.Graphics/Ryujinx.Graphics.csproj +++ b/Ryujinx.Graphics/Ryujinx.Graphics.csproj @@ -21,13 +21,4 @@ - - - GlFbVtxShader - - - GlFbFragShader - - - diff --git a/Ryujinx.HLE/Font/SharedFontManager.cs b/Ryujinx.HLE/Font/SharedFontManager.cs new file mode 100644 index 0000000000..fce270de86 --- /dev/null +++ b/Ryujinx.HLE/Font/SharedFontManager.cs @@ -0,0 +1,177 @@ +using ChocolArm64.Exceptions; +using ChocolArm64.Memory; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.Resource; +using System; +using System.Collections.Generic; +using System.IO; + + +namespace Ryujinx.HLE.Font +{ + public class SharedFontManager + { + private const uint SharedMemorySize = 0x1100000; + private Logger Log; + + private string FontsPath; + + private object ShMemLock; + + private (AMemory, long, long)[] ShMemPositions; + + private Dictionary FontData; + + private uint[] LoadedFonts; + + public SharedFontManager(Logger Log, string SystemPath) + { + this.Log = Log; + this.FontsPath = Path.Combine(SystemPath, "fonts"); + + ShMemLock = new object(); + + ShMemPositions = new(AMemory, long, long)[0]; + + FontData = new Dictionary() + { + { SharedFontType.JapanUsEurope, GetData("FontStandard") }, + { SharedFontType.SimplifiedChinese, GetData("FontChineseSimplified") }, + { SharedFontType.SimplifiedChineseEx, GetData("FontExtendedChineseSimplified") }, + { SharedFontType.TraditionalChinese, GetData("FontChineseTraditional") }, + { SharedFontType.Korean, GetData("FontKorean") }, + { SharedFontType.NintendoEx, GetData("FontNintendoExtended") } + }; + + int FontMemoryUsage = 0; + foreach (byte[] data in FontData.Values) + { + FontMemoryUsage += data.Length; + FontMemoryUsage += 0x8; + } + + if (FontMemoryUsage > SharedMemorySize) + { + throw new InvalidSystemResourceException($"The sum of all fonts size exceed the shared memory size. Please make sure that the fonts don't exceed {SharedMemorySize} bytes in total. (actual size: {FontMemoryUsage} bytes)"); + } + + LoadedFonts = new uint[FontData.Count]; + } + + public byte[] GetData(string FontName) + { + string FontFilePath = Path.Combine(FontsPath, $"{FontName}.ttf"); + if (File.Exists(FontFilePath)) + { + return File.ReadAllBytes(FontFilePath); + } + else + { + throw new InvalidSystemResourceException($"Font \"{FontName}.ttf\" not found. Please provide it in \"{FontsPath}\"."); + } + } + + public void MapFont(SharedFontType FontType, AMemory Memory, long Position) + { + uint SharedMemoryAddressOffset = GetSharedMemoryAddressOffset(FontType); + // TODO: find what are the 8 bytes before the font + Memory.WriteUInt64(Position + SharedMemoryAddressOffset - 8, 0); + Memory.WriteBytes(Position + SharedMemoryAddressOffset, FontData[FontType]); + } + + public void PropagateNewMapFont(SharedFontType Type) + { + lock (ShMemLock) + { + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) + { + AMemoryMapInfo MemoryInfo = Memory.Manager.GetMapInfo(Position); + + if (MemoryInfo == null) + { + throw new VmmPageFaultException(Position); + } + + // The memory is read only, we need to changes that to add the new font + AMemoryPerm originalPerms = MemoryInfo.Perm; + Memory.Manager.Reprotect(Position, Size, AMemoryPerm.RW); + MapFont(Type, Memory, Position); + Memory.Manager.Reprotect(Position, Size, originalPerms); + } + } + } + + internal void ShMemMap(object sender, EventArgs e) + { + HSharedMem SharedMem = (HSharedMem)sender; + + lock (ShMemLock) + { + ShMemPositions = SharedMem.GetVirtualPositions(); + + (AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1]; + + for (int Type = 0; Type < LoadedFonts.Length; Type++) + { + if (LoadedFonts[(int)Type] == 1) + { + MapFont((SharedFontType)Type, Memory, Position); + } + } + } + } + + internal void ShMemUnmap(object sender, EventArgs e) + { + HSharedMem SharedMem = (HSharedMem)sender; + + lock (ShMemLock) + { + ShMemPositions = SharedMem.GetVirtualPositions(); + } + } + + public void Load(SharedFontType FontType) + { + if (LoadedFonts[(int)FontType] == 0) + { + PropagateNewMapFont(FontType); + } + + LoadedFonts[(int)FontType] = 1; + } + + public uint GetLoadState(SharedFontType FontType) + { + if (LoadedFonts[(int)FontType] != 1) + { + // Some games don't request a load, so we need to load it here. + Load(FontType); + return 0; + } + return LoadedFonts[(int)FontType]; + } + + public uint GetFontSize(SharedFontType FontType) + { + return (uint)FontData[FontType].Length; + } + + public uint GetSharedMemoryAddressOffset(SharedFontType FontType) + { + uint Pos = 0x8; + + for (SharedFontType Type = SharedFontType.JapanUsEurope; Type < FontType; Type++) + { + Pos += GetFontSize(Type); + Pos += 0x8; + } + + return Pos; + } + + public int Count => FontData.Count; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs b/Ryujinx.HLE/Font/SharedFontType.cs similarity index 76% rename from Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs rename to Ryujinx.HLE/Font/SharedFontType.cs index 97fd95dc9d..ca8a42b0cd 100644 --- a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs +++ b/Ryujinx.HLE/Font/SharedFontType.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.OsHle.Services.Pl +namespace Ryujinx.HLE.Font { - enum SharedFontType + public enum SharedFontType { JapanUsEurope = 0, SimplifiedChinese = 1, diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs index f150b3f5e9..d2c5f1262b 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Gal; using Ryujinx.HLE.Gpu.Memory; using Ryujinx.HLE.Gpu.Texture; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.Gpu.Engines @@ -64,6 +65,8 @@ namespace Ryujinx.HLE.Gpu.Engines bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0; int SrcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth); int SrcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight); + int SrcPitch = ReadRegister(NvGpuEngine2dReg.SrcPitch); + int SrcBlkDim = ReadRegister(NvGpuEngine2dReg.SrcBlockDimensions); bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0; int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth); @@ -71,75 +74,114 @@ namespace Ryujinx.HLE.Gpu.Engines int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch); int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions); + TextureSwizzle SrcSwizzle = SrcLinear + ? TextureSwizzle.Pitch + : TextureSwizzle.BlockLinear; + TextureSwizzle DstSwizzle = DstLinear ? TextureSwizzle.Pitch : TextureSwizzle.BlockLinear; + int SrcBlockHeight = 1 << ((SrcBlkDim >> 4) & 0xf); int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); - long Key = Vmm.GetPhysicalAddress(MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress)); - long SrcAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress); long DstAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.DstAddress); - bool IsFbTexture = Gpu.Engine3d.IsFrameBufferPosition(Key); + long SrcKey = Vmm.GetPhysicalAddress(SrcAddress); + long DstKey = Vmm.GetPhysicalAddress(DstAddress); - if (IsFbTexture && DstLinear) + bool IsSrcFb = Gpu.Engine3d.IsFrameBufferPosition(SrcKey); + bool IsDstFb = Gpu.Engine3d.IsFrameBufferPosition(DstKey); + + TextureInfo SrcTexture() { - DstSwizzle = TextureSwizzle.BlockLinear; + return new TextureInfo( + SrcAddress, + SrcWidth, + SrcHeight, + SrcPitch, + SrcBlockHeight, 1, + SrcSwizzle, + GalTextureFormat.A8B8G8R8); } - TextureInfo DstTexture = new TextureInfo( - DstAddress, - DstWidth, - DstHeight, - DstBlockHeight, - DstBlockHeight, - DstSwizzle, - GalTextureFormat.A8B8G8R8); - - if (IsFbTexture) + TextureInfo DstTexture() { - //TODO: Change this when the correct frame buffer resolution is used. - //Currently, the frame buffer size is hardcoded to 1280x720. - SrcWidth = 1280; - SrcHeight = 720; + return new TextureInfo( + DstAddress, + DstWidth, + DstHeight, + DstPitch, + DstBlockHeight, 1, + DstSwizzle, + GalTextureFormat.A8B8G8R8); + } - Gpu.Renderer.FrameBuffer.GetBufferData(Key, (byte[] Buffer) => + //TODO: fb -> fb copies, tex -> fb copies, formats other than RGBA8, + //make it throw for unimpl stuff (like the copy mode)... + if (IsSrcFb && IsDstFb) + { + //Frame Buffer -> Frame Buffer copy. + Gpu.Renderer.FrameBuffer.Copy( + SrcKey, + DstKey, + 0, + 0, + SrcWidth, + SrcHeight, + 0, + 0, + DstWidth, + DstHeight); + } + if (IsSrcFb) + { + //Frame Buffer -> Texture copy. + Gpu.Renderer.FrameBuffer.GetBufferData(SrcKey, (byte[] Buffer) => { - CopyTexture( - Vmm, - DstTexture, - Buffer, - SrcWidth, - SrcHeight); + TextureInfo Src = SrcTexture(); + TextureInfo Dst = DstTexture(); + + if (Src.Width != Dst.Width || + Src.Height != Dst.Height) + { + throw new NotImplementedException("Texture resizing is not supported"); + } + + TextureWriter.Write(Vmm, Dst, Buffer); }); } + else if (IsDstFb) + { + //Texture -> Frame Buffer copy. + const GalTextureFormat Format = GalTextureFormat.A8B8G8R8; + + byte[] Buffer = TextureReader.Read(Vmm, SrcTexture()); + + Gpu.Renderer.FrameBuffer.SetBufferData( + DstKey, + DstWidth, + DstHeight, + Format, + Buffer); + } else { - long Size = SrcWidth * SrcHeight * 4; + //Texture -> Texture copy. + TextureInfo Src = SrcTexture(); + TextureInfo Dst = DstTexture(); - byte[] Buffer = Vmm.ReadBytes(SrcAddress, Size); + if (Src.Width != Dst.Width || + Src.Height != Dst.Height) + { + throw new NotImplementedException("Texture resizing is not supported"); + } - CopyTexture( - Vmm, - DstTexture, - Buffer, - SrcWidth, - SrcHeight); + TextureWriter.Write(Vmm, Dst, TextureReader.Read(Vmm, Src)); } } - private void CopyTexture( - NvGpuVmm Vmm, - TextureInfo Texture, - byte[] Buffer, - int Width, - int Height) - { - TextureWriter.Write(Vmm, Texture, Buffer, Width, Height); - } - private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg) { return diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs index 5c474ab0bc..3dd0e98e60 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs @@ -25,6 +25,8 @@ namespace Ryujinx.HLE.Gpu.Engines private HashSet FrameBuffers; + private List[] UploadedKeys; + public NvGpuEngine3d(NvGpu Gpu) { this.Gpu = Gpu; @@ -57,6 +59,13 @@ namespace Ryujinx.HLE.Gpu.Engines } FrameBuffers = new HashSet(); + + UploadedKeys = new List[(int)NvGpuBufferType.Count]; + + for (int i = 0; i < UploadedKeys.Length; i++) + { + UploadedKeys[i] = new List(); + } } public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) @@ -132,10 +141,22 @@ namespace Ryujinx.HLE.Gpu.Engines int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); - //Note: Using the Width/Height results seems to give incorrect results. - //Maybe the size of all frame buffers is hardcoded to screen size? This seems unlikely. - Gpu.Renderer.FrameBuffer.Create(Key, 1280, 720); + float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 4); + float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 4); + + float SX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + FbIndex * 4); + float SY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + FbIndex * 4); + + int VpX = (int)MathF.Max(0, TX - MathF.Abs(SX)); + int VpY = (int)MathF.Max(0, TY - MathF.Abs(SY)); + + int VpW = (int)(TX + MathF.Abs(SX)) - VpX; + int VpH = (int)(TY + MathF.Abs(SY)) - VpY; + + Gpu.Renderer.FrameBuffer.Create(Key, Width, Height); Gpu.Renderer.FrameBuffer.Bind(Key); + + Gpu.Renderer.FrameBuffer.SetViewport(VpX, VpY, VpW, VpH); } private long[] UploadShaders(NvGpuVmm Vmm) @@ -195,8 +216,8 @@ namespace Ryujinx.HLE.Gpu.Engines Gpu.Renderer.Shader.Bind(Key); } - float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX); - float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY); + float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); + float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); Gpu.Renderer.Shader.SetFlip(SignX, SignY); @@ -220,8 +241,8 @@ namespace Ryujinx.HLE.Gpu.Engines private void SetFrontFace() { - float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX); - float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY); + float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); + float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); GalFrontFace FrontFace = (GalFrontFace)ReadRegister(NvGpuEngine3dReg.FrontFace); @@ -504,7 +525,7 @@ namespace Ryujinx.HLE.Gpu.Engines if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalTexture Texture)) { - if (NewTexture.Equals(Texture) && !Vmm.IsRegionModified(Key, Size, NvGpuBufferType.Texture)) + if (NewTexture.Equals(Texture) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture)) { Gpu.Renderer.Texture.Bind(Key, TexIndex); @@ -548,9 +569,9 @@ namespace Ryujinx.HLE.Gpu.Engines if (Cb.Enabled) { - byte[] Data = Vmm.ReadBytes(Cb.Position, (uint)Cb.Size); + IntPtr DataAddress = Vmm.GetHostAddress(Cb.Position, Cb.Size); - Gpu.Renderer.Shader.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data); + Gpu.Renderer.Shader.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Cb.Size, DataAddress); } } } @@ -581,11 +602,11 @@ namespace Ryujinx.HLE.Gpu.Engines bool IboCached = Gpu.Renderer.Rasterizer.IsIboCached(IboKey, (uint)IbSize); - if (!IboCached || Vmm.IsRegionModified(IboKey, (uint)IbSize, NvGpuBufferType.Index)) + if (!IboCached || QueryKeyUpload(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index)) { - byte[] Data = Vmm.ReadBytes(IndexPosition, (uint)IbSize); + IntPtr DataAddress = Vmm.GetHostAddress(IndexPosition, IbSize); - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, Data); + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, DataAddress); } Gpu.Renderer.Rasterizer.SetIndexArray(IbSize, IndexFormat); @@ -645,11 +666,11 @@ namespace Ryujinx.HLE.Gpu.Engines bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize); - if (!VboCached || Vmm.IsRegionModified(VboKey, VbSize, NvGpuBufferType.Vertex)) + if (!VboCached || QueryKeyUpload(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex)) { - byte[] Data = Vmm.ReadBytes(VertexPosition, VbSize); + IntPtr DataAddress = Vmm.GetHostAddress(VertexPosition, VbSize); - Gpu.Renderer.Rasterizer.CreateVbo(VboKey, Data); + Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, DataAddress); } Gpu.Renderer.Rasterizer.SetVertexArray(Stride, VboKey, Attribs[Index].ToArray()); @@ -680,6 +701,11 @@ namespace Ryujinx.HLE.Gpu.Engines if (Mode == 0) { + foreach (List Uploaded in UploadedKeys) + { + Uploaded.Clear(); + } + //Write mode. Vmm.WriteInt32(Position, Seq); } @@ -762,5 +788,19 @@ namespace Ryujinx.HLE.Gpu.Engines { return FrameBuffers.Contains(Position); } + + private bool QueryKeyUpload(NvGpuVmm Vmm, long Key, long Size, NvGpuBufferType Type) + { + List Uploaded = UploadedKeys[(int)Type]; + + if (Uploaded.Contains(Key)) + { + return false; + } + + Uploaded.Add(Key); + + return Vmm.IsRegionModified(Key, Size, Type); + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs index 3de2885ef2..e7dabe44a4 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs @@ -6,12 +6,14 @@ namespace Ryujinx.HLE.Gpu.Engines FrameBufferNWidth = 0x202, FrameBufferNHeight = 0x203, FrameBufferNFormat = 0x204, - ViewportScaleX = 0x280, - ViewportScaleY = 0x281, - ViewportScaleZ = 0x282, - ViewportTranslateX = 0x283, - ViewportTranslateY = 0x284, - ViewportTranslateZ = 0x285, + ViewportNScaleX = 0x280, + ViewportNScaleY = 0x281, + ViewportNScaleZ = 0x282, + ViewportNTranslateX = 0x283, + ViewportNTranslateY = 0x284, + ViewportNTranslateZ = 0x285, + ViewportNHoriz = 0x300, + ViewportNVert = 0x301, VertexArrayFirst = 0x35d, VertexArrayCount = 0x35e, ClearDepth = 0x364, diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs index 0c81dd1508..7b23e49fac 100644 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs +++ b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs @@ -1,5 +1,6 @@ using ChocolArm64.Memory; using Ryujinx.Graphics.Gal; +using System; using System.Collections.Concurrent; namespace Ryujinx.HLE.Gpu.Memory @@ -279,6 +280,11 @@ namespace Ryujinx.HLE.Gpu.Memory return Cache.IsRegionModified(Memory, BufferType, PA, Size); } + public IntPtr GetHostAddress(long Position, long Size) + { + return Memory.GetHostAddress(GetPhysicalAddress(Position), Size); + } + public byte ReadByte(long Position) { Position = GetPhysicalAddress(Position); diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs index ac9bd850e0..3c25604433 100644 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs +++ b/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs @@ -19,12 +19,14 @@ namespace Ryujinx.HLE.Gpu.Memory public Range(long Start, long End) { this.Start = Start; - this.End = End; + this.End = End; } } private List[] Regions; + private HashSet ResidencyKeys; + public LinkedListNode Node { get; set; } public int Timestamp { get; private set; } @@ -37,6 +39,27 @@ namespace Ryujinx.HLE.Gpu.Memory { Regions[Index] = new List(); } + + ResidencyKeys = new HashSet(); + } + + public void AddResidency(long Key) + { + ResidencyKeys.Add(Key); + } + + public void RemoveResidency(HashSet[] Residency, long PageSize) + { + for (int i = 0; i < (int)NvGpuBufferType.Count; i++) + { + foreach (Range Region in Regions[i]) + { + foreach (long Key in ResidencyKeys) + { + Residency[Region.Start / PageSize].Remove(Key); + } + } + } } public bool AddRange(long Start, long End, NvGpuBufferType BufferType) @@ -89,6 +112,10 @@ namespace Ryujinx.HLE.Gpu.Memory private LinkedList SortedCache; + private HashSet[] Residency; + + private long ResidencyPageSize; + private int CpCount; public NvGpuVmmCache() @@ -100,7 +127,7 @@ namespace Ryujinx.HLE.Gpu.Memory public bool IsRegionModified(AMemory Memory, NvGpuBufferType BufferType, long PA, long Size) { - bool[] Modified = Memory.IsRegionModified(PA, Size); + (bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(PA, Size); if (Modified == null) { @@ -111,8 +138,19 @@ namespace Ryujinx.HLE.Gpu.Memory long PageSize = Memory.GetHostPageSize(); + EnsureResidencyInitialized(PageSize); + + bool HasResidents = AddResidency(PA, Size); + + if (!HasResidents && ModifiedCount == 0) + { + return false; + } + long Mask = PageSize - 1; + long ResidencyKey = PA; + long PAEnd = PA + Size; bool RegMod = false; @@ -147,6 +185,8 @@ namespace Ryujinx.HLE.Gpu.Memory Cache[Key] = Cp; } + Cp.AddResidency(ResidencyKey); + Cp.Node = SortedCache.AddLast(Key); RegMod |= Cp.AddRange(PA, PAPgEnd, BufferType); @@ -159,6 +199,53 @@ namespace Ryujinx.HLE.Gpu.Memory return RegMod; } + private bool AddResidency(long PA, long Size) + { + long PageSize = ResidencyPageSize; + + long Mask = PageSize - 1; + + long Key = PA; + + bool ResidentFound = false; + + for (long Cursor = PA & ~Mask; Cursor < ((PA + Size + PageSize - 1) & ~Mask); Cursor += PageSize) + { + long PageIndex = Cursor / PageSize; + + Residency[PageIndex].Add(Key); + + if (Residency[PageIndex].Count > 1) + { + ResidentFound = true; + } + } + + return ResidentFound; + } + + private void EnsureResidencyInitialized(long PageSize) + { + if (Residency == null) + { + Residency = new HashSet[AMemoryMgr.RamSize / PageSize]; + + for (int i = 0; i < Residency.Length; i++) + { + Residency[i] = new HashSet(); + } + + ResidencyPageSize = PageSize; + } + else + { + if (ResidencyPageSize != PageSize) + { + throw new InvalidOperationException("Tried to change residency page size"); + } + } + } + private void ClearCachedPagesIfNeeded() { if (CpCount <= MaxCpCount) @@ -179,6 +266,8 @@ namespace Ryujinx.HLE.Gpu.Memory CachedPage Cp = Cache[Key]; + Cp.RemoveResidency(Residency, ResidencyPageSize); + Cache.Remove(Key); CpCount -= Cp.GetTotalCount(); diff --git a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs index 9df0b60002..4db0b6f106 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs @@ -55,9 +55,11 @@ namespace Ryujinx.HLE.Gpu.Texture int Pitch = (Tic[3] & 0xffff) << 5; - int BlockHeightLog2 = (Tic[3] >> 3) & 7; + int BlockHeightLog2 = (Tic[3] >> 3) & 7; + int TileWidthLog2 = (Tic[3] >> 10) & 7; int BlockHeight = 1 << BlockHeightLog2; + int TileWidth = 1 << TileWidthLog2; int Width = (Tic[4] & 0xffff) + 1; int Height = (Tic[5] & 0xffff) + 1; @@ -68,6 +70,7 @@ namespace Ryujinx.HLE.Gpu.Texture Height, Pitch, BlockHeight, + TileWidth, Swizzle, Format); diff --git a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs index dbbc87d409..1d621c9226 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs @@ -7,8 +7,14 @@ namespace Ryujinx.HLE.Gpu.Texture { static class TextureHelper { - public static ISwizzle GetSwizzle(TextureInfo Texture, int Width, int Bpp) + public static ISwizzle GetSwizzle(TextureInfo Texture, int BlockWidth, int Bpp) { + int Width = (Texture.Width + (BlockWidth - 1)) / BlockWidth; + + int AlignMask = Texture.TileWidth * (64 / Bpp) - 1; + + Width = (Width + AlignMask) & ~AlignMask; + switch (Texture.Swizzle) { case TextureSwizzle._1dBuffer: @@ -35,8 +41,11 @@ namespace Ryujinx.HLE.Gpu.Texture return Texture.Width * Texture.Height * 8; case GalTextureFormat.A8B8G8R8: + case GalTextureFormat.A2B10G10R10: case GalTextureFormat.R32: case GalTextureFormat.ZF32: + case GalTextureFormat.BF10GF11RF11: + case GalTextureFormat.Z24S8: return Texture.Width * Texture.Height * 4; case GalTextureFormat.A1B5G5R5: diff --git a/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs b/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs index 31784bbc58..2a98ce00fd 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs @@ -11,6 +11,7 @@ namespace Ryujinx.HLE.Gpu.Texture public int Pitch { get; private set; } public int BlockHeight { get; private set; } + public int TileWidth { get; private set; } public TextureSwizzle Swizzle { get; private set; } @@ -29,6 +30,8 @@ namespace Ryujinx.HLE.Gpu.Texture BlockHeight = 16; + TileWidth = 1; + Swizzle = TextureSwizzle.BlockLinear; Format = GalTextureFormat.A8B8G8R8; @@ -40,16 +43,18 @@ namespace Ryujinx.HLE.Gpu.Texture int Height, int Pitch, int BlockHeight, + int TileWidth, TextureSwizzle Swizzle, GalTextureFormat Format) { - this.Position = Position; - this.Width = Width; - this.Height = Height; - this.Pitch = Pitch; - this.BlockHeight = BlockHeight; - this.Swizzle = Swizzle; - this.Format = Format; + this.Position = Position; + this.Width = Width; + this.Height = Height; + this.Pitch = Pitch; + this.BlockHeight = BlockHeight; + this.TileWidth = TileWidth; + this.Swizzle = Swizzle; + this.Format = Format; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs index 1d8c8056a6..f8cd676597 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs @@ -13,7 +13,10 @@ namespace Ryujinx.HLE.Gpu.Texture case GalTextureFormat.R32G32B32A32: return Read16Bpp (Memory, Texture); case GalTextureFormat.R16G16B16A16: return Read8Bpp (Memory, Texture); case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture); + case GalTextureFormat.A2B10G10R10: return Read4Bpp (Memory, Texture); case GalTextureFormat.R32: return Read4Bpp (Memory, Texture); + case GalTextureFormat.BF10GF11RF11: return Read4Bpp (Memory, Texture); + case GalTextureFormat.Z24S8: return Read4Bpp (Memory, Texture); case GalTextureFormat.A1B5G5R5: return Read5551 (Memory, Texture); case GalTextureFormat.B5G6R5: return Read565 (Memory, Texture); case GalTextureFormat.G8R8: return Read2Bpp (Memory, Texture); @@ -42,7 +45,7 @@ namespace Ryujinx.HLE.Gpu.Texture case GalTextureFormat.Astc2D8x5: return Read16BptCompressedTexture(Memory, Texture, 8, 5); case GalTextureFormat.Astc2D10x5: return Read16BptCompressedTexture(Memory, Texture, 10, 5); case GalTextureFormat.Astc2D10x6: return Read16BptCompressedTexture(Memory, Texture, 10, 6); - } + } throw new NotImplementedException(Texture.Format.ToString()); } @@ -54,7 +57,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 1); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 1); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -87,7 +90,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 2]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -125,7 +128,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 2]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -162,7 +165,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 2]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -195,7 +198,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 4]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 4); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -228,7 +231,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 8]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 8); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -261,7 +264,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 16]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 16); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -296,7 +299,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 8]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 4, 8); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -329,7 +332,7 @@ namespace Ryujinx.HLE.Gpu.Texture byte[] Output = new byte[Width * Height * 16]; - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, BlockWidth, 16); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, diff --git a/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs b/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs index b64302a5ab..a87d4545b0 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs @@ -6,29 +6,9 @@ namespace Ryujinx.HLE.Gpu.Texture { static class TextureWriter { - public static void Write( - IAMemory Memory, - TextureInfo Texture, - byte[] Data, - int Width, - int Height) + public unsafe static void Write(IAMemory Memory, TextureInfo Texture, byte[] Data) { - switch (Texture.Format) - { - case GalTextureFormat.A8B8G8R8: Write4Bpp(Memory, Texture, Data, Width, Height); break; - - default: throw new NotImplementedException(Texture.Format.ToString()); - } - } - - private unsafe static void Write4Bpp( - IAMemory Memory, - TextureInfo Texture, - byte[] Data, - int Width, - int Height) - { - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4); + ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 4); (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( Memory, @@ -38,8 +18,8 @@ namespace Ryujinx.HLE.Gpu.Texture { long InOffs = 0; - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) + for (int Y = 0; Y < Texture.Height; Y++) + for (int X = 0; X < Texture.Width; X++) { long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); diff --git a/Ryujinx.HLE/Hid/Hid.cs b/Ryujinx.HLE/Hid/Hid.cs index 2f007f1fb0..43c040bb5d 100644 --- a/Ryujinx.HLE/Hid/Hid.cs +++ b/Ryujinx.HLE/Hid/Hid.cs @@ -67,7 +67,7 @@ namespace Ryujinx.HLE.Input private object ShMemLock; - private (AMemory, long)[] ShMemPositions; + private (AMemory, long, long)[] ShMemPositions; public Hid(Logger Log) { @@ -75,7 +75,7 @@ namespace Ryujinx.HLE.Input ShMemLock = new object(); - ShMemPositions = new (AMemory, long)[0]; + ShMemPositions = new (AMemory, long, long)[0]; } internal void ShMemMap(object sender, EventArgs e) @@ -86,7 +86,7 @@ namespace Ryujinx.HLE.Input { ShMemPositions = SharedMem.GetVirtualPositions(); - (AMemory Memory, long Position) = ShMemPositions[ShMemPositions.Length - 1]; + (AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1]; for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8) { @@ -167,7 +167,7 @@ namespace Ryujinx.HLE.Input { lock (ShMemLock) { - foreach ((AMemory Memory, long Position) in ShMemPositions) + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) { long ControllerOffset = Position + HidControllersOffset; @@ -218,7 +218,7 @@ namespace Ryujinx.HLE.Input { lock (ShMemLock) { - foreach ((AMemory Memory, long Position) in ShMemPositions) + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) { long TouchScreenOffset = Position + HidTouchScreenOffset; diff --git a/Ryujinx.HLE/Logging/LogClass.cs b/Ryujinx.HLE/Logging/LogClass.cs index c377ace662..95cae7e0ab 100644 --- a/Ryujinx.HLE/Logging/LogClass.cs +++ b/Ryujinx.HLE/Logging/LogClass.cs @@ -4,6 +4,7 @@ namespace Ryujinx.HLE.Logging { Audio, Cpu, + Font, Gpu, Hid, Kernel, diff --git a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs index 6426e585ea..cd3d82237c 100644 --- a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs +++ b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs @@ -6,37 +6,37 @@ namespace Ryujinx.HLE.OsHle.Handles { class HSharedMem { - private List<(AMemory, long)> Positions; + private List<(AMemory, long, long)> Positions; public EventHandler MemoryMapped; public EventHandler MemoryUnmapped; public HSharedMem() { - Positions = new List<(AMemory, long)>(); + Positions = new List<(AMemory, long, long)>(); } - public void AddVirtualPosition(AMemory Memory, long Position) + public void AddVirtualPosition(AMemory Memory, long Position, long Size) { lock (Positions) { - Positions.Add((Memory, Position)); + Positions.Add((Memory, Position, Size)); MemoryMapped?.Invoke(this, EventArgs.Empty); } } - public void RemoveVirtualPosition(AMemory Memory, long Position) + public void RemoveVirtualPosition(AMemory Memory, long Position, long Size) { lock (Positions) { - Positions.Remove((Memory, Position)); + Positions.Remove((Memory, Position, Size)); MemoryUnmapped?.Invoke(this, EventArgs.Empty); } } - public (AMemory, long)[] GetVirtualPositions() + public (AMemory, long, long)[] GetVirtualPositions() { return Positions.ToArray(); } diff --git a/Ryujinx.HLE/OsHle/Handles/KThread.cs b/Ryujinx.HLE/OsHle/Handles/KThread.cs index 3db46f3d67..2b980d17b1 100644 --- a/Ryujinx.HLE/OsHle/Handles/KThread.cs +++ b/Ryujinx.HLE/OsHle/Handles/KThread.cs @@ -9,10 +9,12 @@ namespace Ryujinx.HLE.OsHle.Handles public int CoreMask { get; set; } - public long MutexAddress { get; set; } - public long CondVarAddress { get; set; } + public long MutexAddress { get; set; } + public long CondVarAddress { get; set; } + public long ArbiterWaitAddress { get; set; } public bool CondVarSignaled { get; set; } + public bool ArbiterSignaled { get; set; } private Process Process; diff --git a/Ryujinx.HLE/OsHle/Horizon.cs b/Ryujinx.HLE/OsHle/Horizon.cs index 9d8a937ff2..f420dc0df8 100644 --- a/Ryujinx.HLE/OsHle/Horizon.cs +++ b/Ryujinx.HLE/OsHle/Horizon.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Logging; using Ryujinx.HLE.OsHle.Handles; using System; @@ -76,6 +77,25 @@ namespace Ryujinx.HLE.OsHle } } + void LoadNpdm(string FileName) + { + string File = Directory.GetFiles(ExeFsDir, FileName)[0]; + + Ns.Log.PrintInfo(LogClass.Loader, "Loading Title Metadata..."); + + using (FileStream Input = new FileStream(File, FileMode.Open)) + { + MainProcess.Metadata = new Npdm(Input); + } + } + + LoadNpdm("*.npdm"); + + if (!MainProcess.Metadata.Is64Bits) + { + throw new NotImplementedException("32-bit titles are unsupported!"); + } + LoadNso("rtld"); MainProcess.SetEmptyArgs(); @@ -96,7 +116,6 @@ namespace Ryujinx.HLE.OsHle if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/"))) { - // TODO: avoid copying the file if we are already inside a sdmc directory string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}"; string TempPath = Ns.VFs.SwitchPathToSystemPath(SwitchPath); diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs index 953cac7643..609cc6e05f 100644 --- a/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs +++ b/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs @@ -47,13 +47,15 @@ namespace Ryujinx.HLE.OsHle.Ipc HasPId = true; } - public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc( - new int[] { Handle }, - new int[0]); + public static IpcHandleDesc MakeCopy(params int[] Handles) + { + return new IpcHandleDesc(Handles, new int[0]); + } - public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc( - new int[0], - new int[] { Handle }); + public static IpcHandleDesc MakeMove(params int[] Handles) + { + return new IpcHandleDesc(new int[0], Handles); + } public byte[] GetBytes() { diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs index 9b46cf4bf1..cdb844cfe2 100644 --- a/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs +++ b/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs @@ -21,7 +21,8 @@ namespace Ryujinx.HLE.OsHle.Ipc { BinaryReader ReqReader = new BinaryReader(Raw); - if (Request.Type == IpcMessageType.Request) + if (Request.Type == IpcMessageType.Request || + Request.Type == IpcMessageType.RequestWithContext) { Response.Type = IpcMessageType.Response; @@ -44,7 +45,8 @@ namespace Ryujinx.HLE.OsHle.Ipc Response.RawData = ResMS.ToArray(); } } - else if (Request.Type == IpcMessageType.Control) + else if (Request.Type == IpcMessageType.Control || + Request.Type == IpcMessageType.ControlWithContext) { long Magic = ReqReader.ReadInt64(); long CmdId = ReqReader.ReadInt64(); diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs index 4e648aec97..0a64a16426 100644 --- a/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs +++ b/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.OsHle.Ipc public List ExchangeBuff { get; private set; } public List RecvListBuff { get; private set; } - public List ResponseObjIds { get; private set; } + public List ObjectIds { get; private set; } public byte[] RawData { get; set; } @@ -27,7 +27,7 @@ namespace Ryujinx.HLE.OsHle.Ipc ExchangeBuff = new List(); RecvListBuff = new List(); - ResponseObjIds = new List(); + ObjectIds = new List(); } public IpcMessage(byte[] Data, long CmdPtr) : this() diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs index f596fea461..3db6844e0a 100644 --- a/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs +++ b/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs @@ -2,9 +2,11 @@ namespace Ryujinx.HLE.OsHle.Ipc { enum IpcMessageType { - Response = 0, - CloseSession = 2, - Request = 4, - Control = 5 + Response = 0, + CloseSession = 2, + Request = 4, + Control = 5, + RequestWithContext = 6, + ControlWithContext = 7 } } \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/AddressArbiter.cs b/Ryujinx.HLE/OsHle/Kernel/AddressArbiter.cs new file mode 100644 index 0000000000..ce9ef0cd87 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/AddressArbiter.cs @@ -0,0 +1,112 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.HLE.OsHle.Handles; + +using static Ryujinx.HLE.OsHle.ErrorCode; + +namespace Ryujinx.HLE.OsHle.Kernel +{ + static class AddressArbiter + { + static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout) + { + KThread CurrentThread = Process.GetThread(ThreadState.Tpidr); + + Process.Scheduler.SetReschedule(CurrentThread.ProcessorId); + + CurrentThread.ArbiterWaitAddress = Address; + CurrentThread.ArbiterSignaled = false; + + Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout)); + + if (!CurrentThread.ArbiterSignaled) + { + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + return 0; + } + + public static ulong WaitForAddressIfLessThan(Process Process, + AThreadState ThreadState, + AMemory Memory, + long Address, + int Value, + ulong Timeout, + bool ShouldDecrement) + { + Memory.SetExclusive(ThreadState, Address); + + int CurrentValue = Memory.ReadInt32(Address); + + while (true) + { + if (Memory.TestExclusive(ThreadState, Address)) + { + if (CurrentValue < Value) + { + if (ShouldDecrement) + { + Memory.WriteInt32(Address, CurrentValue - 1); + } + + Memory.ClearExclusiveForStore(ThreadState); + } + else + { + Memory.ClearExclusiveForStore(ThreadState); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + break; + } + + Memory.SetExclusive(ThreadState, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + + if (Timeout == 0) + { + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + return WaitForAddress(Process, ThreadState, Address, Timeout); + } + + public static ulong WaitForAddressIfEqual(Process Process, + AThreadState ThreadState, + AMemory Memory, + long Address, + int Value, + ulong Timeout) + { + if (Memory.ReadInt32(Address) != Value) + { + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + if (Timeout == 0) + { + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + return WaitForAddress(Process, ThreadState, Address, Timeout); + } + } + + enum ArbitrationType : int + { + WaitIfLessThan, + DecrementAndWaitIfLessThan, + WaitIfEqual + } + + enum SignalType : int + { + Signal, + IncrementAndSignalIfEqual, + ModifyByWaitingCountAndSignalIfEqual + } +} diff --git a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs index ad4fdfb6bd..bbae532555 100644 --- a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs +++ b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs @@ -12,7 +12,7 @@ namespace Ryujinx.HLE.OsHle.Kernel public const int Timeout = 117; public const int Canceled = 118; public const int CountOutOfRange = 119; - public const int InvalidInfo = 120; + public const int InvalidEnumValue = 120; public const int InvalidThread = 122; public const int InvalidState = 125; } diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs index e05073fda3..6f7bc42fd7 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs @@ -22,7 +22,7 @@ namespace Ryujinx.HLE.OsHle.Kernel private ConcurrentDictionary SyncWaits; - private HashSet<(HSharedMem, long)> MappedSharedMems; + private HashSet<(HSharedMem, long, long)> MappedSharedMems; private ulong CurrentHeapSize; @@ -73,7 +73,8 @@ namespace Ryujinx.HLE.OsHle.Kernel { 0x2c, SvcMapPhysicalMemory }, { 0x2d, SvcUnmapPhysicalMemory }, { 0x32, SvcSetThreadActivity }, - { 0x33, SvcGetThreadContext3 } + { 0x33, SvcGetThreadContext3 }, + { 0x34, SvcWaitForAddress } }; this.Ns = Ns; @@ -82,7 +83,7 @@ namespace Ryujinx.HLE.OsHle.Kernel SyncWaits = new ConcurrentDictionary(); - MappedSharedMems = new HashSet<(HSharedMem, long)>(); + MappedSharedMems = new HashSet<(HSharedMem, long, long)>(); } static SvcHandler() @@ -137,9 +138,9 @@ namespace Ryujinx.HLE.OsHle.Kernel { lock (MappedSharedMems) { - foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems) + foreach ((HSharedMem SharedMem, long Position, long Size) in MappedSharedMems) { - SharedMem.RemoveVirtualPosition(Memory, Position); + SharedMem.RemoveVirtualPosition(Memory, Position, Size); } MappedSharedMems.Clear(); diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs index bb73f1ea17..f10cad7a20 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs @@ -174,15 +174,15 @@ namespace Ryujinx.HLE.OsHle.Kernel AMemoryHelper.FillWithZeros(Memory, Src, (int)Size); + SharedMem.AddVirtualPosition(Memory, Src, Size); + Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm); lock (MappedSharedMems) { - MappedSharedMems.Add((SharedMem, Src)); + MappedSharedMems.Add((SharedMem, Src, Size)); } - SharedMem.AddVirtualPosition(Memory, Src); - ThreadState.X0 = 0; } @@ -210,11 +210,11 @@ namespace Ryujinx.HLE.OsHle.Kernel { Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory); - SharedMem.RemoveVirtualPosition(Memory, Src); + SharedMem.RemoveVirtualPosition(Memory, Src, Size); lock (MappedSharedMems) { - MappedSharedMems.Remove((SharedMem, Src)); + MappedSharedMems.Remove((SharedMem, Src, Size)); } ThreadState.X0 = 0; diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs index a32b2d86fd..a968a1dbc1 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs @@ -242,7 +242,6 @@ namespace Ryujinx.HLE.OsHle.Kernel Process.Scheduler.Suspend(CurrThread); IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); - long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr); Thread.Yield(); @@ -294,7 +293,7 @@ namespace Ryujinx.HLE.OsHle.Kernel InfoType == 19 || InfoType == 20) { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo); + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); return; } diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs index ec9c40e08b..9fc426176c 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs @@ -197,6 +197,57 @@ namespace Ryujinx.HLE.OsHle.Kernel Process.Scheduler.EnterWait(CurrThread); } + private void SvcWaitForAddress(AThreadState ThreadState) + { + long Address = (long)ThreadState.X0; + ArbitrationType Type = (ArbitrationType)ThreadState.X1; + int Value = (int)ThreadState.X2; + ulong Timeout = ThreadState.X3; + + Ns.Log.PrintDebug(LogClass.KernelSvc, + "Address = " + Address.ToString("x16") + ", " + + "ArbitrationType = " + Type .ToString() + ", " + + "Value = " + Value .ToString("x8") + ", " + + "Timeout = " + Timeout.ToString("x16")); + + if (IsPointingInsideKernel(Address)) + { + Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); + + return; + } + + if (IsWordAddressUnaligned(Address)) + { + Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); + + return; + } + + switch (Type) + { + case ArbitrationType.WaitIfLessThan: + ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false); + break; + + case ArbitrationType.DecrementAndWaitIfLessThan: + ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true); + break; + + case ArbitrationType.WaitIfEqual: + ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout); + break; + + default: + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); + break; + } + } + private void MutexUnlock(KThread CurrThread, long MutexAddress) { lock (Process.ThreadSyncLock) diff --git a/Ryujinx.HLE/OsHle/Process.cs b/Ryujinx.HLE/OsHle/Process.cs index be27dcc288..ac5ed45602 100644 --- a/Ryujinx.HLE/OsHle/Process.cs +++ b/Ryujinx.HLE/OsHle/Process.cs @@ -4,6 +4,7 @@ using ChocolArm64.Memory; using ChocolArm64.State; using Ryujinx.HLE.Loaders; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Logging; using Ryujinx.HLE.OsHle.Diagnostics; using Ryujinx.HLE.OsHle.Exceptions; @@ -48,6 +49,8 @@ namespace Ryujinx.HLE.OsHle public AppletStateMgr AppletState { get; private set; } + public Npdm Metadata { get; set; } + private SvcHandler SvcHandler; private ConcurrentDictionary TlsSlots; @@ -403,11 +406,6 @@ namespace Ryujinx.HLE.OsHle { if (Disposing && !Disposed) { - if (NeedsHbAbi && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix)) - { - File.Delete(Executables[0].FilePath); - } - //If there is still some thread running, disposing the objects is not //safe as the thread may try to access those resources. Instead, we set //the flag to have the Process disposed when all threads finishes. @@ -431,6 +429,11 @@ namespace Ryujinx.HLE.OsHle } } + if (NeedsHbAbi && Executables.Count > 0 && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix)) + { + File.Delete(Executables[0].FilePath); + } + INvDrvServices.UnloadProcess(this); AppletState.Dispose(); diff --git a/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs b/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs index ee0fb91569..10d69b9b08 100644 --- a/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs +++ b/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs @@ -17,7 +17,9 @@ namespace Ryujinx.HLE.OsHle.Services.Am { m_Commands = new Dictionary() { + { 0, Exit }, { 1, LockExit }, + { 2, UnlockExit }, { 9, GetLibraryAppletLaunchableEvent }, { 10, SetScreenShotPermission }, { 11, SetOperationModeChangedNotification }, @@ -31,8 +33,24 @@ namespace Ryujinx.HLE.OsHle.Services.Am LaunchableEvent = new KEvent(); } + public long Exit(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + public long LockExit(ServiceCtx Context) { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long UnlockExit(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + return 0; } @@ -114,4 +132,4 @@ namespace Ryujinx.HLE.OsHle.Services.Am return 0; } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/OsHle/Services/Bcat/IBcatService.cs b/Ryujinx.HLE/OsHle/Services/Bcat/IBcatService.cs new file mode 100644 index 0000000000..b7754d6b63 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bcat/IBcatService.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Bcat +{ + class IBcatService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IBcatService() + { + m_Commands = new Dictionary() + { + //... + }; + } + + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Bcat/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/OsHle/Services/Bcat/IDeliveryCacheStorageService.cs new file mode 100644 index 0000000000..0b84d809e4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bcat/IDeliveryCacheStorageService.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Bcat +{ + class IDeliveryCacheStorageService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IDeliveryCacheStorageService() + { + m_Commands = new Dictionary() + { + //... + }; + } + + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Bcat/IServiceCreator.cs b/Ryujinx.HLE/OsHle/Services/Bcat/IServiceCreator.cs new file mode 100644 index 0000000000..cc1fc6f8c8 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bcat/IServiceCreator.cs @@ -0,0 +1,39 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Bcat +{ + class IServiceCreator : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IServiceCreator() + { + m_Commands = new Dictionary() + { + { 0, CreateBcatService }, + { 1, CreateDeliveryCacheStorageService } + }; + } + + public long CreateBcatService(ServiceCtx Context) + { + long Id = Context.RequestData.ReadInt64(); + + MakeObject(Context, new IBcatService()); + + return 0; + } + + public long CreateDeliveryCacheStorageService(ServiceCtx Context) + { + long Id = Context.RequestData.ReadInt64(); + + MakeObject(Context, new IDeliveryCacheStorageService()); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs index 441b7e8add..61c6d11507 100644 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs @@ -35,7 +35,7 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv { 10, Commit }, { 11, GetFreeSpaceSize }, { 12, GetTotalSpaceSize }, - //{ 13, CleanDirectoryRecursively }, + { 13, CleanDirectoryRecursively }, //{ 14, GetFileTimeStampRaw } }; @@ -46,8 +46,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long CreateFile(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); long Mode = Context.RequestData.ReadInt64(); @@ -80,8 +78,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long DeleteFile(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); string FileName = Context.Ns.VFs.GetFullPath(Path, Name); @@ -103,8 +99,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long CreateDirectory(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); string DirName = Context.Ns.VFs.GetFullPath(Path, Name); @@ -141,8 +135,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv private long DeleteDirectory(ServiceCtx Context, bool Recursive) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); string DirName = Context.Ns.VFs.GetFullPath(Path, Name); @@ -220,8 +212,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long GetEntryType(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); string FileName = Context.Ns.VFs.GetFullPath(Path, Name); @@ -246,8 +236,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long OpenFile(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - int FilterFlags = Context.RequestData.ReadInt32(); string Name = ReadUtf8String(Context); @@ -282,8 +270,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long OpenDirectory(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - int FilterFlags = Context.RequestData.ReadInt32(); string Name = ReadUtf8String(Context); @@ -321,8 +307,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long GetFreeSpaceSize(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); Context.ResponseData.Write(Context.Ns.VFs.GetDrive().AvailableFreeSpace); @@ -332,8 +316,6 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv public long GetTotalSpaceSize(ServiceCtx Context) { - long Position = Context.Request.PtrBuff[0].Position; - string Name = ReadUtf8String(Context); Context.ResponseData.Write(Context.Ns.VFs.GetDrive().TotalSize); @@ -341,6 +323,37 @@ namespace Ryujinx.HLE.OsHle.Services.FspSrv return 0; } + public long CleanDirectoryRecursively(ServiceCtx Context) + { + string Name = ReadUtf8String(Context); + + string DirName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (!Directory.Exists(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (IsPathAlreadyInUse(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + foreach (string Entry in Directory.EnumerateFileSystemEntries(DirName)) + { + if (Directory.Exists(Entry)) + { + Directory.Delete(Entry, true); + } + else if (File.Exists(Entry)) + { + File.Delete(Entry); + } + } + + return 0; + } + private bool IsPathAlreadyInUse(string Path) { lock (OpenPaths) diff --git a/Ryujinx.HLE/OsHle/Services/IpcService.cs b/Ryujinx.HLE/OsHle/Services/IpcService.cs index 25fd56fec0..3c1a136f1b 100644 --- a/Ryujinx.HLE/OsHle/Services/IpcService.cs +++ b/Ryujinx.HLE/OsHle/Services/IpcService.cs @@ -50,9 +50,18 @@ namespace Ryujinx.HLE.OsHle.Services int DomainWord0 = Context.RequestData.ReadInt32(); int DomainObjId = Context.RequestData.ReadInt32(); - long Padding = Context.RequestData.ReadInt64(); + int DomainCmd = (DomainWord0 >> 0) & 0xff; + int InputObjCount = (DomainWord0 >> 8) & 0xff; + int DataPayloadSize = (DomainWord0 >> 16) & 0xffff; - int DomainCmd = DomainWord0 & 0xff; + Context.RequestData.BaseStream.Seek(0x10 + DataPayloadSize, SeekOrigin.Begin); + + for (int Index = 0; Index < InputObjCount; Index++) + { + Context.Request.ObjectIds.Add(Context.RequestData.ReadInt32()); + } + + Context.RequestData.BaseStream.Seek(0x10, SeekOrigin.Begin); if (DomainCmd == 1) { @@ -88,14 +97,14 @@ namespace Ryujinx.HLE.OsHle.Services if (IsDomain) { - foreach (int Id in Context.Response.ResponseObjIds) + foreach (int Id in Context.Response.ObjectIds) { Context.ResponseData.Write(Id); } Context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); - Context.ResponseData.Write(Context.Response.ResponseObjIds.Count); + Context.ResponseData.Write(Context.Response.ObjectIds.Count); } Context.ResponseData.BaseStream.Seek(IsDomain ? 0x10 : 0, SeekOrigin.Begin); @@ -117,7 +126,7 @@ namespace Ryujinx.HLE.OsHle.Services if (Service.IsDomain) { - Context.Response.ResponseObjIds.Add(Service.Add(Obj)); + Context.Response.ObjectIds.Add(Service.Add(Obj)); } else { @@ -129,6 +138,26 @@ namespace Ryujinx.HLE.OsHle.Services } } + protected static T GetObject(ServiceCtx Context, int Index) where T : IpcService + { + IpcService Service = Context.Session.Service; + + if (!Service.IsDomain) + { + int Handle = Context.Request.HandleDesc.ToMove[Index]; + + KSession Session = Context.Process.HandleTable.GetData(Handle); + + return Session?.Service is T ? (T)Session.Service : null; + } + + int ObjId = Context.Request.ObjectIds[Index]; + + IIpcService Obj = Service.GetObject(ObjId); + + return Obj is T ? (T)Obj : null; + } + private int Add(IIpcService Obj) { return DomainObjects.Add(Obj); diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs index e289a8db8f..83bb9f370c 100644 --- a/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs +++ b/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs @@ -1,6 +1,11 @@ using Ryujinx.HLE.Logging; using Ryujinx.HLE.OsHle.Ipc; +using System; using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; namespace Ryujinx.HLE.OsHle.Services.Nifm { @@ -14,10 +19,13 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm { m_Commands = new Dictionary() { - { 4, CreateRequest } + { 4, CreateRequest }, + { 12, GetCurrentIpAddress } }; } + public const int NoInternetConnection = 0x2586e; + //CreateRequest(i32) public long CreateRequest(ServiceCtx Context) { @@ -29,5 +37,22 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm return 0; } + + public long GetCurrentIpAddress(ServiceCtx Context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return NoInternetConnection; + } + + IPHostEntry Host = Dns.GetHostEntry(Dns.GetHostName()); + IPAddress Address = Host.AddressList.FirstOrDefault(A => A.AddressFamily == AddressFamily.InterNetwork); + + Context.ResponseData.Write(BitConverter.ToUInt32(Address.GetAddressBytes())); + + Context.Ns.Log.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is {Address.ToString()}"); + + return 0; + } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs index c8c679c4c5..2056187ddd 100644 --- a/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs +++ b/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs @@ -12,7 +12,8 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm public override IReadOnlyDictionary Commands => m_Commands; - private KEvent Event; + private KEvent Event0; + private KEvent Event1; public IRequest() { @@ -26,12 +27,13 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm { 11, SetConnectionConfirmationOption } }; - Event = new KEvent(); + Event0 = new KEvent(); + Event1 = new KEvent(); } public long GetRequestState(ServiceCtx Context) { - Context.ResponseData.Write(0); + Context.ResponseData.Write(1); Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); @@ -45,13 +47,12 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm return 0; } - //GetSystemEventReadableHandles() -> (KObject, KObject) public long GetSystemEventReadableHandles(ServiceCtx Context) { - //FIXME: Is this supposed to return 2 events? - int Handle = Context.Process.HandleTable.OpenHandle(Event); + int Handle0 = Context.Process.HandleTable.OpenHandle(Event0); + int Handle1 = Context.Process.HandleTable.OpenHandle(Event1); - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle0, Handle1); return 0; } @@ -86,7 +87,8 @@ namespace Ryujinx.HLE.OsHle.Services.Nifm { if (Disposing) { - Event.Dispose(); + Event0.Dispose(); + Event1.Dispose(); } } } diff --git a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs index 9f85f3d102..b8447ac65c 100644 --- a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs +++ b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.Font; using Ryujinx.HLE.OsHle.Ipc; using System.Collections.Generic; @@ -13,11 +14,12 @@ namespace Ryujinx.HLE.OsHle.Services.Pl { m_Commands = new Dictionary() { - { 0, RequestLoad }, - { 1, GetLoadState }, - { 2, GetFontSize }, - { 3, GetSharedMemoryAddressOffset }, - { 4, GetSharedMemoryNativeHandle } + { 0, RequestLoad }, + { 1, GetLoadState }, + { 2, GetFontSize }, + { 3, GetSharedMemoryAddressOffset }, + { 4, GetSharedMemoryNativeHandle }, + { 5, GetSharedFontInOrderOfPriority } }; } @@ -25,26 +27,34 @@ namespace Ryujinx.HLE.OsHle.Services.Pl { SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + Context.Ns.Font.Load(FontType); + return 0; } public long GetLoadState(ServiceCtx Context) { - Context.ResponseData.Write(1); //Loaded + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetLoadState(FontType)); return 0; } public long GetFontSize(ServiceCtx Context) { - Context.ResponseData.Write(Horizon.FontSize); + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetFontSize(FontType)); return 0; } public long GetSharedMemoryAddressOffset(ServiceCtx Context) { - Context.ResponseData.Write(0); + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetSharedMemoryAddressOffset(FontType)); return 0; } @@ -57,5 +67,51 @@ namespace Ryujinx.HLE.OsHle.Services.Pl return 0; } + + private uint AddFontToOrderOfPriorityList(ServiceCtx Context, SharedFontType FontType, uint BufferPos, out uint LoadState) + { + long TypesPosition = Context.Request.ReceiveBuff[0].Position; + long TypesSize = Context.Request.ReceiveBuff[0].Size; + + long OffsetsPosition = Context.Request.ReceiveBuff[1].Position; + long OffsetsSize = Context.Request.ReceiveBuff[1].Size; + + long FontSizeBufferPosition = Context.Request.ReceiveBuff[2].Position; + long FontSizeBufferSize = Context.Request.ReceiveBuff[2].Size; + + LoadState = Context.Ns.Font.GetLoadState(FontType); + + if (BufferPos >= TypesSize || BufferPos >= OffsetsSize || BufferPos >= FontSizeBufferSize) + { + return 0; + } + + Context.Memory.WriteUInt32(TypesPosition + BufferPos, (uint)FontType); + Context.Memory.WriteUInt32(OffsetsPosition + BufferPos, Context.Ns.Font.GetSharedMemoryAddressOffset(FontType)); + Context.Memory.WriteUInt32(FontSizeBufferPosition + BufferPos, Context.Ns.Font.GetFontSize(FontType)); + + BufferPos += 4; + + return BufferPos; + } + + public long GetSharedFontInOrderOfPriority(ServiceCtx Context) + { + ulong LanguageCode = Context.RequestData.ReadUInt64(); + uint LoadedCount = 0; + uint BufferPos = 0; + uint Loaded = 0; + + for (int Type = 0; Type < Context.Ns.Font.Count; Type++) + { + BufferPos = AddFontToOrderOfPriorityList(Context, (SharedFontType)Type, BufferPos, out Loaded); + LoadedCount += Loaded; + } + + Context.ResponseData.Write(LoadedCount); + Context.ResponseData.Write(Context.Ns.Font.Count); + + return 0; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs index b69fc9f884..712698b9af 100644 --- a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs @@ -2,6 +2,7 @@ using Ryujinx.HLE.OsHle.Services.Acc; using Ryujinx.HLE.OsHle.Services.Am; using Ryujinx.HLE.OsHle.Services.Apm; using Ryujinx.HLE.OsHle.Services.Aud; +using Ryujinx.HLE.OsHle.Services.Bcat; using Ryujinx.HLE.OsHle.Services.Bsd; using Ryujinx.HLE.OsHle.Services.Caps; using Ryujinx.HLE.OsHle.Services.Friend; @@ -55,6 +56,18 @@ namespace Ryujinx.HLE.OsHle.Services case "audren:u": return new IAudioRendererManager(); + case "bcat:a": + return new Bcat.IServiceCreator(); + + case "bcat:m": + return new Bcat.IServiceCreator(); + + case "bcat:u": + return new Bcat.IServiceCreator(); + + case "bcat:s": + return new Bcat.IServiceCreator(); + case "bsd:s": return new IClient(); @@ -71,10 +84,10 @@ namespace Ryujinx.HLE.OsHle.Services return new IRandomInterface(); case "friend:a": - return new IServiceCreator(); + return new Friend.IServiceCreator(); case "friend:u": - return new IServiceCreator(); + return new Friend.IServiceCreator(); case "fsp-srv": return new IFileSystemProxy(); diff --git a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs index a2206a126e..38440eef90 100644 --- a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs @@ -117,7 +117,7 @@ namespace Ryujinx.HLE.OsHle.Services.Time TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID); byte[] TzData = Encoding.ASCII.GetBytes(Info.Id); - // FIXME: This is not in ANY cases accurate, but the games don't about the content of the buffer, they only pass it. + // FIXME: This is not in ANY cases accurate, but the games don't care about the content of the buffer, they only pass it. // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware. Context.Memory.WriteBytes(BufferPosition, TzData); } diff --git a/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs index a3ed3ab51c..41f2916f4a 100644 --- a/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs @@ -279,8 +279,8 @@ namespace Ryujinx.HLE.OsHle.Services.Android private void SendFrameBuffer(ServiceCtx Context, int Slot) { - int FbWidth = 1280; - int FbHeight = 720; + int FbWidth = BufferQueue[Slot].Data.Width; + int FbHeight = BufferQueue[Slot].Data.Height; int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c); int BufferOffset = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x50); @@ -293,54 +293,17 @@ namespace Ryujinx.HLE.OsHle.Services.Android Rect Crop = BufferQueue[Slot].Crop; - int RealWidth = FbWidth; - int RealHeight = FbHeight; + bool FlipX = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX); + bool FlipY = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY); - float XSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX) ? -1 : 1; - float YSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY) ? -1 : 1; + //Rotation is being ignored - float ScaleX = 1; - float ScaleY = 1; + int Top = Crop.Top; + int Left = Crop.Left; + int Right = Crop.Right; + int Bottom = Crop.Bottom; - float OffsX = 0; - float OffsY = 0; - - if (Crop.Right != 0 && - Crop.Bottom != 0) - { - //Who knows if this is right, I was never good with math... - RealWidth = Crop.Right - Crop.Left; - RealHeight = Crop.Bottom - Crop.Top; - - if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) - { - ScaleY = (float)FbHeight / RealHeight; - ScaleX = (float)FbWidth / RealWidth; - - OffsY = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * -XSign; - OffsX = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; - } - else - { - ScaleX = (float)FbWidth / RealWidth; - ScaleY = (float)FbHeight / RealHeight; - - OffsX = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * XSign; - OffsY = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; - } - } - - ScaleX *= XSign; - ScaleY *= YSign; - - float Rotate = 0; - - if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) - { - Rotate = -MathF.PI * 0.5f; - } - - Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(ScaleX, ScaleY, Rotate, OffsX, OffsY)); + Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom)); //TODO: Support double buffering here aswell, it is broken for GPU //frame buffers because it seems to be completely out of sync. diff --git a/Ryujinx.HLE/OsHle/SystemStateMgr.cs b/Ryujinx.HLE/OsHle/SystemStateMgr.cs index e78082c45a..7b6ef7f3cf 100644 --- a/Ryujinx.HLE/OsHle/SystemStateMgr.cs +++ b/Ryujinx.HLE/OsHle/SystemStateMgr.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.Loaders.Npdm; using System; namespace Ryujinx.HLE.OsHle diff --git a/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs b/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs new file mode 100644 index 0000000000..35c4874a34 --- /dev/null +++ b/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.HLE.Resource +{ + public class InvalidSystemResourceException : Exception + { + public InvalidSystemResourceException(string message) + : base(message) + { + } + + } +} diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index acef4be9e7..f7fb84a588 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 74c0564a9f..a80ca86c19 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,5 +1,6 @@ using Ryujinx.Audio; using Ryujinx.Graphics.Gal; +using Ryujinx.HLE.Font; using Ryujinx.HLE.Gpu; using Ryujinx.HLE.Input; using Ryujinx.HLE.Logging; @@ -27,6 +28,8 @@ namespace Ryujinx.HLE public Hid Hid { get; private set; } + public SharedFontManager Font { get; private set; } + public event EventHandler Finish; public Switch(IGalRenderer Renderer, IAalOutput AudioOut) @@ -57,8 +60,13 @@ namespace Ryujinx.HLE Hid = new Hid(Log); - Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; - Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + Font = new SharedFontManager(Log, VFs.GetSystemPath()); + + Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; + Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + + Os.FontSharedMem.MemoryMapped += Font.ShMemMap; + Os.FontSharedMem.MemoryUnmapped += Font.ShMemUnmap; } public void LoadCart(string ExeFsDir, string RomFsFile = null) diff --git a/Ryujinx.HLE/VirtualFileSystem.cs b/Ryujinx.HLE/VirtualFileSystem.cs index 38df81f87d..31b8e184ce 100644 --- a/Ryujinx.HLE/VirtualFileSystem.cs +++ b/Ryujinx.HLE/VirtualFileSystem.cs @@ -8,6 +8,7 @@ namespace Ryujinx.HLE private const string BasePath = "RyuFs"; private const string NandPath = "nand"; private const string SdCardPath = "sdmc"; + private const string SystemPath = "system"; public Stream RomFs { get; private set; } @@ -45,6 +46,8 @@ namespace Ryujinx.HLE public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath); + public string GetSystemPath() => MakeDirAndGetFullPath(SystemPath); + public string SwitchPathToSystemPath(string SwitchPath) { string[] Parts = SwitchPath.Split(":"); @@ -57,11 +60,11 @@ namespace Ryujinx.HLE public string SystemPathToSwitchPath(string SystemPath) { - string BaseSystemPath = GetBasePath() + "/"; + string BaseSystemPath = GetBasePath() + Path.DirectorySeparatorChar; if (SystemPath.StartsWith(BaseSystemPath)) { string RawPath = SystemPath.Replace(BaseSystemPath, ""); - int FirstSeparatorOffset = RawPath.IndexOf('/'); + int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar); if (FirstSeparatorOffset == -1) { return $"{RawPath}:/"; diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index b84d29575d..88c5981cd2 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -22,6 +22,17 @@ namespace Ryujinx.Tests.Cpu } #region "ValueSource" + private static ulong[] _1B1H1S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul }; + } + private static ulong[] _1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, @@ -1126,6 +1137,192 @@ namespace Ryujinx.Tests.Cpu }); } + [Test, Description("SQABS , ")] + public void Sqabs_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x5E207800; // SQABS B0, B0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqabs_S(Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SQABS ., .")] + public void Sqabs_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E207800; // SQABS V0.8B, V0.8B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqabs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SQABS ., .")] + public void Sqabs_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E207800; // SQABS V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqabs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SQNEG , ")] + public void Sqneg_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x7E207800; // SQNEG B0, B0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqneg_S(Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SQNEG ., .")] + public void Sqneg_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E207800; // SQNEG V0.8B, V0.8B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqneg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SQNEG ., .")] + public void Sqneg_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E207800; // SQNEG V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqneg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + [Test, Description("SQXTN , ")] public void Sqxtn_S_HB_SH_DS([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -1138,12 +1335,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1151,7 +1351,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("SQXTN{2} ., .")] @@ -1166,12 +1366,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1179,7 +1382,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("SQXTN{2} ., .")] @@ -1194,12 +1397,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1207,7 +1413,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("SQXTUN , ")] @@ -1222,12 +1428,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtun_S(Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1235,7 +1444,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("SQXTUN{2} ., .")] @@ -1250,12 +1459,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1263,7 +1475,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("SQXTUN{2} ., .")] @@ -1278,12 +1490,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1291,7 +1506,100 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SUQADD , ")] + public void Suqadd_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x5E203800; // SUQADD B0, B0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Suqadd_S(Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SUQADD ., .")] + public void Suqadd_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E203800; // SUQADD V0.8B, V0.8B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Suqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("SUQADD ., .")] + public void Suqadd_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E203800; // SUQADD V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Suqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("UQXTN , ")] @@ -1306,12 +1614,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Uqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1319,7 +1630,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("UQXTN{2} ., .")] @@ -1334,12 +1645,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1347,7 +1661,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); } [Test, Description("UQXTN{2} ., .")] @@ -1362,12 +1676,15 @@ namespace Ryujinx.Tests.Cpu Opcode |= ((size & 3) << 22); Bits Op = new Bits(Opcode); + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1375,7 +1692,154 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("USQADD , ")] + public void Usqadd_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x7E203800; // USQADD B0, B0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Usqadd_S(Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("USQADD ., .")] + public void Usqadd_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E203800; // USQADD V0.8B, V0.8B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Usqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("USQADD ., .")] + public void Usqadd_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E203800; // USQADD V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Usqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Description("XTN{2} ., .")] + public void Xtn_V_8H8B_4S4H_2D2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint Opcode = 0x0E212800; // XTN V0.8B, V0.8H + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + SimdFp.Xtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Description("XTN{2} ., .")] + public void Xtn_V_8H16B_4S8H_2D4S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint Opcode = 0x4E212800; // XTN2 V0.16B, V0.8H + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + SimdFp.Xtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs index 8e2d9a366e..53e1554291 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs @@ -19,9 +19,9 @@ namespace Ryujinx.Tests.Cpu [TestCase(0x1E224820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x000000007F7FFFFFul)] [TestCase(0x1E224820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] [TestCase(0x1E224820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E224820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E224820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E224820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] + [TestCase(0x1E224820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul)] + [TestCase(0x1E224820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul)] + [TestCase(0x1E224820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul)] [TestCase(0x1E624820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x0000000000000000ul)] [TestCase(0x1E624820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] [TestCase(0x1E624820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] @@ -45,9 +45,9 @@ namespace Ryujinx.Tests.Cpu [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] + [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u)] + [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u)] + [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au)] public void Fmax_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) { uint Opcode = 0x4E22F420; @@ -71,9 +71,9 @@ namespace Ryujinx.Tests.Cpu [TestCase(0x1E225820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x00000000807FFFFFul)] [TestCase(0x1E225820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] [TestCase(0x1E225820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E225820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E225820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E225820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] + [TestCase(0x1E225820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul)] + [TestCase(0x1E225820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul)] + [TestCase(0x1E225820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul)] [TestCase(0x1E625820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] [TestCase(0x1E625820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x8000000000000000ul)] [TestCase(0x1E625820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] @@ -97,9 +97,9 @@ namespace Ryujinx.Tests.Cpu [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] + [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u)] + [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u)] + [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au)] public void Fmin_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) { uint Opcode = 0x4EA2F420; diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs index 51db857c31..e6cfcbde5b 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -22,6 +22,17 @@ namespace Ryujinx.Tests.Cpu } #region "ValueSource" + private static ulong[] _1B1H1S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul }; + } + private static ulong[] _1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, @@ -1659,6 +1670,340 @@ namespace Ryujinx.Tests.Cpu }); } + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint Opcode = 0x0E201000; // SADDW V0.8H, V0.8H, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); + SimdFp.Saddw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint Opcode = 0x4E201000; // SADDW2 V0.8H, V0.8H, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE1(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Saddw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("SQADD , , ")] + public void Sqadd_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x5E200C00; // SQADD B0, B0, B0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqadd_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E200C00; // SQADD V0.8B, V0.8B, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqadd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E200C00; // SQADD V0.16B, V0.16B, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); AArch64.Vpart(2, 1, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqadd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SQSUB , , ")] + public void Sqsub_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x5E202C00; // SQSUB B0, B0, B0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqsub_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x0E202C00; // SQSUB V0.8B, V0.8B, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqsub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x4E202C00; // SQSUB V0.16B, V0.16B, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); AArch64.Vpart(2, 1, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Sqsub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint Opcode = 0x0E203000; // SSUBW V0.8H, V0.8H, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); + SimdFp.Ssubw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint Opcode = 0x4E203000; // SSUBW2 V0.8H, V0.8H, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE1(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Ssubw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + [Test, Pairwise, Description("SUB , , ")] public void Sub_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -2184,6 +2529,340 @@ namespace Ryujinx.Tests.Cpu }); } + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint Opcode = 0x2E201000; // UADDW V0.8H, V0.8H, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); + SimdFp.Uaddw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint Opcode = 0x6E201000; // UADDW2 V0.8H, V0.8H, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE1(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Uaddw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("UQADD , , ")] + public void Uqadd_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x7E200C00; // UQADD B0, B0, B0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqadd_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E200C00; // UQADD V0.8B, V0.8B, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqadd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E200C00; // UQADD V0.16B, V0.16B, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); AArch64.Vpart(2, 1, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqadd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("UQSUB , , ")] + public void Uqsub_S_B_H_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint Opcode = 0x7E202C00; // UQSUB B0, B0, B0 + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqsub_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_8B_4H_2S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint Opcode = 0x2E202C00; // UQSUB V0.8B, V0.8B, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqsub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_16B_8H_4S_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint Opcode = 0x6E202C00; // UQSUB V0.16B, V0.16B, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + int Fpsr = (int)TestContext.CurrentContext.Random.NextUInt(); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, Fpsr: Fpsr); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); AArch64.Vpart(2, 1, new Bits(B)); + Shared.FPSR = new Bits((uint)Fpsr); + SimdFp.Uqsub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + Assert.That(ThreadState.Fpsr, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint Opcode = 0x2E203000; // USUBW V0.8H, V0.8H, V0.8B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 0, new Bits(B)); + SimdFp.Usubw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong A, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint Opcode = 0x6E203000; // USUBW2 V0.8H, V0.8H, V0.16B + Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((size & 3) << 22); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE1(B); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.Vpart(1, 0, new Bits(A)); AArch64.Vpart(1, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Usubw_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); + }); + } + [Test, Pairwise, Description("UZP1 ., ., .")] public void Uzp1_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, diff --git a/Ryujinx.Tests/Cpu/Tester/Instructions.cs b/Ryujinx.Tests/Cpu/Tester/Instructions.cs index 68f83423ba..8b1b010f65 100644 --- a/Ryujinx.Tests/Cpu/Tester/Instructions.cs +++ b/Ryujinx.Tests/Cpu/Tester/Instructions.cs @@ -3060,6 +3060,210 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // sqabs_advsimd.html#SQABS_asisdmisc_R + public static void Sqabs_S(Bits size, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + (Bits _result, bool _sat) = SignedSatQ(element, esize); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqabs_advsimd.html#SQABS_asimdmisc_R + public static void Sqabs_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + (Bits _result, bool _sat) = SignedSatQ(element, esize); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqneg_advsimd.html#SQNEG_asisdmisc_R + public static void Sqneg_S(Bits size, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + (Bits _result, bool _sat) = SignedSatQ(element, esize); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqneg_advsimd.html#SQNEG_asimdmisc_R + public static void Sqneg_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool neg = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + BigInteger element; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element = SInt(Elem(operand, e, esize)); + + if (neg) + { + element = -element; + } + else + { + element = Abs(element); + } + + (Bits _result, bool _sat) = SignedSatQ(element, esize); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + // sqxtn_advsimd.html#SQXTN_asisdmisc_N public static void Sqxtn_S(Bits size, Bits Rn, Bits Rd) { @@ -3228,6 +3432,96 @@ namespace Ryujinx.Tests.Cpu.Tester Vpart(d, part, result); } + // suqadd_advsimd.html#SUQADD_asisdmisc_R + public static void Suqadd_S(Bits size, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits operand2 = V(datasize, d); + BigInteger op1; + BigInteger op2; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + op1 = Int(Elem(operand, e, esize), !unsigned); + op2 = Int(Elem(operand2, e, esize), unsigned); + + (Bits _result, bool _sat) = SatQ(op1 + op2, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // suqadd_advsimd.html#SUQADD_asimdmisc_R + public static void Suqadd_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits operand2 = V(datasize, d); + BigInteger op1; + BigInteger op2; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + op1 = Int(Elem(operand, e, esize), !unsigned); + op2 = Int(Elem(operand2, e, esize), unsigned); + + (Bits _result, bool _sat) = SatQ(op1 + op2, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + // uqxtn_advsimd.html#UQXTN_asisdmisc_N public static void Uqxtn_S(Bits size, Bits Rn, Bits Rd) { @@ -3315,6 +3609,127 @@ namespace Ryujinx.Tests.Cpu.Tester Vpart(d, part, result); } + + // usqadd_advsimd.html#USQADD_asisdmisc_R + public static void Usqadd_S(Bits size, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits operand2 = V(datasize, d); + BigInteger op1; + BigInteger op2; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + op1 = Int(Elem(operand, e, esize), !unsigned); + op2 = Int(Elem(operand2, e, esize), unsigned); + + (Bits _result, bool _sat) = SatQ(op1 + op2, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // usqadd_advsimd.html#USQADD_asimdmisc_R + public static void Usqadd_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits operand2 = V(datasize, d); + BigInteger op1; + BigInteger op2; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + op1 = Int(Elem(operand, e, esize), !unsigned); + op2 = Int(Elem(operand2, e, esize), unsigned); + + (Bits _result, bool _sat) = SatQ(op1 + op2, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // xtn_advsimd.html + public static void Xtn_V(bool Q, Bits size, Bits Rn, Bits Rd) + { + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(2 * datasize, n); + Bits element; + + for (int e = 0; e <= elements - 1; e++) + { + element = Elem(operand, e, 2 * esize); + + Elem(result, e, esize, element[esize - 1, 0]); + } + + Vpart(d, part, result); + } #endregion #region "SimdReg" @@ -4395,8 +4810,8 @@ namespace Ryujinx.Tests.Cpu.Tester int part = (int)UInt(Q); int elements = datasize / esize; - bool unsigned = (U == true); bool accumulate = (op == false); + bool unsigned = (U == true); /* Operation */ /* CheckFPAdvSIMDEnabled64(); */ @@ -4484,8 +4899,8 @@ namespace Ryujinx.Tests.Cpu.Tester int part = (int)UInt(Q); int elements = datasize / esize; - bool unsigned = (U == true); bool accumulate = (op == false); + bool unsigned = (U == true); /* Operation */ /* CheckFPAdvSIMDEnabled64(); */ @@ -4511,6 +4926,304 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // saddw_advsimd.html + public static void Saddw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + const bool o1 = false; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(2 * datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = Vpart(datasize, m, part); + BigInteger element1; + BigInteger element2; + BigInteger sum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, 2 * esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + Elem(result, e, 2 * esize, sum.SubBigInteger(2 * esize - 1, 0)); + } + + V(d, result); + } + + // sqadd_advsimd.html#SQADD_asisdsame_only + public static void Sqadd_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger sum; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + sum = element1 + element2; + + (Bits _result, bool _sat) = SatQ(sum, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqadd_advsimd.html#SQADD_asimdsame_only + public static void Sqadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger sum; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + sum = element1 + element2; + + (Bits _result, bool _sat) = SatQ(sum, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqsub_advsimd.html#SQSUB_asisdsame_only + public static void Sqsub_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger diff; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + diff = element1 - element2; + + (Bits _result, bool _sat) = SatQ(diff, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // sqsub_advsimd.html#SQSUB_asimdsame_only + public static void Sqsub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger diff; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + diff = element1 - element2; + + (Bits _result, bool _sat) = SatQ(diff, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // ssubw_advsimd.html + public static void Ssubw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = false; + const bool o1 = true; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(2 * datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = Vpart(datasize, m, part); + BigInteger element1; + BigInteger element2; + BigInteger sum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, 2 * esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + Elem(result, e, 2 * esize, sum.SubBigInteger(2 * esize - 1, 0)); + } + + V(d, result); + } + // sub_advsimd.html#SUB_asisdsame_only public static void Sub_S(Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -4785,8 +5498,8 @@ namespace Ryujinx.Tests.Cpu.Tester int part = (int)UInt(Q); int elements = datasize / esize; - bool unsigned = (U == true); bool accumulate = (op == false); + bool unsigned = (U == true); /* Operation */ /* CheckFPAdvSIMDEnabled64(); */ @@ -4874,8 +5587,8 @@ namespace Ryujinx.Tests.Cpu.Tester int part = (int)UInt(Q); int elements = datasize / esize; - bool unsigned = (U == true); bool accumulate = (op == false); + bool unsigned = (U == true); /* Operation */ /* CheckFPAdvSIMDEnabled64(); */ @@ -4901,6 +5614,304 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // uaddw_advsimd.html + public static void Uaddw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + const bool o1 = false; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(2 * datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = Vpart(datasize, m, part); + BigInteger element1; + BigInteger element2; + BigInteger sum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, 2 * esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + Elem(result, e, 2 * esize, sum.SubBigInteger(2 * esize - 1, 0)); + } + + V(d, result); + } + + // uqadd_advsimd.html#UQADD_asisdsame_only + public static void Uqadd_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger sum; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + sum = element1 + element2; + + (Bits _result, bool _sat) = SatQ(sum, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // uqadd_advsimd.html#UQADD_asimdsame_only + public static void Uqadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger sum; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + sum = element1 + element2; + + (Bits _result, bool _sat) = SatQ(sum, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // uqsub_advsimd.html#UQSUB_asisdsame_only + public static void Uqsub_S(Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + int esize = 8 << (int)UInt(size); + int datasize = esize; + int elements = 1; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger diff; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + diff = element1 - element2; + + (Bits _result, bool _sat) = SatQ(diff, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // uqsub_advsimd.html#UQSUB_asimdsame_only + public static void Uqsub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size:Q == '110' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand1 = V(datasize, n); + Bits operand2 = V(datasize, m); + BigInteger element1; + BigInteger element2; + BigInteger diff; + bool sat; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + diff = element1 - element2; + + (Bits _result, bool _sat) = SatQ(diff, esize, unsigned); + Elem(result, e, esize, _result); + sat = _sat; + + if (sat) + { + /* FPSR.QC = '1'; */ + FPSR[27] = true; // TODO: Add named fields. + } + } + + V(d, result); + } + + // usubw_advsimd.html + public static void Usubw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = true; + const bool o1 = true; + + /* Decode */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + int m = (int)UInt(Rm); + + /* if size == '11' then ReservedValue(); */ + + int esize = 8 << (int)UInt(size); + int datasize = 64; + int part = (int)UInt(Q); + int elements = datasize / esize; + + bool sub_op = (o1 == true); + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(2 * datasize); + Bits operand1 = V(2 * datasize, n); + Bits operand2 = Vpart(datasize, m, part); + BigInteger element1; + BigInteger element2; + BigInteger sum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, 2 * esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + if (sub_op) + { + sum = element1 - element2; + } + else + { + sum = element1 + element2; + } + + Elem(result, e, 2 * esize, sum.SubBigInteger(2 * esize - 1, 0)); + } + + V(d, result); + } + // uzp1_advsimd.html public static void Uzp1_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) { diff --git a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs index 3a877fb1a2..6c4dfa92ba 100644 --- a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs +++ b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs @@ -1193,9 +1193,9 @@ namespace Ryujinx.Tests.Cpu.Tester result = BigInteger.Pow(2, N) - 1; saturated = true; } - else if (i < 0) + else if (i < (BigInteger)0) { - result = 0; + result = (BigInteger)0; saturated = true; } else diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs index 0f346122af..4ed35b3d7e 100644 --- a/Ryujinx/Config.cs +++ b/Ryujinx/Config.cs @@ -14,11 +14,6 @@ namespace Ryujinx public static JoyConKeyboard JoyConKeyboard { get; private set; } public static JoyConController JoyConController { get; private set; } - public static float GamePadDeadzone { get; private set; } - public static bool GamePadEnable { get; private set; } - public static int GamePadIndex { get; private set; } - public static float GamePadTriggerThreshold { get; private set; } - public static void Read(Logger Log) { string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); @@ -37,11 +32,6 @@ namespace Ryujinx Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); - GamePadEnable = Convert.ToBoolean(Parser.Value("GamePad_Enable")); - GamePadIndex = Convert.ToInt32 (Parser.Value("GamePad_Index")); - GamePadDeadzone = (float)Convert.ToDouble (Parser.Value("GamePad_Deadzone"), CultureInfo.InvariantCulture); - GamePadTriggerThreshold = (float)Convert.ToDouble (Parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture); - string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries); //When the classes are specified on the list, we only @@ -70,9 +60,9 @@ namespace Ryujinx } } - JoyConKeyboard = new JoyConKeyboard - { - Left = new JoyConKeyboardLeft + JoyConKeyboard = new JoyConKeyboard( + + new JoyConKeyboardLeft { StickUp = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Up")), StickDown = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Down")), @@ -88,7 +78,7 @@ namespace Ryujinx ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_ZL")) }, - Right = new JoyConKeyboardRight + new JoyConKeyboardRight { StickUp = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Up")), StickDown = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Down")), @@ -102,37 +92,69 @@ namespace Ryujinx ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_Plus")), ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_R")), ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_ZR")) - } - }; + }); - JoyConController = new JoyConController - { - Left = new JoyConControllerLeft + JoyConController = new JoyConController( + + Convert.ToBoolean(Parser.Value("GamePad_Enable")), + Convert.ToInt32 (Parser.Value("GamePad_Index")), + (float)Convert.ToDouble (Parser.Value("GamePad_Deadzone"), CultureInfo.InvariantCulture), + (float)Convert.ToDouble (Parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture), + + new JoyConControllerLeft { - Stick = Parser.Value("Controls_Left_JoyConController_Stick"), - StickButton = Parser.Value("Controls_Left_JoyConController_Stick_Button"), - DPadUp = Parser.Value("Controls_Left_JoyConController_DPad_Up"), - DPadDown = Parser.Value("Controls_Left_JoyConController_DPad_Down"), - DPadLeft = Parser.Value("Controls_Left_JoyConController_DPad_Left"), - DPadRight = Parser.Value("Controls_Left_JoyConController_DPad_Right"), - ButtonMinus = Parser.Value("Controls_Left_JoyConController_Button_Minus"), - ButtonL = Parser.Value("Controls_Left_JoyConController_Button_L"), - ButtonZL = Parser.Value("Controls_Left_JoyConController_Button_ZL") + Stick = ToID(Parser.Value("Controls_Left_JoyConController_Stick")), + StickButton = ToID(Parser.Value("Controls_Left_JoyConController_Stick_Button")), + DPadUp = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Up")), + DPadDown = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Down")), + DPadLeft = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Left")), + DPadRight = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Right")), + ButtonMinus = ToID(Parser.Value("Controls_Left_JoyConController_Button_Minus")), + ButtonL = ToID(Parser.Value("Controls_Left_JoyConController_Button_L")), + ButtonZL = ToID(Parser.Value("Controls_Left_JoyConController_Button_ZL")) }, - Right = new JoyConControllerRight + new JoyConControllerRight { - Stick = Parser.Value("Controls_Right_JoyConController_Stick"), - StickButton = Parser.Value("Controls_Right_JoyConController_Stick_Button"), - ButtonA = Parser.Value("Controls_Right_JoyConController_Button_A"), - ButtonB = Parser.Value("Controls_Right_JoyConController_Button_B"), - ButtonX = Parser.Value("Controls_Right_JoyConController_Button_X"), - ButtonY = Parser.Value("Controls_Right_JoyConController_Button_Y"), - ButtonPlus = Parser.Value("Controls_Right_JoyConController_Button_Plus"), - ButtonR = Parser.Value("Controls_Right_JoyConController_Button_R"), - ButtonZR = Parser.Value("Controls_Right_JoyConController_Button_ZR") - } - }; + Stick = ToID(Parser.Value("Controls_Right_JoyConController_Stick")), + StickButton = ToID(Parser.Value("Controls_Right_JoyConController_Stick_Button")), + ButtonA = ToID(Parser.Value("Controls_Right_JoyConController_Button_A")), + ButtonB = ToID(Parser.Value("Controls_Right_JoyConController_Button_B")), + ButtonX = ToID(Parser.Value("Controls_Right_JoyConController_Button_X")), + ButtonY = ToID(Parser.Value("Controls_Right_JoyConController_Button_Y")), + ButtonPlus = ToID(Parser.Value("Controls_Right_JoyConController_Button_Plus")), + ButtonR = ToID(Parser.Value("Controls_Right_JoyConController_Button_R")), + ButtonZR = ToID(Parser.Value("Controls_Right_JoyConController_Button_ZR")) + }); + } + + private static ControllerInputID ToID(string Key) + { + switch (Key.ToUpper()) + { + case "LSTICK": return ControllerInputID.LStick; + case "DPADUP": return ControllerInputID.DPadUp; + case "DPADDOWN": return ControllerInputID.DPadDown; + case "DPADLEFT": return ControllerInputID.DPadLeft; + case "DPADRIGHT": return ControllerInputID.DPadRight; + case "BACK": return ControllerInputID.Back; + case "LSHOULDER": return ControllerInputID.LShoulder; + case "LTRIGGER": return ControllerInputID.LTrigger; + + case "RSTICK": return ControllerInputID.RStick; + case "A": return ControllerInputID.A; + case "B": return ControllerInputID.B; + case "X": return ControllerInputID.X; + case "Y": return ControllerInputID.Y; + case "START": return ControllerInputID.Start; + case "RSHOULDER": return ControllerInputID.RShoulder; + case "RTRIGGER": return ControllerInputID.RTrigger; + + case "LJOYSTICK": return ControllerInputID.LJoystick; + case "RJOYSTICK": return ControllerInputID.RJoystick; + + default: return ControllerInputID.Invalid; + } } } diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 9b5dda4f0c..dfc0b9a418 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -4,6 +4,7 @@ using OpenTK.Input; using Ryujinx.Graphics.Gal; using Ryujinx.HLE; using Ryujinx.HLE.Input; +using Ryujinx.UI.Input; using System; using System.Threading; @@ -16,9 +17,6 @@ namespace Ryujinx private const int TouchScreenWidth = 1280; private const int TouchScreenHeight = 720; - private const float TouchScreenRatioX = (float)TouchScreenWidth / TouchScreenHeight; - private const float TouchScreenRatioY = (float)TouchScreenHeight / TouchScreenWidth; - private const int TargetFPS = 60; private Switch Ns; @@ -49,10 +47,6 @@ namespace Ryujinx Location = new Point( (DisplayDevice.Default.Width / 2) - (Width / 2), (DisplayDevice.Default.Height / 2) - (Height / 2)); - - ResizeEvent = false; - - TitleEvent = false; } private void RenderLoop() @@ -127,60 +121,9 @@ namespace Ryujinx Title = NewTitle; } } - } - } - - private bool IsGamePadButtonPressedFromString(GamePadState GamePad, string Button) - { - if (Button.ToUpper() == "LTRIGGER" || Button.ToUpper() == "RTRIGGER") - { - return GetGamePadTriggerFromString(GamePad, Button) >= Config.GamePadTriggerThreshold; - } - else - { - return (GetGamePadButtonFromString(GamePad, Button) == ButtonState.Pressed); - } - } - private ButtonState GetGamePadButtonFromString(GamePadState GamePad, string Button) - { - switch (Button.ToUpper()) - { - case "A": return GamePad.Buttons.A; - case "B": return GamePad.Buttons.B; - case "X": return GamePad.Buttons.X; - case "Y": return GamePad.Buttons.Y; - case "LSTICK": return GamePad.Buttons.LeftStick; - case "RSTICK": return GamePad.Buttons.RightStick; - case "LSHOULDER": return GamePad.Buttons.LeftShoulder; - case "RSHOULDER": return GamePad.Buttons.RightShoulder; - case "DPADUP": return GamePad.DPad.Up; - case "DPADDOWN": return GamePad.DPad.Down; - case "DPADLEFT": return GamePad.DPad.Left; - case "DPADRIGHT": return GamePad.DPad.Right; - case "START": return GamePad.Buttons.Start; - case "BACK": return GamePad.Buttons.Back; - default: throw new ArgumentException(); - } - } - - private float GetGamePadTriggerFromString(GamePadState GamePad, string Trigger) - { - switch (Trigger.ToUpper()) - { - case "LTRIGGER": return GamePad.Triggers.Left; - case "RTRIGGER": return GamePad.Triggers.Right; - default: throw new ArgumentException(); - } - } - - private Vector2 GetJoystickAxisFromString(GamePadState GamePad, string Joystick) - { - switch (Joystick.ToUpper()) - { - case "LJOYSTICK": return GamePad.ThumbSticks.Left; - case "RJOYSTICK": return new Vector2(-GamePad.ThumbSticks.Right.Y, -GamePad.ThumbSticks.Right.X); - default: throw new ArgumentException(); + //Polling becomes expensive if it's not slept + Thread.Sleep(1); } } @@ -190,95 +133,37 @@ namespace Ryujinx HidJoystickPosition LeftJoystick; HidJoystickPosition RightJoystick; - int LeftJoystickDX = 0; - int LeftJoystickDY = 0; - int RightJoystickDX = 0; - int RightJoystickDY = 0; - float AnalogStickDeadzone = Config.GamePadDeadzone; + int LeftJoystickDX = 0; + int LeftJoystickDY = 0; + int RightJoystickDX = 0; + int RightJoystickDY = 0; //Keyboard Input if (Keyboard.HasValue) { KeyboardState Keyboard = this.Keyboard.Value; - if (Keyboard[Key.Escape]) this.Exit(); + CurrentButton = Config.JoyConKeyboard.GetButtons(Keyboard); - //LeftJoystick - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickUp]) LeftJoystickDY = short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickDown]) LeftJoystickDY = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickLeft]) LeftJoystickDX = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickRight]) LeftJoystickDX = short.MaxValue; + (LeftJoystickDX, LeftJoystickDY) = Config.JoyConKeyboard.GetLeftStick(Keyboard); - //LeftButtons - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickButton]) CurrentButton |= HidControllerButtons.KEY_LSTICK; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadUp]) CurrentButton |= HidControllerButtons.KEY_DUP; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadDown]) CurrentButton |= HidControllerButtons.KEY_DDOWN; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadLeft]) CurrentButton |= HidControllerButtons.KEY_DLEFT; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadRight]) CurrentButton |= HidControllerButtons.KEY_DRIGHT; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonMinus]) CurrentButton |= HidControllerButtons.KEY_MINUS; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonL]) CurrentButton |= HidControllerButtons.KEY_L; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonZL]) CurrentButton |= HidControllerButtons.KEY_ZL; - - //RightJoystick - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickUp]) RightJoystickDY = short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickDown]) RightJoystickDY = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickLeft]) RightJoystickDX = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickRight]) RightJoystickDX = short.MaxValue; - - //RightButtons - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickButton]) CurrentButton |= HidControllerButtons.KEY_RSTICK; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonA]) CurrentButton |= HidControllerButtons.KEY_A; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonB]) CurrentButton |= HidControllerButtons.KEY_B; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonX]) CurrentButton |= HidControllerButtons.KEY_X; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonY]) CurrentButton |= HidControllerButtons.KEY_Y; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonPlus]) CurrentButton |= HidControllerButtons.KEY_PLUS; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonR]) CurrentButton |= HidControllerButtons.KEY_R; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonZR]) CurrentButton |= HidControllerButtons.KEY_ZR; + (RightJoystickDX, RightJoystickDY) = Config.JoyConKeyboard.GetRightStick(Keyboard); } //Controller Input - if (Config.GamePadEnable) + CurrentButton |= Config.JoyConController.GetButtons(); + + //Keyboard has priority stick-wise + if (LeftJoystickDX == 0 && LeftJoystickDY == 0) { - GamePadState GamePad = OpenTK.Input.GamePad.GetState(Config.GamePadIndex); - //LeftButtons - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadUp)) CurrentButton |= HidControllerButtons.KEY_DUP; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadDown)) CurrentButton |= HidControllerButtons.KEY_DDOWN; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadLeft)) CurrentButton |= HidControllerButtons.KEY_DLEFT; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadRight)) CurrentButton |= HidControllerButtons.KEY_DRIGHT; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.StickButton)) CurrentButton |= HidControllerButtons.KEY_LSTICK; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonMinus)) CurrentButton |= HidControllerButtons.KEY_MINUS; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonL)) CurrentButton |= HidControllerButtons.KEY_L; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonZL)) CurrentButton |= HidControllerButtons.KEY_ZL; - - //RightButtons - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonA)) CurrentButton |= HidControllerButtons.KEY_A; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonB)) CurrentButton |= HidControllerButtons.KEY_B; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonX)) CurrentButton |= HidControllerButtons.KEY_X; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonY)) CurrentButton |= HidControllerButtons.KEY_Y; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.StickButton)) CurrentButton |= HidControllerButtons.KEY_RSTICK; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonPlus)) CurrentButton |= HidControllerButtons.KEY_PLUS; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonR)) CurrentButton |= HidControllerButtons.KEY_R; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonZR)) CurrentButton |= HidControllerButtons.KEY_ZR; - - //LeftJoystick - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X <= -AnalogStickDeadzone) - LeftJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X * short.MaxValue); - - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y <= -AnalogStickDeadzone) - LeftJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y * short.MaxValue); - - //RightJoystick - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X <= -AnalogStickDeadzone) - RightJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X * short.MaxValue); - - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y <= -AnalogStickDeadzone) - RightJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y * short.MaxValue); + (LeftJoystickDX, LeftJoystickDY) = Config.JoyConController.GetLeftStick(); } + if (RightJoystickDX == 0 && RightJoystickDY == 0) + { + (RightJoystickDX, RightJoystickDY) = Config.JoyConController.GetRightStick(); + } + LeftJoystick = new HidJoystickPosition { DX = LeftJoystickDX, @@ -302,13 +187,13 @@ namespace Ryujinx int ScrnWidth = Width; int ScrnHeight = Height; - if (Width > Height * TouchScreenRatioX) + if (Width > (Height * TouchScreenWidth) / TouchScreenHeight) { - ScrnWidth = (int)(Height * TouchScreenRatioX); + ScrnWidth = (Height * TouchScreenWidth) / TouchScreenHeight; } else { - ScrnHeight = (int)(Width * TouchScreenRatioY); + ScrnHeight = (Width * TouchScreenHeight) / TouchScreenWidth; } int StartX = (Width - ScrnWidth) >> 1; @@ -325,8 +210,8 @@ namespace Ryujinx int ScrnMouseX = Mouse.X - StartX; int ScrnMouseY = Mouse.Y - StartY; - int MX = (int)(((float)ScrnMouseX / ScrnWidth) * TouchScreenWidth); - int MY = (int)(((float)ScrnMouseY / ScrnHeight) * TouchScreenHeight); + int MX = (ScrnMouseX * TouchScreenWidth) / ScrnWidth; + int MY = (ScrnMouseY * TouchScreenHeight) / ScrnHeight; HidTouchPoint CurrentPoint = new HidTouchPoint { @@ -397,6 +282,29 @@ namespace Ryujinx protected override void OnKeyDown(KeyboardKeyEventArgs e) { + bool ToggleFullscreen = e.Key == Key.F11 || + (e.Modifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Enter); + + if (WindowState == WindowState.Fullscreen) + { + if (e.Key == Key.Escape || ToggleFullscreen) + { + WindowState = WindowState.Normal; + } + } + else + { + if (e.Key == Key.Escape) + { + Exit(); + } + + if (ToggleFullscreen) + { + WindowState = WindowState.Fullscreen; + } + } + Keyboard = e.Keyboard; } diff --git a/Ryujinx/Ui/JoyConController.cs b/Ryujinx/Ui/JoyConController.cs index e525017d3e..aac8efd7c4 100644 --- a/Ryujinx/Ui/JoyConController.cs +++ b/Ryujinx/Ui/JoyConController.cs @@ -1,38 +1,216 @@ -using System; -using System.Collections.Generic; -using System.Text; +using OpenTK; +using OpenTK.Input; +using Ryujinx.HLE.Input; +using System; namespace Ryujinx.UI.Input { + public enum ControllerInputID + { + Invalid, + + LStick, + DPadUp, + DPadDown, + DPadLeft, + DPadRight, + Back, + LShoulder, + + RStick, + A, + B, + X, + Y, + Start, + RShoulder, + + LTrigger, + RTrigger, + + LJoystick, + RJoystick + } + public struct JoyConControllerLeft { - public string Stick; - public string StickButton; - public string DPadUp; - public string DPadDown; - public string DPadLeft; - public string DPadRight; - public string ButtonMinus; - public string ButtonL; - public string ButtonZL; + public ControllerInputID Stick; + public ControllerInputID StickButton; + public ControllerInputID DPadUp; + public ControllerInputID DPadDown; + public ControllerInputID DPadLeft; + public ControllerInputID DPadRight; + public ControllerInputID ButtonMinus; + public ControllerInputID ButtonL; + public ControllerInputID ButtonZL; } public struct JoyConControllerRight { - public string Stick; - public string StickButton; - public string ButtonA; - public string ButtonB; - public string ButtonX; - public string ButtonY; - public string ButtonPlus; - public string ButtonR; - public string ButtonZR; + public ControllerInputID Stick; + public ControllerInputID StickButton; + public ControllerInputID ButtonA; + public ControllerInputID ButtonB; + public ControllerInputID ButtonX; + public ControllerInputID ButtonY; + public ControllerInputID ButtonPlus; + public ControllerInputID ButtonR; + public ControllerInputID ButtonZR; } - public struct JoyConController + public class JoyConController { - public JoyConControllerLeft Left; - public JoyConControllerRight Right; + public bool Enabled { private set; get; } + public int Index { private set; get; } + public float Deadzone { private set; get; } + public float TriggerThreshold { private set; get; } + + public JoyConControllerLeft Left { private set; get; } + public JoyConControllerRight Right { private set; get; } + + public JoyConController( + bool Enabled, + int Index, + float Deadzone, + float TriggerThreshold, + JoyConControllerLeft Left, + JoyConControllerRight Right) + { + this.Enabled = Enabled; + this.Index = Index; + this.Deadzone = Deadzone; + this.TriggerThreshold = TriggerThreshold; + this.Left = Left; + this.Right = Right; + + //Unmapped controllers are problematic, skip them + if (GamePad.GetName(Index) == "Unmapped Controller") + { + this.Enabled = false; + } + } + + public HidControllerButtons GetButtons() + { + if (!Enabled) + { + return 0; + } + + GamePadState GpState = GamePad.GetState(Index); + + HidControllerButtons Buttons = 0; + + if (IsPressed(GpState, Left.DPadUp)) Buttons |= HidControllerButtons.KEY_DUP; + if (IsPressed(GpState, Left.DPadDown)) Buttons |= HidControllerButtons.KEY_DDOWN; + if (IsPressed(GpState, Left.DPadLeft)) Buttons |= HidControllerButtons.KEY_DLEFT; + if (IsPressed(GpState, Left.DPadRight)) Buttons |= HidControllerButtons.KEY_DRIGHT; + if (IsPressed(GpState, Left.StickButton)) Buttons |= HidControllerButtons.KEY_LSTICK; + if (IsPressed(GpState, Left.ButtonMinus)) Buttons |= HidControllerButtons.KEY_MINUS; + if (IsPressed(GpState, Left.ButtonL)) Buttons |= HidControllerButtons.KEY_L; + if (IsPressed(GpState, Left.ButtonZL)) Buttons |= HidControllerButtons.KEY_ZL; + + if (IsPressed(GpState, Right.ButtonA)) Buttons |= HidControllerButtons.KEY_A; + if (IsPressed(GpState, Right.ButtonB)) Buttons |= HidControllerButtons.KEY_B; + if (IsPressed(GpState, Right.ButtonX)) Buttons |= HidControllerButtons.KEY_X; + if (IsPressed(GpState, Right.ButtonY)) Buttons |= HidControllerButtons.KEY_Y; + if (IsPressed(GpState, Right.StickButton)) Buttons |= HidControllerButtons.KEY_RSTICK; + if (IsPressed(GpState, Right.ButtonPlus)) Buttons |= HidControllerButtons.KEY_PLUS; + if (IsPressed(GpState, Right.ButtonR)) Buttons |= HidControllerButtons.KEY_R; + if (IsPressed(GpState, Right.ButtonZR)) Buttons |= HidControllerButtons.KEY_ZR; + + return Buttons; + } + + public (short, short) GetLeftStick() + { + if (!Enabled) + { + return (0, 0); + } + + return GetStick(Left.Stick); + } + + public (short, short) GetRightStick() + { + if (!Enabled) + { + return (0, 0); + } + + return GetStick(Right.Stick); + } + + private (short, short) GetStick(ControllerInputID Joystick) + { + GamePadState GpState = GamePad.GetState(Index); + + switch (Joystick) + { + case ControllerInputID.LJoystick: + return ApplyDeadzone(GpState.ThumbSticks.Left); + + case ControllerInputID.RJoystick: + return ApplyDeadzone(GpState.ThumbSticks.Right); + + default: + return (0, 0); + } + } + + private (short, short) ApplyDeadzone(Vector2 Axis) + { + return (ClampAxis(MathF.Abs(Axis.X) > Deadzone ? Axis.X : 0f), + ClampAxis(MathF.Abs(Axis.Y) > Deadzone ? Axis.Y : 0f)); + } + + private static short ClampAxis(float Value) + { + if (Value <= -short.MaxValue) + { + return -short.MaxValue; + } + else + { + return (short)(Value * short.MaxValue); + } + } + + private bool IsPressed(GamePadState GpState, ControllerInputID Button) + { + switch (Button) + { + case ControllerInputID.A: return GpState.Buttons.A == ButtonState.Pressed; + case ControllerInputID.B: return GpState.Buttons.B == ButtonState.Pressed; + case ControllerInputID.X: return GpState.Buttons.X == ButtonState.Pressed; + case ControllerInputID.Y: return GpState.Buttons.Y == ButtonState.Pressed; + case ControllerInputID.LStick: return GpState.Buttons.LeftStick == ButtonState.Pressed; + case ControllerInputID.RStick: return GpState.Buttons.RightStick == ButtonState.Pressed; + case ControllerInputID.LShoulder: return GpState.Buttons.LeftShoulder == ButtonState.Pressed; + case ControllerInputID.RShoulder: return GpState.Buttons.RightShoulder == ButtonState.Pressed; + case ControllerInputID.DPadUp: return GpState.DPad.Up == ButtonState.Pressed; + case ControllerInputID.DPadDown: return GpState.DPad.Down == ButtonState.Pressed; + case ControllerInputID.DPadLeft: return GpState.DPad.Left == ButtonState.Pressed; + case ControllerInputID.DPadRight: return GpState.DPad.Right == ButtonState.Pressed; + case ControllerInputID.Start: return GpState.Buttons.Start == ButtonState.Pressed; + case ControllerInputID.Back: return GpState.Buttons.Back == ButtonState.Pressed; + + case ControllerInputID.LTrigger: return GpState.Triggers.Left >= TriggerThreshold; + case ControllerInputID.RTrigger: return GpState.Triggers.Right >= TriggerThreshold; + + //Using thumbsticks as buttons is not common, but it would be nice not to ignore them + case ControllerInputID.LJoystick: + return GpState.ThumbSticks.Left.X >= Deadzone || + GpState.ThumbSticks.Left.Y >= Deadzone; + + case ControllerInputID.RJoystick: + return GpState.ThumbSticks.Right.X >= Deadzone || + GpState.ThumbSticks.Right.Y >= Deadzone; + + default: + return false; + } + } } } diff --git a/Ryujinx/Ui/JoyConKeyboard.cs b/Ryujinx/Ui/JoyConKeyboard.cs index b329d9ecd1..ea9645539a 100644 --- a/Ryujinx/Ui/JoyConKeyboard.cs +++ b/Ryujinx/Ui/JoyConKeyboard.cs @@ -1,3 +1,6 @@ +using OpenTK.Input; +using Ryujinx.HLE.Input; + namespace Ryujinx.UI.Input { public struct JoyConKeyboardLeft @@ -32,9 +35,68 @@ namespace Ryujinx.UI.Input public int ButtonZR; } - public struct JoyConKeyboard + public class JoyConKeyboard { - public JoyConKeyboardLeft Left; + public JoyConKeyboardLeft Left; public JoyConKeyboardRight Right; + + public JoyConKeyboard( + JoyConKeyboardLeft Left, + JoyConKeyboardRight Right) + { + this.Left = Left; + this.Right = Right; + } + + public HidControllerButtons GetButtons(KeyboardState Keyboard) + { + HidControllerButtons Buttons = 0; + + if (Keyboard[(Key)Left.StickButton]) Buttons |= HidControllerButtons.KEY_LSTICK; + if (Keyboard[(Key)Left.DPadUp]) Buttons |= HidControllerButtons.KEY_DUP; + if (Keyboard[(Key)Left.DPadDown]) Buttons |= HidControllerButtons.KEY_DDOWN; + if (Keyboard[(Key)Left.DPadLeft]) Buttons |= HidControllerButtons.KEY_DLEFT; + if (Keyboard[(Key)Left.DPadRight]) Buttons |= HidControllerButtons.KEY_DRIGHT; + if (Keyboard[(Key)Left.ButtonMinus]) Buttons |= HidControllerButtons.KEY_MINUS; + if (Keyboard[(Key)Left.ButtonL]) Buttons |= HidControllerButtons.KEY_L; + if (Keyboard[(Key)Left.ButtonZL]) Buttons |= HidControllerButtons.KEY_ZL; + + if (Keyboard[(Key)Right.StickButton]) Buttons |= HidControllerButtons.KEY_RSTICK; + if (Keyboard[(Key)Right.ButtonA]) Buttons |= HidControllerButtons.KEY_A; + if (Keyboard[(Key)Right.ButtonB]) Buttons |= HidControllerButtons.KEY_B; + if (Keyboard[(Key)Right.ButtonX]) Buttons |= HidControllerButtons.KEY_X; + if (Keyboard[(Key)Right.ButtonY]) Buttons |= HidControllerButtons.KEY_Y; + if (Keyboard[(Key)Right.ButtonPlus]) Buttons |= HidControllerButtons.KEY_PLUS; + if (Keyboard[(Key)Right.ButtonR]) Buttons |= HidControllerButtons.KEY_R; + if (Keyboard[(Key)Right.ButtonZR]) Buttons |= HidControllerButtons.KEY_ZR; + + return Buttons; + } + + public (short, short) GetLeftStick(KeyboardState Keyboard) + { + short DX = 0; + short DY = 0; + + if (Keyboard[(Key)Left.StickUp]) DY = short.MaxValue; + if (Keyboard[(Key)Left.StickDown]) DY = -short.MaxValue; + if (Keyboard[(Key)Left.StickLeft]) DX = -short.MaxValue; + if (Keyboard[(Key)Left.StickRight]) DX = short.MaxValue; + + return (DX, DY); + } + + public (short, short) GetRightStick(KeyboardState Keyboard) + { + short DX = 0; + short DY = 0; + + if (Keyboard[(Key)Right.StickUp]) DY = short.MaxValue; + if (Keyboard[(Key)Right.StickDown]) DY = -short.MaxValue; + if (Keyboard[(Key)Right.StickLeft]) DX = -short.MaxValue; + if (Keyboard[(Key)Right.StickRight]) DX = short.MaxValue; + + return (DX, DY); + } } } diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..5392128132 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,28 @@ +version: 1.0.{build} +branches: + only: + - master +image: Visual Studio 2017 +configuration: Release +build_script: +- ps: >- + dotnet --version + + dotnet publish -c Release -r win-x64 + + dotnet publish -c Release -r linux-x64 + + dotnet publish -c Release -r osx-x64 + + 7z a ryujinx-$env:APPVEYOR_BUILD_VERSION-win_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\Release\netcoreapp2.1\win-x64\publish\ + + 7z a ryujinx-$env:APPVEYOR_BUILD_VERSION-linux_x64.tar $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\Release\netcoreapp2.1\linux-x64\publish\ + + 7z a ryujinx-$env:APPVEYOR_BUILD_VERSION-linux_x64.tar.gz ryujinx-$env:APPVEYOR_BUILD_VERSION-linux_x64.tar + + 7z a ryujinx-$env:APPVEYOR_BUILD_VERSION-osx_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\Release\netcoreapp2.1\osx-x64\publish\ + +artifacts: +- path: ryujinx-%APPVEYOR_BUILD_VERSION%-win_x64.zip +- path: ryujinx-%APPVEYOR_BUILD_VERSION%-linux_x64.tar.gz +- path: ryujinx-%APPVEYOR_BUILD_VERSION%-osx_x64.zip