diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs index 97404bbcc..a73466ae1 100644 --- a/ChocolArm64/AOpCodeTable.cs +++ b/ChocolArm64/AOpCodeTable.cs @@ -180,6 +180,10 @@ namespace ChocolArm64 SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", AInstEmit.Addp_V, typeof(AOpCodeSimdReg)); SetA64("000011100x110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); SetA64("01001110<<110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); + SetA64("0100111000101000010110xxxxxxxxxx", AInstEmit.Aesd_V, typeof(AOpCodeSimd)); + SetA64("0100111000101000010010xxxxxxxxxx", AInstEmit.Aese_V, typeof(AOpCodeSimd)); + SetA64("0100111000101000011110xxxxxxxxxx", AInstEmit.Aesimc_V, typeof(AOpCodeSimd)); + SetA64("0100111000101000011010xxxxxxxxxx", AInstEmit.Aesmc_V, typeof(AOpCodeSimd)); SetA64("0x001110001xxxxx000111xxxxxxxxxx", AInstEmit.And_V, typeof(AOpCodeSimdReg)); SetA64("0x001110011xxxxx000111xxxxxxxxxx", AInstEmit.Bic_V, typeof(AOpCodeSimdReg)); SetA64("0x10111100000xxx<0011100<100001101010xxxxxxxxxx", AInstEmit.Fcvtns_V, typeof(AOpCodeSimd)); + SetA64("011111100x100001101010xxxxxxxxxx", AInstEmit.Fcvtnu_S, typeof(AOpCodeSimd)); + SetA64("0>1011100<100001101010xxxxxxxxxx", AInstEmit.Fcvtnu_V, typeof(AOpCodeSimd)); SetA64("x00111100x101000000000xxxxxxxxxx", AInstEmit.Fcvtps_Gp, typeof(AOpCodeSimdCvt)); SetA64("x00111100x101001000000xxxxxxxxxx", AInstEmit.Fcvtpu_Gp, typeof(AOpCodeSimdCvt)); SetA64("x00111100x111000000000xxxxxxxxxx", AInstEmit.Fcvtzs_Gp, typeof(AOpCodeSimdCvt)); @@ -361,6 +369,7 @@ namespace ChocolArm64 SetA64("0x001110<<1xxxxx011101xxxxxxxxxx", AInstEmit.Sabd_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx011100xxxxxxxxxx", AInstEmit.Sabdl_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<100000011010xxxxxxxxxx", AInstEmit.Sadalp_V, typeof(AOpCodeSimd)); + SetA64("0x001110<<1xxxxx000000xxxxxxxxxx", AInstEmit.Saddl_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<100000001010xxxxxxxxxx", AInstEmit.Saddlp_V, typeof(AOpCodeSimd)); SetA64("0x001110<<1xxxxx000100xxxxxxxxxx", AInstEmit.Saddw_V, typeof(AOpCodeSimdReg)); SetA64("x0011110xx100010000000xxxxxxxxxx", AInstEmit.Scvtf_Gp, typeof(AOpCodeSimdCvt)); @@ -370,10 +379,12 @@ namespace ChocolArm64 SetA64("01011110000xxxxx010100xxxxxxxxxx", AInstEmit.Sha256h2_V, typeof(AOpCodeSimdReg)); SetA64("0101111000101000001010xxxxxxxxxx", AInstEmit.Sha256su0_V, typeof(AOpCodeSimd)); SetA64("01011110000xxxxx011000xxxxxxxxxx", AInstEmit.Sha256su1_V, typeof(AOpCodeSimdReg)); + SetA64("0x001110<<1xxxxx000001xxxxxxxxxx", AInstEmit.Shadd_V, typeof(AOpCodeSimdReg)); SetA64("010111110>>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_S, typeof(AOpCodeSimdShImm)); SetA64("0x0011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_V, typeof(AOpCodeSimdShImm)); SetA64("0x101110<<100001001110xxxxxxxxxx", AInstEmit.Shll_V, typeof(AOpCodeSimd)); SetA64("0x00111100>>>xxx100001xxxxxxxxxx", AInstEmit.Shrn_V, typeof(AOpCodeSimdShImm)); + SetA64("0x001110<<1xxxxx001001xxxxxxxxxx", AInstEmit.Shsub_V, typeof(AOpCodeSimdReg)); SetA64("0x1011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Sli_V, typeof(AOpCodeSimdShImm)); SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Smax_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", AInstEmit.Smaxp_V, typeof(AOpCodeSimdReg)); @@ -403,6 +414,7 @@ namespace ChocolArm64 SetA64("0x001110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_V, typeof(AOpCodeSimd)); SetA64("01111110<<100001001010xxxxxxxxxx", AInstEmit.Sqxtun_S, typeof(AOpCodeSimd)); SetA64("0x101110<<100001001010xxxxxxxxxx", AInstEmit.Sqxtun_V, typeof(AOpCodeSimd)); + SetA64("0x001110<<1xxxxx000101xxxxxxxxxx", AInstEmit.Srhadd_V, typeof(AOpCodeSimdReg)); SetA64("0x00111100>>>xxx001001xxxxxxxxxx", AInstEmit.Srshr_V, typeof(AOpCodeSimdShImm)); SetA64("0100111101xxxxxx001001xxxxxxxxxx", AInstEmit.Srshr_V, typeof(AOpCodeSimdShImm)); SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Sshl_V, typeof(AOpCodeSimdReg)); @@ -412,6 +424,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<<1xxxxx001000xxxxxxxxxx", AInstEmit.Ssubl_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", AInstEmit.Ssubw_V, typeof(AOpCodeSimdReg)); SetA64("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); @@ -445,10 +458,13 @@ namespace ChocolArm64 SetA64("011111100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_S, typeof(AOpCodeSimd)); SetA64("0x1011100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_V, typeof(AOpCodeSimd)); SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", AInstEmit.Uhadd_V, typeof(AOpCodeSimdReg)); + SetA64("0x101110<<1xxxxx001001xxxxxxxxxx", AInstEmit.Uhsub_V, typeof(AOpCodeSimdReg)); SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Umax_V, typeof(AOpCodeSimdReg)); SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", AInstEmit.Umaxp_V, typeof(AOpCodeSimdReg)); SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", AInstEmit.Umin_V, typeof(AOpCodeSimdReg)); SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", AInstEmit.Uminp_V, typeof(AOpCodeSimdReg)); + SetA64("0x101110<<1xxxxx100000xxxxxxxxxx", AInstEmit.Umlal_V, typeof(AOpCodeSimdReg)); + SetA64("0x101110<<1xxxxx101000xxxxxxxxxx", AInstEmit.Umlsl_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)); @@ -457,6 +473,7 @@ namespace ChocolArm64 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("0x101110<<1xxxxx000101xxxxxxxxxx", AInstEmit.Urhadd_V, typeof(AOpCodeSimdReg)); SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg)); SetA64("0x10111100>>>xxx101001xxxxxxxxxx", AInstEmit.Ushll_V, typeof(AOpCodeSimdShImm)); SetA64("0111111101xxxxxx000001xxxxxxxxxx", AInstEmit.Ushr_S, typeof(AOpCodeSimdShImm)); @@ -466,6 +483,7 @@ namespace ChocolArm64 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<<1xxxxx001000xxxxxxxxxx", AInstEmit.Usubl_V, typeof(AOpCodeSimdReg)); 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)); diff --git a/ChocolArm64/Instruction/ACryptoHelper.cs b/ChocolArm64/Instruction/ACryptoHelper.cs new file mode 100644 index 000000000..2dc65972b --- /dev/null +++ b/ChocolArm64/Instruction/ACryptoHelper.cs @@ -0,0 +1,328 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace ChocolArm64.Instruction +{ + static class ACryptoHelper + { +#region "LookUp Tables" + private static byte[] SBox = + { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 + }; + + private static byte[] InvSBox = + { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d + }; + + private static byte[] GFMul_02 = + { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, + 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, + 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, + 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, + 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, + 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, + 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, + 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, + 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, + 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, + 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, + 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 + }; + + private static byte[] GFMul_03 = + { + 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, + 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, + 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, + 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, + 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, + 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, + 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, + 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, + 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, + 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, + 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, + 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, + 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, + 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, + 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, + 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a + }; + + private static byte[] GFMul_09 = + { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, + 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, + 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, + 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, + 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, + 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, + 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, + 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, + 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, + 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, + 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, + 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, + 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46 + }; + + private static byte[] GFMul_0B = + { + 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, + 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, + 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, + 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, + 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, + 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, + 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, + 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, + 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, + 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, + 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, + 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, + 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, + 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, + 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, + 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3 + }; + + private static byte[] GFMul_0D = + { + 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, + 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, + 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, + 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, + 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, + 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, + 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, + 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, + 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, + 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, + 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, + 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, + 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, + 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, + 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, + 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97 + }; + + private static byte[] GFMul_0E = + { + 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, + 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, + 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, + 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, + 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, + 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, + 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, + 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, + 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, + 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, + 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, + 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, + 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, + 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, + 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, + 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d + }; + + private static byte[] SRPerm = { 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3 }; + + private static byte[] ISRPerm = { 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11 }; +#endregion + + public static Vector128 AESInvMixColumns(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Columns = 0; Columns <= 3; Columns++) + { + int Idx = Columns << 2; + + byte Row0 = InState[Idx + 0]; // A, E, I, M: [Row0, Col0-Col3] + byte Row1 = InState[Idx + 1]; // B, F, J, N: [Row1, Col0-Col3] + byte Row2 = InState[Idx + 2]; // C, G, K, O: [Row2, Col0-Col3] + byte Row3 = InState[Idx + 3]; // D, H, L, P: [Row3, Col0-Col3] + + OutState[Idx + 0] = (byte)((uint)GFMul_0E[Row0] ^ GFMul_0B[Row1] ^ GFMul_0D[Row2] ^ GFMul_09[Row3]); + OutState[Idx + 1] = (byte)((uint)GFMul_09[Row0] ^ GFMul_0E[Row1] ^ GFMul_0B[Row2] ^ GFMul_0D[Row3]); + OutState[Idx + 2] = (byte)((uint)GFMul_0D[Row0] ^ GFMul_09[Row1] ^ GFMul_0E[Row2] ^ GFMul_0B[Row3]); + OutState[Idx + 3] = (byte)((uint)GFMul_0B[Row0] ^ GFMul_0D[Row1] ^ GFMul_09[Row2] ^ GFMul_0E[Row3]); + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + public static Vector128 AESInvShiftRows(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Idx = 0; Idx <= 15; Idx++) + { + OutState[ISRPerm[Idx]] = InState[Idx]; + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + public static Vector128 AESInvSubBytes(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Idx = 0; Idx <= 15; Idx++) + { + OutState[Idx] = InvSBox[InState[Idx]]; + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + public static Vector128 AESMixColumns(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Columns = 0; Columns <= 3; Columns++) + { + int Idx = Columns << 2; + + byte Row0 = InState[Idx + 0]; // A, E, I, M: [Row0, Col0-Col3] + byte Row1 = InState[Idx + 1]; // B, F, J, N: [Row1, Col0-Col3] + byte Row2 = InState[Idx + 2]; // C, G, K, O: [Row2, Col0-Col3] + byte Row3 = InState[Idx + 3]; // D, H, L, P: [Row3, Col0-Col3] + + OutState[Idx + 0] = (byte)((uint)GFMul_02[Row0] ^ GFMul_03[Row1] ^ Row2 ^ Row3); + OutState[Idx + 1] = (byte)((uint)Row0 ^ GFMul_02[Row1] ^ GFMul_03[Row2] ^ Row3); + OutState[Idx + 2] = (byte)((uint)Row0 ^ Row1 ^ GFMul_02[Row2] ^ GFMul_03[Row3]); + OutState[Idx + 3] = (byte)((uint)GFMul_03[Row0] ^ Row1 ^ Row2 ^ GFMul_02[Row3]); + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + public static Vector128 AESShiftRows(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Idx = 0; Idx <= 15; Idx++) + { + OutState[SRPerm[Idx]] = InState[Idx]; + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + public static Vector128 AESSubBytes(Vector128 op) + { + byte[] InState = new byte[16]; + byte[] OutState = new byte[16]; + + FromVectorToByteArray(InState, ref op); + + for (int Idx = 0; Idx <= 15; Idx++) + { + OutState[Idx] = SBox[InState[Idx]]; + } + + FromByteArrayToVector(OutState, ref op); + + return op; + } + + private static void FromVectorToByteArray(byte[] State, ref Vector128 op) + { + ulong ULongLow = AVectorHelper.VectorExtractIntZx((op), (byte)0, 3); + ulong ULongHigh = AVectorHelper.VectorExtractIntZx((op), (byte)1, 3); + + for (int Idx = 0; Idx <= 7; Idx++) + { + State[Idx + 0] = (byte)(ULongLow & 0xFFUL); + State[Idx + 8] = (byte)(ULongHigh & 0xFFUL); + + ULongLow >>= 8; + ULongHigh >>= 8; + } + } + + private static void FromByteArrayToVector(byte[] State, ref Vector128 op) + { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + + op = Sse.StaticCast(Sse2.SetVector128( + State[15], State[14], State[13], State[12], + State[11], State[10], State[9], State[8], + State[7], State[6], State[5], State[4], + State[3], State[2], State[1], State[0])); + } + } +} diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs index 92da9ff9c..a291a7e51 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs @@ -1032,6 +1032,11 @@ namespace ChocolArm64.Instruction EmitAddLongPairwise(Context, Signed: true, Accumulate: true); } + public static void Saddl_V(AILEmitterCtx Context) + { + EmitVectorWidenRnRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Add)); + } + public static void Saddlp_V(AILEmitterCtx Context) { EmitAddLongPairwise(Context, Signed: true, Accumulate: false); @@ -1042,6 +1047,28 @@ namespace ChocolArm64.Instruction EmitVectorWidenRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Add)); } + public static void Shadd_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpSx(Context, () => + { + Context.Emit(OpCodes.Add); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Shr); + }); + } + + public static void Shsub_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpSx(Context, () => + { + Context.Emit(OpCodes.Sub); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Shr); + }); + } + public static void Smax_V(AILEmitterCtx Context) { Type[] Types = new Type[] { typeof(long), typeof(long) }; @@ -1181,6 +1208,25 @@ namespace ChocolArm64.Instruction EmitVectorSaturatingNarrowOpSxZx(Context, () => { }); } + public static void Srhadd_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpSx(Context, () => + { + Context.Emit(OpCodes.Add); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Add); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Shr); + }); + } + + public static void Ssubl_V(AILEmitterCtx Context) + { + EmitVectorWidenRnRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Sub)); + } + public static void Ssubw_V(AILEmitterCtx Context) { EmitVectorWidenRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Sub)); @@ -1303,12 +1349,40 @@ namespace ChocolArm64.Instruction { Context.Emit(OpCodes.Add); - Context.EmitLdc_I4(1); - + Context.Emit(OpCodes.Ldc_I4_1); Context.Emit(OpCodes.Shr_Un); }); } + public static void Uhsub_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpZx(Context, () => + { + Context.Emit(OpCodes.Sub); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Shr_Un); + }); + } + + public static void Umax_V(AILEmitterCtx Context) + { + Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; + + MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); + + EmitVectorBinaryOpZx(Context, () => Context.EmitCall(MthdInfo)); + } + + public static void Umaxp_V(AILEmitterCtx Context) + { + Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; + + MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); + + EmitVectorPairwiseOpZx(Context, () => Context.EmitCall(MthdInfo)); + } + public static void Umin_V(AILEmitterCtx Context) { Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; @@ -1327,22 +1401,22 @@ namespace ChocolArm64.Instruction EmitVectorPairwiseOpZx(Context, () => Context.EmitCall(MthdInfo)); } - public static void Umax_V(AILEmitterCtx Context) + public static void Umlal_V(AILEmitterCtx Context) { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorBinaryOpZx(Context, () => Context.EmitCall(MthdInfo)); + EmitVectorWidenRnRmTernaryOpZx(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Add); + }); } - public static void Umaxp_V(AILEmitterCtx Context) + public static void Umlsl_V(AILEmitterCtx Context) { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorPairwiseOpZx(Context, () => Context.EmitCall(MthdInfo)); + EmitVectorWidenRnRmTernaryOpZx(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + }); } public static void Umull_V(AILEmitterCtx Context) @@ -1380,6 +1454,20 @@ namespace ChocolArm64.Instruction EmitVectorSaturatingNarrowOpZxZx(Context, () => { }); } + public static void Urhadd_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpZx(Context, () => + { + Context.Emit(OpCodes.Add); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Add); + + Context.Emit(OpCodes.Ldc_I4_1); + Context.Emit(OpCodes.Shr_Un); + }); + } + public static void Usqadd_S(AILEmitterCtx Context) { EmitScalarSaturatingBinaryOpZx(Context, SaturatingFlags.Accumulate); @@ -1390,6 +1478,11 @@ namespace ChocolArm64.Instruction EmitVectorSaturatingBinaryOpZx(Context, SaturatingFlags.Accumulate); } + public static void Usubl_V(AILEmitterCtx Context) + { + EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); + } + public static void Usubw_V(AILEmitterCtx Context) { EmitVectorWidenRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); diff --git a/ChocolArm64/Instruction/AInstEmitSimdCrypto.cs b/ChocolArm64/Instruction/AInstEmitSimdCrypto.cs new file mode 100644 index 000000000..b2680a588 --- /dev/null +++ b/ChocolArm64/Instruction/AInstEmitSimdCrypto.cs @@ -0,0 +1,54 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Translation; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Aesd_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdvec(Op.Rn); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Decrypt)); + + Context.EmitStvec(Op.Rd); + } + + public static void Aese_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdvec(Op.Rn); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Encrypt)); + + Context.EmitStvec(Op.Rd); + } + + public static void Aesimc_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InverseMixColumns)); + + Context.EmitStvec(Op.Rd); + } + + public static void Aesmc_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.MixColumns)); + + Context.EmitStvec(Op.Rd); + } + } +} diff --git a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs index 7b355494d..231de0aff 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs @@ -106,6 +106,26 @@ namespace ChocolArm64.Instruction } } + public static void Fcvtns_S(AILEmitterCtx Context) + { + EmitFcvtn(Context, Signed: true, Scalar: true); + } + + public static void Fcvtns_V(AILEmitterCtx Context) + { + EmitFcvtn(Context, Signed: true, Scalar: false); + } + + public static void Fcvtnu_S(AILEmitterCtx Context) + { + EmitFcvtn(Context, Signed: false, Scalar: true); + } + + public static void Fcvtnu_V(AILEmitterCtx Context) + { + EmitFcvtn(Context, Signed: false, Scalar: false); + } + public static void Fcvtps_Gp(AILEmitterCtx Context) { EmitFcvt_s_Gp(Context, () => EmitUnaryMathCall(Context, nameof(Math.Ceiling))); @@ -250,6 +270,54 @@ namespace ChocolArm64.Instruction } } + private static void EmitFcvtn(AILEmitterCtx Context, bool Signed, bool Scalar) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int SizeF = Op.Size & 1; + int SizeI = SizeF + 2; + + int Bytes = Op.GetBitsCount() >> 3; + int Elems = !Scalar ? Bytes >> SizeI : 1; + + if (Scalar && (SizeF == 0)) + { + EmitVectorZeroLowerTmp(Context); + } + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); + + EmitRoundMathCall(Context, MidpointRounding.ToEven); + + if (SizeF == 0) + { + AVectorHelper.EmitCall(Context, Signed + ? nameof(AVectorHelper.SatF32ToS32) + : nameof(AVectorHelper.SatF32ToU32)); + + Context.Emit(OpCodes.Conv_U8); + } + else /* if (SizeF == 1) */ + { + AVectorHelper.EmitCall(Context, Signed + ? nameof(AVectorHelper.SatF64ToS64) + : nameof(AVectorHelper.SatF64ToU64)); + } + + EmitVectorInsertTmp(Context, Index, SizeI); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if ((Op.RegisterSize == ARegisterSize.SIMD64) || Scalar) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + private static void EmitFcvt_s_Gp(AILEmitterCtx Context, Action Emit) { EmitFcvt___Gp(Context, Emit, true); @@ -569,4 +637,4 @@ namespace ChocolArm64.Instruction } } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/ASoftFallback.cs b/ChocolArm64/Instruction/ASoftFallback.cs index 0c8a39a4a..0ae84ab2d 100644 --- a/ChocolArm64/Instruction/ASoftFallback.cs +++ b/ChocolArm64/Instruction/ASoftFallback.cs @@ -410,6 +410,42 @@ namespace ChocolArm64.Instruction } #endregion +#region "Aes" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Decrypt(Vector128 value, Vector128 roundKey) + { + if (!Sse.IsSupported) + { + throw new PlatformNotSupportedException(); + } + + return ACryptoHelper.AESInvSubBytes(ACryptoHelper.AESInvShiftRows(Sse.Xor(value, roundKey))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Encrypt(Vector128 value, Vector128 roundKey) + { + if (!Sse.IsSupported) + { + throw new PlatformNotSupportedException(); + } + + return ACryptoHelper.AESSubBytes(ACryptoHelper.AESShiftRows(Sse.Xor(value, roundKey))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 InverseMixColumns(Vector128 value) + { + return ACryptoHelper.AESInvMixColumns(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 MixColumns(Vector128 value) + { + return ACryptoHelper.AESMixColumns(value); + } +#endregion + #region "Sha256" [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 HashLower(Vector128 hash_abcd, Vector128 hash_efgh, Vector128 wk) diff --git a/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs b/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs new file mode 100644 index 000000000..3180aeff9 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalFrameBufferFormat + { + Bitmap = 0x1c, + Unknown1D = 0x1d, + RGBA32Float = 0xc0, + RGBA32Sint = 0xc1, + RGBA32Uint = 0xc2, + RGBX32Float = 0xc3, + RGBX32Sint = 0xc4, + RGBX32Uint = 0xc5, + RGBA16Unorm = 0xc6, + RGBA16Snorm = 0xc7, + RGBA16Sint = 0xc8, + RGBA16Uint = 0xc9, + RGBA16Float = 0xca, + RG32Float = 0xcb, + RG32Sint = 0xcc, + RG32Uint = 0xcd, + RGBX16Float = 0xce, + BGRA8Unorm = 0xcf, + BGRA8Srgb = 0xd0, + RGB10A2Unorm = 0xd1, + RGB10A2Uint = 0xd2, + RGBA8Unorm = 0xd5, + RGBA8Srgb = 0xd6, + RGBA8Snorm = 0xd7, + RGBA8Sint = 0xd8, + RGBA8Uint = 0xd9, + RG16Unorm = 0xda, + RG16Snorm = 0xdb, + RG16Sint = 0xdc, + RG16Uint = 0xdd, + RG16Float = 0xde, + BGR10A2Unorm = 0xdf, + R11G11B10Float = 0xe0, + R32Sint = 0xe3, + R32Uint = 0xe4, + R32Float = 0xe5, + BGRX8Unorm = 0xe6, + BGRX8Srgb = 0xe7, + B5G6R5Unorm = 0xe8, + BGR5A1Unorm = 0xe9, + RG8Unorm = 0xea, + RG8Snorm = 0xeb, + RG8Sint = 0xec, + RG8Uint = 0xed, + R16Unorm = 0xee, + R16Snorm = 0xef, + R16Sint = 0xf0, + R16Uint = 0xf1, + R16Float = 0xf2, + R8Unorm = 0xf3, + R8Snorm = 0xf4, + R8Sint = 0xf5, + R8Uint = 0xf6, + A8Unorm = 0xf7, + BGR5X1Unorm = 0xf8, + RGBX8Unorm = 0xf9, + RGBX8Srgb = 0xfa, + BGR5X1UnormUnknownFB = 0xfb, + BGR5X1UnormUnknownFC = 0xfc, + BGRX8UnormUnknownFD = 0xfd, + BGRX8UnormUnknownFE = 0xfe, + Y32UintUnknownFF = 0xff + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalImage.cs similarity index 61% rename from Ryujinx.Graphics/Gal/GalTexture.cs rename to Ryujinx.Graphics/Gal/GalImage.cs index 2c1be65b2..dc6f02e04 100644 --- a/Ryujinx.Graphics/Gal/GalTexture.cs +++ b/Ryujinx.Graphics/Gal/GalImage.cs @@ -1,25 +1,25 @@ namespace Ryujinx.Graphics.Gal { - public struct GalTexture + public struct GalImage { public int Width; public int Height; - public GalTextureFormat Format; + public GalImageFormat Format; public GalTextureSource XSource; public GalTextureSource YSource; public GalTextureSource ZSource; public GalTextureSource WSource; - public GalTexture( + public GalImage( int Width, int Height, - GalTextureFormat Format, - GalTextureSource XSource, - GalTextureSource YSource, - GalTextureSource ZSource, - GalTextureSource WSource) + GalImageFormat Format, + GalTextureSource XSource = GalTextureSource.Red, + GalTextureSource YSource = GalTextureSource.Green, + GalTextureSource ZSource = GalTextureSource.Blue, + GalTextureSource WSource = GalTextureSource.Alpha) { this.Width = Width; this.Height = Height; diff --git a/Ryujinx.Graphics/Gal/GalImageFormat.cs b/Ryujinx.Graphics/Gal/GalImageFormat.cs new file mode 100644 index 000000000..4e84067bb --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalImageFormat.cs @@ -0,0 +1,204 @@ +namespace Ryujinx.Graphics.Gal +{ + //These are Vulkan-based enumerations, do not take them as Tegra values + public enum GalImageFormat + { + Undefined = 0, + + R4G4_UNORM_PACK8 = 1, + R4G4B4A4_UNORM_PACK16 = 2, + B4G4R4A4_UNORM_PACK16 = 3, + R5G6B5_UNORM_PACK16 = 4, + B5G6R5_UNORM_PACK16 = 5, + R5G5B5A1_UNORM_PACK16 = 6, + B5G5R5A1_UNORM_PACK16 = 7, + A1R5G5B5_UNORM_PACK16 = 8, + R8_UNORM = 9, + R8_SNORM = 10, + R8_USCALED = 11, + R8_SSCALED = 12, + R8_UINT = 13, + R8_SINT = 14, + R8_SRGB = 15, + R8G8_UNORM = 16, + R8G8_SNORM = 17, + R8G8_USCALED = 18, + R8G8_SSCALED = 19, + R8G8_UINT = 20, + R8G8_SINT = 21, + R8G8_SRGB = 22, + R8G8B8_UNORM = 23, + R8G8B8_SNORM = 24, + R8G8B8_USCALED = 25, + R8G8B8_SSCALED = 26, + R8G8B8_UINT = 27, + R8G8B8_SINT = 28, + R8G8B8_SRGB = 29, + B8G8R8_UNORM = 30, + B8G8R8_SNORM = 31, + B8G8R8_USCALED = 32, + B8G8R8_SSCALED = 33, + B8G8R8_UINT = 34, + B8G8R8_SINT = 35, + B8G8R8_SRGB = 36, + R8G8B8A8_UNORM = 37, + R8G8B8A8_SNORM = 38, + R8G8B8A8_USCALED = 39, + R8G8B8A8_SSCALED = 40, + R8G8B8A8_UINT = 41, + R8G8B8A8_SINT = 42, + R8G8B8A8_SRGB = 43, + B8G8R8A8_UNORM = 44, + B8G8R8A8_SNORM = 45, + B8G8R8A8_USCALED = 46, + B8G8R8A8_SSCALED = 47, + B8G8R8A8_UINT = 48, + B8G8R8A8_SINT = 49, + B8G8R8A8_SRGB = 50, + A8B8G8R8_UNORM_PACK32 = 51, + A8B8G8R8_SNORM_PACK32 = 52, + A8B8G8R8_USCALED_PACK32 = 53, + A8B8G8R8_SSCALED_PACK32 = 54, + A8B8G8R8_UINT_PACK32 = 55, + A8B8G8R8_SINT_PACK32 = 56, + A8B8G8R8_SRGB_PACK32 = 57, + A2R10G10B10_UNORM_PACK32 = 58, + A2R10G10B10_SNORM_PACK32 = 59, + A2R10G10B10_USCALED_PACK32 = 60, + A2R10G10B10_SSCALED_PACK32 = 61, + A2R10G10B10_UINT_PACK32 = 62, + A2R10G10B10_SINT_PACK32 = 63, + A2B10G10R10_UNORM_PACK32 = 64, + A2B10G10R10_SNORM_PACK32 = 65, + A2B10G10R10_USCALED_PACK32 = 66, + A2B10G10R10_SSCALED_PACK32 = 67, + A2B10G10R10_UINT_PACK32 = 68, + A2B10G10R10_SINT_PACK32 = 69, + R16_UNORM = 70, + R16_SNORM = 71, + R16_USCALED = 72, + R16_SSCALED = 73, + R16_UINT = 74, + R16_SINT = 75, + R16_SFLOAT = 76, + R16G16_UNORM = 77, + R16G16_SNORM = 78, + R16G16_USCALED = 79, + R16G16_SSCALED = 80, + R16G16_UINT = 81, + R16G16_SINT = 82, + R16G16_SFLOAT = 83, + R16G16B16_UNORM = 84, + R16G16B16_SNORM = 85, + R16G16B16_USCALED = 86, + R16G16B16_SSCALED = 87, + R16G16B16_UINT = 88, + R16G16B16_SINT = 89, + R16G16B16_SFLOAT = 90, + R16G16B16A16_UNORM = 91, + R16G16B16A16_SNORM = 92, + R16G16B16A16_USCALED = 93, + R16G16B16A16_SSCALED = 94, + R16G16B16A16_UINT = 95, + R16G16B16A16_SINT = 96, + R16G16B16A16_SFLOAT = 97, + R32_UINT = 98, + R32_SINT = 99, + R32_SFLOAT = 100, + R32G32_UINT = 101, + R32G32_SINT = 102, + R32G32_SFLOAT = 103, + R32G32B32_UINT = 104, + R32G32B32_SINT = 105, + R32G32B32_SFLOAT = 106, + R32G32B32A32_UINT = 107, + R32G32B32A32_SINT = 108, + R32G32B32A32_SFLOAT = 109, + R64_UINT = 110, + R64_SINT = 111, + R64_SFLOAT = 112, + R64G64_UINT = 113, + R64G64_SINT = 114, + R64G64_SFLOAT = 115, + R64G64B64_UINT = 116, + R64G64B64_SINT = 117, + R64G64B64_SFLOAT = 118, + R64G64B64A64_UINT = 119, + R64G64B64A64_SINT = 120, + R64G64B64A64_SFLOAT = 121, + B10G11R11_UFLOAT_PACK32 = 122, + E5B9G9R9_UFLOAT_PACK32 = 123, + D16_UNORM = 124, + X8_D24_UNORM_PACK32 = 125, + D32_SFLOAT = 126, + S8_UINT = 127, + D16_UNORM_S8_UINT = 128, + D24_UNORM_S8_UINT = 129, + D32_SFLOAT_S8_UINT = 130, + BC1_RGB_UNORM_BLOCK = 131, + BC1_RGB_SRGB_BLOCK = 132, + BC1_RGBA_UNORM_BLOCK = 133, + BC1_RGBA_SRGB_BLOCK = 134, + BC2_UNORM_BLOCK = 135, + BC2_SRGB_BLOCK = 136, + BC3_UNORM_BLOCK = 137, + BC3_SRGB_BLOCK = 138, + BC4_UNORM_BLOCK = 139, + BC4_SNORM_BLOCK = 140, + BC5_UNORM_BLOCK = 141, + BC5_SNORM_BLOCK = 142, + BC6H_UFLOAT_BLOCK = 143, + BC6H_SFLOAT_BLOCK = 144, + BC7_UNORM_BLOCK = 145, + BC7_SRGB_BLOCK = 146, + ETC2_R8G8B8_UNORM_BLOCK = 147, + ETC2_R8G8B8_SRGB_BLOCK = 148, + ETC2_R8G8B8A1_UNORM_BLOCK = 149, + ETC2_R8G8B8A1_SRGB_BLOCK = 150, + ETC2_R8G8B8A8_UNORM_BLOCK = 151, + ETC2_R8G8B8A8_SRGB_BLOCK = 152, + EAC_R11_UNORM_BLOCK = 153, + EAC_R11_SNORM_BLOCK = 154, + EAC_R11G11_UNORM_BLOCK = 155, + EAC_R11G11_SNORM_BLOCK = 156, + + ASTC_BEGIN = ASTC_4x4_UNORM_BLOCK, + + ASTC_4x4_UNORM_BLOCK = 157, + ASTC_4x4_SRGB_BLOCK = 158, + ASTC_5x4_UNORM_BLOCK = 159, + ASTC_5x4_SRGB_BLOCK = 160, + ASTC_5x5_UNORM_BLOCK = 161, + ASTC_5x5_SRGB_BLOCK = 162, + ASTC_6x5_UNORM_BLOCK = 163, + ASTC_6x5_SRGB_BLOCK = 164, + ASTC_6x6_UNORM_BLOCK = 165, + ASTC_6x6_SRGB_BLOCK = 166, + ASTC_8x5_UNORM_BLOCK = 167, + ASTC_8x5_SRGB_BLOCK = 168, + ASTC_8x6_UNORM_BLOCK = 169, + ASTC_8x6_SRGB_BLOCK = 170, + ASTC_8x8_UNORM_BLOCK = 171, + ASTC_8x8_SRGB_BLOCK = 172, + ASTC_10x5_UNORM_BLOCK = 173, + ASTC_10x5_SRGB_BLOCK = 174, + ASTC_10x6_UNORM_BLOCK = 175, + ASTC_10x6_SRGB_BLOCK = 176, + ASTC_10x8_UNORM_BLOCK = 177, + ASTC_10x8_SRGB_BLOCK = 178, + ASTC_10x10_UNORM_BLOCK = 179, + ASTC_10x10_SRGB_BLOCK = 180, + ASTC_12x10_UNORM_BLOCK = 181, + ASTC_12x10_SRGB_BLOCK = 182, + ASTC_12x12_UNORM_BLOCK = 183, + ASTC_12x12_SRGB_BLOCK = 184, + + ASTC_END = ASTC_12x12_SRGB_BLOCK, + + REVERSED_BEGIN, + + R4G4B4A4_UNORM_PACK16_REVERSED = REVERSED_BEGIN, + + REVERSED_END + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPipelineState.cs b/Ryujinx.Graphics/Gal/GalPipelineState.cs index d1ffbe76d..7c6695141 100644 --- a/Ryujinx.Graphics/Gal/GalPipelineState.cs +++ b/Ryujinx.Graphics/Gal/GalPipelineState.cs @@ -7,6 +7,8 @@ public bool Enabled; public int Stride; public long VboKey; + public bool Instanced; + public int Divisor; public GalVertexAttrib[] Attribs; } @@ -22,6 +24,8 @@ public float FlipX; public float FlipY; + public int Instance; + public GalFrontFace FrontFace; public bool CullFaceEnabled; diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs index 7e3e65e82..009d2b826 100644 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -4,11 +4,13 @@ namespace Ryujinx.Graphics.Gal { R32G32B32A32 = 0x1, R16G16B16A16 = 0x3, + R32G32 = 0x4, A8B8G8R8 = 0x8, A2B10G10R10 = 0x9, R32 = 0xf, BC6H_SF16 = 0x10, BC6H_UF16 = 0x11, + A4B4G4R4 = 0x12, A1B5G5R5 = 0x14, B5G6R5 = 0x15, BC7U = 0x17, @@ -23,6 +25,7 @@ namespace Ryujinx.Graphics.Gal BC5 = 0x28, Z24S8 = 0x29, ZF32 = 0x2f, + ZF32_X24S8 = 0x30, Astc2D4x4 = 0x40, Astc2D5x5 = 0x41, Astc2D6x6 = 0x42, diff --git a/Ryujinx.Graphics/Gal/GalTextureType.cs b/Ryujinx.Graphics/Gal/GalTextureType.cs new file mode 100644 index 000000000..f7dd16d15 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureType + { + Snorm = 1, + Unorm = 2, + Sint = 3, + Uint = 4, + Snorm_Force_Fp16 = 5, + Unorm_Force_Fp16 = 6, + Float = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalZetaFormat.cs b/Ryujinx.Graphics/Gal/GalZetaFormat.cs new file mode 100644 index 000000000..759e31217 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalZetaFormat.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalZetaFormat + { + Z32Float = 0x0a, + Z16Unorm = 0x13, + S8Z24Unorm = 0x14, + Z24X8Unorm = 0x15, + Z24S8Unorm = 0x16, + Z24C8Unorm = 0x18, + Z32S8X24Float = 0x19, + Z24X8S8C8X16Unorm = 0x1d, + Z32X8C8X16Float = 0x1e, + Z32S8C8X16Float = 0x1f + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs index c0287ef8b..108d3d9b1 100644 --- a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs +++ b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs @@ -4,9 +4,13 @@ namespace Ryujinx.Graphics.Gal { public interface IGalFrameBuffer { - void Create(long Key, int Width, int Height); + void BindColor(long Key, int Attachment); - void Bind(long Key); + void UnbindColor(int Attachment); + + void BindZeta(long Key); + + void UnbindZeta(); void BindTexture(long Key, int Index); @@ -14,6 +18,8 @@ namespace Ryujinx.Graphics.Gal void Set(byte[] Data, int Width, int Height); + void SetMap(int[] Map); + void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom); void SetWindowSize(int Width, int Height); @@ -40,7 +46,6 @@ namespace Ryujinx.Graphics.Gal 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 89e50b1f1..a20b6f532 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Gal void ClearBuffers( GalClearBufferFlags Flags, + int Attachment, float Red, float Green, float Blue, float Alpha, float Depth, int Stencil); diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs index e906e6cdc..4b951fa61 100644 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ b/Ryujinx.Graphics/Gal/IGalShader.cs @@ -11,8 +11,6 @@ namespace Ryujinx.Graphics.Gal IEnumerable GetConstBufferUsage(long Key); IEnumerable GetTextureUsage(long Key); - void EnsureTextureBinding(string UniformName, int Value); - void Bind(long Key); void Unbind(GalShaderType Type); diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs index 2ab411990..292f59efa 100644 --- a/Ryujinx.Graphics/Gal/IGalTexture.cs +++ b/Ryujinx.Graphics/Gal/IGalTexture.cs @@ -5,9 +5,11 @@ namespace Ryujinx.Graphics.Gal void LockCache(); void UnlockCache(); - void Create(long Key, byte[] Data, GalTexture Texture); + void Create(long Key, byte[] Data, GalImage Image); - bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture); + void CreateFb(long Key, long Size, GalImage Image); + + bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image); void Bind(long Key, int Index); diff --git a/Ryujinx.Graphics/Gal/ImageFormatConverter.cs b/Ryujinx.Graphics/Gal/ImageFormatConverter.cs new file mode 100644 index 000000000..7aa39f5fb --- /dev/null +++ b/Ryujinx.Graphics/Gal/ImageFormatConverter.cs @@ -0,0 +1,279 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + public static class ImageFormatConverter + { + public static GalImageFormat ConvertTexture( + GalTextureFormat Format, + GalTextureType RType, + GalTextureType GType, + GalTextureType BType, + GalTextureType AType) + { + if (RType != GType || RType != BType || RType != AType) + { + throw new NotImplementedException("Per component types are not implemented"); + } + + GalTextureType Type = RType; + + switch (Type) + { + case GalTextureType.Snorm: + switch (Format) + { + case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SNORM; + case GalTextureFormat.A8B8G8R8: return GalImageFormat.A8B8G8R8_SNORM_PACK32; + case GalTextureFormat.A2B10G10R10: return GalImageFormat.A2B10G10R10_SNORM_PACK32; + case GalTextureFormat.G8R8: return GalImageFormat.R8G8_SNORM; + case GalTextureFormat.R16: return GalImageFormat.R16_SNORM; + case GalTextureFormat.R8: return GalImageFormat.R8_SNORM; + case GalTextureFormat.BC4: return GalImageFormat.BC4_SNORM_BLOCK; + case GalTextureFormat.BC5: return GalImageFormat.BC5_SNORM_BLOCK; + } + break; + + case GalTextureType.Unorm: + switch (Format) + { + case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_UNORM; + case GalTextureFormat.A8B8G8R8: return GalImageFormat.A8B8G8R8_UNORM_PACK32; + case GalTextureFormat.A2B10G10R10: return GalImageFormat.A2B10G10R10_UNORM_PACK32; + case GalTextureFormat.A4B4G4R4: return GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED; + case GalTextureFormat.A1B5G5R5: return GalImageFormat.A1R5G5B5_UNORM_PACK16; + case GalTextureFormat.B5G6R5: return GalImageFormat.B5G6R5_UNORM_PACK16; + case GalTextureFormat.BC7U: return GalImageFormat.BC7_UNORM_BLOCK; + case GalTextureFormat.G8R8: return GalImageFormat.R8G8_UNORM; + case GalTextureFormat.R16: return GalImageFormat.R16_UNORM; + case GalTextureFormat.R8: return GalImageFormat.R8_UNORM; + case GalTextureFormat.BC1: return GalImageFormat.BC1_RGBA_UNORM_BLOCK; + case GalTextureFormat.BC2: return GalImageFormat.BC2_UNORM_BLOCK; + case GalTextureFormat.BC3: return GalImageFormat.BC3_UNORM_BLOCK; + case GalTextureFormat.BC4: return GalImageFormat.BC4_UNORM_BLOCK; + case GalTextureFormat.BC5: return GalImageFormat.BC5_UNORM_BLOCK; + case GalTextureFormat.Z24S8: return GalImageFormat.D24_UNORM_S8_UINT; + case GalTextureFormat.ZF32_X24S8: return GalImageFormat.D32_SFLOAT_S8_UINT; + case GalTextureFormat.Astc2D4x4: return GalImageFormat.ASTC_4x4_UNORM_BLOCK; + case GalTextureFormat.Astc2D5x5: return GalImageFormat.ASTC_5x5_UNORM_BLOCK; + case GalTextureFormat.Astc2D6x6: return GalImageFormat.ASTC_6x6_UNORM_BLOCK; + case GalTextureFormat.Astc2D8x8: return GalImageFormat.ASTC_8x8_UNORM_BLOCK; + case GalTextureFormat.Astc2D10x10: return GalImageFormat.ASTC_10x10_UNORM_BLOCK; + case GalTextureFormat.Astc2D12x12: return GalImageFormat.ASTC_12x12_UNORM_BLOCK; + case GalTextureFormat.Astc2D5x4: return GalImageFormat.ASTC_5x4_UNORM_BLOCK; + case GalTextureFormat.Astc2D6x5: return GalImageFormat.ASTC_6x5_UNORM_BLOCK; + case GalTextureFormat.Astc2D8x6: return GalImageFormat.ASTC_8x6_UNORM_BLOCK; + case GalTextureFormat.Astc2D10x8: return GalImageFormat.ASTC_10x8_UNORM_BLOCK; + case GalTextureFormat.Astc2D12x10: return GalImageFormat.ASTC_12x10_UNORM_BLOCK; + case GalTextureFormat.Astc2D8x5: return GalImageFormat.ASTC_8x5_UNORM_BLOCK; + case GalTextureFormat.Astc2D10x5: return GalImageFormat.ASTC_10x5_UNORM_BLOCK; + case GalTextureFormat.Astc2D10x6: return GalImageFormat.ASTC_10x6_UNORM_BLOCK; + } + break; + + case GalTextureType.Sint: + switch (Format) + { + case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_SINT; + case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SINT; + case GalTextureFormat.R32G32: return GalImageFormat.R32G32_SINT; + case GalTextureFormat.A8B8G8R8: return GalImageFormat.A8B8G8R8_SINT_PACK32; + case GalTextureFormat.A2B10G10R10: return GalImageFormat.A2B10G10R10_SINT_PACK32; + case GalTextureFormat.R32: return GalImageFormat.R32_SINT; + case GalTextureFormat.G8R8: return GalImageFormat.R8G8_SINT; + case GalTextureFormat.R16: return GalImageFormat.R16_SINT; + case GalTextureFormat.R8: return GalImageFormat.R8_SINT; + } + break; + + case GalTextureType.Uint: + switch (Format) + { + case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_UINT; + case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_UINT; + case GalTextureFormat.R32G32: return GalImageFormat.R32G32_UINT; + case GalTextureFormat.A8B8G8R8: return GalImageFormat.A8B8G8R8_UINT_PACK32; + case GalTextureFormat.A2B10G10R10: return GalImageFormat.A2B10G10R10_UINT_PACK32; + case GalTextureFormat.R32: return GalImageFormat.R32_UINT; + case GalTextureFormat.G8R8: return GalImageFormat.R8G8_UINT; + case GalTextureFormat.R16: return GalImageFormat.R16_UINT; + case GalTextureFormat.R8: return GalImageFormat.R8_UINT; + } + break; + + case GalTextureType.Snorm_Force_Fp16: + //TODO + break; + + case GalTextureType.Unorm_Force_Fp16: + //TODO + break; + + case GalTextureType.Float: + switch (Format) + { + case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_SFLOAT; + case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SFLOAT; + case GalTextureFormat.R32G32: return GalImageFormat.R32G32_SFLOAT; + case GalTextureFormat.R32: return GalImageFormat.R32_SFLOAT; + case GalTextureFormat.BC6H_SF16: return GalImageFormat.BC6H_SFLOAT_BLOCK; + case GalTextureFormat.BC6H_UF16: return GalImageFormat.BC6H_UFLOAT_BLOCK; + case GalTextureFormat.R16: return GalImageFormat.R16_SFLOAT; + case GalTextureFormat.BF10GF11RF11: return GalImageFormat.B10G11R11_UFLOAT_PACK32; + case GalTextureFormat.ZF32: return GalImageFormat.D32_SFLOAT; + } + break; + } + + throw new NotImplementedException("0x" + ((int)Format).ToString("x2") + " " + Type.ToString()); + } + + public static GalImageFormat ConvertFrameBuffer(GalFrameBufferFormat Format) + { + switch (Format) + { + case GalFrameBufferFormat.R32Float: return GalImageFormat.R32_SFLOAT; + case GalFrameBufferFormat.RGB10A2Unorm: return GalImageFormat.A2B10G10R10_UNORM_PACK32; + case GalFrameBufferFormat.RGBA8Srgb: return GalImageFormat.A8B8G8R8_SRGB_PACK32; + case GalFrameBufferFormat.RGBA16Float: return GalImageFormat.R16G16B16A16_SFLOAT; + case GalFrameBufferFormat.R16Float: return GalImageFormat.R16_SFLOAT; + case GalFrameBufferFormat.R8Unorm: return GalImageFormat.R8_UNORM; + case GalFrameBufferFormat.RGBA8Unorm: return GalImageFormat.A8B8G8R8_UNORM_PACK32; + case GalFrameBufferFormat.R11G11B10Float: return GalImageFormat.B10G11R11_UFLOAT_PACK32; + case GalFrameBufferFormat.RGBA32Float: return GalImageFormat.R32G32B32A32_SFLOAT; + case GalFrameBufferFormat.RG16Snorm: return GalImageFormat.R16G16_SNORM; + case GalFrameBufferFormat.RG16Float: return GalImageFormat.R16G16_SFLOAT; + case GalFrameBufferFormat.RG8Snorm: return GalImageFormat.R8_SNORM; + case GalFrameBufferFormat.RGBA8Snorm: return GalImageFormat.A8B8G8R8_SNORM_PACK32; + case GalFrameBufferFormat.RG8Unorm: return GalImageFormat.R8G8_UNORM; + case GalFrameBufferFormat.BGRA8Unorm: return GalImageFormat.A8B8G8R8_UNORM_PACK32; + case GalFrameBufferFormat.BGRA8Srgb: return GalImageFormat.A8B8G8R8_SRGB_PACK32; + case GalFrameBufferFormat.RG32Float: return GalImageFormat.R32G32_SFLOAT; + case GalFrameBufferFormat.RG32Sint: return GalImageFormat.R32G32_SINT; + case GalFrameBufferFormat.RG32Uint: return GalImageFormat.R32G32_UINT; + } + + throw new NotImplementedException(Format.ToString()); + } + + public static GalImageFormat ConvertZeta(GalZetaFormat Format) + { + switch (Format) + { + case GalZetaFormat.Z32Float: return GalImageFormat.D32_SFLOAT; + case GalZetaFormat.S8Z24Unorm: return GalImageFormat.D24_UNORM_S8_UINT; + case GalZetaFormat.Z16Unorm: return GalImageFormat.D16_UNORM; + case GalZetaFormat.Z32S8X24Float: return GalImageFormat.D32_SFLOAT_S8_UINT; + } + + throw new NotImplementedException(Format.ToString()); + } + + public static bool HasColor(GalImageFormat Format) + { + switch (Format) + { + case GalImageFormat.R32G32B32A32_SFLOAT: + case GalImageFormat.R32G32B32A32_SINT: + case GalImageFormat.R32G32B32A32_UINT: + case GalImageFormat.R16G16B16A16_SFLOAT: + case GalImageFormat.R16G16B16A16_SINT: + case GalImageFormat.R16G16B16A16_UINT: + case GalImageFormat.R32G32_SFLOAT: + case GalImageFormat.R32G32_SINT: + case GalImageFormat.R32G32_UINT: + case GalImageFormat.A8B8G8R8_SNORM_PACK32: + case GalImageFormat.A8B8G8R8_UNORM_PACK32: + case GalImageFormat.A8B8G8R8_SINT_PACK32: + case GalImageFormat.A8B8G8R8_UINT_PACK32: + case GalImageFormat.A2B10G10R10_SINT_PACK32: + case GalImageFormat.A2B10G10R10_SNORM_PACK32: + case GalImageFormat.A2B10G10R10_UINT_PACK32: + case GalImageFormat.A2B10G10R10_UNORM_PACK32: + case GalImageFormat.R32_SFLOAT: + case GalImageFormat.R32_SINT: + case GalImageFormat.R32_UINT: + case GalImageFormat.BC6H_SFLOAT_BLOCK: + case GalImageFormat.BC6H_UFLOAT_BLOCK: + case GalImageFormat.A1R5G5B5_UNORM_PACK16: + case GalImageFormat.B5G6R5_UNORM_PACK16: + case GalImageFormat.BC7_UNORM_BLOCK: + case GalImageFormat.R16G16_SFLOAT: + case GalImageFormat.R16G16_SINT: + case GalImageFormat.R16G16_SNORM: + case GalImageFormat.R16G16_UNORM: + case GalImageFormat.R8G8_SINT: + case GalImageFormat.R8G8_SNORM: + case GalImageFormat.R8G8_UINT: + case GalImageFormat.R8G8_UNORM: + case GalImageFormat.R16_SFLOAT: + case GalImageFormat.R16_SINT: + case GalImageFormat.R16_SNORM: + case GalImageFormat.R16_UINT: + case GalImageFormat.R16_UNORM: + case GalImageFormat.R8_SINT: + case GalImageFormat.R8_SNORM: + case GalImageFormat.R8_UINT: + case GalImageFormat.R8_UNORM: + case GalImageFormat.B10G11R11_UFLOAT_PACK32: + case GalImageFormat.BC1_RGBA_UNORM_BLOCK: + case GalImageFormat.BC2_UNORM_BLOCK: + case GalImageFormat.BC3_UNORM_BLOCK: + case GalImageFormat.BC4_UNORM_BLOCK: + case GalImageFormat.BC5_UNORM_BLOCK: + case GalImageFormat.ASTC_4x4_UNORM_BLOCK: + case GalImageFormat.ASTC_5x5_UNORM_BLOCK: + case GalImageFormat.ASTC_6x6_UNORM_BLOCK: + case GalImageFormat.ASTC_8x8_UNORM_BLOCK: + case GalImageFormat.ASTC_10x10_UNORM_BLOCK: + case GalImageFormat.ASTC_12x12_UNORM_BLOCK: + case GalImageFormat.ASTC_5x4_UNORM_BLOCK: + case GalImageFormat.ASTC_6x5_UNORM_BLOCK: + case GalImageFormat.ASTC_8x6_UNORM_BLOCK: + case GalImageFormat.ASTC_10x8_UNORM_BLOCK: + case GalImageFormat.ASTC_12x10_UNORM_BLOCK: + case GalImageFormat.ASTC_8x5_UNORM_BLOCK: + case GalImageFormat.ASTC_10x5_UNORM_BLOCK: + case GalImageFormat.ASTC_10x6_UNORM_BLOCK: + case GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED: + return true; + + case GalImageFormat.D24_UNORM_S8_UINT: + case GalImageFormat.D32_SFLOAT: + case GalImageFormat.D16_UNORM: + case GalImageFormat.D32_SFLOAT_S8_UINT: + return false; + } + + throw new NotImplementedException(Format.ToString()); + } + + public static bool HasDepth(GalImageFormat Format) + { + switch (Format) + { + case GalImageFormat.D24_UNORM_S8_UINT: + case GalImageFormat.D32_SFLOAT: + case GalImageFormat.D16_UNORM: + case GalImageFormat.D32_SFLOAT_S8_UINT: + return true; + } + + //Depth formats are fewer than colors, so it's harder to miss one + //Instead of checking for individual formats, return false + return false; + } + + public static bool HasStencil(GalImageFormat Format) + { + switch (Format) + { + case GalImageFormat.D24_UNORM_S8_UINT: + case GalImageFormat.D32_SFLOAT_S8_UINT: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs new file mode 100644 index 000000000..74f18dcd3 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs @@ -0,0 +1,124 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class ImageHandler + { + //TODO: Use a variable value here + public const int MaxBpp = 16; + + private static int CopyBuffer = 0; + private static int CopyBufferSize = 0; + + public GalImage Image { get; private set; } + + public int Width => Image.Width; + public int Height => Image.Height; + + public GalImageFormat Format => Image.Format; + + public PixelInternalFormat InternalFormat { get; private set; } + public PixelFormat PixelFormat { get; private set; } + public PixelType PixelType { get; private set; } + + public int Handle { get; private set; } + + private bool Initialized; + + public ImageHandler() + { + Handle = GL.GenTexture(); + } + + public ImageHandler(int Handle, GalImage Image) + { + this.Handle = Handle; + + this.Image = Image; + } + + public void EnsureSetup(GalImage Image) + { + if (Width != Image.Width || + Height != Image.Height || + Format != Image.Format || + !Initialized) + { + (PixelInternalFormat InternalFormat, PixelFormat PixelFormat, PixelType PixelType) = + OGLEnumConverter.GetImageFormat(Image.Format); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + + if (Initialized) + { + if (CopyBuffer == 0) + { + CopyBuffer = GL.GenBuffer(); + } + + int MaxWidth = Math.Max(Image.Width, Width); + int MaxHeight = Math.Max(Image.Height, Height); + + int CurrentSize = MaxWidth * MaxHeight * MaxBpp; + + GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyBuffer); + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyBuffer); + + if (CopyBufferSize < CurrentSize) + { + CopyBufferSize = CurrentSize; + + GL.BufferData(BufferTarget.PixelPackBuffer, CurrentSize, IntPtr.Zero, BufferUsageHint.StreamCopy); + } + + GL.GetTexImage(TextureTarget.Texture2D, 0, this.PixelFormat, this.PixelType, IntPtr.Zero); + + GL.DeleteTexture(Handle); + + Handle = GL.GenTexture(); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + } + + const int MinFilter = (int)TextureMinFilter.Linear; + const int MagFilter = (int)TextureMagFilter.Linear; + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); + + const int Level = 0; + const int Border = 0; + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFormat, + Image.Width, + Image.Height, + Border, + PixelFormat, + PixelType, + IntPtr.Zero); + + if (Initialized) + { + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); + } + + this.Image = Image; + + this.InternalFormat = InternalFormat; + this.PixelFormat = PixelFormat; + this.PixelType = PixelType; + + Initialized = true; + } + } + + public bool HasColor { get => ImageFormatConverter.HasColor(Format); } + public bool HasDepth { get => ImageFormatConverter.HasDepth(Format); } + public bool HasStencil { get => ImageFormatConverter.HasStencil(Format); } + } +} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs index 508255413..4958b53b3 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs @@ -36,12 +36,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void SetData(long Key, long Size, IntPtr HostAddress) { - if (!Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) + if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) { - throw new InvalidOperationException(); + Buffer.SetData(Size, HostAddress); } - - Buffer.SetData(Size, HostAddress); } public bool TryGetUbo(long Key, out int UboHandle) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index 3c42e5d38..959d0e329 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -125,40 +125,75 @@ namespace Ryujinx.Graphics.Gal.OpenGL throw new ArgumentException(nameof(Type)); } - public static (PixelFormat, PixelType) GetTextureFormat(GalTextureFormat Format) + public static (PixelInternalFormat, PixelFormat, PixelType) GetImageFormat(GalImageFormat Format) { switch (Format) { - 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); - case GalTextureFormat.G8R8: return (PixelFormat.Rg, PixelType.UnsignedByte); - 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); + case GalImageFormat.R32G32B32A32_SFLOAT: return (PixelInternalFormat.Rgba32f, PixelFormat.Rgba, PixelType.Float); + case GalImageFormat.R32G32B32A32_SINT: return (PixelInternalFormat.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int); + case GalImageFormat.R32G32B32A32_UINT: return (PixelInternalFormat.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt); + case GalImageFormat.R16G16B16A16_SFLOAT: return (PixelInternalFormat.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat); + case GalImageFormat.R16G16B16A16_SINT: return (PixelInternalFormat.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short); + case GalImageFormat.R16G16B16A16_UINT: return (PixelInternalFormat.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort); + case GalImageFormat.R32G32_SFLOAT: return (PixelInternalFormat.Rg32f, PixelFormat.Rg, PixelType.Float); + case GalImageFormat.R32G32_SINT: return (PixelInternalFormat.Rg32i, PixelFormat.RgInteger, PixelType.Int); + case GalImageFormat.R32G32_UINT: return (PixelInternalFormat.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt); + case GalImageFormat.A8B8G8R8_SNORM_PACK32: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte); + case GalImageFormat.A8B8G8R8_UNORM_PACK32: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte); + case GalImageFormat.A8B8G8R8_SINT_PACK32: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte); + case GalImageFormat.A8B8G8R8_UINT_PACK32: return (PixelInternalFormat.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte); + case GalImageFormat.A8B8G8R8_SRGB_PACK32: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte); + case GalImageFormat.A2B10G10R10_UINT_PACK32: return (PixelInternalFormat.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed); + case GalImageFormat.A2B10G10R10_UNORM_PACK32: return (PixelInternalFormat.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed); + case GalImageFormat.R32_SFLOAT: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float); + case GalImageFormat.R32_SINT: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int); + case GalImageFormat.R32_UINT: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt); + case GalImageFormat.A1R5G5B5_UNORM_PACK16: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551); + case GalImageFormat.B5G6R5_UNORM_PACK16: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565); + case GalImageFormat.R16G16_SFLOAT: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat); + case GalImageFormat.R16G16_SINT: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short); + case GalImageFormat.R16G16_SNORM: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Byte); + case GalImageFormat.R16G16_UNORM: return (PixelInternalFormat.Rg16, PixelFormat.Rg, PixelType.UnsignedShort); + case GalImageFormat.R8G8_SINT: return (PixelInternalFormat.Rg8i, PixelFormat.RgInteger, PixelType.Byte); + case GalImageFormat.R8G8_SNORM: return (PixelInternalFormat.Rg8Snorm, PixelFormat.Rg, PixelType.Byte); + case GalImageFormat.R8G8_UINT: return (PixelInternalFormat.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte); + case GalImageFormat.R8G8_UNORM: return (PixelInternalFormat.Rg8, PixelFormat.Rg, PixelType.UnsignedByte); + case GalImageFormat.R16_SFLOAT: return (PixelInternalFormat.R16f, PixelFormat.Red, PixelType.HalfFloat); + case GalImageFormat.R16_SINT: return (PixelInternalFormat.R16i, PixelFormat.RedInteger, PixelType.Short); + case GalImageFormat.R16_SNORM: return (PixelInternalFormat.R16Snorm, PixelFormat.Red, PixelType.Byte); + case GalImageFormat.R16_UINT: return (PixelInternalFormat.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort); + case GalImageFormat.R16_UNORM: return (PixelInternalFormat.R16, PixelFormat.Red, PixelType.UnsignedShort); + case GalImageFormat.R8_SINT: return (PixelInternalFormat.R8i, PixelFormat.RedInteger, PixelType.Byte); + case GalImageFormat.R8_SNORM: return (PixelInternalFormat.R8Snorm, PixelFormat.Red, PixelType.Byte); + case GalImageFormat.R8_UINT: return (PixelInternalFormat.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte); + case GalImageFormat.R8_UNORM: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte); + case GalImageFormat.B10G11R11_UFLOAT_PACK32: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev); + + case GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed); + + case GalImageFormat.D24_UNORM_S8_UINT: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); + case GalImageFormat.D32_SFLOAT: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float); + case GalImageFormat.D16_UNORM: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort); + case GalImageFormat.D32_SFLOAT_S8_UINT: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); } throw new NotImplementedException(Format.ToString()); } - public static InternalFormat GetCompressedTextureFormat(GalTextureFormat Format) + public static InternalFormat GetCompressedImageFormat(GalImageFormat Format) { switch (Format) { - case GalTextureFormat.BC6H_UF16: return InternalFormat.CompressedRgbBptcUnsignedFloat; - case GalTextureFormat.BC6H_SF16: return InternalFormat.CompressedRgbBptcSignedFloat; - case GalTextureFormat.BC7U: return InternalFormat.CompressedRgbaBptcUnorm; - case GalTextureFormat.BC1: return InternalFormat.CompressedRgbaS3tcDxt1Ext; - case GalTextureFormat.BC2: return InternalFormat.CompressedRgbaS3tcDxt3Ext; - case GalTextureFormat.BC3: return InternalFormat.CompressedRgbaS3tcDxt5Ext; - case GalTextureFormat.BC4: return InternalFormat.CompressedRedRgtc1; - case GalTextureFormat.BC5: return InternalFormat.CompressedRgRgtc2; + case GalImageFormat.BC6H_UFLOAT_BLOCK: return InternalFormat.CompressedRgbBptcUnsignedFloat; + case GalImageFormat.BC6H_SFLOAT_BLOCK: return InternalFormat.CompressedRgbBptcSignedFloat; + case GalImageFormat.BC7_UNORM_BLOCK: return InternalFormat.CompressedRgbaBptcUnorm; + case GalImageFormat.BC1_RGBA_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt1Ext; + case GalImageFormat.BC2_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt3Ext; + case GalImageFormat.BC3_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt5Ext; + case GalImageFormat.BC4_SNORM_BLOCK: return InternalFormat.CompressedSignedRedRgtc1; + case GalImageFormat.BC4_UNORM_BLOCK: return InternalFormat.CompressedRedRgtc1; + case GalImageFormat.BC5_SNORM_BLOCK: return InternalFormat.CompressedSignedRgRgtc2; + case GalImageFormat.BC5_UNORM_BLOCK: return InternalFormat.CompressedRgRgtc2; } throw new NotImplementedException(Format.ToString()); @@ -184,16 +219,31 @@ namespace Ryujinx.Graphics.Gal.OpenGL { switch (Wrap) { - case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; - case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; - case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; + case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; + case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; + case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; + } - //TODO: Those needs extensions (and are currently wrong). - case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; + if (OGLExtension.HasTextureMirrorClamp()) + { + switch (Wrap) + { + case GalTextureWrap.MirrorClampToEdge: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToEdgeExt; + case GalTextureWrap.MirrorClampToBorder: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToBorderExt; + case GalTextureWrap.MirrorClamp: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampExt; + } + } + else + { + //Fallback to non-mirrored clamps + switch (Wrap) + { + case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; + } } throw new ArgumentException(nameof(Wrap)); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs index 69fce6d31..5ad422980 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL private static bool EnhancedLayouts; + private static bool TextureMirrorClamp; + public static bool HasEnhancedLayouts() { EnsureInitialized(); @@ -15,6 +17,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL return EnhancedLayouts; } + public static bool HasTextureMirrorClamp() + { + EnsureInitialized(); + + return TextureMirrorClamp; + } + private static void EnsureInitialized() { if (Initialized) @@ -23,6 +32,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL } EnhancedLayouts = HasExtension("GL_ARB_enhanced_layouts"); + + TextureMirrorClamp = HasExtension("GL_EXT_texture_mirror_clamp"); } private static bool HasExtension(string Name) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs index 62f82495c..12239c4f0 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs @@ -1,10 +1,9 @@ using OpenTK.Graphics.OpenGL; using System; -using System.Collections.Generic; namespace Ryujinx.Graphics.Gal.OpenGL { - public class OGLFrameBuffer : IGalFrameBuffer + class OGLFrameBuffer : IGalFrameBuffer { private struct Rect { @@ -15,50 +14,26 @@ namespace Ryujinx.Graphics.Gal.OpenGL public Rect(int X, int Y, int Width, int Height) { - this.X = X; - this.Y = Y; - this.Width = Width; + this.X = X; + this.Y = Y; + this.Width = Width; this.Height = Height; } } - private class FrameBuffer - { - public int Width { get; set; } - public int Height { get; set; } - - public int Handle { get; private set; } - public int RbHandle { get; private set; } - public int TexHandle { get; private set; } - - public FrameBuffer(int Width, int Height, bool HasRenderBuffer) - { - this.Width = Width; - this.Height = Height; - - Handle = GL.GenFramebuffer(); - TexHandle = GL.GenTexture(); - - if (HasRenderBuffer) - { - RbHandle = GL.GenRenderbuffer(); - } - } - } - private const int NativeWidth = 1280; private const int NativeHeight = 720; - private Dictionary Fbs; + private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8_UNORM_PACK32; + + private OGLTexture Texture; + + private ImageHandler RawTex; + private ImageHandler ReadTex; private Rect Viewport; private Rect Window; - private FrameBuffer CurrFb; - private FrameBuffer CurrReadFb; - - private FrameBuffer RawFb; - private bool FlipX; private bool FlipY; @@ -67,111 +42,163 @@ namespace Ryujinx.Graphics.Gal.OpenGL private int CropRight; private int CropBottom; - public OGLFrameBuffer() + //This framebuffer is used to attach guest rendertargets, + //think of it as a dummy OpenGL VAO + private int DummyFrameBuffer; + + //These framebuffers are used to blit images + private int SrcFb; + private int DstFb; + + //Holds current attachments, used to avoid unnecesary calls to OpenGL + private int[] ColorAttachments; + + private int DepthAttachment; + private int StencilAttachment; + + public OGLFrameBuffer(OGLTexture Texture) { - Fbs = new Dictionary(); + ColorAttachments = new int[8]; + + this.Texture = Texture; } - public void Create(long Key, int Width, int Height) + public void BindColor(long Key, int Attachment) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) + if (Texture.TryGetImage(Key, out ImageHandler Tex)) { - if (Fb.Width != Width || - Fb.Height != Height) - { - SetupTexture(Fb.TexHandle, Width, Height); + EnsureFrameBuffer(); - Fb.Width = Width; - Fb.Height = Height; - } - - return; + Attach(ref ColorAttachments[Attachment], Tex.Handle, FramebufferAttachment.ColorAttachment0 + Attachment); + } + else + { + UnbindColor(Attachment); } - - Fb = new FrameBuffer(Width, Height, true); - - SetupTexture(Fb.TexHandle, Width, Height); - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); - - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fb.RbHandle); - - GL.RenderbufferStorage( - RenderbufferTarget.Renderbuffer, - RenderbufferStorage.Depth24Stencil8, - Width, - Height); - - GL.FramebufferRenderbuffer( - FramebufferTarget.Framebuffer, - FramebufferAttachment.DepthStencilAttachment, - RenderbufferTarget.Renderbuffer, - Fb.RbHandle); - - GL.FramebufferTexture( - FramebufferTarget.Framebuffer, - FramebufferAttachment.ColorAttachment0, - Fb.TexHandle, - 0); - - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); - - Fbs.Add(Key, Fb); } - public void Bind(long Key) + public void UnbindColor(int Attachment) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); + EnsureFrameBuffer(); - CurrFb = Fb; + Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment); + } + + public void BindZeta(long Key) + { + if (Texture.TryGetImage(Key, out ImageHandler Tex)) + { + EnsureFrameBuffer(); + + if (Tex.HasDepth && Tex.HasStencil) + { + if (DepthAttachment != Tex.Handle || + StencilAttachment != Tex.Handle) + { + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.DepthStencilAttachment, + Tex.Handle, + 0); + + DepthAttachment = Tex.Handle; + + StencilAttachment = Tex.Handle; + } + } + else if (Tex.HasDepth) + { + Attach(ref DepthAttachment, Tex.Handle, FramebufferAttachment.DepthAttachment); + + Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment); + } + else if (Tex.HasStencil) + { + Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment); + + Attach(ref StencilAttachment, Tex.Handle, FramebufferAttachment.StencilAttachment); + } + else + { + throw new InvalidOperationException(); + } + } + else + { + UnbindZeta(); + } + } + + public void UnbindZeta() + { + EnsureFrameBuffer(); + + if (DepthAttachment != 0 || + StencilAttachment != 0) + { + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.DepthStencilAttachment, + 0, + 0); + + DepthAttachment = 0; + + StencilAttachment = 0; } } public void BindTexture(long Key, int Index) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) + if (Texture.TryGetImage(Key, out ImageHandler Tex)) { GL.ActiveTexture(TextureUnit.Texture0 + Index); - GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle); + GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); } } public void Set(long Key) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) + if (Texture.TryGetImage(Key, out ImageHandler Tex)) { - CurrReadFb = Fb; + ReadTex = Tex; } } public void Set(byte[] Data, int Width, int Height) { - if (RawFb == null) + if (RawTex == null) { - CreateRawFb(Width, Height); + RawTex = new ImageHandler(); } - if (RawFb.Width != Width || - RawFb.Height != Height) + RawTex.EnsureSetup(new GalImage(Width, Height, RawFormat)); + + GL.BindTexture(TextureTarget.Texture2D, RawTex.Handle); + + GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, RawTex.PixelFormat, RawTex.PixelType, Data); + + ReadTex = RawTex; + } + + public void SetMap(int[] Map) + { + if (Map != null && Map.Length > 0) { - SetupTexture(RawFb.TexHandle, Width, Height); + DrawBuffersEnum[] Mode = new DrawBuffersEnum[Map.Length]; - RawFb.Width = Width; - RawFb.Height = Height; + for (int i = 0; i < Map.Length; i++) + { + Mode[i] = DrawBuffersEnum.ColorAttachment0 + Map[i]; + } + + GL.DrawBuffers(Mode.Length, Mode); + } + else + { + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); } - - GL.ActiveTexture(TextureUnit.Texture0); - - 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); - - CurrReadFb = RawFb; } public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom) @@ -208,60 +235,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void Render() { - if (CurrReadFb != null) + if (ReadTex == null) { - int SrcX0, SrcX1, SrcY0, SrcY1; - - if (CropLeft == 0 && CropRight == 0) - { - SrcX0 = 0; - SrcX1 = CurrReadFb.Width; - } - else - { - SrcX0 = CropLeft; - SrcX1 = CropRight; - } - - if (CropTop == 0 && CropBottom == 0) - { - SrcY0 = 0; - SrcY1 = CurrReadFb.Height; - } - else - { - SrcY0 = CropTop; - SrcY1 = CropBottom; - } - - 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)); - - int DstWidth = (int)(Window.Width * RatioX); - int DstHeight = (int)(Window.Height * RatioY); - - int DstPaddingX = (Window.Width - DstWidth) / 2; - int DstPaddingY = (Window.Height - DstHeight) / 2; - - int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX; - int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX; - - int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY; - int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY; - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - - GL.Viewport(0, 0, Window.Width, Window.Height); - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrReadFb.Handle); - - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - - GL.BlitFramebuffer( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear); + return; } + + int SrcX0, SrcX1, SrcY0, SrcY1; + + if (CropLeft == 0 && CropRight == 0) + { + SrcX0 = 0; + SrcX1 = ReadTex.Width; + } + else + { + SrcX0 = CropLeft; + SrcX1 = CropRight; + } + + if (CropTop == 0 && CropBottom == 0) + { + SrcY0 = 0; + SrcY1 = ReadTex.Height; + } + else + { + SrcY0 = CropTop; + SrcY1 = CropBottom; + } + + 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)); + + int DstWidth = (int)(Window.Width * RatioX); + int DstHeight = (int)(Window.Height * RatioY); + + int DstPaddingX = (Window.Width - DstWidth) / 2; + int DstPaddingY = (Window.Height - DstHeight) / 2; + + int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX; + int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX; + + int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY; + int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY; + + if (SrcFb == 0) SrcFb = GL.GenFramebuffer(); + + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); + + GL.Viewport(0, 0, Window.Width, Window.Height); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); + + GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0); + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + GL.BlitFramebuffer( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear); + + EnsureFrameBuffer(); } public void Copy( @@ -276,39 +314,80 @@ namespace Ryujinx.Graphics.Gal.OpenGL int DstX1, int DstY1) { - if (Fbs.TryGetValue(SrcKey, out FrameBuffer SrcFb) && - Fbs.TryGetValue(DstKey, out FrameBuffer DstFb)) + if (Texture.TryGetImage(SrcKey, out ImageHandler SrcTex) && + Texture.TryGetImage(DstKey, out ImageHandler DstTex)) { - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb.Handle); - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb.Handle); + if (SrcTex.HasColor != DstTex.HasColor || + SrcTex.HasDepth != DstTex.HasDepth || + SrcTex.HasStencil != DstTex.HasStencil) + { + throw new NotImplementedException(); + } - GL.Clear(ClearBufferMask.ColorBufferBit); - - GL.BlitFramebuffer( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - ClearBufferMask.ColorBufferBit, - BlitFramebufferFilter.Linear); + if (SrcTex.HasColor) + { + CopyTextures( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + SrcTex.Handle, + DstTex.Handle, + FramebufferAttachment.ColorAttachment0, + ClearBufferMask.ColorBufferBit, + true); + } + else if (SrcTex.HasDepth && SrcTex.HasStencil) + { + CopyTextures( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + SrcTex.Handle, + DstTex.Handle, + FramebufferAttachment.DepthStencilAttachment, + ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit, + false); + } + else if (SrcTex.HasDepth) + { + CopyTextures( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + SrcTex.Handle, + DstTex.Handle, + FramebufferAttachment.DepthAttachment, + ClearBufferMask.DepthBufferBit, + false); + } + else if (SrcTex.HasStencil) + { + CopyTextures( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + SrcTex.Handle, + DstTex.Handle, + FramebufferAttachment.StencilAttachment, + ClearBufferMask.StencilBufferBit, + false); + } + else + { + throw new InvalidOperationException(); + } } -} + } public void GetBufferData(long Key, Action Callback) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) + if (Texture.TryGetImage(Key, out ImageHandler Tex)) { - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, Fb.Handle); + byte[] Data = new byte[Tex.Width * Tex.Height * ImageHandler.MaxBpp]; - byte[] Data = new byte[Fb.Width * Fb.Height * 4]; + GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); - - GL.ReadPixels( + GL.GetTexImage( + TextureTarget.Texture2D, 0, - 0, - Fb.Width, - Fb.Height, - Format, - Type, + Tex.PixelFormat, + Tex.PixelType, Data); Callback(Data); @@ -319,83 +398,99 @@ namespace Ryujinx.Graphics.Gal.OpenGL long Key, int Width, int Height, - GalTextureFormat Format, byte[] Buffer) { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) + if (Texture.TryGetImage(Key, out ImageHandler Tex)) { - GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle); + GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); 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, + Tex.InternalFormat, Width, Height, Border, - GlFormat, - Type, + Tex.PixelFormat, + Tex.PixelType, Buffer); } } - private void CreateRawFb(int Width, int Height) + private void EnsureFrameBuffer() { - if (RawFb == null) + if (DummyFrameBuffer == 0) { - RawFb = new FrameBuffer(Width, Height, false); + DummyFrameBuffer = GL.GenFramebuffer(); + } - SetupTexture(RawFb.TexHandle, Width, Height); - - RawFb.Width = Width; - RawFb.Height = Height; - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, RawFb.Handle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer); + } + private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment) + { + if (OldHandle != NewHandle) + { GL.FramebufferTexture( - FramebufferTarget.Framebuffer, - FramebufferAttachment.ColorAttachment0, - RawFb.TexHandle, + FramebufferTarget.DrawFramebuffer, + FbAttachment, + NewHandle, 0); - GL.Viewport(0, 0, Width, Height); + OldHandle = NewHandle; } } - private void SetupTexture(int Handle, int Width, int Height) + private void CopyTextures( + int SrcX0, + int SrcY0, + int SrcX1, + int SrcY1, + int DstX0, + int DstY0, + int DstX1, + int DstY1, + int SrcTexture, + int DstTexture, + FramebufferAttachment Attachment, + ClearBufferMask Mask, + bool Color) { - GL.BindTexture(TextureTarget.Texture2D, Handle); + if (SrcFb == 0) SrcFb = GL.GenFramebuffer(); + if (DstFb == 0) DstFb = GL.GenFramebuffer(); - const int MinFilter = (int)TextureMinFilter.Linear; - const int MagFilter = (int)TextureMagFilter.Linear; + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); + GL.FramebufferTexture( + FramebufferTarget.ReadFramebuffer, + Attachment, + SrcTexture, + 0); - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + Attachment, + DstTexture, + 0); - const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; + if (Color) + { + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + } - const int Level = 0; - const int Border = 0; + GL.Clear(Mask); - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Width, - Height, - Border, - Format, - Type, - IntPtr.Zero); + GL.BlitFramebuffer( + SrcX0, SrcY0, SrcX1, SrcY1, + DstX0, DstY0, DstX1, DstY1, + Mask, + Color ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest); + + EnsureFrameBuffer(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index c9e7f6c3a..051b10504 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -25,23 +25,41 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GalVertexAttribSize._11_11_10, 3 } }; - private static Dictionary AttribTypes = + private static Dictionary SignedAttribTypes = new Dictionary() { - { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int }, //? - { GalVertexAttribSize._11_11_10, VertexAttribPointerType.Int } //? + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int2101010Rev } + }; + + private static Dictionary UnsignedAttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.UnsignedInt2101010Rev }, + { GalVertexAttribSize._11_11_10, VertexAttribPointerType.UnsignedInt10F11F11FRev } }; private GalPipelineState Old; @@ -108,9 +126,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL BindVertexLayout(New); - if (New.FlipX != Old.FlipX || New.FlipY != Old.FlipY) + if (New.FlipX != Old.FlipX || New.FlipY != Old.FlipY || New.Instance != Old.Instance) { - Shader.SetFlip(New.FlipX, New.FlipY); + Shader.SetExtraData(New.FlipX, New.FlipY, New.Instance); } //Note: Uncomment SetFrontFace and SetCullFace when flipping issues are solved @@ -272,8 +290,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL private void BindConstBuffers(GalPipelineState New) { - //Index 0 is reserved - int FreeBinding = 1; + int FreeBinding = OGLShader.ReservedCbufCount; void BindIfNotNull(OGLShaderStage Stage) { @@ -326,7 +343,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL bool Unsigned = Attrib.Type == GalVertexAttribType.Unorm || - Attrib.Type == GalVertexAttribType.Uint || + Attrib.Type == GalVertexAttribType.Uint || Attrib.Type == GalVertexAttribType.Uscaled; bool Normalize = @@ -341,7 +358,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL } else { - Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0); + if (Unsigned) + { + Type = UnsignedAttribTypes[Attrib.Size]; + } + else + { + Type = SignedAttribTypes[Attrib.Size]; + } } int Size = AttribElements[Attrib.Size]; @@ -360,6 +384,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Binding.Stride, Offset); } + + if (Binding.Instanced && Binding.Divisor != 0) + { + GL.VertexAttribDivisor(Attrib.Index, 1); + } + else + { + GL.VertexAttribDivisor(Attrib.Index, 0); + } } } } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index b6e974545..451066928 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -3,7 +3,7 @@ using System; namespace Ryujinx.Graphics.Gal.OpenGL { - public class OGLRasterizer : IGalRasterizer + class OGLRasterizer : IGalRasterizer { private int[] VertexBuffers; @@ -44,36 +44,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void ClearBuffers( GalClearBufferFlags Flags, + int Attachment, float Red, float Green, float Blue, float Alpha, float Depth, int Stencil) { - ClearBufferMask Mask = ClearBufferMask.ColorBufferBit; - GL.ColorMask( Flags.HasFlag(GalClearBufferFlags.ColorRed), Flags.HasFlag(GalClearBufferFlags.ColorGreen), Flags.HasFlag(GalClearBufferFlags.ColorBlue), Flags.HasFlag(GalClearBufferFlags.ColorAlpha)); + GL.ClearBuffer(ClearBuffer.Color, Attachment, new float[] { Red, Green, Blue, Alpha }); + if (Flags.HasFlag(GalClearBufferFlags.Depth)) { - Mask |= ClearBufferMask.DepthBufferBit; + GL.ClearBuffer(ClearBuffer.Depth, 0, ref Depth); } if (Flags.HasFlag(GalClearBufferFlags.Stencil)) { - Mask |= ClearBufferMask.StencilBufferBit; + GL.ClearBuffer(ClearBuffer.Stencil, 0, ref Stencil); } - GL.ClearColor(Red, Green, Blue, Alpha); - - GL.ClearDepth(Depth); - - GL.ClearStencil(Stencil); - - GL.Clear(Mask); - GL.ColorMask(true, true, true, true); } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs index b0f6da45e..985f1086f 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs @@ -23,7 +23,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL { Buffer = new OGLConstBuffer(); - FrameBuffer = new OGLFrameBuffer(); + Texture = new OGLTexture(); + + FrameBuffer = new OGLFrameBuffer(Texture as OGLTexture); Rasterizer = new OGLRasterizer(); @@ -31,8 +33,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL Pipeline = new OGLPipeline(Buffer as OGLConstBuffer, Rasterizer as OGLRasterizer, Shader as OGLShader); - Texture = new OGLTexture(); - ActionsQueue = new ConcurrentQueue(); } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs index 9c7b8668f..73d37b879 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -9,6 +9,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLShader : IGalShader { + public const int ReservedCbufCount = 1; + + private const int ExtraDataSize = 4; + public OGLShaderProgram Current; private ConcurrentDictionary Stages; @@ -96,16 +100,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL return Enumerable.Empty(); } - public void EnsureTextureBinding(string UniformName, int Value) - { - BindProgram(); - - int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); - - GL.Uniform1(Location, Value); - } - - public unsafe void SetFlip(float X, float Y) + public unsafe void SetExtraData(float FlipX, float FlipY, int Instance) { BindProgram(); @@ -113,14 +108,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BindBuffer(BufferTarget.UniformBuffer, ExtraUboHandle); - float* Data = stackalloc float[4]; - Data[0] = X; - Data[1] = Y; + float* Data = stackalloc float[ExtraDataSize]; + Data[0] = FlipX; + Data[1] = FlipY; + Data[2] = BitConverter.Int32BitsToSingle(Instance); //Invalidate buffer - GL.BufferData(BufferTarget.UniformBuffer, 4 * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.UniformBuffer, ExtraDataSize * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); - GL.BufferSubData(BufferTarget.UniformBuffer, IntPtr.Zero, 4 * sizeof(float), (IntPtr)Data); + GL.BufferSubData(BufferTarget.UniformBuffer, IntPtr.Zero, ExtraDataSize * sizeof(float), (IntPtr)Data); } public void Bind(long Key) @@ -188,6 +184,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL CheckProgramLink(Handle); BindUniformBlocks(Handle); + BindTextureLocations(Handle); Programs.Add(Current, Handle); } @@ -205,7 +202,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BindBuffer(BufferTarget.UniformBuffer, ExtraUboHandle); - GL.BufferData(BufferTarget.UniformBuffer, 4 * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.UniformBuffer, ExtraDataSize * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); GL.BindBufferBase(BufferRangeTarget.UniformBuffer, 0, ExtraUboHandle); } @@ -227,8 +224,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.UniformBlockBinding(ProgramHandle, ExtraBlockindex, 0); - //First index is reserved - int FreeBinding = 1; + int FreeBinding = ReservedCbufCount; void BindUniformBlocksIfNotNull(OGLShaderStage Stage) { @@ -258,6 +254,34 @@ namespace Ryujinx.Graphics.Gal.OpenGL BindUniformBlocksIfNotNull(Current.Fragment); } + private void BindTextureLocations(int ProgramHandle) + { + int Index = 0; + + void BindTexturesIfNotNull(OGLShaderStage Stage) + { + if (Stage != null) + { + foreach (ShaderDeclInfo Decl in Stage.TextureUsage) + { + int Location = GL.GetUniformLocation(ProgramHandle, Decl.Name); + + GL.Uniform1(Location, Index); + + Index++; + } + } + } + + GL.UseProgram(ProgramHandle); + + BindTexturesIfNotNull(Current.Vertex); + BindTexturesIfNotNull(Current.TessControl); + BindTexturesIfNotNull(Current.TessEvaluation); + BindTexturesIfNotNull(Current.Geometry); + BindTexturesIfNotNull(Current.Fragment); + } + private static void CheckProgramLink(int Handle) { int Status = 0; diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index ac30e6fd8..e4d4bd648 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -4,26 +4,13 @@ using System; namespace Ryujinx.Graphics.Gal.OpenGL { - public class OGLTexture : IGalTexture + class OGLTexture : IGalTexture { - private class TCE - { - public int Handle; - - public GalTexture Texture; - - public TCE(int Handle, GalTexture Texture) - { - this.Handle = Handle; - this.Texture = Texture; - } - } - - private OGLCachedResource TextureCache; + private OGLCachedResource TextureCache; public OGLTexture() { - TextureCache = new OGLCachedResource(DeleteTexture); + TextureCache = new OGLCachedResource(DeleteTexture); } public void LockCache() @@ -36,73 +23,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL TextureCache.Unlock(); } - private static void DeleteTexture(TCE CachedTexture) + private static void DeleteTexture(ImageHandler CachedImage) { - GL.DeleteTexture(CachedTexture.Handle); + GL.DeleteTexture(CachedImage.Handle); } - public void Create(long Key, byte[] Data, GalTexture Texture) + public void Create(long Key, byte[] Data, GalImage Image) { int Handle = GL.GenTexture(); - TextureCache.AddOrUpdate(Key, new TCE(Handle, Texture), (uint)Data.Length); + TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length); GL.BindTexture(TextureTarget.Texture2D, Handle); const int Level = 0; //TODO: Support mipmap textures. const int Border = 0; - if (IsCompressedTextureFormat(Texture.Format)) + if (IsCompressedTextureFormat(Image.Format)) { - InternalFormat InternalFmt = OGLEnumConverter.GetCompressedTextureFormat(Texture.Format); + InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format); GL.CompressedTexImage2D( TextureTarget.Texture2D, Level, InternalFmt, - Texture.Width, - Texture.Height, + Image.Width, + Image.Height, Border, Data.Length, Data); } else { - if (Texture.Format >= GalTextureFormat.Astc2D4x4) + if (Image.Format >= GalImageFormat.ASTC_BEGIN && Image.Format <= GalImageFormat.ASTC_END) { - int TextureBlockWidth = GetAstcBlockWidth(Texture.Format); - int TextureBlockHeight = GetAstcBlockHeight(Texture.Format); + int TextureBlockWidth = GetAstcBlockWidth(Image.Format); + int TextureBlockHeight = GetAstcBlockHeight(Image.Format); Data = ASTCDecoder.DecodeToRGBA8888( Data, TextureBlockWidth, TextureBlockHeight, 1, - Texture.Width, - Texture.Height, 1); + Image.Width, + Image.Height, 1); - Texture.Format = GalTextureFormat.A8B8G8R8; + Image.Format = GalImageFormat.A8B8G8R8_UNORM_PACK32; } - const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; - - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(Texture.Format); + (PixelInternalFormat InternalFormat, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); GL.TexImage2D( TextureTarget.Texture2D, Level, - InternalFmt, - Texture.Width, - Texture.Height, + InternalFormat, + Image.Width, + Image.Height, Border, Format, Type, Data); } - int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Texture.XSource); - int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Texture.YSource); - int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Texture.ZSource); - int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Texture.WSource); + int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Image.XSource); + int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Image.YSource); + int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource); + int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Image.WSource); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, SwizzleR); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, SwizzleG); @@ -110,76 +95,100 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA); } - private static int GetAstcBlockWidth(GalTextureFormat Format) + public void CreateFb(long Key, long Size, GalImage Image) + { + if (!TryGetImage(Key, out ImageHandler CachedImage)) + { + CachedImage = new ImageHandler(); + + TextureCache.AddOrUpdate(Key, CachedImage, Size); + } + + CachedImage.EnsureSetup(Image); + } + + public bool TryGetImage(long Key, out ImageHandler CachedImage) + { + if (TextureCache.TryGetValue(Key, out CachedImage)) + { + return true; + } + + CachedImage = null; + + return false; + } + + private static int GetAstcBlockWidth(GalImageFormat Format) { switch (Format) { - case GalTextureFormat.Astc2D4x4: return 4; - case GalTextureFormat.Astc2D5x5: return 5; - case GalTextureFormat.Astc2D6x6: return 6; - case GalTextureFormat.Astc2D8x8: return 8; - case GalTextureFormat.Astc2D10x10: return 10; - case GalTextureFormat.Astc2D12x12: return 12; - case GalTextureFormat.Astc2D5x4: return 5; - case GalTextureFormat.Astc2D6x5: return 6; - case GalTextureFormat.Astc2D8x6: return 8; - case GalTextureFormat.Astc2D10x8: return 10; - case GalTextureFormat.Astc2D12x10: return 12; - case GalTextureFormat.Astc2D8x5: return 8; - case GalTextureFormat.Astc2D10x5: return 10; - case GalTextureFormat.Astc2D10x6: return 10; + case GalImageFormat.ASTC_4x4_UNORM_BLOCK: return 4; + case GalImageFormat.ASTC_5x5_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_6x6_UNORM_BLOCK: return 6; + case GalImageFormat.ASTC_8x8_UNORM_BLOCK: return 8; + case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10; + case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12; + case GalImageFormat.ASTC_5x4_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_6x5_UNORM_BLOCK: return 6; + case GalImageFormat.ASTC_8x6_UNORM_BLOCK: return 8; + case GalImageFormat.ASTC_10x8_UNORM_BLOCK: return 10; + case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 12; + case GalImageFormat.ASTC_8x5_UNORM_BLOCK: return 8; + case GalImageFormat.ASTC_10x5_UNORM_BLOCK: return 10; + case GalImageFormat.ASTC_10x6_UNORM_BLOCK: return 10; } throw new ArgumentException(nameof(Format)); } - private static int GetAstcBlockHeight(GalTextureFormat Format) + private static int GetAstcBlockHeight(GalImageFormat Format) { switch (Format) { - case GalTextureFormat.Astc2D4x4: return 4; - case GalTextureFormat.Astc2D5x5: return 5; - case GalTextureFormat.Astc2D6x6: return 6; - case GalTextureFormat.Astc2D8x8: return 8; - case GalTextureFormat.Astc2D10x10: return 10; - case GalTextureFormat.Astc2D12x12: return 12; - case GalTextureFormat.Astc2D5x4: return 4; - case GalTextureFormat.Astc2D6x5: return 5; - case GalTextureFormat.Astc2D8x6: return 6; - case GalTextureFormat.Astc2D10x8: return 8; - case GalTextureFormat.Astc2D12x10: return 10; - case GalTextureFormat.Astc2D8x5: return 5; - case GalTextureFormat.Astc2D10x5: return 5; - case GalTextureFormat.Astc2D10x6: return 6; + case GalImageFormat.ASTC_4x4_UNORM_BLOCK: return 4; + case GalImageFormat.ASTC_5x5_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_6x6_UNORM_BLOCK: return 6; + case GalImageFormat.ASTC_8x8_UNORM_BLOCK: return 8; + case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10; + case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12; + case GalImageFormat.ASTC_5x4_UNORM_BLOCK: return 4; + case GalImageFormat.ASTC_6x5_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_8x6_UNORM_BLOCK: return 6; + case GalImageFormat.ASTC_10x8_UNORM_BLOCK: return 8; + case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 10; + case GalImageFormat.ASTC_8x5_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_10x5_UNORM_BLOCK: return 5; + case GalImageFormat.ASTC_10x6_UNORM_BLOCK: return 6; } throw new ArgumentException(nameof(Format)); } - public bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture) + public bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image) { if (TextureCache.TryGetSize(Key, out long Size) && Size == DataSize) { - if (TextureCache.TryGetValue(Key, out TCE CachedTexture)) + if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) { - Texture = CachedTexture.Texture; + Image = CachedImage.Image; return true; } } - Texture = default(GalTexture); + Image = default(GalImage); return false; } public void Bind(long Key, int Index) { - if (TextureCache.TryGetValue(Key, out TCE CachedTexture)) + if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) { GL.ActiveTexture(TextureUnit.Texture0 + Index); - GL.BindTexture(TextureTarget.Texture2D, CachedTexture.Handle); + GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle); } } @@ -208,18 +217,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color); } - private static bool IsCompressedTextureFormat(GalTextureFormat Format) + private static bool IsCompressedTextureFormat(GalImageFormat Format) { switch (Format) { - case GalTextureFormat.BC6H_UF16: - case GalTextureFormat.BC6H_SF16: - case GalTextureFormat.BC7U: - case GalTextureFormat.BC1: - case GalTextureFormat.BC2: - case GalTextureFormat.BC3: - case GalTextureFormat.BC4: - case GalTextureFormat.BC5: + case GalImageFormat.BC6H_UFLOAT_BLOCK: + case GalImageFormat.BC6H_SFLOAT_BLOCK: + case GalImageFormat.BC7_UNORM_BLOCK: + case GalImageFormat.BC1_RGBA_UNORM_BLOCK: + case GalImageFormat.BC2_UNORM_BLOCK: + case GalImageFormat.BC3_UNORM_BLOCK: + case GalImageFormat.BC4_SNORM_BLOCK: + case GalImageFormat.BC4_UNORM_BLOCK: + case GalImageFormat.BC5_SNORM_BLOCK: + case GalImageFormat.BC5_UNORM_BLOCK: return true; } diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs index ccc59e048..c837632ec 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs @@ -41,10 +41,15 @@ namespace Ryujinx.Graphics.Gal.Shader public const string ExtraUniformBlockName = "Extra"; public const string FlipUniformName = "flip"; + public const string InstanceUniformName = "instance"; - public const string ProgramName = "program"; - public const string ProgramAName = ProgramName + "_a"; - public const string ProgramBName = ProgramName + "_b"; + public const string BasicBlockName = "bb"; + public const string BasicBlockAName = BasicBlockName + "_a"; + public const string BasicBlockBName = BasicBlockName + "_b"; + + public const int SsyStackSize = 16; + public const string SsyStackName = "ssy_stack"; + public const string SsyCursorName = "ssy_cursor"; private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; @@ -93,13 +98,34 @@ namespace Ryujinx.Graphics.Gal.Shader m_Preds = new Dictionary(); } - public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType) : this(ShaderType) + public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType, ShaderHeader Header) + : this(ShaderType) { StagePrefix = StagePrefixes[(int)ShaderType] + "_"; if (ShaderType == GalShaderType.Fragment) { - m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, false, 0, 4)); + int Index = 0; + + for (int Attachment = 0; Attachment < 8; Attachment++) + { + for (int Component = 0; Component < 4; Component++) + { + if (Header.OmapTargets[Attachment].ComponentEnabled(Component)) + { + m_Gprs.TryAdd(Index, new ShaderDeclInfo(GetGprName(Index), Index)); + + Index++; + } + } + } + + if (Header.OmapDepth) + { + Index = Header.DepthRegister; + + m_Gprs.TryAdd(Index, new ShaderDeclInfo(GetGprName(Index), Index)); + } } foreach (ShaderIrBlock Block in Blocks) @@ -148,6 +174,11 @@ namespace Ryujinx.Graphics.Gal.Shader return Combined; } + public static string GetGprName(int Index) + { + return GprName + Index; + } + private static void Merge( Dictionary C, Dictionary A, @@ -311,9 +342,9 @@ namespace Ryujinx.Graphics.Gal.Shader case ShaderIrOperGpr Gpr: { - if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index)) + if (!Gpr.IsConst) { - string Name = GprName + Gpr.Index; + string Name = GetGprName(Gpr.Index); m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index)); } diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 7f1cfabc8..ac34400e3 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -120,8 +120,8 @@ namespace Ryujinx.Graphics.Gal.Shader Blocks = ShaderDecoder.Decode(Memory, VpAPosition); BlocksB = ShaderDecoder.Decode(Memory, VpBPosition); - GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType); - GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType); + GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType, Header); + GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType, HeaderB); Decl = GlslDecl.Merge(DeclVpA, DeclVpB); @@ -136,7 +136,7 @@ namespace Ryujinx.Graphics.Gal.Shader Blocks = ShaderDecoder.Decode(Memory, Position); BlocksB = null; - Decl = new GlslDecl(Blocks, ShaderType); + Decl = new GlslDecl(Blocks, ShaderType, Header); return Decompile(); } @@ -155,18 +155,19 @@ namespace Ryujinx.Graphics.Gal.Shader PrintDeclOutAttributes(); PrintDeclGprs(); PrintDeclPreds(); + PrintDeclSsy(); if (BlocksB != null) { - PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramAName + "()", IdentationStr); + PrintBlockScope(Blocks, GlslDecl.BasicBlockAName); SB.AppendLine(); - PrintBlockScope(BlocksB[0], null, null, "void " + GlslDecl.ProgramBName + "()", IdentationStr); + PrintBlockScope(BlocksB, GlslDecl.BasicBlockBName); } else { - PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramName + "()", IdentationStr); + PrintBlockScope(Blocks, GlslDecl.BasicBlockName); } SB.AppendLine(); @@ -241,10 +242,15 @@ namespace Ryujinx.Graphics.Gal.Shader { if (Decl.ShaderType == GalShaderType.Vertex) { - SB.AppendLine("layout (std140) uniform " + GlslDecl.ExtraUniformBlockName + "{"); + //Memory layout here is [flip_x, flip_y, instance, unused] + //It's using 4 bytes, not 8 + + SB.AppendLine("layout (std140) uniform " + GlslDecl.ExtraUniformBlockName + " {"); SB.AppendLine(IdentationStr + "vec2 " + GlslDecl.FlipUniformName + ";"); + SB.AppendLine(IdentationStr + "int " + GlslDecl.InstanceUniformName + ";"); + SB.AppendLine("};"); } @@ -304,7 +310,17 @@ namespace Ryujinx.Graphics.Gal.Shader private void PrintDeclOutAttributes() { - if (Decl.ShaderType != GalShaderType.Fragment) + if (Decl.ShaderType == GalShaderType.Fragment) + { + for (int Attachment = 0; Attachment < 8; Attachment++) + { + if (Header.OmapTargets[Attachment].Enabled) + { + SB.AppendLine("layout (location = " + Attachment + ") out vec4 " + GlslDecl.FragmentOutputName + Attachment + ";"); + } + } + } + else { SB.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") out vec4 " + GlslDecl.PositionOutAttrName + ";"); } @@ -342,6 +358,13 @@ namespace Ryujinx.Graphics.Gal.Shader PrintDecls(Decl.Preds, "bool"); } + private void PrintDeclSsy() + { + SB.AppendLine("uint " + GlslDecl.SsyCursorName + ";"); + + SB.AppendLine("uint " + GlslDecl.SsyStackName + "[" + GlslDecl.SsyStackSize + "];" + Environment.NewLine); + } + private void PrintDecls(IReadOnlyDictionary Dict, string CustomType = null, string Suffix = "") { foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector)) @@ -352,9 +375,9 @@ namespace Ryujinx.Graphics.Gal.Shader { Name = CustomType + " " + DeclInfo.Name + Suffix + ";"; } - else if (DeclInfo.Name == GlslDecl.FragmentOutputName) + else if (DeclInfo.Name.Contains(GlslDecl.FragmentOutputName)) { - Name = "layout (location = 0) out vec4 " + DeclInfo.Name + Suffix + ";" + Environment.NewLine; + Name = "layout (location = " + DeclInfo.Index / 4 + ") out vec4 " + DeclInfo.Name + Suffix + ";"; } else { @@ -417,14 +440,16 @@ namespace Ryujinx.Graphics.Gal.Shader } } + SB.AppendLine(IdentationStr + "uint pc;"); + if (BlocksB != null) { - SB.AppendLine(IdentationStr + GlslDecl.ProgramAName + "();"); - SB.AppendLine(IdentationStr + GlslDecl.ProgramBName + "();"); + PrintProgram(Blocks, GlslDecl.BasicBlockAName); + PrintProgram(BlocksB, GlslDecl.BasicBlockBName); } else { - SB.AppendLine(IdentationStr + GlslDecl.ProgramName + "();"); + PrintProgram(Blocks, GlslDecl.BasicBlockName); } if (Decl.ShaderType != GalShaderType.Geometry) @@ -432,9 +457,62 @@ namespace Ryujinx.Graphics.Gal.Shader PrintAttrToOutput(); } + if (Decl.ShaderType == GalShaderType.Fragment) + { + if (Header.OmapDepth) + { + SB.AppendLine(IdentationStr + "gl_FragDepth = " + GlslDecl.GetGprName(Header.DepthRegister) + ";"); + } + + int GprIndex = 0; + + for (int Attachment = 0; Attachment < 8; Attachment++) + { + string Output = GlslDecl.FragmentOutputName + Attachment; + + OmapTarget Target = Header.OmapTargets[Attachment]; + + for (int Component = 0; Component < 4; Component++) + { + if (Target.ComponentEnabled(Component)) + { + SB.AppendLine(IdentationStr + Output + "[" + Component + "] = " + GlslDecl.GetGprName(GprIndex) + ";"); + + GprIndex++; + } + } + } + } + SB.AppendLine("}"); } + private void PrintProgram(ShaderIrBlock[] Blocks, string Name) + { + const string Ident1 = IdentationStr; + const string Ident2 = Ident1 + IdentationStr; + const string Ident3 = Ident2 + IdentationStr; + const string Ident4 = Ident3 + IdentationStr; + + SB.AppendLine(Ident1 + "pc = " + GetBlockPosition(Blocks[0]) + ";"); + SB.AppendLine(Ident1 + "do {"); + SB.AppendLine(Ident2 + "switch (pc) {"); + + foreach (ShaderIrBlock Block in Blocks) + { + string FunctionName = Block.Position.ToString("x8"); + + SB.AppendLine(Ident3 + "case 0x" + FunctionName + ": pc = " + Name + "_" + FunctionName + "(); break;"); + } + + SB.AppendLine(Ident3 + "default:"); + SB.AppendLine(Ident4 + "pc = 0;"); + SB.AppendLine(Ident4 + "break;"); + + SB.AppendLine(Ident2 + "}"); + SB.AppendLine(Ident1 + "} while (pc != 0);"); + } + private void PrintAttrToOutput(string Identation = IdentationStr) { foreach (KeyValuePair KV in Decl.OutAttributes) @@ -468,193 +546,145 @@ namespace Ryujinx.Graphics.Gal.Shader } } - private void PrintBlockScope( - ShaderIrBlock Block, - ShaderIrBlock EndBlock, - ShaderIrBlock LoopBlock, - string ScopeName, - string Identation, - bool IsDoWhile = false) + private void PrintBlockScope(ShaderIrBlock[] Blocks, string Name) { - string UpIdent = Identation.Substring(0, Identation.Length - IdentationStr.Length); + foreach (ShaderIrBlock Block in Blocks) + { + SB.AppendLine("uint " + Name + "_" + Block.Position.ToString("x8") + "() {"); - if (IsDoWhile) - { - SB.AppendLine(UpIdent + "do {"); - } - else - { - SB.AppendLine(UpIdent + ScopeName + " {"); - } + PrintNodes(Block, Block.GetNodes()); - while (Block != null && Block != EndBlock) - { - ShaderIrNode[] Nodes = Block.GetNodes(); - - Block = PrintNodes(Block, EndBlock, LoopBlock, Identation, Nodes); - } - - if (IsDoWhile) - { - SB.AppendLine(UpIdent + "} " + ScopeName + ";"); - } - else - { - SB.AppendLine(UpIdent + "}"); + SB.AppendLine("}" + Environment.NewLine); } } - private ShaderIrBlock PrintNodes( - ShaderIrBlock Block, - ShaderIrBlock EndBlock, - ShaderIrBlock LoopBlock, - string Identation, - params ShaderIrNode[] Nodes) + private void PrintNode(ShaderIrBlock Block, ShaderIrNode Node, string Identation) { - /* - * Notes about control flow and if-else/loop generation: - * The code assumes that the program has sane control flow, - * that is, there's no jumps to a location after another jump or - * jump target (except for the end of an if-else block), and backwards - * jumps to a location before the last loop dominator. - * Such cases needs to be transformed on a step before the GLSL code - * generation to ensure that we have sane graphs to work with. - * TODO: Such transformation is not yet implemented. - */ - string NewIdent = Identation + IdentationStr; - - ShaderIrBlock LoopTail = GetLoopTailBlock(Block); - - if (LoopTail != null && LoopBlock != Block) + if (Node is ShaderIrCond Cond) { - //Shoock! kuma shock! We have a loop here! - //The entire sequence needs to be inside a do-while block. - ShaderIrBlock LoopEnd = GetDownBlock(LoopTail); + string IfExpr = GetSrcExpr(Cond.Pred, true); - PrintBlockScope(Block, LoopEnd, Block, "while (false)", NewIdent, IsDoWhile: true); + if (Cond.Not) + { + IfExpr = "!(" + IfExpr + ")"; + } - return LoopEnd; + SB.AppendLine(Identation + "if (" + IfExpr + ") {"); + + if (Cond.Child is ShaderIrOp Op && Op.Inst == ShaderIrInst.Bra) + { + SB.AppendLine(Identation + IdentationStr + "return " + GetBlockPosition(Block.Branch) + ";"); + } + else + { + PrintNode(Block, Cond.Child, Identation + IdentationStr); + } + + SB.AppendLine(Identation + "}"); } - - foreach (ShaderIrNode Node in Nodes) + else if (Node is ShaderIrAsg Asg) { - if (Node is ShaderIrCond Cond) + if (IsValidOutOper(Asg.Dst)) { - string IfExpr = GetSrcExpr(Cond.Pred, true); + string Expr = GetSrcExpr(Asg.Src, true); - if (Cond.Not) - { - IfExpr = "!(" + IfExpr + ")"; - } + Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr); - if (Cond.Child is ShaderIrOp Op && Op.Inst == ShaderIrInst.Bra) - { - //Branch is a loop branch and would result in infinite recursion. - if (Block.Branch.Position <= Block.Position) - { - SB.AppendLine(Identation + "if (" + IfExpr + ") {"); - - SB.AppendLine(Identation + IdentationStr + "continue;"); - - SB.AppendLine(Identation + "}"); - - continue; - } - - string SubScopeName = "if (!" + IfExpr + ")"; - - PrintBlockScope(Block.Next, Block.Branch, LoopBlock, SubScopeName, NewIdent); - - ShaderIrBlock IfElseEnd = GetUpBlock(Block.Branch).Branch; - - if (IfElseEnd?.Position > Block.Branch.Position) - { - PrintBlockScope(Block.Branch, IfElseEnd, LoopBlock, "else", NewIdent); - - return IfElseEnd; - } - - return Block.Branch; - } - else - { - SB.AppendLine(Identation + "if (" + IfExpr + ") {"); - - PrintNodes(Block, EndBlock, LoopBlock, NewIdent, Cond.Child); - - SB.AppendLine(Identation + "}"); - } + SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";"); } - else if (Node is ShaderIrAsg Asg) + } + else if (Node is ShaderIrOp Op) + { + switch (Op.Inst) { - if (IsValidOutOper(Asg.Dst)) + case ShaderIrInst.Bra: { - string Expr = GetSrcExpr(Asg.Src, true); + SB.AppendLine(Identation + "return " + GetBlockPosition(Block.Branch) + ";"); - Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr); + break; + } - SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";"); - } - } - else if (Node is ShaderIrOp Op) - { - if (Op.Inst == ShaderIrInst.Bra) - { - if (Block.Branch.Position <= Block.Position) - { - SB.AppendLine(Identation + "continue;"); - } - } - else if (Op.Inst == ShaderIrInst.Emit) + case ShaderIrInst.Emit: { PrintAttrToOutput(Identation); SB.AppendLine(Identation + "EmitVertex();"); + + break; } - else + + case ShaderIrInst.Ssy: { + string StackIndex = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]"; + + int TargetPosition = (Op.OperandA as ShaderIrOperImm).Value; + + string Target = "0x" + TargetPosition.ToString("x8") + "u"; + + SB.AppendLine(Identation + StackIndex + " = " + Target + ";"); + + SB.AppendLine(Identation + GlslDecl.SsyCursorName + "++;"); + + break; + } + + case ShaderIrInst.Sync: + { + SB.AppendLine(Identation + GlslDecl.SsyCursorName + "--;"); + + string Target = GlslDecl.SsyStackName + "[" + GlslDecl.SsyCursorName + "]"; + + SB.AppendLine(Identation + "return " + Target + ";"); + + break; + } + + default: SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); - } - } - else if (Node is ShaderIrCmnt Cmnt) - { - SB.AppendLine(Identation + "// " + Cmnt.Comment); - } - else - { - throw new InvalidOperationException(); + break; } } - - return Block.Next; - } - - private ShaderIrBlock GetUpBlock(ShaderIrBlock Block) - { - return Blocks.FirstOrDefault(x => x.EndPosition == Block.Position); - } - - private ShaderIrBlock GetDownBlock(ShaderIrBlock Block) - { - return Blocks.FirstOrDefault(x => x.Position == Block.EndPosition); - } - - private ShaderIrBlock GetLoopTailBlock(ShaderIrBlock LoopHead) - { - ShaderIrBlock Tail = null; - - foreach (ShaderIrBlock Block in LoopHead.Sources) + else if (Node is ShaderIrCmnt Cmnt) { - if (Block.Position >= LoopHead.Position) - { - if (Tail == null || Tail.Position < Block.Position) - { - Tail = Block; - } - } + SB.AppendLine(Identation + "// " + Cmnt.Comment); + } + else + { + throw new InvalidOperationException(); + } + } + + private void PrintNodes(ShaderIrBlock Block, ShaderIrNode[] Nodes) + { + foreach (ShaderIrNode Node in Nodes) + { + PrintNode(Block, Node, IdentationStr); } - return Tail; + if (Nodes.Length > 0) + { + ShaderIrNode Last = Nodes[Nodes.Length - 1]; + + bool UnconditionalFlowChange = false; + + if (Last is ShaderIrOp Op) + { + switch (Op.Inst) + { + case ShaderIrInst.Bra: + case ShaderIrInst.Exit: + case ShaderIrInst.Kil: + case ShaderIrInst.Sync: + UnconditionalFlowChange = true; + break; + } + } + + if (!UnconditionalFlowChange) + { + SB.AppendLine(IdentationStr + "return " + GetBlockPosition(Block.Next) + ";"); + } + } } private bool IsValidOutOper(ShaderIrNode Node) @@ -779,7 +809,7 @@ namespace Ryujinx.Graphics.Gal.Shader switch (Abuf.Offs) { case GlslDecl.VertexIdAttr: return "gl_VertexID"; - case GlslDecl.InstanceIdAttr: return "gl_InstanceID"; + case GlslDecl.InstanceIdAttr: return GlslDecl.InstanceUniformName; } } else if (Decl.ShaderType == GalShaderType.TessEvaluation) @@ -829,8 +859,11 @@ namespace Ryujinx.Graphics.Gal.Shader { return "gl_PointSize"; } + } - throw new InvalidOperationException(); + if (DeclInfo.Index >= 16) + { + throw new InvalidOperationException($"Shader attribute offset {Abuf.Offs} is invalid."); } if (Decl.ShaderType == GalShaderType.Geometry) @@ -876,7 +909,7 @@ namespace Ryujinx.Graphics.Gal.Shader private string GetNameWithSwizzle(IReadOnlyDictionary Dict, int Index) { - int VecIndex = Index >> 2; + int VecIndex = Index & ~3; if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) { @@ -961,7 +994,7 @@ namespace Ryujinx.Graphics.Gal.Shader private string GetCnumExpr(ShaderIrOp Op) => GetUnaryCall(Op, "!isnan"); - private string GetExitExpr(ShaderIrOp Op) => "return"; + private string GetExitExpr(ShaderIrOp Op) => "return 0u"; private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos"); @@ -993,7 +1026,31 @@ namespace Ryujinx.Graphics.Gal.Shader return "int(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; } - private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA); + private string GetIpaExpr(ShaderIrOp Op) + { + ShaderIrMetaIpa Meta = (ShaderIrMetaIpa)Op.MetaData; + + ShaderIrOperAbuf Abuf = (ShaderIrOperAbuf)Op.OperandA; + + if (Meta.Mode == ShaderIpaMode.Pass) + { + int Index = Abuf.Offs >> 4; + int Elem = (Abuf.Offs >> 2) & 3; + + if (Decl.ShaderType == GalShaderType.Fragment && Index == GlslDecl.GlPositionVec4Index) + { + switch (Elem) + { + case 0: return "gl_FragCoord.x"; + case 1: return "gl_FragCoord.y"; + case 2: return "gl_FragCoord.z"; + case 3: return "1"; + } + } + } + + return GetSrcExpr(Op.OperandA); + } private string GetKilExpr(ShaderIrOp Op) => "discard"; @@ -1306,5 +1363,17 @@ namespace Ryujinx.Graphics.Gal.Shader throw new ArgumentException(nameof(Node)); } + + private static string GetBlockPosition(ShaderIrBlock Block) + { + if (Block != null) + { + return "0x" + Block.Position.ToString("x8") + "u"; + } + else + { + return "0u"; + } + } } } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs index ef0fd78bd..73625f65f 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs @@ -1,4 +1,4 @@ namespace Ryujinx.Graphics.Gal.Shader { - delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode); + delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode, long Position); } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs index b60da7c1c..afff7e9b3 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -6,32 +6,32 @@ namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Bfe_C(ShaderIrBlock Block, long OpCode) + public static void Bfe_C(ShaderIrBlock Block, long OpCode, long Position) { EmitBfe(Block, OpCode, ShaderOper.CR); } - public static void Bfe_I(ShaderIrBlock Block, long OpCode) + public static void Bfe_I(ShaderIrBlock Block, long OpCode, long Position) { EmitBfe(Block, OpCode, ShaderOper.Imm); } - public static void Bfe_R(ShaderIrBlock Block, long OpCode) + public static void Bfe_R(ShaderIrBlock Block, long OpCode, long Position) { EmitBfe(Block, OpCode, ShaderOper.RR); } - public static void Fadd_C(ShaderIrBlock Block, long OpCode) + public static void Fadd_C(ShaderIrBlock Block, long OpCode, long Position) { EmitFadd(Block, OpCode, ShaderOper.CR); } - public static void Fadd_I(ShaderIrBlock Block, long OpCode) + public static void Fadd_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFadd(Block, OpCode, ShaderOper.Immf); } - public static void Fadd_I32(ShaderIrBlock Block, long OpCode) + public static void Fadd_I32(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperA = GetOperGpr8 (OpCode); ShaderIrNode OperB = GetOperImmf32_20(OpCode); @@ -49,47 +49,47 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); } - public static void Fadd_R(ShaderIrBlock Block, long OpCode) + public static void Fadd_R(ShaderIrBlock Block, long OpCode, long Position) { EmitFadd(Block, OpCode, ShaderOper.RR); } - public static void Ffma_CR(ShaderIrBlock Block, long OpCode) + public static void Ffma_CR(ShaderIrBlock Block, long OpCode, long Position) { EmitFfma(Block, OpCode, ShaderOper.CR); } - public static void Ffma_I(ShaderIrBlock Block, long OpCode) + public static void Ffma_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFfma(Block, OpCode, ShaderOper.Immf); } - public static void Ffma_RC(ShaderIrBlock Block, long OpCode) + public static void Ffma_RC(ShaderIrBlock Block, long OpCode, long Position) { EmitFfma(Block, OpCode, ShaderOper.RC); } - public static void Ffma_RR(ShaderIrBlock Block, long OpCode) + public static void Ffma_RR(ShaderIrBlock Block, long OpCode, long Position) { EmitFfma(Block, OpCode, ShaderOper.RR); } - public static void Fmnmx_C(ShaderIrBlock Block, long OpCode) + public static void Fmnmx_C(ShaderIrBlock Block, long OpCode, long Position) { EmitFmnmx(Block, OpCode, ShaderOper.CR); } - public static void Fmnmx_I(ShaderIrBlock Block, long OpCode) + public static void Fmnmx_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFmnmx(Block, OpCode, ShaderOper.Immf); } - public static void Fmnmx_R(ShaderIrBlock Block, long OpCode) + public static void Fmnmx_R(ShaderIrBlock Block, long OpCode, long Position) { EmitFmnmx(Block, OpCode, ShaderOper.RR); } - public static void Fmul_I32(ShaderIrBlock Block, long OpCode) + public static void Fmul_I32(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperA = GetOperGpr8 (OpCode); ShaderIrNode OperB = GetOperImmf32_20(OpCode); @@ -99,62 +99,62 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); } - public static void Fmul_C(ShaderIrBlock Block, long OpCode) + public static void Fmul_C(ShaderIrBlock Block, long OpCode, long Position) { EmitFmul(Block, OpCode, ShaderOper.CR); } - public static void Fmul_I(ShaderIrBlock Block, long OpCode) + public static void Fmul_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFmul(Block, OpCode, ShaderOper.Immf); } - public static void Fmul_R(ShaderIrBlock Block, long OpCode) + public static void Fmul_R(ShaderIrBlock Block, long OpCode, long Position) { EmitFmul(Block, OpCode, ShaderOper.RR); } - public static void Fset_C(ShaderIrBlock Block, long OpCode) + public static void Fset_C(ShaderIrBlock Block, long OpCode, long Position) { EmitFset(Block, OpCode, ShaderOper.CR); } - public static void Fset_I(ShaderIrBlock Block, long OpCode) + public static void Fset_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFset(Block, OpCode, ShaderOper.Immf); } - public static void Fset_R(ShaderIrBlock Block, long OpCode) + public static void Fset_R(ShaderIrBlock Block, long OpCode, long Position) { EmitFset(Block, OpCode, ShaderOper.RR); } - public static void Fsetp_C(ShaderIrBlock Block, long OpCode) + public static void Fsetp_C(ShaderIrBlock Block, long OpCode, long Position) { EmitFsetp(Block, OpCode, ShaderOper.CR); } - public static void Fsetp_I(ShaderIrBlock Block, long OpCode) + public static void Fsetp_I(ShaderIrBlock Block, long OpCode, long Position) { EmitFsetp(Block, OpCode, ShaderOper.Immf); } - public static void Fsetp_R(ShaderIrBlock Block, long OpCode) + public static void Fsetp_R(ShaderIrBlock Block, long OpCode, long Position) { EmitFsetp(Block, OpCode, ShaderOper.RR); } - public static void Iadd_C(ShaderIrBlock Block, long OpCode) + public static void Iadd_C(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd(Block, OpCode, ShaderOper.CR); } - public static void Iadd_I(ShaderIrBlock Block, long OpCode) + public static void Iadd_I(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd(Block, OpCode, ShaderOper.Imm); } - public static void Iadd_I32(ShaderIrBlock Block, long OpCode) + public static void Iadd_I32(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperA = GetOperGpr8 (OpCode); ShaderIrNode OperB = GetOperImm32_20(OpCode); @@ -168,97 +168,101 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); } - public static void Iadd_R(ShaderIrBlock Block, long OpCode) + public static void Iadd_R(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd(Block, OpCode, ShaderOper.RR); } - public static void Iadd3_C(ShaderIrBlock Block, long OpCode) + public static void Iadd3_C(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd3(Block, OpCode, ShaderOper.CR); } - public static void Iadd3_I(ShaderIrBlock Block, long OpCode) + public static void Iadd3_I(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd3(Block, OpCode, ShaderOper.Imm); } - public static void Iadd3_R(ShaderIrBlock Block, long OpCode) + public static void Iadd3_R(ShaderIrBlock Block, long OpCode, long Position) { EmitIadd3(Block, OpCode, ShaderOper.RR); } - public static void Imnmx_C(ShaderIrBlock Block, long OpCode) + public static void Imnmx_C(ShaderIrBlock Block, long OpCode, long Position) { EmitImnmx(Block, OpCode, ShaderOper.CR); } - public static void Imnmx_I(ShaderIrBlock Block, long OpCode) + public static void Imnmx_I(ShaderIrBlock Block, long OpCode, long Position) { EmitImnmx(Block, OpCode, ShaderOper.Imm); } - public static void Imnmx_R(ShaderIrBlock Block, long OpCode) + public static void Imnmx_R(ShaderIrBlock Block, long OpCode, long Position) { EmitImnmx(Block, OpCode, ShaderOper.RR); } - public static void Ipa(ShaderIrBlock Block, long OpCode) + public static void Ipa(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperA = GetOperAbuf28(OpCode); ShaderIrNode OperB = GetOperGpr20 (OpCode); - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB); + ShaderIpaMode Mode = (ShaderIpaMode)((OpCode >> 54) & 3); + + ShaderIrMetaIpa Meta = new ShaderIrMetaIpa(Mode); + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB, null, Meta); Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); } - public static void Iscadd_C(ShaderIrBlock Block, long OpCode) + public static void Iscadd_C(ShaderIrBlock Block, long OpCode, long Position) { EmitIscadd(Block, OpCode, ShaderOper.CR); } - public static void Iscadd_I(ShaderIrBlock Block, long OpCode) + public static void Iscadd_I(ShaderIrBlock Block, long OpCode, long Position) { EmitIscadd(Block, OpCode, ShaderOper.Imm); } - public static void Iscadd_R(ShaderIrBlock Block, long OpCode) + public static void Iscadd_R(ShaderIrBlock Block, long OpCode, long Position) { EmitIscadd(Block, OpCode, ShaderOper.RR); } - public static void Iset_C(ShaderIrBlock Block, long OpCode) + public static void Iset_C(ShaderIrBlock Block, long OpCode, long Position) { EmitIset(Block, OpCode, ShaderOper.CR); } - public static void Iset_I(ShaderIrBlock Block, long OpCode) + public static void Iset_I(ShaderIrBlock Block, long OpCode, long Position) { EmitIset(Block, OpCode, ShaderOper.Imm); } - public static void Iset_R(ShaderIrBlock Block, long OpCode) + public static void Iset_R(ShaderIrBlock Block, long OpCode, long Position) { EmitIset(Block, OpCode, ShaderOper.RR); } - public static void Isetp_C(ShaderIrBlock Block, long OpCode) + public static void Isetp_C(ShaderIrBlock Block, long OpCode, long Position) { EmitIsetp(Block, OpCode, ShaderOper.CR); } - public static void Isetp_I(ShaderIrBlock Block, long OpCode) + public static void Isetp_I(ShaderIrBlock Block, long OpCode, long Position) { EmitIsetp(Block, OpCode, ShaderOper.Imm); } - public static void Isetp_R(ShaderIrBlock Block, long OpCode) + public static void Isetp_R(ShaderIrBlock Block, long OpCode, long Position) { EmitIsetp(Block, OpCode, ShaderOper.RR); } - public static void Lop_I32(ShaderIrBlock Block, long OpCode) + public static void Lop_I32(ShaderIrBlock Block, long OpCode, long Position) { int SubOp = (int)(OpCode >> 53) & 3; @@ -292,22 +296,22 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Lop_C(ShaderIrBlock Block, long OpCode) + public static void Lop_C(ShaderIrBlock Block, long OpCode, long Position) { EmitLop(Block, OpCode, ShaderOper.CR); } - public static void Lop_I(ShaderIrBlock Block, long OpCode) + public static void Lop_I(ShaderIrBlock Block, long OpCode, long Position) { EmitLop(Block, OpCode, ShaderOper.Imm); } - public static void Lop_R(ShaderIrBlock Block, long OpCode) + public static void Lop_R(ShaderIrBlock Block, long OpCode, long Position) { EmitLop(Block, OpCode, ShaderOper.RR); } - public static void Mufu(ShaderIrBlock Block, long OpCode) + public static void Mufu(ShaderIrBlock Block, long OpCode, long Position) { int SubOp = (int)(OpCode >> 20) & 0xf; @@ -336,7 +340,7 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); } - public static void Psetp(ShaderIrBlock Block, long OpCode) + public static void Psetp(ShaderIrBlock Block, long OpCode, long Position) { bool NegA = ((OpCode >> 15) & 1) != 0; bool NegB = ((OpCode >> 32) & 1) != 0; @@ -390,47 +394,47 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); } - public static void Rro_C(ShaderIrBlock Block, long OpCode) + public static void Rro_C(ShaderIrBlock Block, long OpCode, long Position) { EmitRro(Block, OpCode, ShaderOper.CR); } - public static void Rro_I(ShaderIrBlock Block, long OpCode) + public static void Rro_I(ShaderIrBlock Block, long OpCode, long Position) { EmitRro(Block, OpCode, ShaderOper.Immf); } - public static void Rro_R(ShaderIrBlock Block, long OpCode) + public static void Rro_R(ShaderIrBlock Block, long OpCode, long Position) { EmitRro(Block, OpCode, ShaderOper.RR); } - public static void Shl_C(ShaderIrBlock Block, long OpCode) + public static void Shl_C(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.CR, ShaderIrInst.Lsl); } - public static void Shl_I(ShaderIrBlock Block, long OpCode) + public static void Shl_I(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.Imm, ShaderIrInst.Lsl); } - public static void Shl_R(ShaderIrBlock Block, long OpCode) + public static void Shl_R(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.RR, ShaderIrInst.Lsl); } - public static void Shr_C(ShaderIrBlock Block, long OpCode) + public static void Shr_C(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode)); } - public static void Shr_I(ShaderIrBlock Block, long OpCode) + public static void Shr_I(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode)); } - public static void Shr_R(ShaderIrBlock Block, long OpCode) + public static void Shr_R(ShaderIrBlock Block, long OpCode, long Position) { EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode)); } @@ -442,7 +446,7 @@ namespace Ryujinx.Graphics.Gal.Shader return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; } - public static void Vmad(ShaderIrBlock Block, long OpCode) + public static void Vmad(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperA = GetOperGpr8(OpCode); @@ -477,22 +481,22 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Final), OpCode)); } - public static void Xmad_CR(ShaderIrBlock Block, long OpCode) + public static void Xmad_CR(ShaderIrBlock Block, long OpCode, long Position) { EmitXmad(Block, OpCode, ShaderOper.CR); } - public static void Xmad_I(ShaderIrBlock Block, long OpCode) + public static void Xmad_I(ShaderIrBlock Block, long OpCode, long Position) { EmitXmad(Block, OpCode, ShaderOper.Imm); } - public static void Xmad_RC(ShaderIrBlock Block, long OpCode) + public static void Xmad_RC(ShaderIrBlock Block, long OpCode, long Position) { EmitXmad(Block, OpCode, ShaderOper.RC); } - public static void Xmad_RR(ShaderIrBlock Block, long OpCode) + public static void Xmad_RR(ShaderIrBlock Block, long OpCode, long Position) { EmitXmad(Block, OpCode, ShaderOper.RR); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs index 8d0925a32..2c699a1b8 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Bra(ShaderIrBlock Block, long OpCode) + public static void Bra(ShaderIrBlock Block, long OpCode, long Position) { if ((OpCode & 0x20) != 0) { @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Imm), OpCode)); } - public static void Exit(ShaderIrBlock Block, long OpCode) + public static void Exit(ShaderIrBlock Block, long OpCode, long Position) { int CCode = (int)OpCode & 0x1f; @@ -34,9 +34,34 @@ namespace Ryujinx.Graphics.Gal.Shader } - public static void Kil(ShaderIrBlock Block, long OpCode) + public static void Kil(ShaderIrBlock Block, long OpCode, long Position) { Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode)); } + + public static void Ssy(ShaderIrBlock Block, long OpCode, long Position) + { + if ((OpCode & 0x20) != 0) + { + //This reads the target offset from the constant buffer. + //Almost impossible to support with GLSL. + throw new NotImplementedException(); + } + + int Offset = ((int)(OpCode >> 20) << 8) >> 8; + + int Target = (int)(Position + Offset); + + ShaderIrOperImm Imm = new ShaderIrOperImm(Target); + + Block.AddNode(new ShaderIrOp(ShaderIrInst.Ssy, Imm)); + } + + public static void Sync(ShaderIrBlock Block, long OpCode, long Position) + { + //TODO: Implement Sync condition codes + + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Sync), OpCode)); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs index 7d7b2f6c6..2093f0706 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -35,13 +35,6 @@ namespace Ryujinx.Graphics.Gal.Shader (int)(OpCode >> 20) & 0x3fff); } - public static ShaderIrOperCbuf GetOperCbuf36(long OpCode) - { - return new ShaderIrOperCbuf( - (int)(OpCode >> 36) & 0x1f, - (int)(OpCode >> 22) & 0x3fff, GetOperGpr8(OpCode)); - } - public static ShaderIrOperGpr GetOperGpr8(long OpCode) { return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs index e794e1f87..2ae58bf89 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs @@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gal.Shader { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } }; - public static void Ld_A(ShaderIrBlock Block, long OpCode) + public static void Ld_A(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode[] Opers = GetOperAbuf20(OpCode); @@ -50,25 +50,37 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Ld_C(ShaderIrBlock Block, long OpCode) + public static void Ld_C(ShaderIrBlock Block, long OpCode, long Position) { - int Type = (int)(OpCode >> 48) & 7; + int CbufPos = (int)(OpCode >> 22) & 0x3fff; + int CbufIndex = (int)(OpCode >> 36) & 0x1f; + int Type = (int)(OpCode >> 48) & 7; if (Type > 5) { throw new InvalidOperationException(); } + ShaderIrOperGpr Temp = ShaderIrOperGpr.MakeTemporary(); + + Block.AddNode(new ShaderIrAsg(Temp, GetOperGpr8(OpCode))); + int Count = Type == 5 ? 2 : 1; for (int Index = 0; Index < Count; Index++) { - ShaderIrOperCbuf OperA = GetOperCbuf36(OpCode); - ShaderIrOperGpr OperD = GetOperGpr0 (OpCode); + ShaderIrOperCbuf OperA = new ShaderIrOperCbuf(CbufIndex, CbufPos, Temp); + + ShaderIrOperGpr OperD = GetOperGpr0(OpCode); OperA.Pos += Index; OperD.Index += Index; + if (!OperD.IsValidRegister) + { + break; + } + ShaderIrNode Node = OperA; if (Type < 4) @@ -85,7 +97,7 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void St_A(ShaderIrBlock Block, long OpCode) + public static void St_A(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode[] Opers = GetOperAbuf20(OpCode); @@ -101,7 +113,7 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Texq(ShaderIrBlock Block, long OpCode) + public static void Texq(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrNode OperD = GetOperGpr0(OpCode); ShaderIrNode OperA = GetOperGpr8(OpCode); @@ -120,12 +132,12 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, Op1), OpCode)); //Is this right? } - public static void Tex(ShaderIrBlock Block, long OpCode) + public static void Tex(ShaderIrBlock Block, long OpCode, long Position) { EmitTex(Block, OpCode, GprHandle: false); } - public static void Tex_B(ShaderIrBlock Block, long OpCode) + public static void Tex_B(ShaderIrBlock Block, long OpCode, long Position) { EmitTex(Block, OpCode, GprHandle: true); } @@ -190,12 +202,12 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Texs(ShaderIrBlock Block, long OpCode) + public static void Texs(ShaderIrBlock Block, long OpCode, long Position) { EmitTexs(Block, OpCode, ShaderIrInst.Texs); } - public static void Tlds(ShaderIrBlock Block, long OpCode) + public static void Tlds(ShaderIrBlock Block, long OpCode, long Position) { EmitTexs(Block, OpCode, ShaderIrInst.Txlf); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs index c6b71fb01..aef92c5a9 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs @@ -25,67 +25,67 @@ namespace Ryujinx.Graphics.Gal.Shader F64 = 3 } - public static void F2f_C(ShaderIrBlock Block, long OpCode) + public static void F2f_C(ShaderIrBlock Block, long OpCode, long Position) { EmitF2f(Block, OpCode, ShaderOper.CR); } - public static void F2f_I(ShaderIrBlock Block, long OpCode) + public static void F2f_I(ShaderIrBlock Block, long OpCode, long Position) { EmitF2f(Block, OpCode, ShaderOper.Immf); } - public static void F2f_R(ShaderIrBlock Block, long OpCode) + public static void F2f_R(ShaderIrBlock Block, long OpCode, long Position) { EmitF2f(Block, OpCode, ShaderOper.RR); } - public static void F2i_C(ShaderIrBlock Block, long OpCode) + public static void F2i_C(ShaderIrBlock Block, long OpCode, long Position) { EmitF2i(Block, OpCode, ShaderOper.CR); } - public static void F2i_I(ShaderIrBlock Block, long OpCode) + public static void F2i_I(ShaderIrBlock Block, long OpCode, long Position) { EmitF2i(Block, OpCode, ShaderOper.Immf); } - public static void F2i_R(ShaderIrBlock Block, long OpCode) + public static void F2i_R(ShaderIrBlock Block, long OpCode, long Position) { EmitF2i(Block, OpCode, ShaderOper.RR); } - public static void I2f_C(ShaderIrBlock Block, long OpCode) + public static void I2f_C(ShaderIrBlock Block, long OpCode, long Position) { EmitI2f(Block, OpCode, ShaderOper.CR); } - public static void I2f_I(ShaderIrBlock Block, long OpCode) + public static void I2f_I(ShaderIrBlock Block, long OpCode, long Position) { EmitI2f(Block, OpCode, ShaderOper.Imm); } - public static void I2f_R(ShaderIrBlock Block, long OpCode) + public static void I2f_R(ShaderIrBlock Block, long OpCode, long Position) { EmitI2f(Block, OpCode, ShaderOper.RR); } - public static void I2i_C(ShaderIrBlock Block, long OpCode) + public static void I2i_C(ShaderIrBlock Block, long OpCode, long Position) { EmitI2i(Block, OpCode, ShaderOper.CR); } - public static void I2i_I(ShaderIrBlock Block, long OpCode) + public static void I2i_I(ShaderIrBlock Block, long OpCode, long Position) { EmitI2i(Block, OpCode, ShaderOper.Imm); } - public static void I2i_R(ShaderIrBlock Block, long OpCode) + public static void I2i_R(ShaderIrBlock Block, long OpCode, long Position) { EmitI2i(Block, OpCode, ShaderOper.RR); } - public static void Isberd(ShaderIrBlock Block, long OpCode) + public static void Isberd(ShaderIrBlock Block, long OpCode, long Position) { //This instruction seems to be used to translate from an address to a vertex index in a GS //Stub it as such @@ -95,50 +95,50 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), GetOperGpr8(OpCode)), OpCode)); } - public static void Mov_C(ShaderIrBlock Block, long OpCode) + public static void Mov_C(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrOperCbuf Cbuf = GetOperCbuf34(OpCode); Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Cbuf), OpCode)); } - public static void Mov_I(ShaderIrBlock Block, long OpCode) + public static void Mov_I(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrOperImm Imm = GetOperImm19_20(OpCode); Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); } - public static void Mov_I32(ShaderIrBlock Block, long OpCode) + public static void Mov_I32(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrOperImm Imm = GetOperImm32_20(OpCode); Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); } - public static void Mov_R(ShaderIrBlock Block, long OpCode) + public static void Mov_R(ShaderIrBlock Block, long OpCode, long Position) { ShaderIrOperGpr Gpr = GetOperGpr20(OpCode); Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Gpr), OpCode)); } - public static void Sel_C(ShaderIrBlock Block, long OpCode) + public static void Sel_C(ShaderIrBlock Block, long OpCode, long Position) { EmitSel(Block, OpCode, ShaderOper.CR); } - public static void Sel_I(ShaderIrBlock Block, long OpCode) + public static void Sel_I(ShaderIrBlock Block, long OpCode, long Position) { EmitSel(Block, OpCode, ShaderOper.Imm); } - public static void Sel_R(ShaderIrBlock Block, long OpCode) + public static void Sel_R(ShaderIrBlock Block, long OpCode, long Position) { EmitSel(Block, OpCode, ShaderOper.RR); } - public static void Mov_S(ShaderIrBlock Block, long OpCode) + public static void Mov_S(ShaderIrBlock Block, long OpCode, long Position) { Block.AddNode(new ShaderIrCmnt("Stubbed.")); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs index f1be005f9..4300c32e3 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs @@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Out_R(ShaderIrBlock Block, long OpCode) + public static void Out_R(ShaderIrBlock Block, long OpCode, long Position) { //TODO: Those registers have to be used for something ShaderIrOperGpr Gpr0 = GetOperGpr0(OpCode); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs index 98f371b57..81d8f3126 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -50,17 +50,29 @@ namespace Ryujinx.Graphics.Gal.Shader { ShaderIrNode LastNode = Current.GetLastNode(); - ShaderIrOp Op = GetInnermostOp(LastNode); + ShaderIrOp InnerOp = GetInnermostOp(LastNode); - if (Op?.Inst == ShaderIrInst.Bra) + if (InnerOp?.Inst == ShaderIrInst.Bra) { - int Offset = ((ShaderIrOperImm)Op.OperandA).Value; + int Offset = ((ShaderIrOperImm)InnerOp.OperandA).Value; long Target = Current.EndPosition + Offset; Current.Branch = Enqueue(Target, Current); } + foreach (ShaderIrNode Node in Current.Nodes) + { + if (Node is ShaderIrOp CurrOp && CurrOp.Inst == ShaderIrInst.Ssy) + { + int Offset = ((ShaderIrOperImm)CurrOp.OperandA).Value; + + long Target = Offset; + + Current.Branch = Enqueue(Target, Current); + } + } + if (NodeHasNext(LastNode)) { Current.Next = Enqueue(Current.EndPosition); @@ -157,7 +169,7 @@ namespace Ryujinx.Graphics.Gal.Shader { int Offset = ((int)(OpCode >> 20) << 8) >> 8; - long Target = Position + Offset; + long Target = Position + Offset - Beginning; DbgOpCode += " (0x" + Target.ToString("x16") + ")"; } @@ -170,7 +182,7 @@ namespace Ryujinx.Graphics.Gal.Shader continue; } - Decode(Block, OpCode); + Decode(Block, OpCode, Position); } while (!IsFlowChange(Block.GetLastNode())); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs index 8e5057ed9..eca90fc3a 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs @@ -1,5 +1,30 @@ -namespace Ryujinx.Graphics.Gal.Shader +using System; + +namespace Ryujinx.Graphics.Gal.Shader { + struct OmapTarget + { + public bool Red; + public bool Green; + public bool Blue; + public bool Alpha; + + public bool Enabled => Red || Green || Blue || Alpha; + + public bool ComponentEnabled(int Component) + { + switch (Component) + { + case 0: return Red; + case 1: return Green; + case 2: return Blue; + case 3: return Alpha; + } + + throw new ArgumentException(nameof(Component)); + } + } + class ShaderHeader { public const int PointList = 1; @@ -30,6 +55,10 @@ public int StoreReqStart { get; private set; } public int StoreReqEnd { get; private set; } + public OmapTarget[] OmapTargets { get; private set; } + public bool OmapSampleMask { get; private set; } + public bool OmapDepth { get; private set; } + public ShaderHeader(IGalMemory Memory, long Position) { uint CommonWord0 = (uint)Memory.ReadInt32(Position + 0); @@ -61,6 +90,50 @@ MaxOutputVertexCount = ReadBits(CommonWord4, 0, 12); StoreReqStart = ReadBits(CommonWord4, 12, 8); StoreReqEnd = ReadBits(CommonWord4, 24, 8); + + //Type 2 (fragment?) reading + uint Type2OmapTarget = (uint)Memory.ReadInt32(Position + 72); + uint Type2Omap = (uint)Memory.ReadInt32(Position + 76); + + OmapTargets = new OmapTarget[8]; + + for (int i = 0; i < OmapTargets.Length; i++) + { + int Offset = i * 4; + + OmapTargets[i] = new OmapTarget + { + Red = ReadBits(Type2OmapTarget, Offset + 0, 1) != 0, + Green = ReadBits(Type2OmapTarget, Offset + 1, 1) != 0, + Blue = ReadBits(Type2OmapTarget, Offset + 2, 1) != 0, + Alpha = ReadBits(Type2OmapTarget, Offset + 3, 1) != 0 + }; + } + + OmapSampleMask = ReadBits(Type2Omap, 0, 1) != 0; + OmapDepth = ReadBits(Type2Omap, 1, 1) != 0; + } + + public int DepthRegister + { + get + { + int Count = 0; + + for (int Index = 0; Index < OmapTargets.Length; Index++) + { + for (int Component = 0; Component < 4; Component++) + { + if (OmapTargets[Index].ComponentEnabled(Component)) + { + Count++; + } + } + } + + // Depth register is always two registers after the last color output + return Count + 1; + } } private static int ReadBits(uint Word, int Offset, int BitWidth) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs new file mode 100644 index 000000000..b3713fa48 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIpaMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderIpaMode + { + Pass = 0, + None = 1, + Constant = 2, + Sc = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs index d197835a7..35dea6121 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs @@ -84,6 +84,8 @@ namespace Ryujinx.Graphics.Gal.Shader Bra, Exit, Kil, + Ssy, + Sync, Emit, Cut diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs new file mode 100644 index 000000000..3b884621b --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaIpa.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrMetaIpa : ShaderIrMeta + { + public ShaderIpaMode Mode { get; private set; } + + public ShaderIrMetaIpa(ShaderIpaMode Mode) + { + this.Mode = Mode; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs index 5c69d6a67..9dd196e69 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs @@ -6,11 +6,18 @@ namespace Ryujinx.Graphics.Gal.Shader public bool IsConst => Index == ZRIndex; + public bool IsValidRegister => (Index <= ZRIndex); + public int Index { get; set; } public ShaderIrOperGpr(int Index) { this.Index = Index; } + + public static ShaderIrOperGpr MakeTemporary(int Index = 0) + { + return new ShaderIrOperGpr(0x100 + Index); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs index 95b8e467d..1e76eab16 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -112,7 +112,9 @@ namespace Ryujinx.Graphics.Gal.Shader Set("0100110000101x", ShaderDecode.Shr_C); Set("0011100x00101x", ShaderDecode.Shr_I); Set("0101110000101x", ShaderDecode.Shr_R); + Set("1110001010010x", ShaderDecode.Ssy); Set("1110111111110x", ShaderDecode.St_A); + Set("1111000011111x", ShaderDecode.Sync); Set("110000xxxx111x", ShaderDecode.Tex); Set("1101111010111x", ShaderDecode.Tex_B); Set("1101111101001x", ShaderDecode.Texq); diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs index d2c5f1262..7fb5ea8af 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs @@ -154,16 +154,12 @@ namespace Ryujinx.HLE.Gpu.Engines } 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 diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs index 6f6012445..0d2f3befd 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs @@ -27,6 +27,8 @@ namespace Ryujinx.HLE.Gpu.Engines private List[] UploadedKeys; + private int CurrentInstance = 0; + public NvGpuEngine3d(NvGpu Gpu) { this.Gpu = Gpu; @@ -102,7 +104,14 @@ namespace Ryujinx.HLE.Gpu.Engines SetAlphaBlending(State); SetPrimitiveRestart(State); - SetFrameBuffer(Vmm, 0); + for (int FbIndex = 0; FbIndex < 8; FbIndex++) + { + SetFrameBuffer(Vmm, 0); + } + + SetZeta(Vmm); + + SetRenderTargets(); long[] Keys = UploadShaders(Vmm); @@ -149,9 +158,11 @@ namespace Ryujinx.HLE.Gpu.Engines int Stencil = ReadRegister(NvGpuEngine3dReg.ClearStencil); SetFrameBuffer(Vmm, FbIndex); + SetZeta(Vmm); Gpu.Renderer.Rasterizer.ClearBuffers( Flags, + FbIndex, Red, Green, Blue, Alpha, Depth, Stencil); @@ -161,6 +172,15 @@ namespace Ryujinx.HLE.Gpu.Engines { long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10); + int Format = ReadRegister(NvGpuEngine3dReg.FrameBufferNFormat + FbIndex * 0x10); + + if (VA == 0 || Format == 0) + { + Gpu.Renderer.FrameBuffer.UnbindColor(FbIndex); + + return; + } + long Key = Vmm.GetPhysicalAddress(VA); FrameBuffers.Add(Key); @@ -168,11 +188,11 @@ namespace Ryujinx.HLE.Gpu.Engines int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); - float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 4); - float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 4); + float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 8); + float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 8); - float SX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + FbIndex * 4); - float SY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + FbIndex * 4); + float SX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + FbIndex * 8); + float SY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + FbIndex * 8); int VpX = (int)MathF.Max(0, TX - MathF.Abs(SX)); int VpY = (int)MathF.Max(0, TY - MathF.Abs(SY)); @@ -180,12 +200,48 @@ namespace Ryujinx.HLE.Gpu.Engines 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); + GalImageFormat ImageFormat = ImageFormatConverter.ConvertFrameBuffer((GalFrameBufferFormat)Format); + + GalImage Image = new GalImage(Width, Height, ImageFormat); + + long Size = TextureHelper.GetTextureSize(Image); + + Gpu.Renderer.Texture.CreateFb(Key, Size, Image); + Gpu.Renderer.FrameBuffer.BindColor(Key, FbIndex); Gpu.Renderer.FrameBuffer.SetViewport(VpX, VpY, VpW, VpH); } + private void SetZeta(NvGpuVmm Vmm) + { + long ZA = MakeInt64From2xInt32(NvGpuEngine3dReg.ZetaAddress); + + int Format = ReadRegister(NvGpuEngine3dReg.ZetaFormat); + + bool ZetaEnable = (ReadRegister(NvGpuEngine3dReg.ZetaEnable) & 1) != 0; + + if (ZA == 0 || Format == 0 || !ZetaEnable) + { + Gpu.Renderer.FrameBuffer.UnbindZeta(); + + return; + } + + long Key = Vmm.GetPhysicalAddress(ZA); + + int Width = ReadRegister(NvGpuEngine3dReg.ZetaHoriz); + int Height = ReadRegister(NvGpuEngine3dReg.ZetaVert); + + GalImageFormat ImageFormat = ImageFormatConverter.ConvertZeta((GalZetaFormat)Format); + + GalImage Image = new GalImage(Width, Height, ImageFormat); + + long Size = TextureHelper.GetTextureSize(Image); + + Gpu.Renderer.Texture.CreateFb(Key, Size, Image); + Gpu.Renderer.FrameBuffer.BindZeta(Key); + } + private long[] UploadShaders(NvGpuVmm Vmm) { long[] Keys = new long[5]; @@ -366,6 +422,33 @@ namespace Ryujinx.HLE.Gpu.Engines } } + private void SetRenderTargets() + { + bool SeparateFragData = (ReadRegister(NvGpuEngine3dReg.RTSeparateFragData) & 1) != 0; + + if (SeparateFragData) + { + uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl)); + + uint Count = Control & 0xf; + + int[] Map = new int[Count]; + + for (int i = 0; i < Count; i++) + { + int Shift = 4 + i * 3; + + Map[i] = (int)((Control >> Shift) & 7); + } + + Gpu.Renderer.FrameBuffer.SetMap(Map); + } + else + { + Gpu.Renderer.FrameBuffer.SetMap(null); + } + } + private void UploadTextures(NvGpuVmm Vmm, GalPipelineState State, long[] Keys) { long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); @@ -393,8 +476,6 @@ namespace Ryujinx.HLE.Gpu.Engines UploadTexture(Vmm, TexIndex, TextureHandle); - Gpu.Renderer.Shader.EnsureTextureBinding(DeclInfo.Name, TexIndex); - TexIndex++; } } @@ -442,15 +523,15 @@ namespace Ryujinx.HLE.Gpu.Engines } else { - GalTexture NewTexture = TextureFactory.MakeTexture(Vmm, TicPosition); + GalImage NewImage = TextureFactory.MakeTexture(Vmm, TicPosition); - long Size = (uint)TextureHelper.GetTextureSize(NewTexture); + long Size = (uint)TextureHelper.GetTextureSize(NewImage); bool HasCachedTexture = false; - if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalTexture Texture)) + if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalImage Image)) { - if (NewTexture.Equals(Texture) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture)) + if (NewImage.Equals(Image) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture)) { Gpu.Renderer.Texture.Bind(Key, TexIndex); @@ -462,7 +543,7 @@ namespace Ryujinx.HLE.Gpu.Engines { byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition); - Gpu.Renderer.Texture.Create(Key, Data, NewTexture); + Gpu.Renderer.Texture.Create(Key, Data, NewImage); } Gpu.Renderer.Texture.Bind(Key, TexIndex); @@ -575,10 +656,25 @@ namespace Ryujinx.HLE.Gpu.Engines long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 2); - long VboKey = Vmm.GetPhysicalAddress(VertexPosition); + int VertexDivisor = ReadRegister(NvGpuEngine3dReg.VertexArrayNDivisor + Index * 4); + + bool Instanced = (ReadRegister(NvGpuEngine3dReg.VertexArrayNInstance + Index) & 1) != 0; int Stride = Control & 0xfff; + if (Instanced && VertexDivisor != 0) + { + VertexPosition += Stride * (CurrentInstance / VertexDivisor); + } + + if (VertexPosition > VertexEndPos) + { + //Instance is invalid, ignore the draw call + continue; + } + + long VboKey = Vmm.GetPhysicalAddress(VertexPosition); + long VbSize = (VertexEndPos - VertexPosition) + 1; bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize); @@ -590,10 +686,12 @@ namespace Ryujinx.HLE.Gpu.Engines Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, DataAddress); } - State.VertexBindings[Index].Enabled = true; - State.VertexBindings[Index].Stride = Stride; - State.VertexBindings[Index].VboKey = VboKey; - State.VertexBindings[Index].Attribs = Attribs[Index].ToArray(); + State.VertexBindings[Index].Enabled = true; + State.VertexBindings[Index].Stride = Stride; + State.VertexBindings[Index].VboKey = VboKey; + State.VertexBindings[Index].Instanced = Instanced; + State.VertexBindings[Index].Divisor = VertexDivisor; + State.VertexBindings[Index].Attribs = Attribs[Index].ToArray(); } } @@ -604,6 +702,25 @@ namespace Ryujinx.HLE.Gpu.Engines GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); + bool InstanceNext = ((PrimCtrl >> 26) & 1) != 0; + bool InstanceCont = ((PrimCtrl >> 27) & 1) != 0; + + if (InstanceNext && InstanceCont) + { + throw new InvalidOperationException("GPU tried to increase and reset instance count at the same time"); + } + + if (InstanceNext) + { + CurrentInstance++; + } + else if (!InstanceCont) + { + CurrentInstance = 0; + } + + State.Instance = CurrentInstance; + Gpu.Renderer.Pipeline.Bind(State); if (IndexCount != 0) @@ -631,22 +748,43 @@ namespace Ryujinx.HLE.Gpu.Engines WriteRegister(NvGpuEngine3dReg.IndexBatchCount, 0); } + private enum QueryMode + { + WriteSeq, + Sync, + WriteCounterAndTimestamp + } + private void QueryControl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) { + WriteRegister(PBEntry); + long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress); int Seq = Registers[(int)NvGpuEngine3dReg.QuerySequence]; int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl]; - int Mode = Ctrl & 3; + QueryMode Mode = (QueryMode)(Ctrl & 3); - if (Mode == 0) + switch (Mode) { - //Write mode. - Vmm.WriteInt32(Position, Seq); - } + case QueryMode.WriteSeq: Vmm.WriteInt32(Position, Seq); break; - WriteRegister(PBEntry); + case QueryMode.WriteCounterAndTimestamp: + { + //TODO: Implement counters. + long Counter = 1; + + long Timestamp = (uint)Environment.TickCount; + + Timestamp = (long)(Timestamp * 615384.615385); + + Vmm.WriteInt64(Position + 0, Counter); + Vmm.WriteInt64(Position + 8, Timestamp); + + break; + } + } } private void CbData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) @@ -754,4 +892,4 @@ namespace Ryujinx.HLE.Gpu.Engines 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 39a5ee8cf..ace324e91 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs +++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs @@ -22,7 +22,16 @@ namespace Ryujinx.HLE.Gpu.Engines StencilBackFuncRef = 0x3d5, StencilBackMask = 0x3d6, StencilBackFuncMask = 0x3d7, + RTSeparateFragData = 0x3eb, + ZetaAddress = 0x3f8, + ZetaFormat = 0x3fa, + ZetaBlockDimensions = 0x3fb, + ZetaLayerStride = 0x3fc, VertexAttribNFormat = 0x458, + RTControl = 0x487, + ZetaHoriz = 0x48a, + ZetaVert = 0x48b, + ZetaArrayMode = 0x48c, DepthTestEnable = 0x4b3, IBlendEnable = 0x4b9, DepthTestFunction = 0x4c3, @@ -44,6 +53,8 @@ namespace Ryujinx.HLE.Gpu.Engines StencilFrontFuncMask = 0x4e6, StencilFrontMask = 0x4e7, VertexArrayElemBase = 0x50d, + VertexArrayInstBase = 0x50e, + ZetaEnable = 0x54e, TexHeaderPoolOffset = 0x55d, TexSamplerPoolOffset = 0x557, StencilTwoSideEnable = 0x565, @@ -60,6 +71,7 @@ namespace Ryujinx.HLE.Gpu.Engines IndexArrayFormat = 0x5f6, IndexBatchFirst = 0x5f7, IndexBatchCount = 0x5f8, + VertexArrayNInstance = 0x620, CullFaceEnable = 0x646, FrontFace = 0x647, CullFace = 0x648, diff --git a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs index 4db0b6f10..0ef33d3b7 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs @@ -6,11 +6,16 @@ namespace Ryujinx.HLE.Gpu.Texture { static class TextureFactory { - public static GalTexture MakeTexture(NvGpuVmm Vmm, long TicPosition) + public static GalImage MakeTexture(NvGpuVmm Vmm, long TicPosition) { int[] Tic = ReadWords(Vmm, TicPosition, 8); - GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); + GalTextureType RType = (GalTextureType)((Tic[0] >> 7) & 7); + GalTextureType GType = (GalTextureType)((Tic[0] >> 10) & 7); + GalTextureType BType = (GalTextureType)((Tic[0] >> 13) & 7); + GalTextureType AType = (GalTextureType)((Tic[0] >> 16) & 7); + + GalImageFormat Format = ImageFormatConverter.ConvertTexture((GalTextureFormat)(Tic[0] & 0x7f), RType, GType, BType, AType); GalTextureSource XSource = (GalTextureSource)((Tic[0] >> 19) & 7); GalTextureSource YSource = (GalTextureSource)((Tic[0] >> 22) & 7); @@ -20,7 +25,7 @@ namespace Ryujinx.HLE.Gpu.Texture int Width = (Tic[4] & 0xffff) + 1; int Height = (Tic[5] & 0xffff) + 1; - return new GalTexture( + return new GalImage( Width, Height, Format, diff --git a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs index 10a64f364..2683174d8 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs @@ -30,117 +30,155 @@ namespace Ryujinx.HLE.Gpu.Texture throw new NotImplementedException(Texture.Swizzle.ToString()); } - public static int GetTextureSize(GalTexture Texture) + public static int GetTextureSize(GalImage Image) { - switch (Texture.Format) + switch (Image.Format) { - case GalTextureFormat.R32G32B32A32: - return Texture.Width * Texture.Height * 16; + case GalImageFormat.R32G32B32A32_SFLOAT: + case GalImageFormat.R32G32B32A32_SINT: + case GalImageFormat.R32G32B32A32_UINT: + return Image.Width * Image.Height * 16; - case GalTextureFormat.R16G16B16A16: - return Texture.Width * Texture.Height * 8; + case GalImageFormat.R16G16B16A16_SFLOAT: + case GalImageFormat.R16G16B16A16_SINT: + case GalImageFormat.R16G16B16A16_SNORM: + case GalImageFormat.R16G16B16A16_UINT: + case GalImageFormat.R16G16B16A16_UNORM: + case GalImageFormat.D32_SFLOAT_S8_UINT: + case GalImageFormat.R32G32_SFLOAT: + case GalImageFormat.R32G32_SINT: + case GalImageFormat.R32G32_UINT: + return Image.Width * Image.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 GalImageFormat.A8B8G8R8_SINT_PACK32: + case GalImageFormat.A8B8G8R8_SNORM_PACK32: + case GalImageFormat.A8B8G8R8_UINT_PACK32: + case GalImageFormat.A8B8G8R8_UNORM_PACK32: + case GalImageFormat.A8B8G8R8_SRGB_PACK32: + case GalImageFormat.A2B10G10R10_SINT_PACK32: + case GalImageFormat.A2B10G10R10_SNORM_PACK32: + case GalImageFormat.A2B10G10R10_UINT_PACK32: + case GalImageFormat.A2B10G10R10_UNORM_PACK32: + case GalImageFormat.R16G16_SFLOAT: + case GalImageFormat.R16G16_SINT: + case GalImageFormat.R16G16_SNORM: + case GalImageFormat.R16G16_UINT: + case GalImageFormat.R16G16_UNORM: + case GalImageFormat.R32_SFLOAT: + case GalImageFormat.R32_SINT: + case GalImageFormat.R32_UINT: + case GalImageFormat.D32_SFLOAT: + case GalImageFormat.B10G11R11_UFLOAT_PACK32: + case GalImageFormat.D24_UNORM_S8_UINT: + return Image.Width * Image.Height * 4; - case GalTextureFormat.A1B5G5R5: - case GalTextureFormat.B5G6R5: - case GalTextureFormat.G8R8: - case GalTextureFormat.R16: - return Texture.Width * Texture.Height * 2; + case GalImageFormat.B4G4R4A4_UNORM_PACK16: + case GalImageFormat.A1R5G5B5_UNORM_PACK16: + case GalImageFormat.B5G6R5_UNORM_PACK16: + case GalImageFormat.R8G8_SINT: + case GalImageFormat.R8G8_SNORM: + case GalImageFormat.R8G8_UINT: + case GalImageFormat.R8G8_UNORM: + case GalImageFormat.R16_SFLOAT: + case GalImageFormat.R16_SINT: + case GalImageFormat.R16_SNORM: + case GalImageFormat.R16_UINT: + case GalImageFormat.R16_UNORM: + case GalImageFormat.D16_UNORM: + return Image.Width * Image.Height * 2; - case GalTextureFormat.R8: - return Texture.Width * Texture.Height; + case GalImageFormat.R8_SINT: + case GalImageFormat.R8_SNORM: + case GalImageFormat.R8_UINT: + case GalImageFormat.R8_UNORM: + return Image.Width * Image.Height; - case GalTextureFormat.BC1: - case GalTextureFormat.BC4: + case GalImageFormat.BC1_RGBA_UNORM_BLOCK: + case GalImageFormat.BC4_SNORM_BLOCK: + case GalImageFormat.BC4_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 4, 4, 8); + return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 8); } - case GalTextureFormat.BC6H_SF16: - case GalTextureFormat.BC6H_UF16: - case GalTextureFormat.BC7U: - case GalTextureFormat.BC2: - case GalTextureFormat.BC3: - case GalTextureFormat.BC5: - case GalTextureFormat.Astc2D4x4: + case GalImageFormat.BC6H_SFLOAT_BLOCK: + case GalImageFormat.BC6H_UFLOAT_BLOCK: + case GalImageFormat.BC7_UNORM_BLOCK: + case GalImageFormat.BC2_UNORM_BLOCK: + case GalImageFormat.BC3_UNORM_BLOCK: + case GalImageFormat.BC5_SNORM_BLOCK: + case GalImageFormat.BC5_UNORM_BLOCK: + case GalImageFormat.ASTC_4x4_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 4, 4, 16); + return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 16); } - case GalTextureFormat.Astc2D5x5: + case GalImageFormat.ASTC_5x5_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 5, 5, 16); + return CompressedTextureSize(Image.Width, Image.Height, 5, 5, 16); } - case GalTextureFormat.Astc2D6x6: + case GalImageFormat.ASTC_6x6_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 6, 6, 16); + return CompressedTextureSize(Image.Width, Image.Height, 6, 6, 16); } - case GalTextureFormat.Astc2D8x8: + case GalImageFormat.ASTC_8x8_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 8, 8, 16); + return CompressedTextureSize(Image.Width, Image.Height, 8, 8, 16); } - case GalTextureFormat.Astc2D10x10: + case GalImageFormat.ASTC_10x10_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 10, 10, 16); + return CompressedTextureSize(Image.Width, Image.Height, 10, 10, 16); } - case GalTextureFormat.Astc2D12x12: + case GalImageFormat.ASTC_12x12_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 12, 12, 16); + return CompressedTextureSize(Image.Width, Image.Height, 12, 12, 16); } - case GalTextureFormat.Astc2D5x4: + case GalImageFormat.ASTC_5x4_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 5, 4, 16); + return CompressedTextureSize(Image.Width, Image.Height, 5, 4, 16); } - case GalTextureFormat.Astc2D6x5: + case GalImageFormat.ASTC_6x5_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 6, 5, 16); + return CompressedTextureSize(Image.Width, Image.Height, 6, 5, 16); } - case GalTextureFormat.Astc2D8x6: + case GalImageFormat.ASTC_8x6_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 8, 6, 16); + return CompressedTextureSize(Image.Width, Image.Height, 8, 6, 16); } - case GalTextureFormat.Astc2D10x8: + case GalImageFormat.ASTC_10x8_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 10, 8, 16); + return CompressedTextureSize(Image.Width, Image.Height, 10, 8, 16); } - case GalTextureFormat.Astc2D12x10: + case GalImageFormat.ASTC_12x10_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 12, 10, 16); + return CompressedTextureSize(Image.Width, Image.Height, 12, 10, 16); } - case GalTextureFormat.Astc2D8x5: + case GalImageFormat.ASTC_8x5_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 8, 5, 16); + return CompressedTextureSize(Image.Width, Image.Height, 8, 5, 16); } - case GalTextureFormat.Astc2D10x5: + case GalImageFormat.ASTC_10x5_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 10, 5, 16); + return CompressedTextureSize(Image.Width, Image.Height, 10, 5, 16); } - case GalTextureFormat.Astc2D10x6: + case GalImageFormat.ASTC_10x6_UNORM_BLOCK: { - return CompressedTextureSize(Texture.Width, Texture.Height, 10, 6, 16); + return CompressedTextureSize(Image.Width, Image.Height, 10, 6, 16); } } - throw new NotImplementedException("0x" + Texture.Format.ToString("x2")); + throw new NotImplementedException(Image.Format.ToString()); } public static int CompressedTextureSize(int TextureWidth, int TextureHeight, int BlockWidth, int BlockHeight, int Bpb) diff --git a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs index 0cf055db8..d293bf9f1 100644 --- a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs +++ b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs @@ -12,6 +12,7 @@ namespace Ryujinx.HLE.Gpu.Texture { case GalTextureFormat.R32G32B32A32: return Read16Bpp (Memory, Texture); case GalTextureFormat.R16G16B16A16: return Read8Bpp (Memory, Texture); + case GalTextureFormat.R32G32: 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); @@ -19,6 +20,7 @@ namespace Ryujinx.HLE.Gpu.Texture case GalTextureFormat.Z24S8: return Read4Bpp (Memory, Texture); case GalTextureFormat.A1B5G5R5: return Read5551 (Memory, Texture); case GalTextureFormat.B5G6R5: return Read565 (Memory, Texture); + case GalTextureFormat.A4B4G4R4: return Read2Bpp (Memory, Texture); case GalTextureFormat.G8R8: return Read2Bpp (Memory, Texture); case GalTextureFormat.R16: return Read2Bpp (Memory, Texture); case GalTextureFormat.R8: return Read1Bpp (Memory, Texture); @@ -31,6 +33,7 @@ namespace Ryujinx.HLE.Gpu.Texture case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture); case GalTextureFormat.BC5: return Read16BptCompressedTexture(Memory, Texture, 4, 4); case GalTextureFormat.ZF32: return Read4Bpp (Memory, Texture); + case GalTextureFormat.ZF32_X24S8: return Read8Bpp (Memory, Texture); case GalTextureFormat.Astc2D4x4: return Read16BptCompressedTexture(Memory, Texture, 4, 4); case GalTextureFormat.Astc2D5x5: return Read16BptCompressedTexture(Memory, Texture, 5, 5); case GalTextureFormat.Astc2D6x6: return Read16BptCompressedTexture(Memory, Texture, 6, 6); @@ -47,7 +50,7 @@ namespace Ryujinx.HLE.Gpu.Texture case GalTextureFormat.Astc2D10x6: return Read16BptCompressedTexture(Memory, Texture, 10, 6); } - throw new NotImplementedException("0x" + Texture.Format.ToString("x2")); + throw new NotImplementedException("0x" + ((int)Texture.Format).ToString("x2")); } private unsafe static byte[] Read1Bpp(IAMemory Memory, TextureInfo Texture) diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs index c8153fdb4..02900444a 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -174,39 +174,39 @@ namespace Ryujinx.HLE.HOS.Ipc return 0; } - public (long Position, long Size) GetBufferType0x21() + public (long Position, long Size) GetBufferType0x21(int Index = 0) { - if (PtrBuff.Count != 0 && - PtrBuff[0].Position != 0 && - PtrBuff[0].Size != 0) + if (PtrBuff.Count > Index && + PtrBuff[Index].Position != 0 && + PtrBuff[Index].Size != 0) { - return (PtrBuff[0].Position, PtrBuff[0].Size); + return (PtrBuff[Index].Position, PtrBuff[Index].Size); } - if (SendBuff.Count != 0 && - SendBuff[0].Position != 0 && - SendBuff[0].Size != 0) + if (SendBuff.Count > Index && + SendBuff[Index].Position != 0 && + SendBuff[Index].Size != 0) { - return (SendBuff[0].Position, SendBuff[0].Size); + return (SendBuff[Index].Position, SendBuff[Index].Size); } return (0, 0); } - public (long Position, long Size) GetBufferType0x22() + public (long Position, long Size) GetBufferType0x22(int Index = 0) { - if (RecvListBuff.Count != 0 && - RecvListBuff[0].Position != 0 && - RecvListBuff[0].Size != 0) + if (RecvListBuff.Count > Index && + RecvListBuff[Index].Position != 0 && + RecvListBuff[Index].Size != 0) { - return (RecvListBuff[0].Position, RecvListBuff[0].Size); + return (RecvListBuff[Index].Position, RecvListBuff[Index].Size); } - if (ReceiveBuff.Count != 0 && - ReceiveBuff[0].Position != 0 && - ReceiveBuff[0].Size != 0) + if (ReceiveBuff.Count > Index && + ReceiveBuff[Index].Position != 0 && + ReceiveBuff[Index].Size != 0) { - return (ReceiveBuff[0].Position, ReceiveBuff[0].Size); + return (ReceiveBuff[Index].Position, ReceiveBuff[Index].Size); } return (0, 0); diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs index b9bca74ad..b9e71b183 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs @@ -9,9 +9,9 @@ namespace Ryujinx.HLE.HOS.Kernel { private void SvcSetHeapSize(AThreadState ThreadState) { - long Size = (long)ThreadState.X1; + ulong Size = ThreadState.X1; - if ((Size & 0x1fffff) != 0 || Size != (uint)Size) + if ((Size & 0xFFFFFFFE001FFFFF) != 0) { Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!"); @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = Process.MemoryManager.TrySetHeapSize(Size, out long Position); + long Result = Process.MemoryManager.TrySetHeapSize((long)Size, out long Position); ThreadState.X0 = (ulong)Result; diff --git a/Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs similarity index 97% rename from Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs rename to Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs index 36dfd9efa..8fd7bfeaf 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs @@ -7,13 +7,13 @@ using static Ryujinx.HLE.HOS.ErrorCode; namespace Ryujinx.HLE.HOS.Services.Acc { - class IAccountServiceForApplication : IpcService + class IAccountService : IpcService { private Dictionary m_Commands; public override IReadOnlyDictionary Commands => m_Commands; - public IAccountServiceForApplication() + public IAccountService() { m_Commands = new Dictionary() { diff --git a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs index 960cc49c0..316f16d08 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs @@ -3,7 +3,10 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; +using System; using System.Collections.Generic; +using System.IO; +using System.Reflection; using System.Text; namespace Ryujinx.HLE.HOS.Services.Acc @@ -16,15 +19,21 @@ namespace Ryujinx.HLE.HOS.Services.Acc private UserProfile Profile; + private Stream ProfilePictureStream; + public IProfile(UserProfile Profile) { m_Commands = new Dictionary() { - { 0, Get }, - { 1, GetBase } + { 0, Get }, + { 1, GetBase }, + { 10, GetImageSize }, + { 11, LoadImage }, }; this.Profile = Profile; + + ProfilePictureStream = Assembly.GetCallingAssembly().GetManifestResourceStream("Ryujinx.HLE.RyujinxProfileImage.jpg"); } public long Get(ServiceCtx Context) @@ -54,5 +63,28 @@ namespace Ryujinx.HLE.HOS.Services.Acc return 0; } + + private long LoadImage(ServiceCtx Context) + { + long BufferPosition = Context.Request.ReceiveBuff[0].Position; + long BufferLen = Context.Request.ReceiveBuff[0].Size; + + byte[] ProfilePictureData = new byte[BufferLen]; + + ProfilePictureStream.Read(ProfilePictureData, 0, ProfilePictureData.Length); + + Context.Memory.WriteBytes(BufferPosition, ProfilePictureData); + + Context.ResponseData.Write(ProfilePictureStream.Length); + + return 0; + } + + private long GetImageSize(ServiceCtx Context) + { + Context.ResponseData.Write(ProfilePictureStream.Length); + + return 0; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs index b5a457db5..e2cd0dcdb 100644 --- a/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs @@ -32,6 +32,8 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { 14, Connect }, { 18, Listen }, { 21, SetSockOpt }, + { 24, Write }, + { 25, Read }, { 26, Close } }; } @@ -122,15 +124,15 @@ namespace Ryujinx.HLE.HOS.Services.Bsd int SocketId = Context.RequestData.ReadInt32(); int SocketFlags = Context.RequestData.ReadInt32(); - byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; + (long ReceivePosition, long ReceiveLength) = Context.Request.GetBufferType0x22(); + + byte[] ReceivedBuffer = new byte[ReceiveLength]; try { int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); - //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer)); - - Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, ReceivedBuffer); + Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); Context.ResponseData.Write(BytesRead); Context.ResponseData.Write(0); @@ -150,13 +152,12 @@ namespace Ryujinx.HLE.HOS.Services.Bsd int SocketId = Context.RequestData.ReadInt32(); int SocketFlags = Context.RequestData.ReadInt32(); - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); + (long SentPosition, long SentSize) = Context.Request.GetBufferType0x21(); + + byte[] SentBuffer = Context.Memory.ReadBytes(SentPosition, SentSize); try { - //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); Context.ResponseData.Write(BytesSent); @@ -180,8 +181,9 @@ namespace Ryujinx.HLE.HOS.Services.Bsd byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, Context.Request.SendBuff[0].Size); - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[1].Position, - Context.Request.SendBuff[1].Size); + (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(Index: 1); + + byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); if (!Sockets[SocketId].Handle.Connected) { @@ -200,8 +202,6 @@ namespace Ryujinx.HLE.HOS.Services.Bsd try { - //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); Context.ResponseData.Write(BytesSent); @@ -221,7 +221,7 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { int SocketId = Context.RequestData.ReadInt32(); - long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position; + (long AddrBufferPosition, long AddrBuffSize) = Context.Request.GetBufferType0x22(); Socket HandleAccept = null; @@ -246,7 +246,7 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), - Handle = HandleAccept + Handle = HandleAccept }; Sockets.Add(NewBsdSocket); @@ -265,7 +265,7 @@ namespace Ryujinx.HLE.HOS.Services.Bsd Writer.Write(IpAddress); - Context.Memory.WriteBytes(AddrBufferPtr, MS.ToArray()); + Context.Memory.WriteBytes(AddrBufferPosition, MS.ToArray()); Context.ResponseData.Write(Sockets.Count - 1); Context.ResponseData.Write(0); @@ -286,8 +286,9 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { int SocketId = Context.RequestData.ReadInt32(); - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); + (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(); + + byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); try { @@ -310,8 +311,9 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { int SocketId = Context.RequestData.ReadInt32(); - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); + (long AddressPosition, long AddressSize) = Context.Request.GetBufferType0x21(); + + byte[] AddressBuffer = Context.Memory.ReadBytes(AddressPosition, AddressSize); try { @@ -359,8 +361,8 @@ namespace Ryujinx.HLE.HOS.Services.Bsd { int SocketId = Context.RequestData.ReadInt32(); - SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); - SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); + SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); + SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position, Context.Request.PtrBuff[0].Size); @@ -383,6 +385,60 @@ namespace Ryujinx.HLE.HOS.Services.Bsd return 0; } + //(u32 socket, buffer message) -> (i32 ret, u32 bsd_errno) + public long Write(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + (long SentPosition, long SentSize) = Context.Request.GetBufferType0x21(); + + byte[] SentBuffer = Context.Memory.ReadBytes(SentPosition, SentSize); + + try + { + //Logging.Debug("Wrote Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); + + int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + + Context.ResponseData.Write(BytesSent); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket) -> (i32 ret, u32 bsd_errno, buffer message) + public long Read(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + (long ReceivePosition, long ReceiveLength) = Context.Request.GetBufferType0x22(); + + byte[] ReceivedBuffer = new byte[ReceiveLength]; + + try + { + int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + + Context.Memory.WriteBytes(ReceivePosition, ReceivedBuffer); + + Context.ResponseData.Write(BytesRead); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + //(u32 socket) -> (i32 ret, u32 bsd_errno) public long Close(ServiceCtx Context) { @@ -413,7 +469,7 @@ namespace Ryujinx.HLE.HOS.Services.Bsd int Size = Reader.ReadByte(); int Family = Reader.ReadByte(); - int Port = EndianSwap.Swap16(Reader.ReadInt16()); + int Port = EndianSwap.Swap16(Reader.ReadUInt16()); string IpAddress = Reader.ReadByte().ToString() + "." + Reader.ReadByte().ToString() + "." + @@ -421,8 +477,7 @@ namespace Ryujinx.HLE.HOS.Services.Bsd Reader.ReadByte().ToString(); Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); - - Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); + Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); } } diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs index 42d57c6fc..5d9997099 100644 --- a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs @@ -22,9 +22,9 @@ namespace Ryujinx.HLE.HOS.Services.Lm public long Log(ServiceCtx Context) { - byte[] LogBuffer = Context.Memory.ReadBytes( - Context.Request.PtrBuff[0].Position, - Context.Request.PtrBuff[0].Size); + + (long BufPos, long BufSize) = Context.Request.GetBufferType0x21(); + byte[] LogBuffer = Context.Memory.ReadBytes(BufPos, BufSize); using (MemoryStream MS = new MemoryStream(LogBuffer)) { @@ -50,20 +50,36 @@ namespace Ryujinx.HLE.HOS.Services.Lm string FieldStr = string.Empty; - if (Field == LmLogField.Skip) + if (Field == LmLogField.Start) { - Reader.ReadByte(); + Reader.ReadBytes(Size); continue; } + else if (Field == LmLogField.Stop) + { + break; + } else if (Field == LmLogField.Line) { FieldStr = Field + ": " + Reader.ReadInt32(); } - else + else if (Field == LmLogField.DropCount) + { + FieldStr = Field + ": " + Reader.ReadInt64(); + } + else if (Field == LmLogField.Time) + { + FieldStr = Field + ": " + Reader.ReadInt64() + "s"; + } + else if (Field < LmLogField.Count) { FieldStr = Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\""; } + else + { + FieldStr = "Field" + Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\""; + } SB.AppendLine(" " + FieldStr); } diff --git a/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs b/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs index bd8c8e886..954746346 100644 --- a/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs @@ -2,12 +2,17 @@ namespace Ryujinx.HLE.HOS.Services.Lm { enum LmLogField { - Skip = 1, - Message = 2, - Line = 3, - Filename = 4, - Function = 5, - Module = 6, - Thread = 7 + Start = 0, + Stop = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7, + DropCount = 8, + Time = 9, + ProgramName = 10, + Count } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs index d69ec719c..7b6a8676b 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs @@ -187,7 +187,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS { Left = Middle + 1; - LtRg = Rg; + if ((ulong)Position > Rg.Start) + { + LtRg = Rg; + } } } diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index babceb7db..5e65d1d10 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -31,7 +31,10 @@ namespace Ryujinx.HLE.HOS.Services switch (Name) { case "acc:u0": - return new IAccountServiceForApplication(); + return new IAccountService(); + + case "acc:u1": + return new IAccountService(); case "aoc:u": return new IAddOnContentManager(); diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index f7fb84a58..fa4c254e2 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -13,6 +13,14 @@ true + + + + + + + + diff --git a/Ryujinx.HLE/RyujinxProfileImage.jpg b/Ryujinx.HLE/RyujinxProfileImage.jpg new file mode 100644 index 000000000..fe9ec2a95 Binary files /dev/null and b/Ryujinx.HLE/RyujinxProfileImage.jpg differ diff --git a/Ryujinx.HLE/Utilities/EndianSwap.cs b/Ryujinx.HLE/Utilities/EndianSwap.cs index d773516d3..5d0c8a845 100644 --- a/Ryujinx.HLE/Utilities/EndianSwap.cs +++ b/Ryujinx.HLE/Utilities/EndianSwap.cs @@ -2,7 +2,7 @@ { static class EndianSwap { - public static short Swap16(short Value) => (short)(((Value >> 8) & 0xff) | (Value << 8)); + public static ushort Swap16(ushort Value) => (ushort)(((Value >> 8) & 0xff) | (Value << 8)); public static int Swap32(int Value) { diff --git a/Ryujinx.Tests.Unicorn/IndexedProperty.cs b/Ryujinx.Tests.Unicorn/IndexedProperty.cs new file mode 100644 index 000000000..946620c69 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/IndexedProperty.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public class IndexedProperty + { + readonly Action SetAction; + readonly Func GetFunc; + + public IndexedProperty(Func getFunc, Action setAction) + { + this.GetFunc = getFunc; + this.SetAction = setAction; + } + + public TValue this[TIndex i] + { + get + { + return GetFunc(i); + } + set + { + SetAction(i, value); + } + } + } +} diff --git a/Ryujinx.Tests.Unicorn/MemoryPermission.cs b/Ryujinx.Tests.Unicorn/MemoryPermission.cs new file mode 100644 index 000000000..d79b2adda --- /dev/null +++ b/Ryujinx.Tests.Unicorn/MemoryPermission.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public enum MemoryPermission + { + NONE = 0, + READ = 1, + WRITE = 2, + EXEC = 4, + ALL = 7, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs new file mode 100644 index 000000000..3554480c1 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs @@ -0,0 +1,296 @@ +using System; + +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum ArmRegister + { + INVALID = 0, + + X29, + X30, + NZCV, + SP, + WSP, + WZR, + XZR, + B0, + B1, + B2, + B3, + B4, + B5, + B6, + B7, + B8, + B9, + B10, + B11, + B12, + B13, + B14, + B15, + B16, + B17, + B18, + B19, + B20, + B21, + B22, + B23, + B24, + B25, + B26, + B27, + B28, + B29, + B30, + B31, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, + D10, + D11, + D12, + D13, + D14, + D15, + D16, + D17, + D18, + D19, + D20, + D21, + D22, + D23, + D24, + D25, + D26, + D27, + D28, + D29, + D30, + D31, + H0, + H1, + H2, + H3, + H4, + H5, + H6, + H7, + H8, + H9, + H10, + H11, + H12, + H13, + H14, + H15, + H16, + H17, + H18, + H19, + H20, + H21, + H22, + H23, + H24, + H25, + H26, + H27, + H28, + H29, + H30, + H31, + Q0, + Q1, + Q2, + Q3, + Q4, + Q5, + Q6, + Q7, + Q8, + Q9, + Q10, + Q11, + Q12, + Q13, + Q14, + Q15, + Q16, + Q17, + Q18, + Q19, + Q20, + Q21, + Q22, + Q23, + Q24, + Q25, + Q26, + Q27, + Q28, + Q29, + Q30, + Q31, + S0, + S1, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + S12, + S13, + S14, + S15, + S16, + S17, + S18, + S19, + S20, + S21, + S22, + S23, + S24, + S25, + S26, + S27, + S28, + S29, + S30, + S31, + W0, + W1, + W2, + W3, + W4, + W5, + W6, + W7, + W8, + W9, + W10, + W11, + W12, + W13, + W14, + W15, + W16, + W17, + W18, + W19, + W20, + W21, + W22, + W23, + W24, + W25, + W26, + W27, + W28, + W29, + W30, + X0, + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + + V0, + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + V13, + V14, + V15, + V16, + V17, + V18, + V19, + V20, + V21, + V22, + V23, + V24, + V25, + V26, + V27, + V28, + V29, + V30, + V31, + + //> pseudo registers + PC, // program counter register + + CPACR_EL1, + ESR, + + //> thread registers + TPIDR_EL0, + TPIDRRO_EL0, + TPIDR_EL1, + + PSTATE, // PSTATE pseudoregister + + //> floating point control and status registers + FPCR, + FPSR, + + ENDING, // <-- mark the end of the list of registers + + //> alias registers + + IP0 = X16, + IP1 = X17, + FP = X29, + LR = X30, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/Interface.cs b/Ryujinx.Tests.Unicorn/Native/Interface.cs new file mode 100644 index 000000000..a6563220f --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/Interface.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.InteropServices; +using Ryujinx.Tests.Unicorn; + +namespace Ryujinx.Tests.Unicorn.Native +{ + public class Interface + { + public static void Checked(UnicornError error) + { + if (error != UnicornError.UC_ERR_OK) + { + throw new UnicornException(error); + } + } + + public static void MarshalArrayOf(IntPtr input, int length, out T[] output) + { + var size = Marshal.SizeOf(typeof(T)); + output = new T[length]; + + for (int i = 0; i < length; i++) + { + IntPtr item = new IntPtr(input.ToInt64() + i * size); + output[i] = Marshal.PtrToStructure(item); + } + } + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern uint uc_version(out uint major, out uint minor); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_open(uint arch, uint mode, out IntPtr uc); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_close(IntPtr uc); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr uc_strerror(UnicornError err); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_reg_write(IntPtr uc, int regid, byte[] value); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_reg_read(IntPtr uc, int regid, byte[] value); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_write(IntPtr uc, ulong address, byte[] bytes, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_read(IntPtr uc, ulong address, byte[] bytes, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_emu_start(IntPtr uc, ulong begin, ulong until, ulong timeout, ulong count); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_map(IntPtr uc, ulong address, ulong size, uint perms); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_unmap(IntPtr uc, ulong address, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_protect(IntPtr uc, ulong address, ulong size, uint perms); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_regions(IntPtr uc, out IntPtr regions, out uint count); + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs new file mode 100644 index 000000000..be088366d --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum UnicornArch + { + UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2) + UC_ARCH_ARM64, // ARM-64, also called AArch64 + UC_ARCH_MIPS, // Mips architecture + UC_ARCH_X86, // X86 architecture (including x86 & x86-64) + UC_ARCH_PPC, // PowerPC architecture (currently unsupported) + UC_ARCH_SPARC, // Sparc architecture + UC_ARCH_M68K, // M68K architecture + UC_ARCH_MAX, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs b/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs new file mode 100644 index 000000000..7ee34a74a --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Unicorn.Native +{ + [StructLayout(LayoutKind.Sequential)] + public struct UnicornMemoryRegion + { + public UInt64 begin; // begin address of the region (inclusive) + public UInt64 end; // end address of the region (inclusive) + public UInt32 perms; // memory permissions of the region + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs new file mode 100644 index 000000000..950583bdc --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum UnicornMode + { + UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode) + UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode + // arm / arm64 + UC_MODE_ARM = 0, // ARM mode + UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2) + UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series (currently unsupported) + UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (currently unsupported) + // mips + UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported) + UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported) + UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported) + UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA + UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA + // x86 / x64 + UC_MODE_16 = 1 << 1, // 16-bit mode + UC_MODE_32 = 1 << 2, // 32-bit mode + UC_MODE_64 = 1 << 3, // 64-bit mode + // ppc + UC_MODE_PPC32 = 1 << 2, // 32-bit mode (currently unsupported) + UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported) + UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (currently unsupported) + // sparc + UC_MODE_SPARC32 = 1 << 2, // 32-bit mode + UC_MODE_SPARC64 = 1 << 3, // 64-bit mode + UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported) + // m68k + } +} diff --git a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj new file mode 100644 index 000000000..ee7c103d5 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.1 + win10-x64;osx-x64;linux-x64 + true + + + + false + + + + + + + + diff --git a/Ryujinx.Tests.Unicorn/UnicornAArch64.cs b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs new file mode 100644 index 000000000..1cd3671fe --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs @@ -0,0 +1,315 @@ +using System; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornAArch64 + { + internal readonly IntPtr uc; + + public IndexedProperty X + { + get + { + return new IndexedProperty( + (int i) => GetX(i), + (int i, ulong value) => SetX(i, value)); + } + } + + public IndexedProperty> Q + { + get + { + return new IndexedProperty>( + (int i) => GetQ(i), + (int i, Vector128 value) => SetQ(i, value)); + } + } + + public ulong LR + { + get { return GetRegister(Native.ArmRegister.LR); } + set { SetRegister(Native.ArmRegister.LR, value); } + } + + public ulong SP + { + get { return GetRegister(Native.ArmRegister.SP); } + set { SetRegister(Native.ArmRegister.SP, value); } + } + + public ulong PC + { + get { return GetRegister(Native.ArmRegister.PC); } + set { SetRegister(Native.ArmRegister.PC, value); } + } + + public uint Pstate + { + get { return (uint)GetRegister(Native.ArmRegister.PSTATE); } + set { SetRegister(Native.ArmRegister.PSTATE, (uint)value); } + } + + public int Fpcr + { + get { return (int)GetRegister(Native.ArmRegister.FPCR); } + set { SetRegister(Native.ArmRegister.FPCR, (uint)value); } + } + + public int Fpsr + { + get { return (int)GetRegister(Native.ArmRegister.FPSR); } + set { SetRegister(Native.ArmRegister.FPSR, (uint)value); } + } + + public bool OverflowFlag + { + get { return (Pstate & 0x10000000u) != 0; } + set { Pstate = (Pstate & ~0x10000000u) | (value ? 0x10000000u : 0u); } + } + + public bool CarryFlag + { + get { return (Pstate & 0x20000000u) != 0; } + set { Pstate = (Pstate & ~0x20000000u) | (value ? 0x20000000u : 0u); } + } + + public bool ZeroFlag + { + get { return (Pstate & 0x40000000u) != 0; } + set { Pstate = (Pstate & ~0x40000000u) | (value ? 0x40000000u : 0u); } + } + + public bool NegativeFlag + { + get { return (Pstate & 0x80000000u) != 0; } + set { Pstate = (Pstate & ~0x80000000u) | (value ? 0x80000000u : 0u); } + } + + public UnicornAArch64() + { + Native.Interface.Checked(Native.Interface.uc_open((uint)Native.UnicornArch.UC_ARCH_ARM64, (uint)Native.UnicornMode.UC_MODE_LITTLE_ENDIAN, out uc)); + SetRegister(Native.ArmRegister.CPACR_EL1, 0x00300000); + } + + ~UnicornAArch64() + { + Native.Interface.Checked(Native.Interface.uc_close(uc)); + } + + public void RunForCount(ulong count) + { + Native.Interface.Checked(Native.Interface.uc_emu_start(uc, this.PC, 0xFFFFFFFFFFFFFFFFu, 0, count)); + } + + public void Step() + { + RunForCount(1); + } + + internal static Native.ArmRegister[] X_registers = new Native.ArmRegister[31] + { + Native.ArmRegister.X0, + Native.ArmRegister.X1, + Native.ArmRegister.X2, + Native.ArmRegister.X3, + Native.ArmRegister.X4, + Native.ArmRegister.X5, + Native.ArmRegister.X6, + Native.ArmRegister.X7, + Native.ArmRegister.X8, + Native.ArmRegister.X9, + Native.ArmRegister.X10, + Native.ArmRegister.X11, + Native.ArmRegister.X12, + Native.ArmRegister.X13, + Native.ArmRegister.X14, + Native.ArmRegister.X15, + Native.ArmRegister.X16, + Native.ArmRegister.X17, + Native.ArmRegister.X18, + Native.ArmRegister.X19, + Native.ArmRegister.X20, + Native.ArmRegister.X21, + Native.ArmRegister.X22, + Native.ArmRegister.X23, + Native.ArmRegister.X24, + Native.ArmRegister.X25, + Native.ArmRegister.X26, + Native.ArmRegister.X27, + Native.ArmRegister.X28, + Native.ArmRegister.X29, + Native.ArmRegister.X30, + }; + + internal static Native.ArmRegister[] Q_registers = new Native.ArmRegister[32] + { + Native.ArmRegister.Q0, + Native.ArmRegister.Q1, + Native.ArmRegister.Q2, + Native.ArmRegister.Q3, + Native.ArmRegister.Q4, + Native.ArmRegister.Q5, + Native.ArmRegister.Q6, + Native.ArmRegister.Q7, + Native.ArmRegister.Q8, + Native.ArmRegister.Q9, + Native.ArmRegister.Q10, + Native.ArmRegister.Q11, + Native.ArmRegister.Q12, + Native.ArmRegister.Q13, + Native.ArmRegister.Q14, + Native.ArmRegister.Q15, + Native.ArmRegister.Q16, + Native.ArmRegister.Q17, + Native.ArmRegister.Q18, + Native.ArmRegister.Q19, + Native.ArmRegister.Q20, + Native.ArmRegister.Q21, + Native.ArmRegister.Q22, + Native.ArmRegister.Q23, + Native.ArmRegister.Q24, + Native.ArmRegister.Q25, + Native.ArmRegister.Q26, + Native.ArmRegister.Q27, + Native.ArmRegister.Q28, + Native.ArmRegister.Q29, + Native.ArmRegister.Q30, + Native.ArmRegister.Q31, + }; + + internal ulong GetRegister(Native.ArmRegister register) + { + byte[] value_bytes = new byte[8]; + Native.Interface.Checked(Native.Interface.uc_reg_read(uc, (int)register, value_bytes)); + return (ulong)BitConverter.ToInt64(value_bytes, 0); + } + + internal void SetRegister(Native.ArmRegister register, ulong value) + { + byte[] value_bytes = BitConverter.GetBytes(value); + Native.Interface.Checked(Native.Interface.uc_reg_write(uc, (int)register, value_bytes)); + } + + internal Vector128 GetVector(Native.ArmRegister register) + { + byte[] value_bytes = new byte[16]; + Native.Interface.Checked(Native.Interface.uc_reg_read(uc, (int)register, value_bytes)); + unsafe + { + fixed (byte* p = &value_bytes[0]) + { + return Sse.LoadVector128((float*)p); + } + } + } + + internal void SetVector(Native.ArmRegister register, Vector128 value) + { + byte[] value_bytes = new byte[16]; + unsafe + { + fixed (byte* p = &value_bytes[0]) + { + Sse.Store((float*)p, value); + } + } + Native.Interface.Checked(Native.Interface.uc_reg_write(uc, (int)register, value_bytes)); + } + + public ulong GetX(int index) + { + Contract.Requires(index <= 30, "invalid register"); + + return GetRegister(X_registers[index]); + } + + public void SetX(int index, ulong value) + { + Contract.Requires(index <= 30, "invalid register"); + + SetRegister(X_registers[index], value); + } + + public Vector128 GetQ(int index) + { + Contract.Requires(index <= 31, "invalid vector"); + + return GetVector(Q_registers[index]); + } + + public void SetQ(int index, Vector128 value) + { + Contract.Requires(index <= 31, "invalid vector"); + + SetVector(Q_registers[index], value); + } + + public byte[] MemoryRead(ulong address, ulong size) + { + byte[] value = new byte[size]; + Native.Interface.Checked(Native.Interface.uc_mem_read(uc, address, value, size)); + return value; + } + + public byte MemoryRead8 (ulong address) { return MemoryRead(address, 1)[0]; } + public UInt16 MemoryRead16(ulong address) { return (UInt16)BitConverter.ToInt16(MemoryRead(address, 2), 0); } + public UInt32 MemoryRead32(ulong address) { return (UInt32)BitConverter.ToInt32(MemoryRead(address, 4), 0); } + public UInt64 MemoryRead64(ulong address) { return (UInt64)BitConverter.ToInt64(MemoryRead(address, 8), 0); } + + public void MemoryWrite(ulong address, byte[] value) + { + Native.Interface.Checked(Native.Interface.uc_mem_write(uc, address, value, (ulong)value.Length)); + } + + public void MemoryWrite8 (ulong address, byte value) { MemoryWrite(address, new byte[]{value}); } + public void MemoryWrite16(ulong address, Int16 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + public void MemoryWrite16(ulong address, UInt16 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + public void MemoryWrite32(ulong address, Int32 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + public void MemoryWrite32(ulong address, UInt32 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + public void MemoryWrite64(ulong address, Int64 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + public void MemoryWrite64(ulong address, UInt64 value) { MemoryWrite(address, BitConverter.GetBytes(value)); } + + public void MemoryMap(ulong address, ulong size, MemoryPermission permissions) + { + Native.Interface.Checked(Native.Interface.uc_mem_map(uc, address, size, (uint)permissions)); + } + + public void MemoryUnmap(ulong address, ulong size) + { + Native.Interface.Checked(Native.Interface.uc_mem_unmap(uc, address, size)); + } + + public void MemoryProtect(ulong address, ulong size, MemoryPermission permissions) + { + Native.Interface.Checked(Native.Interface.uc_mem_protect(uc, address, size, (uint)permissions)); + } + + public void DumpMemoryInformation() + { + Native.Interface.Checked(Native.Interface.uc_mem_regions(uc, out IntPtr regions_raw, out uint length)); + Native.Interface.MarshalArrayOf(regions_raw, (int)length, out var regions); + foreach (var region in regions) + { + Console.WriteLine("region: begin {0:X16} end {1:X16} perms {2:X8}", region.begin, region.end, region.perms); + } + } + + public static bool IsAvailable() + { + try + { + Native.Interface.uc_version(out uint major, out uint minor); + return true; + } + catch (DllNotFoundException) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Tests.Unicorn/UnicornError.cs b/Ryujinx.Tests.Unicorn/UnicornError.cs new file mode 100644 index 000000000..db56c615d --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public enum UnicornError + { + UC_ERR_OK = 0, // No error: everything was fine + UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate() + UC_ERR_ARCH, // Unsupported architecture: uc_open() + UC_ERR_HANDLE, // Invalid handle + UC_ERR_MODE, // Invalid/unsupported mode: uc_open() + UC_ERR_VERSION, // Unsupported version (bindings) + UC_ERR_READ_UNMAPPED, // Quit emulation due to READ on unmapped memory: uc_emu_start() + UC_ERR_WRITE_UNMAPPED, // Quit emulation due to WRITE on unmapped memory: uc_emu_start() + UC_ERR_FETCH_UNMAPPED, // Quit emulation due to FETCH on unmapped memory: uc_emu_start() + UC_ERR_HOOK, // Invalid hook type: uc_hook_add() + UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start() + UC_ERR_MAP, // Invalid memory mapping: uc_mem_map() + UC_ERR_WRITE_PROT, // Quit emulation due to UC_MEM_WRITE_PROT violation: uc_emu_start() + UC_ERR_READ_PROT, // Quit emulation due to UC_MEM_READ_PROT violation: uc_emu_start() + UC_ERR_FETCH_PROT, // Quit emulation due to UC_MEM_FETCH_PROT violation: uc_emu_start() + UC_ERR_ARG, // Inavalid argument provided to uc_xxx function (See specific function API) + UC_ERR_READ_UNALIGNED, // Unaligned read + UC_ERR_WRITE_UNALIGNED, // Unaligned write + UC_ERR_FETCH_UNALIGNED, // Unaligned fetch + UC_ERR_HOOK_EXIST, // hook for this event already existed + UC_ERR_RESOURCE, // Insufficient resource: uc_emu_start() + UC_ERR_EXCEPTION // Unhandled CPU exception + } +} diff --git a/Ryujinx.Tests.Unicorn/UnicornException.cs b/Ryujinx.Tests.Unicorn/UnicornException.cs new file mode 100644 index 000000000..3cb693703 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornException.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornException : Exception + { + public readonly UnicornError Error; + + internal UnicornException(UnicornError error) + { + Error = error; + } + + public override string Message + { + get + { + return Marshal.PtrToStringAnsi(Native.Interface.uc_strerror(Error)); + } + } + } +} diff --git a/Ryujinx.Tests.Unicorn/libs/README.md b/Ryujinx.Tests.Unicorn/libs/README.md new file mode 100644 index 000000000..bb3701641 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/libs/README.md @@ -0,0 +1,3 @@ +The pre-compiled dynamic libraries in this directory are licenced under the GPLv2. + +The source code for windows/unicorn.dll is available at: https://github.com/MerryMage/UnicornDotNet/tree/299451c02d9c810d2feca51f5e9cb6d8b2f38960 diff --git a/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll b/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll new file mode 100644 index 000000000..0e3cea589 Binary files /dev/null and b/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll differ diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index e6a023798..6e013aa05 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -4,6 +4,8 @@ using ChocolArm64.State; using NUnit.Framework; +using Ryujinx.Tests.Unicorn; + using System; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -25,10 +27,22 @@ namespace Ryujinx.Tests.Cpu private AMemory Memory; private AThread Thread; + private static bool UnicornAvailable; + private UnicornAArch64 UnicornEmu; + + static CpuTest() + { + UnicornAvailable = UnicornAArch64.IsAvailable(); + if (!UnicornAvailable) + { + Console.WriteLine("WARNING: Could not find unicorn"); + } + } + [SetUp] public void Setup() { - Position = 0x0; + Position = 0x1000; Size = 0x1000; EntryPoint = Position; @@ -38,6 +52,13 @@ namespace Ryujinx.Tests.Cpu Memory = new AMemory(RamPointer); Memory.Map(Position, 0, Size); Thread = new AThread(Translator, Memory, EntryPoint); + + if (UnicornAvailable) + { + UnicornEmu = new UnicornAArch64(); + UnicornEmu.MemoryMap((ulong)Position, (ulong)Size, MemoryPermission.READ | MemoryPermission.EXEC); + UnicornEmu.PC = (ulong)EntryPoint; + } } [TearDown] @@ -46,6 +67,7 @@ namespace Ryujinx.Tests.Cpu Marshal.FreeHGlobal(RamPointer); Memory = null; Thread = null; + UnicornEmu = null; } protected void Reset() @@ -57,6 +79,10 @@ namespace Ryujinx.Tests.Cpu protected void Opcode(uint Opcode) { Thread.Memory.WriteUInt32(Position, Opcode); + if (UnicornAvailable) + { + UnicornEmu.MemoryWrite32((ulong)Position, Opcode); + } Position += 4; } @@ -81,6 +107,24 @@ namespace Ryujinx.Tests.Cpu Thread.ThreadState.Negative = Negative; Thread.ThreadState.Fpcr = Fpcr; Thread.ThreadState.Fpsr = Fpsr; + + if (UnicornAvailable) + { + UnicornEmu.X[0] = X0; + UnicornEmu.X[1] = X1; + UnicornEmu.X[2] = X2; + UnicornEmu.X[3] = X3; + UnicornEmu.SP = X31; + UnicornEmu.Q[0] = V0; + UnicornEmu.Q[1] = V1; + UnicornEmu.Q[2] = V2; + UnicornEmu.OverflowFlag = Overflow; + UnicornEmu.CarryFlag = Carry; + UnicornEmu.ZeroFlag = Zero; + UnicornEmu.NegativeFlag = Negative; + UnicornEmu.Fpcr = Fpcr; + UnicornEmu.Fpsr = Fpsr; + } } protected void ExecuteOpcodes() @@ -93,6 +137,11 @@ namespace Ryujinx.Tests.Cpu Thread.Execute(); Wait.WaitOne(); } + + if (UnicornAvailable) + { + UnicornEmu.RunForCount((ulong)(Position - EntryPoint - 8) / 4); + } } protected AThreadState GetThreadState() @@ -117,24 +166,124 @@ namespace Ryujinx.Tests.Cpu return GetThreadState(); } + protected void CompareAgainstUnicorn() + { + if (!UnicornAvailable) + { + return; + } + + Assert.That(Thread.ThreadState.X0, Is.EqualTo(UnicornEmu.X[0])); + Assert.That(Thread.ThreadState.X1, Is.EqualTo(UnicornEmu.X[1])); + Assert.That(Thread.ThreadState.X2, Is.EqualTo(UnicornEmu.X[2])); + Assert.That(Thread.ThreadState.X3, Is.EqualTo(UnicornEmu.X[3])); + Assert.That(Thread.ThreadState.X4, Is.EqualTo(UnicornEmu.X[4])); + Assert.That(Thread.ThreadState.X5, Is.EqualTo(UnicornEmu.X[5])); + Assert.That(Thread.ThreadState.X6, Is.EqualTo(UnicornEmu.X[6])); + Assert.That(Thread.ThreadState.X7, Is.EqualTo(UnicornEmu.X[7])); + Assert.That(Thread.ThreadState.X8, Is.EqualTo(UnicornEmu.X[8])); + Assert.That(Thread.ThreadState.X9, Is.EqualTo(UnicornEmu.X[9])); + Assert.That(Thread.ThreadState.X10, Is.EqualTo(UnicornEmu.X[10])); + Assert.That(Thread.ThreadState.X11, Is.EqualTo(UnicornEmu.X[11])); + Assert.That(Thread.ThreadState.X12, Is.EqualTo(UnicornEmu.X[12])); + Assert.That(Thread.ThreadState.X13, Is.EqualTo(UnicornEmu.X[13])); + Assert.That(Thread.ThreadState.X14, Is.EqualTo(UnicornEmu.X[14])); + Assert.That(Thread.ThreadState.X15, Is.EqualTo(UnicornEmu.X[15])); + Assert.That(Thread.ThreadState.X16, Is.EqualTo(UnicornEmu.X[16])); + Assert.That(Thread.ThreadState.X17, Is.EqualTo(UnicornEmu.X[17])); + Assert.That(Thread.ThreadState.X18, Is.EqualTo(UnicornEmu.X[18])); + Assert.That(Thread.ThreadState.X19, Is.EqualTo(UnicornEmu.X[19])); + Assert.That(Thread.ThreadState.X20, Is.EqualTo(UnicornEmu.X[20])); + Assert.That(Thread.ThreadState.X21, Is.EqualTo(UnicornEmu.X[21])); + Assert.That(Thread.ThreadState.X22, Is.EqualTo(UnicornEmu.X[22])); + Assert.That(Thread.ThreadState.X23, Is.EqualTo(UnicornEmu.X[23])); + Assert.That(Thread.ThreadState.X24, Is.EqualTo(UnicornEmu.X[24])); + Assert.That(Thread.ThreadState.X25, Is.EqualTo(UnicornEmu.X[25])); + Assert.That(Thread.ThreadState.X26, Is.EqualTo(UnicornEmu.X[26])); + Assert.That(Thread.ThreadState.X27, Is.EqualTo(UnicornEmu.X[27])); + Assert.That(Thread.ThreadState.X28, Is.EqualTo(UnicornEmu.X[28])); + Assert.That(Thread.ThreadState.X29, Is.EqualTo(UnicornEmu.X[29])); + Assert.That(Thread.ThreadState.X30, Is.EqualTo(UnicornEmu.X[30])); + Assert.That(Thread.ThreadState.X31, Is.EqualTo(UnicornEmu.SP)); + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + Assert.That(Thread.ThreadState.V1, Is.EqualTo(UnicornEmu.Q[1])); + Assert.That(Thread.ThreadState.V2, Is.EqualTo(UnicornEmu.Q[2])); + Assert.That(Thread.ThreadState.V3, Is.EqualTo(UnicornEmu.Q[3])); + Assert.That(Thread.ThreadState.V4, Is.EqualTo(UnicornEmu.Q[4])); + Assert.That(Thread.ThreadState.V5, Is.EqualTo(UnicornEmu.Q[5])); + Assert.That(Thread.ThreadState.V6, Is.EqualTo(UnicornEmu.Q[6])); + Assert.That(Thread.ThreadState.V7, Is.EqualTo(UnicornEmu.Q[7])); + Assert.That(Thread.ThreadState.V8, Is.EqualTo(UnicornEmu.Q[8])); + Assert.That(Thread.ThreadState.V9, Is.EqualTo(UnicornEmu.Q[9])); + Assert.That(Thread.ThreadState.V10, Is.EqualTo(UnicornEmu.Q[10])); + Assert.That(Thread.ThreadState.V11, Is.EqualTo(UnicornEmu.Q[11])); + Assert.That(Thread.ThreadState.V12, Is.EqualTo(UnicornEmu.Q[12])); + Assert.That(Thread.ThreadState.V13, Is.EqualTo(UnicornEmu.Q[13])); + Assert.That(Thread.ThreadState.V14, Is.EqualTo(UnicornEmu.Q[14])); + Assert.That(Thread.ThreadState.V15, Is.EqualTo(UnicornEmu.Q[15])); + Assert.That(Thread.ThreadState.V16, Is.EqualTo(UnicornEmu.Q[16])); + Assert.That(Thread.ThreadState.V17, Is.EqualTo(UnicornEmu.Q[17])); + Assert.That(Thread.ThreadState.V18, Is.EqualTo(UnicornEmu.Q[18])); + Assert.That(Thread.ThreadState.V19, Is.EqualTo(UnicornEmu.Q[19])); + Assert.That(Thread.ThreadState.V20, Is.EqualTo(UnicornEmu.Q[20])); + Assert.That(Thread.ThreadState.V21, Is.EqualTo(UnicornEmu.Q[21])); + Assert.That(Thread.ThreadState.V22, Is.EqualTo(UnicornEmu.Q[22])); + Assert.That(Thread.ThreadState.V23, Is.EqualTo(UnicornEmu.Q[23])); + Assert.That(Thread.ThreadState.V24, Is.EqualTo(UnicornEmu.Q[24])); + Assert.That(Thread.ThreadState.V25, Is.EqualTo(UnicornEmu.Q[25])); + Assert.That(Thread.ThreadState.V26, Is.EqualTo(UnicornEmu.Q[26])); + Assert.That(Thread.ThreadState.V27, Is.EqualTo(UnicornEmu.Q[27])); + Assert.That(Thread.ThreadState.V28, Is.EqualTo(UnicornEmu.Q[28])); + Assert.That(Thread.ThreadState.V29, Is.EqualTo(UnicornEmu.Q[29])); + Assert.That(Thread.ThreadState.V30, Is.EqualTo(UnicornEmu.Q[30])); + Assert.That(Thread.ThreadState.V31, Is.EqualTo(UnicornEmu.Q[31])); + Assert.That(Thread.ThreadState.V31, Is.EqualTo(UnicornEmu.Q[31])); + Assert.That(Thread.ThreadState.Fpcr, Is.EqualTo(UnicornEmu.Fpcr)); + Assert.That(Thread.ThreadState.Fpsr & 0x08000000, Is.EqualTo(UnicornEmu.Fpsr & 0x08000000)); + Assert.That(Thread.ThreadState.Overflow, Is.EqualTo(UnicornEmu.OverflowFlag)); + Assert.That(Thread.ThreadState.Carry, Is.EqualTo(UnicornEmu.CarryFlag)); + Assert.That(Thread.ThreadState.Zero, Is.EqualTo(UnicornEmu.ZeroFlag)); + Assert.That(Thread.ThreadState.Negative, Is.EqualTo(UnicornEmu.NegativeFlag)); + } + protected static Vector128 MakeVectorE0(double E0) { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse.StaticCast(Sse2.SetVector128(0, BitConverter.DoubleToInt64Bits(E0))); } protected static Vector128 MakeVectorE0E1(double E0, double E1) { - return Sse.StaticCast(Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), - BitConverter.DoubleToInt64Bits(E0))); + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + + return Sse.StaticCast( + Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), BitConverter.DoubleToInt64Bits(E0))); } protected static Vector128 MakeVectorE1(double E1) { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse.StaticCast(Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), 0)); } protected static double VectorExtractDouble(Vector128 Vector, byte Index) { + if (!Sse41.IsSupported) + { + throw new PlatformNotSupportedException(); + } + long Value = Sse41.Extract(Sse.StaticCast(Vector), Index); return BitConverter.Int64BitsToDouble(Value); @@ -142,26 +291,51 @@ namespace Ryujinx.Tests.Cpu protected static Vector128 MakeVectorE0(ulong E0) { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse.StaticCast(Sse2.SetVector128(0, E0)); } protected static Vector128 MakeVectorE0E1(ulong E0, ulong E1) { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse.StaticCast(Sse2.SetVector128(E1, E0)); } protected static Vector128 MakeVectorE1(ulong E1) { + if (!Sse2.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse.StaticCast(Sse2.SetVector128(E1, 0)); } protected static ulong GetVectorE0(Vector128 Vector) { + if (!Sse41.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse41.Extract(Sse.StaticCast(Vector), (byte)0); } protected static ulong GetVectorE1(Vector128 Vector) { + if (!Sse41.IsSupported) + { + throw new PlatformNotSupportedException(); + } + return Sse41.Extract(Sse.StaticCast(Vector), (byte)1); } } diff --git a/Ryujinx.Tests/Cpu/CpuTestAlu.cs b/Ryujinx.Tests/Cpu/CpuTestAlu.cs index c89cb646e..3a47dbee6 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAlu.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAlu.cs @@ -45,6 +45,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CLS , ")] @@ -101,6 +102,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CLZ , ")] @@ -129,6 +131,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("RBIT , ")] @@ -157,6 +160,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("RBIT , ")] @@ -185,6 +189,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("REV16 , ")] @@ -213,6 +218,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("REV16 , ")] @@ -241,6 +247,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("REV32 , ")] @@ -269,6 +276,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("REV , ")] @@ -297,6 +305,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("REV64 , ")] @@ -325,6 +334,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluImm.cs b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs index d436d65f8..b648a337e 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs @@ -61,6 +61,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , #{, }")] @@ -105,6 +106,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ADDS , , #{, }")] @@ -156,6 +158,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , #{, }")] @@ -207,6 +210,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("AND , , #")] @@ -240,6 +244,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("AND , , #")] @@ -273,6 +278,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("AND , , #")] @@ -306,6 +312,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ANDS , , #")] @@ -343,6 +350,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ANDS , , #")] @@ -380,6 +388,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ANDS , , #")] @@ -417,6 +426,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("EOR , , #")] @@ -450,6 +460,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("EOR , , #")] @@ -483,6 +494,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("EOR , , #")] @@ -516,6 +528,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ORR , , #")] @@ -549,6 +562,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ORR , , #")] @@ -582,6 +596,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ORR , , #")] @@ -615,6 +630,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , #{, }")] @@ -659,6 +675,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , #{, }")] @@ -703,6 +720,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("SUBS , , #{, }")] @@ -754,6 +772,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , #{, }")] @@ -805,6 +824,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRs.cs b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs index 880794cf8..49c82b742 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluRs.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs @@ -51,6 +51,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("ADC , , ")] @@ -85,6 +86,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("ADCS , , ")] @@ -125,6 +127,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADCS , , ")] @@ -165,6 +168,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, #}")] @@ -200,6 +204,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, #}")] @@ -234,6 +239,7 @@ namespace Ryujinx.Tests.Cpu else { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + CompareAgainstUnicorn(); } } @@ -276,6 +282,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, #}")] @@ -317,6 +324,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("AND , , {, #}")] @@ -352,6 +360,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("AND , , {, #}")] @@ -387,6 +396,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("ANDS , , {, #}")] @@ -428,6 +438,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ANDS , , {, #}")] @@ -469,6 +480,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ASRV , , ")] @@ -501,6 +513,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("ASRV , , ")] @@ -533,6 +546,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("BIC , , {, #}")] @@ -568,6 +582,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("BIC , , {, #}")] @@ -603,6 +618,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("BICS , , {, #}")] @@ -644,6 +660,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("BICS , , {, #}")] @@ -685,6 +702,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CRC32X , , ")] @@ -718,6 +736,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32W , , ")] @@ -749,6 +768,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32H , , ")] @@ -780,6 +800,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32B , , ")] @@ -811,6 +832,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32CX , , ")] @@ -844,6 +866,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32CW , , ")] @@ -875,6 +898,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32CH , , ")] @@ -906,6 +930,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CRC32CB , , ")] @@ -937,6 +962,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("EON , , {, #}")] @@ -972,6 +998,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("EON , , {, #}")] @@ -1007,6 +1034,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("EOR , , {, #}")] @@ -1042,6 +1070,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("EOR , , {, #}")] @@ -1077,6 +1106,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("EXTR , , , #")] @@ -1111,6 +1141,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("EXTR , , , #")] @@ -1145,6 +1176,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("LSLV , , ")] @@ -1177,6 +1209,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("LSLV , , ")] @@ -1209,6 +1242,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("LSRV , , ")] @@ -1241,6 +1275,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("LSRV , , ")] @@ -1273,6 +1308,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("ORN , , {, #}")] @@ -1308,6 +1344,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("ORN , , {, #}")] @@ -1343,6 +1380,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("ORR , , {, #}")] @@ -1378,6 +1416,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("ORR , , {, #}")] @@ -1413,6 +1452,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("RORV , , ")] @@ -1445,6 +1485,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("RORV , , ")] @@ -1477,6 +1518,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SBC , , ")] @@ -1511,6 +1553,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SBC , , ")] @@ -1545,6 +1588,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SBCS , , ")] @@ -1585,6 +1629,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SBCS , , ")] @@ -1625,6 +1670,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SDIV , , ")] @@ -1657,6 +1703,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SDIV , , ")] @@ -1689,6 +1736,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, #}")] @@ -1724,6 +1772,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, #}")] @@ -1759,6 +1808,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, #}")] @@ -1800,6 +1850,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, #}")] @@ -1841,6 +1892,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("UDIV , , ")] @@ -1873,6 +1925,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("UDIV , , ")] @@ -1905,6 +1958,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRx.cs b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs index d6cf8dc40..41fd580a6 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluRx.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs @@ -66,6 +66,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -116,6 +117,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -166,6 +168,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -216,6 +219,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -266,6 +270,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -316,6 +321,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ADD , , {, {#}}")] @@ -366,6 +372,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -410,6 +417,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -455,6 +463,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -500,6 +509,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -545,6 +555,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -590,6 +601,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -635,6 +647,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("ADDS , , {, {#}}")] @@ -680,6 +693,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -729,6 +743,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -779,6 +794,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -829,6 +845,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -879,6 +896,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -929,6 +947,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -979,6 +998,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("SUB , , {, {#}}")] @@ -1029,6 +1049,7 @@ namespace Ryujinx.Tests.Cpu Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); } + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1073,6 +1094,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1118,6 +1140,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1163,6 +1186,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1208,6 +1232,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1253,6 +1278,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1298,6 +1324,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("SUBS , , {, {#}}")] @@ -1343,6 +1370,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestBfm.cs b/Ryujinx.Tests/Cpu/CpuTestBfm.cs index 0a2f9ad3a..c5be57885 100644 --- a/Ryujinx.Tests/Cpu/CpuTestBfm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestBfm.cs @@ -50,6 +50,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("BFM , , #, #")] @@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SBFM , , #, #")] @@ -114,6 +116,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SBFM , , #, #")] @@ -145,6 +148,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("UBFM , , #, #")] @@ -176,6 +180,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("UBFM , , #, #")] @@ -207,6 +212,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs index 7ba44ed9c..245b04c0a 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs @@ -49,6 +49,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMN , #, #, ")] @@ -81,6 +82,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMP , #, #, ")] @@ -113,6 +115,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMP , #, #, ")] @@ -145,6 +148,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs index 82556464c..8c4e503ee 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs @@ -52,6 +52,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMN , , #, ")] @@ -87,6 +88,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMP , , #, ")] @@ -122,6 +124,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } [Test, Description("CCMP , , #, ")] @@ -157,6 +160,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCsel.cs b/Ryujinx.Tests/Cpu/CpuTestCsel.cs index fef56fee2..d532a12c6 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCsel.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCsel.cs @@ -54,6 +54,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CSEL , , , ")] @@ -91,6 +92,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CSINC , , , ")] @@ -128,6 +130,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CSINC , , , ")] @@ -165,6 +168,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CSINV , , , ")] @@ -202,6 +206,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CSINV , , , ")] @@ -239,6 +244,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("CSNEG , , , ")] @@ -276,6 +282,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("CSNEG , , , ")] @@ -313,6 +320,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestMov.cs b/Ryujinx.Tests/Cpu/CpuTestMov.cs index db544f865..a8bf33ef4 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMov.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMov.cs @@ -46,6 +46,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("MOVK , #{, LSL #}")] @@ -75,6 +76,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("MOVN , #{, LSL #}")] @@ -102,6 +104,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("MOVN , #{, LSL #}")] @@ -129,6 +132,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("MOVZ , #{, LSL #}")] @@ -156,6 +160,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("MOVZ , #{, LSL #}")] @@ -183,6 +188,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestMul.cs b/Ryujinx.Tests/Cpu/CpuTestMul.cs index 19b8ecd0a..2e8aa76fa 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMul.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMul.cs @@ -53,6 +53,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("MADD , , , ")] @@ -89,6 +90,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("MSUB , , , ")] @@ -125,6 +127,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("MSUB , , , ")] @@ -161,6 +164,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); } + CompareAgainstUnicorn(); } [Test, Description("SMADDL , , , ")] @@ -197,6 +201,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("UMADDL , , , ")] @@ -233,6 +238,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SMSUBL , , , ")] @@ -269,6 +275,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("UMSUBL , , , ")] @@ -305,6 +312,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("SMULH , , ")] @@ -337,6 +345,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } [Test, Description("UMULH , , ")] @@ -369,6 +378,7 @@ namespace Ryujinx.Tests.Cpu { Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index 68e2d721d..37fb3e976 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -85,11 +85,80 @@ namespace Ryujinx.Tests.Cpu 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } + + private static ulong[] _1S_F_() + { + return new ulong[] + { + 0x00000000FFFFFFFFul, // -QNaN (all ones payload) + 0x00000000FFBFFFFFul, // -SNaN (all ones payload) + 0x00000000FF800000ul, // -INF + 0x00000000FF7FFFFFul, // -Max Normal, float.MinValue + 0x0000000080800000ul, // -Min Normal + 0x00000000807FFFFFul, // -Max SubNormal + 0x0000000080000001ul, // -Min SubNormal + 0x0000000080000000ul, // -0 + 0x0000000000000000ul, // +0 + 0x0000000000000001ul, // +Min SubNormal + 0x00000000007FFFFFul, // +Max SubNormal + 0x0000000000800000ul, // +Min Normal + 0x000000007F7FFFFFul, // +Max Normal, float.MaxValue + 0x000000007F800000ul, // +INF + 0x000000007FBFFFFFul, // +SNaN (all ones payload) + 0x000000007FFFFFFFul // +QNaN (all ones payload) + }; + } + + private static ulong[] _2S_F_() + { + return new ulong[] + { + 0xFFFFFFFFFFFFFFFFul, // -QNaN (all ones payload) + 0xFFBFFFFFFFBFFFFFul, // -SNaN (all ones payload) + 0xFF800000FF800000ul, // -INF + 0xFF7FFFFFFF7FFFFFul, // -Max Normal, float.MinValue + 0x8080000080800000ul, // -Min Normal + 0x807FFFFF807FFFFFul, // -Max SubNormal + 0x8000000180000001ul, // -Min SubNormal + 0x8000000080000000ul, // -0 + 0x0000000000000000ul, // +0 + 0x0000000100000001ul, // +Min SubNormal + 0x007FFFFF007FFFFFul, // +Max SubNormal + 0x0080000000800000ul, // +Min Normal + 0x7F7FFFFF7F7FFFFFul, // +Max Normal, float.MaxValue + 0x7F8000007F800000ul, // +INF + 0x7FBFFFFF7FBFFFFFul, // +SNaN (all ones payload) + 0x7FFFFFFF7FFFFFFFul // +QNaN (all ones payload) + }; + } + + private static ulong[] _1D_F_() + { + return new ulong[] + { + 0xFFFFFFFFFFFFFFFFul, // -QNaN (all ones payload) + 0xFFF7FFFFFFFFFFFFul, // -SNaN (all ones payload) + 0xFFF0000000000000ul, // -INF + 0xFFEFFFFFFFFFFFFFul, // -Max Normal, double.MinValue + 0x8010000000000000ul, // -Min Normal + 0x800FFFFFFFFFFFFFul, // -Max SubNormal + 0x8000000000000001ul, // -Min SubNormal + 0x8000000000000000ul, // -0 + 0x0000000000000000ul, // +0 + 0x0000000000000001ul, // +Min SubNormal + 0x000FFFFFFFFFFFFFul, // +Max SubNormal + 0x0010000000000000ul, // +Min Normal + 0x7FEFFFFFFFFFFFFFul, // +Max Normal, double.MaxValue + 0x7FF0000000000000ul, // +INF + 0x7FF7FFFFFFFFFFFFul, // +SNaN (all ones payload) + 0x7FFFFFFFFFFFFFFFul // +QNaN (all ones payload) + }; + } #endregion - private const int RndCnt = 1; + private const int RndCnt = 4; - [Test, Description("ABS , ")] + [Test, Pairwise, Description("ABS , ")] public void Abs_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -112,9 +181,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("ABS ., .")] + [Test, Pairwise, Description("ABS ., .")] public void Abs_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -139,9 +209,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("ABS ., .")] + [Test, Pairwise, Description("ABS ., .")] public void Abs_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -166,9 +237,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("ADDP , .")] + [Test, Pairwise, Description("ADDP , .")] public void Addp_S_2DD([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -191,9 +263,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("ADDV , .")] + [Test, Pairwise, Description("ADDV , .")] public void Addv_V_8BB_4HH([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H_")] [Random(RndCnt)] ulong Z, @@ -218,9 +291,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("ADDV , .")] + [Test, Pairwise, Description("ADDV , .")] public void Addv_V_16BB_8HH_4SS([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -245,9 +319,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CLS ., .")] + [Test, Pairwise, Description("CLS ., .")] public void Cls_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -272,9 +347,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CLS ., .")] + [Test, Pairwise, Description("CLS ., .")] public void Cls_V_16B_8H_4S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -299,9 +375,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CLZ ., .")] + [Test, Pairwise, Description("CLZ ., .")] public void Clz_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -326,9 +403,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CLZ ., .")] + [Test, Pairwise, Description("CLZ ., .")] public void Clz_V_16B_8H_4S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -353,9 +431,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ , , #0")] + [Test, Pairwise, Description("CMEQ , , #0")] public void Cmeq_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -378,9 +457,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ ., ., #0")] + [Test, Pairwise, Description("CMEQ ., ., #0")] public void Cmeq_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -405,9 +485,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ ., ., #0")] + [Test, Pairwise, Description("CMEQ ., ., #0")] public void Cmeq_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -432,9 +513,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGE , , #0")] + [Test, Pairwise, Description("CMGE , , #0")] public void Cmge_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -457,9 +539,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGE ., ., #0")] + [Test, Pairwise, Description("CMGE ., ., #0")] public void Cmge_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -484,9 +567,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGE ., ., #0")] + [Test, Pairwise, Description("CMGE ., ., #0")] public void Cmge_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -511,9 +595,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGT , , #0")] + [Test, Pairwise, Description("CMGT , , #0")] public void Cmgt_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -536,9 +621,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGT ., ., #0")] + [Test, Pairwise, Description("CMGT ., ., #0")] public void Cmgt_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -563,9 +649,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMGT ., ., #0")] + [Test, Pairwise, Description("CMGT ., ., #0")] public void Cmgt_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -590,9 +677,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLE , , #0")] + [Test, Pairwise, Description("CMLE , , #0")] public void Cmle_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -615,9 +703,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLE ., ., #0")] + [Test, Pairwise, Description("CMLE ., ., #0")] public void Cmle_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -642,9 +731,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLE ., ., #0")] + [Test, Pairwise, Description("CMLE ., ., #0")] public void Cmle_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -669,9 +759,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLT , , #0")] + [Test, Pairwise, Description("CMLT , , #0")] public void Cmlt_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -694,9 +785,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLT ., ., #0")] + [Test, Pairwise, Description("CMLT ., ., #0")] public void Cmlt_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -721,9 +813,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CMLT ., ., #0")] + [Test, Pairwise, Description("CMLT ., ., #0")] public void Cmlt_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -748,9 +841,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CNT ., .")] + [Test, Pairwise, Description("CNT ., .")] public void Cnt_V_8B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -773,9 +867,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("CNT ., .")] + [Test, Pairwise, Description("CNT ., .")] public void Cnt_V_16B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -798,9 +893,246 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("NEG , ")] + [Test, Pairwise, Description("FCVTNS , ")] + public void Fcvtns_S_S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1S_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1S_F_")] [Random(RndCnt)] ulong A) + { + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + //const int IDCFlagBit = 7; // Input Denormal cumulative floating-point exception bit. + //const int IXCFlagBit = 4; // Inexact cumulative floating-point exception bit. + //const int IOCFlagBit = 0; // Invalid Operation cumulative floating-point exception bit. + + uint Opcode = 0x5E21A800; // FCVTNS S0, S0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + //Shared.FPCR = new Bits((uint)Fpcr); + SimdFp.Fcvtns_S(Op[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.Multiple(() => + { + Assert.That(((ThreadState.Fpsr >> IDCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IDCFlagBit])); + Assert.That(((ThreadState.Fpsr >> IXCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IXCFlagBit])); + Assert.That(((ThreadState.Fpsr >> IOCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IOCFlagBit])); + });*/ + } + + [Test, Pairwise, Description("FCVTNS , ")] + public void Fcvtns_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + { + uint Opcode = 0x5E61A800; // FCVTNS D0, D0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + SimdFp.Fcvtns_S(Op[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, Pairwise, Description("FCVTNS ., .")] + public void Fcvtns_V_2S_4S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_2S_F_")] [Random(RndCnt)] ulong A, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + uint Opcode = 0x0E21A800; // FCVTNS V0.2S, V0.2S + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((Q & 1) << 30); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + 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 * Q)); + SimdFp.Fcvtns_V(Op[30], Op[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, Pairwise, Description("FCVTNS ., .")] + public void Fcvtns_V_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + { + uint Opcode = 0x4E61A800; // FCVTNS V0.2D, V0.2D + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + 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.Fcvtns_V(Op[30], Op[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, Pairwise, Description("FCVTNU , ")] + public void Fcvtnu_S_S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1S_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1S_F_")] [Random(RndCnt)] ulong A) + { + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + //const int IDCFlagBit = 7; // Input Denormal cumulative floating-point exception bit. + //const int IXCFlagBit = 4; // Inexact cumulative floating-point exception bit. + //const int IOCFlagBit = 0; // Invalid Operation cumulative floating-point exception bit. + + uint Opcode = 0x7E21A800; // FCVTNU S0, S0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + //Shared.FPCR = new Bits((uint)Fpcr); + SimdFp.Fcvtnu_S(Op[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.Multiple(() => + { + Assert.That(((ThreadState.Fpsr >> IDCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IDCFlagBit])); + Assert.That(((ThreadState.Fpsr >> IXCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IXCFlagBit])); + Assert.That(((ThreadState.Fpsr >> IOCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[IOCFlagBit])); + });*/ + } + + [Test, Pairwise, Description("FCVTNU , ")] + public void Fcvtnu_S_D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + { + uint Opcode = 0x7E61A800; // FCVTNU D0, D0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + AArch64.Vpart(0, 0, new Bits(Z)); AArch64.Vpart(0, 1, new Bits(Z)); + AArch64.V(1, new Bits(A)); + SimdFp.Fcvtnu_S(Op[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, Pairwise, Description("FCVTNU ., .")] + public void Fcvtnu_V_2S_4S([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_2S_F_")] [Random(RndCnt)] ulong A, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + uint Opcode = 0x2E21A800; // FCVTNU V0.2S, V0.2S + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= ((Q & 1) << 30); + Bits Op = new Bits(Opcode); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + 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 * Q)); + SimdFp.Fcvtnu_V(Op[30], Op[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, Pairwise, Description("FCVTNU ., .")] + public void Fcvtnu_V_2D([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + { + uint Opcode = 0x6E61A800; // FCVTNU V0.2D, V0.2D + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + 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.Fcvtnu_V(Op[30], Op[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, Pairwise, Description("NEG , ")] public void Neg_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, @@ -823,9 +1155,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("NEG ., .")] + [Test, Pairwise, Description("NEG ., .")] public void Neg_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -850,9 +1183,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("NEG ., .")] + [Test, Pairwise, Description("NEG ., .")] public void Neg_V_16B_8H_4S_2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong Z, @@ -877,9 +1211,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("NOT ., .")] + [Test, Pairwise, Description("NOT ., .")] public void Not_V_8B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -902,9 +1237,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("NOT ., .")] + [Test, Pairwise, Description("NOT ., .")] public void Not_V_16B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -927,9 +1263,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("RBIT ., .")] + [Test, Pairwise, Description("RBIT ., .")] public void Rbit_V_8B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -952,9 +1289,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("RBIT ., .")] + [Test, Pairwise, Description("RBIT ., .")] public void Rbit_V_16B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -977,9 +1315,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV16 ., .")] + [Test, Pairwise, Description("REV16 ., .")] public void Rev16_V_8B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -1002,9 +1341,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV16 ., .")] + [Test, Pairwise, Description("REV16 ., .")] public void Rev16_V_16B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B_")] [Random(RndCnt)] ulong Z, @@ -1027,9 +1367,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV32 ., .")] + [Test, Pairwise, Description("REV32 ., .")] public void Rev32_V_8B_4H([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H_")] [Random(RndCnt)] ulong Z, @@ -1054,9 +1395,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV32 ., .")] + [Test, Pairwise, Description("REV32 ., .")] public void Rev32_V_16B_8H([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H_")] [Random(RndCnt)] ulong Z, @@ -1081,9 +1423,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV64 ., .")] + [Test, Pairwise, Description("REV64 ., .")] public void Rev64_V_8B_4H_2S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1108,9 +1451,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("REV64 ., .")] + [Test, Pairwise, Description("REV64 ., .")] public void Rev64_V_16B_8H_4S([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1135,9 +1479,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("SADALP ., .")] + [Test, Pairwise, Description("SADALP ., .")] public void Sadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1162,9 +1507,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("SADALP ., .")] + [Test, Pairwise, Description("SADALP ., .")] public void Sadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1189,9 +1535,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("SADDLP ., .")] + [Test, Pairwise, Description("SADDLP ., .")] public void Saddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1216,9 +1563,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("SADDLP ., .")] + [Test, Pairwise, Description("SADDLP ., .")] public void Saddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1243,13 +1591,14 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Explicit, Description("SHA256SU0 .4S, .4S")] // 1250 tests. + [Test, Pairwise, Description("SHA256SU0 .4S, .4S")] public void Sha256su0_V([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, - [Random(5)] ulong Z0, [Random(5)] ulong Z1, - [Random(5)] ulong A0, [Random(5)] ulong A1) + [Random(RndCnt / 2)] ulong Z0, [Random(RndCnt / 2)] ulong Z1, + [Random(RndCnt / 2)] ulong A0, [Random(RndCnt / 2)] ulong A1) { uint Opcode = 0x5E282800; // SHA256SU0 V0.4S, V0.4S Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); @@ -1273,29 +1622,29 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V1), Is.EqualTo(AArch64.Vpart(64, 1, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V1), Is.EqualTo(AArch64.Vpart(64, 1, 1).ToUInt64())); }); + CompareAgainstUnicorn(); } - [Test, Description("SQABS , ")] + [Test, Pairwise, 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) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1303,30 +1652,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQABS ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1334,30 +1683,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQABS ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqabs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1365,30 +1714,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQNEG , ")] + [Test, Pairwise, 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) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1396,30 +1745,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQNEG ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1427,30 +1776,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQNEG ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqneg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1458,30 +1807,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTN , ")] + [Test, Pairwise, Description("SQXTN , ")] public void Sqxtn_S_HB_SH_DS([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong Z, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong A, [Values(0b00u, 0b01u, 0b10u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x5E214800; // SQXTN B0, H0 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1489,30 +1838,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTN{2} ., .")] + [Test, Pairwise, Description("SQXTN{2} ., .")] public void Sqxtn_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x0E214800; // SQXTN V0.8B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1520,30 +1869,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTN{2} ., .")] + [Test, Pairwise, Description("SQXTN{2} ., .")] public void Sqxtn_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x4E214800; // SQXTN2 V0.16B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1551,30 +1900,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTUN , ")] + [Test, Pairwise, Description("SQXTUN , ")] public void Sqxtun_S_HB_SH_DS([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong Z, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong A, [Values(0b00u, 0b01u, 0b10u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x7E212800; // SQXTUN B0, H0 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1582,30 +1931,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTUN{2} ., .")] + [Test, Pairwise, Description("SQXTUN{2} ., .")] public void Sqxtun_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x2E212800; // SQXTUN V0.8B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1613,30 +1962,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SQXTUN{2} ., .")] + [Test, Pairwise, Description("SQXTUN{2} ., .")] public void Sqxtun_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x6E212800; // SQXTUN2 V0.16B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1644,30 +1993,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SUQADD , ")] + [Test, Pairwise, 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) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1675,30 +2024,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SUQADD ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1706,30 +2055,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("SUQADD ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Suqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1737,10 +2086,11 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("UADALP ., .")] + [Test, Pairwise, Description("UADALP ., .")] public void Uadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1765,9 +2115,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("UADALP ., .")] + [Test, Pairwise, Description("UADALP ., .")] public void Uadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1792,9 +2143,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("UADDLP ., .")] + [Test, Pairwise, Description("UADDLP ., .")] public void Uaddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1819,9 +2171,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("UADDLP ., .")] + [Test, Pairwise, Description("UADDLP ., .")] public void Uaddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong Z, @@ -1846,29 +2199,29 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("UQXTN , ")] + [Test, Pairwise, Description("UQXTN , ")] public void Uqxtn_S_HB_SH_DS([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong Z, [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong A, [Values(0b00u, 0b01u, 0b10u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x7E214800; // UQXTN B0, H0 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1876,30 +2229,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("UQXTN{2} ., .")] + [Test, Pairwise, Description("UQXTN{2} ., .")] public void Uqxtn_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x2E214800; // UQXTN V0.8B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1907,30 +2260,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("UQXTN{2} ., .")] + [Test, Pairwise, Description("UQXTN{2} ., .")] public void Uqxtn_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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x6E214800; // UQXTN2 V0.16B, V0.8H 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1938,30 +2291,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("USQADD , ")] + [Test, Pairwise, 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) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -1969,30 +2322,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("USQADD ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); 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(() => @@ -2000,30 +2353,30 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("USQADD ., .")] + [Test, Pairwise, 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> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Usqadd_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2031,10 +2384,11 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + CompareAgainstUnicorn(); } - [Test, Description("XTN{2} ., .")] + [Test, Pairwise, 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, @@ -2059,9 +2413,10 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Description("XTN{2} ., .")] + [Test, Pairwise, 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, @@ -2086,6 +2441,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())); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs index 53e155429..e9fd462eb 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs @@ -33,6 +33,7 @@ namespace Ryujinx.Tests.Cpu V1: Sse.StaticCast(Sse2.SetVector128(0, A)), V2: Sse.StaticCast(Sse2.SetVector128(0, B))); Assert.AreEqual(Result, Sse41.Extract(Sse.StaticCast(ThreadState.V0), 0)); + CompareAgainstUnicorn(); } [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u)] @@ -59,6 +60,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x1E225820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] @@ -85,6 +87,7 @@ namespace Ryujinx.Tests.Cpu V1: Sse.StaticCast(Sse2.SetVector128(0, A)), V2: Sse.StaticCast(Sse2.SetVector128(0, B))); Assert.AreEqual(Result, Sse41.Extract(Sse.StaticCast(ThreadState.V0), 0)); + CompareAgainstUnicorn(); } [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u)] @@ -111,6 +114,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [Test, Description("FMUL S6, S1, V0.S[2]")] @@ -121,6 +125,7 @@ namespace Ryujinx.Tests.Cpu V0: Sse.SetVector128(0, B, 0, 0)); Assert.That(Sse41.Extract(ThreadState.V6, (byte)0), Is.EqualTo(A * B)); + CompareAgainstUnicorn(); } [TestCase(0x00000000u, 0x7F800000u)] @@ -135,6 +140,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x5EA1D820, V1: V1); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [Test, Description("FRECPS D0, D1, D2")] @@ -145,6 +151,7 @@ namespace Ryujinx.Tests.Cpu V2: MakeVectorE0(B)); Assert.That(VectorExtractDouble(ThreadState.V0, 0), Is.EqualTo(2 - (A * B))); + //CompareAgainstUnicorn(); // Not accurate enough } [Test, Description("FRECPS V4.4S, V2.4S, V0.4S")] @@ -163,6 +170,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(ThreadState.V4, (byte)2), Is.EqualTo(Result)); Assert.That(Sse41.Extract(ThreadState.V4, (byte)3), Is.EqualTo(Result)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, false, 0x40000000u)] @@ -213,6 +221,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x6E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] @@ -240,6 +249,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] @@ -310,6 +320,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E27C020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] @@ -376,6 +387,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, false, 0x3F800000u)] @@ -426,6 +438,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E254020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x4E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] @@ -450,6 +463,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, false, 0x40000000u)] @@ -500,6 +514,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x4E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] @@ -527,6 +542,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, false, 0x40000000u)] @@ -577,6 +593,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E24C020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x4EE18820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] @@ -601,6 +618,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] @@ -671,6 +689,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x1E274020, V1: V1, Fpcr: FpcrTemp); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] @@ -737,6 +756,7 @@ namespace Ryujinx.Tests.Cpu Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); }); + CompareAgainstUnicorn(); } [TestCase(0x41200000u, 0x3EA18000u)] @@ -745,6 +765,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); AThreadState ThreadState = SingleOpcode(0x7EA1D820, V1: V1); Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); + CompareAgainstUnicorn(); } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs index 41f5113d6..acf7000b9 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs @@ -52,6 +52,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); Assert.That(VectorExtractDouble(ThreadState.V0, (byte)1), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMEQ S0, S1, S2 | FCMGE S0, S1, S2 | FCMGT S0, S1, S2")] @@ -84,6 +85,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMEQ V0.2D, V1.2D, V2.2D | FCMGE V0.2D, V1.2D, V2.2D | FCMGT V0.2D, V1.2D, V2.2D")] @@ -113,6 +115,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); }); + CompareAgainstUnicorn(); } [Test, Description("FCMEQ V0.2S, V1.2S, V2.2S | FCMGE V0.2S, V1.2S, V2.2S | FCMGT V0.2S, V1.2S, V2.2S")] @@ -145,6 +148,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMEQ V0.4S, V1.4S, V2.4S | FCMGE V0.4S, V1.4S, V2.4S | FCMGT V0.4S, V1.4S, V2.4S")] @@ -176,6 +180,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)2)), Is.EquivalentTo(Exp)); Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)3)), Is.EquivalentTo(Exp)); }); + CompareAgainstUnicorn(); } [Test, Description("FCMGT D0, D1, #0.0 | FCMGE D0, D1, #0.0 | FCMEQ D0, D1, #0.0 | FCMLE D0, D1, #0.0 | FCMLT D0, D1, #0.0")] @@ -214,6 +219,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); Assert.That(VectorExtractDouble(ThreadState.V0, (byte)1), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMGT S0, S1, #0.0 | FCMGE S0, S1, #0.0 | FCMEQ S0, S1, #0.0 | FCMLE S0, S1, #0.0 | FCMLT S0, S1, #0.0")] @@ -254,6 +260,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMGT V0.2D, V1.2D, #0.0 | FCMGE V0.2D, V1.2D, #0.0 | FCMEQ V0.2D, V1.2D, #0.0 | FCMLE V0.2D, V1.2D, #0.0 | FCMLT V0.2D, V1.2D, #0.0")] @@ -291,6 +298,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); }); + CompareAgainstUnicorn(); } [Test, Description("FCMGT V0.2S, V1.2S, #0.0 | FCMGE V0.2S, V1.2S, #0.0 | FCMEQ V0.2S, V1.2S, #0.0 | FCMLE V0.2S, V1.2S, #0.0 | FCMLT V0.2S, V1.2S, #0.0")] @@ -331,6 +339,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); }); + CompareAgainstUnicorn(); } [Test, Description("FCMGT V0.4S, V1.4S, #0.0 | FCMGE V0.4S, V1.4S, #0.0 | FCMEQ V0.4S, V1.4S, #0.0 | FCMLE V0.4S, V1.4S, #0.0 | FCMLT V0.4S, V1.4S, #0.0")] @@ -370,6 +379,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)2)), Is.EquivalentTo(Exp)); Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)3)), Is.EquivalentTo(Exp)); }); + CompareAgainstUnicorn(); } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs new file mode 100644 index 000000000..1e58a68a6 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs @@ -0,0 +1,139 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ChocolArm64.State; + +using NUnit.Framework; + +using System.Runtime.Intrinsics; + +namespace Ryujinx.Tests.Cpu +{ + public class CpuTestSimdCrypto : CpuTest + { + [Test, Description("AESD .16B, .16B")] + public void Aesd_V([Values(0u)] uint Rd, + [Values(1u)] uint Rn, + [Values(0x7B5B546573745665ul)] ulong ValueH, + [Values(0x63746F725D53475Dul)] ulong ValueL, + [Random(2)] ulong RoundKeyH, + [Random(2)] ulong RoundKeyL, + [Values(0x8DCAB9BC035006BCul)] ulong ResultH, + [Values(0x8F57161E00CAFD8Dul)] ulong ResultL) + { + uint Opcode = 0x4E285800; // AESD V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Vector128 V0 = MakeVectorE0E1(RoundKeyL ^ ValueL, RoundKeyH ^ ValueH); + Vector128 V1 = MakeVectorE0E1(RoundKeyL, RoundKeyH); + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(ResultL)); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(ResultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V1), Is.EqualTo(RoundKeyL)); + Assert.That(GetVectorE1(ThreadState.V1), Is.EqualTo(RoundKeyH)); + }); + CompareAgainstUnicorn(); + } + + [Test, Description("AESE .16B, .16B")] + public void Aese_V([Values(0u)] uint Rd, + [Values(1u)] uint Rn, + [Values(0x7B5B546573745665ul)] ulong ValueH, + [Values(0x63746F725D53475Dul)] ulong ValueL, + [Random(2)] ulong RoundKeyH, + [Random(2)] ulong RoundKeyL, + [Values(0x8F92A04DFBED204Dul)] ulong ResultH, + [Values(0x4C39B1402192A84Cul)] ulong ResultL) + { + uint Opcode = 0x4E284800; // AESE V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Vector128 V0 = MakeVectorE0E1(RoundKeyL ^ ValueL, RoundKeyH ^ ValueH); + Vector128 V1 = MakeVectorE0E1(RoundKeyL, RoundKeyH); + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(ResultL)); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(ResultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V1), Is.EqualTo(RoundKeyL)); + Assert.That(GetVectorE1(ThreadState.V1), Is.EqualTo(RoundKeyH)); + }); + CompareAgainstUnicorn(); + } + + [Test, Description("AESIMC .16B, .16B")] + public void Aesimc_V([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(0x8DCAB9DC035006BCul)] ulong ValueH, + [Values(0x8F57161E00CAFD8Dul)] ulong ValueL, + [Values(0xD635A667928B5EAEul)] ulong ResultH, + [Values(0xEEC9CC3BC55F5777ul)] ulong ResultL) + { + uint Opcode = 0x4E287800; // AESIMC V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Vector128 V = MakeVectorE0E1(ValueL, ValueH); + + AThreadState ThreadState = SingleOpcode( + Opcode, + V0: Rn == 0u ? V : default(Vector128), + V1: Rn == 1u ? V : default(Vector128)); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(ResultL)); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(ResultH)); + }); + if (Rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V1), Is.EqualTo(ValueL)); + Assert.That(GetVectorE1(ThreadState.V1), Is.EqualTo(ValueH)); + }); + } + CompareAgainstUnicorn(); + } + + [Test, Description("AESMC .16B, .16B")] + public void Aesmc_V([Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(0x627A6F6644B109C8ul)] ulong ValueH, + [Values(0x2B18330A81C3B3E5ul)] ulong ValueL, + [Values(0x7B5B546573745665ul)] ulong ResultH, + [Values(0x63746F725D53475Dul)] ulong ResultL) + { + uint Opcode = 0x4E286800; // AESMC V0.16B, V0.16B + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Vector128 V = MakeVectorE0E1(ValueL, ValueH); + + AThreadState ThreadState = SingleOpcode( + Opcode, + V0: Rn == 0u ? V : default(Vector128), + V1: Rn == 1u ? V : default(Vector128)); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(ResultL)); + Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(ResultH)); + }); + if (Rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(ThreadState.V1), Is.EqualTo(ValueL)); + Assert.That(GetVectorE1(ThreadState.V1), Is.EqualTo(ValueH)); + }); + } + CompareAgainstUnicorn(); + } + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs index 2d021616c..7f5f05ee4 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -35,6 +35,7 @@ namespace Ryujinx.Tests.Cpu Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V1), (byte)2), Is.EqualTo(Result)); Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V1), (byte)3), Is.EqualTo(Result)); }); + CompareAgainstUnicorn(); } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs index 9aa938568..b7633e8be 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -86,7 +86,7 @@ namespace Ryujinx.Tests.Cpu } #endregion - private const int RndCnt = 4; + private const int RndCnt = 2; [Test, Pairwise, Description("ADD , , ")] public void Add_S_D([Values(0u)] uint Rd, @@ -115,6 +115,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADD ., ., .")] @@ -146,6 +147,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADD ., ., .")] @@ -177,6 +179,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDHN{2} ., ., .")] @@ -208,6 +211,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDHN{2} ., ., .")] @@ -239,6 +243,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDP ., ., .")] @@ -270,6 +275,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDP ., ., .")] @@ -301,6 +307,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("AND ., ., .")] @@ -330,6 +337,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("AND ., ., .")] @@ -359,6 +367,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIC ., ., .")] @@ -388,6 +397,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIC ., ., .")] @@ -417,6 +427,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIF ., ., .")] @@ -446,6 +457,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIF ., ., .")] @@ -475,6 +487,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIT ., ., .")] @@ -504,6 +517,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIT ., ., .")] @@ -533,6 +547,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BSL ., ., .")] @@ -562,6 +577,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BSL ., ., .")] @@ -591,6 +607,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMEQ , , ")] @@ -620,6 +637,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMEQ ., ., .")] @@ -651,6 +669,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMEQ ., ., .")] @@ -682,6 +701,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGE , , ")] @@ -711,6 +731,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGE ., ., .")] @@ -742,6 +763,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGE ., ., .")] @@ -773,6 +795,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGT , , ")] @@ -802,6 +825,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGT ., ., .")] @@ -833,6 +857,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGT ., ., .")] @@ -864,6 +889,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHI , , ")] @@ -893,6 +919,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHI ., ., .")] @@ -924,6 +951,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHI ., ., .")] @@ -955,6 +983,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHS , , ")] @@ -984,6 +1013,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHS ., ., .")] @@ -1015,6 +1045,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHS ., ., .")] @@ -1046,6 +1077,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMTST , , ")] @@ -1075,6 +1107,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMTST ., ., .")] @@ -1106,6 +1139,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMTST ., ., .")] @@ -1137,6 +1171,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("EOR ., ., .")] @@ -1166,6 +1201,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("EOR ., ., .")] @@ -1195,6 +1231,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORN ., ., .")] @@ -1224,6 +1261,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORN ., ., .")] @@ -1253,6 +1291,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORR ., ., .")] @@ -1282,6 +1321,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORR ., ., .")] @@ -1311,6 +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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RADDHN{2} ., ., .")] @@ -1342,6 +1383,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RADDHN{2} ., ., .")] @@ -1373,6 +1415,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RSUBHN{2} ., ., .")] @@ -1404,6 +1447,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RSUBHN{2} ., ., .")] @@ -1435,6 +1479,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABA ., ., .")] @@ -1466,6 +1511,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABA ., ., .")] @@ -1497,6 +1543,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABAL{2} ., ., .")] @@ -1528,6 +1575,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABAL{2} ., ., .")] @@ -1559,6 +1607,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABD ., ., .")] @@ -1590,6 +1639,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABD ., ., .")] @@ -1621,6 +1671,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABDL{2} ., ., .")] @@ -1652,6 +1703,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABDL{2} ., ., .")] @@ -1678,6 +1730,69 @@ namespace Ryujinx.Tests.Cpu AArch64.Vpart(2, 1, new Bits(B)); SimdFp.Sabdl_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())); + }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDL{2} ., ., .")] + public void Saddl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x0E200000; // SADDL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Saddl_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("SADDL{2} ., ., .")] + public void Saddl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x4E200000; // SADDL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Saddl_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())); @@ -1690,8 +1805,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -1714,6 +1829,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SADDW{2} ., ., .")] @@ -1721,8 +1837,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -1745,15 +1861,16 @@ 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())); }); + CompareAgainstUnicorn(); } - [Test, Explicit, Description("SHA256H , , .4S")] // 2916 tests. + [Test, Pairwise, Description("SHA256H , , .4S")] public void Sha256h_V([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [Values(2u, 0u)] uint Rm, - [Random(3)] ulong Z0, [Random(3)] ulong Z1, - [Random(3)] ulong A0, [Random(3)] ulong A1, - [Random(3)] ulong B0, [Random(3)] ulong B1) + [Random(RndCnt / 2)] ulong Z0, [Random(RndCnt / 2)] ulong Z1, + [Random(RndCnt / 2)] ulong A0, [Random(RndCnt / 2)] ulong A1, + [Random(RndCnt / 2)] ulong B0, [Random(RndCnt / 2)] ulong B1) { uint Opcode = 0x5E004000; // SHA256H Q0, Q0, V0.4S Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); @@ -1782,15 +1899,16 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 1).ToUInt64())); }); + CompareAgainstUnicorn(); } - [Test, Explicit, Description("SHA256H2 , , .4S")] // 2916 tests. + [Test, Pairwise, Description("SHA256H2 , , .4S")] public void Sha256h2_V([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [Values(2u, 0u)] uint Rm, - [Random(3)] ulong Z0, [Random(3)] ulong Z1, - [Random(3)] ulong A0, [Random(3)] ulong A1, - [Random(3)] ulong B0, [Random(3)] ulong B1) + [Random(RndCnt / 2)] ulong Z0, [Random(RndCnt / 2)] ulong Z1, + [Random(RndCnt / 2)] ulong A0, [Random(RndCnt / 2)] ulong A1, + [Random(RndCnt / 2)] ulong B0, [Random(RndCnt / 2)] ulong B1) { uint Opcode = 0x5E005000; // SHA256H2 Q0, Q0, V0.4S Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); @@ -1819,15 +1937,16 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 1).ToUInt64())); }); + CompareAgainstUnicorn(); } - [Test, Explicit, Description("SHA256SU1 .4S, .4S, .4S")] // 2916 tests. + [Test, Pairwise, Description("SHA256SU1 .4S, .4S, .4S")] public void Sha256su1_V([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, [Values(2u, 0u)] uint Rm, - [Random(3)] ulong Z0, [Random(3)] ulong Z1, - [Random(3)] ulong A0, [Random(3)] ulong A1, - [Random(3)] ulong B0, [Random(3)] ulong B1) + [Random(RndCnt / 2)] ulong Z0, [Random(RndCnt / 2)] ulong Z1, + [Random(RndCnt / 2)] ulong A0, [Random(RndCnt / 2)] ulong A1, + [Random(RndCnt / 2)] ulong B0, [Random(RndCnt / 2)] ulong B1) { uint Opcode = 0x5E006000; // SHA256SU1 V0.4S, V0.4S, V0.4S Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); @@ -1856,6 +1975,255 @@ namespace Ryujinx.Tests.Cpu Assert.That(GetVectorE0(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 0).ToUInt64())); Assert.That(GetVectorE1(ThreadState.V2), Is.EqualTo(AArch64.Vpart(64, 2, 1).ToUInt64())); }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHADD ., ., .")] + public void Shadd_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 = 0x0E200400; // SHADD 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Shadd_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("SHADD ., ., .")] + public void Shadd_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x4E200400; // SHADD 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); + + 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); + + 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)); + SimdFp.Shadd_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("SHSUB ., ., .")] + public void Shsub_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 = 0x0E202400; // SHSUB 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Shsub_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("SHSUB ., ., .")] + public void Shsub_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x4E202400; // SHSUB 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); + + 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); + + 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)); + SimdFp.Shsub_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("SMLAL{2} ., ., .")] + public void Smlal_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x0E208000; // SMLAL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Smlal_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("SMLAL{2} ., ., .")] + public void Smlal_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x4E208000; // SMLAL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Smlal_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("SMLSL{2} ., ., .")] + public void Smlsl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x0E20A000; // SMLSL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Smlsl_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("SMLSL{2} ., ., .")] + public void Smlsl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x4E20A000; // SMLSL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Smlsl_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 , , ")] @@ -1867,22 +2235,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -1890,7 +2257,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQADD ., ., .")] @@ -1902,22 +2270,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -1925,7 +2292,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQADD ., ., .")] @@ -1937,22 +2305,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); 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(() => @@ -1960,7 +2327,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQDMULH , , ")] @@ -1972,22 +2340,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1H1S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x5E20B400; // SQDMULH B0, B0, B0 (RESERVED) 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); + 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.V(1, new Bits(A)); AArch64.V(2, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqdmulh_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -1995,7 +2362,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQDMULH ., ., .")] @@ -2007,22 +2375,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_4H2S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // <4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x0E20B400; // SQDMULH V0.8B, V0.8B, V0.8B (RESERVED) 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); + 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.V(1, new Bits(A)); AArch64.V(2, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqdmulh_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2030,7 +2397,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQDMULH ., ., .")] @@ -2042,22 +2410,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_4H2S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // <8H, 4S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x4E20B400; // SQDMULH V0.16B, V0.16B, V0.16B (RESERVED) 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); + 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)); AArch64.Vpart(2, 1, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqdmulh_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2065,7 +2432,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQRDMULH , , ")] @@ -2077,22 +2445,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1H1S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x7E20B400; // SQRDMULH B0, B0, B0 (RESERVED) 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); + 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.V(1, new Bits(A)); AArch64.V(2, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqrdmulh_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2100,7 +2467,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQRDMULH ., ., .")] @@ -2112,22 +2480,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_4H2S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // <4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x2E20B400; // SQRDMULH V0.8B, V0.8B, V0.8B (RESERVED) 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); + 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.V(1, new Bits(A)); AArch64.V(2, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqrdmulh_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2135,7 +2502,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQRDMULH ., ., .")] @@ -2147,22 +2515,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_4H2S_")] [Random(RndCnt)] ulong B, [Values(0b01u, 0b10u)] uint size) // <8H, 4S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + uint Opcode = 0x6E20B400; // SQRDMULH V0.16B, V0.16B, V0.16B (RESERVED) 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); + 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)); AArch64.Vpart(2, 1, new Bits(B)); - Shared.FPSR = new Bits((uint)Fpsr); SimdFp.Sqrdmulh_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); Assert.Multiple(() => @@ -2170,7 +2537,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQSUB , , ")] @@ -2182,22 +2550,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -2205,7 +2572,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQSUB ., ., .")] @@ -2217,22 +2585,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -2240,7 +2607,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("SQSUB ., ., .")] @@ -2252,22 +2620,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); 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(() => @@ -2275,7 +2642,132 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + } + + [Test, Pairwise, Description("SRHADD ., ., .")] + public void Srhadd_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 = 0x0E201400; // SRHADD 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Srhadd_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("SRHADD ., ., .")] + public void Srhadd_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x4E201400; // SRHADD 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); + + 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); + + 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)); + SimdFp.Srhadd_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("SSUBL{2} ., ., .")] + public void Ssubl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x0E202000; // SSUBL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Ssubl_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("SSUBL{2} ., ., .")] + public void Ssubl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x4E202000; // SSUBL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Ssubl_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} ., ., .")] @@ -2283,8 +2775,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -2307,6 +2799,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SSUBW{2} ., ., .")] @@ -2314,8 +2807,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -2338,6 +2831,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUB , , ")] @@ -2367,6 +2861,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUB ., ., .")] @@ -2398,6 +2893,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUB ., ., .")] @@ -2429,6 +2925,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUBHN{2} ., ., .")] @@ -2460,6 +2957,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUBHN{2} ., ., .")] @@ -2491,6 +2989,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("TRN1 ., ., .")] @@ -2522,6 +3021,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("TRN1 ., ., .")] @@ -2553,6 +3053,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("TRN2 ., ., .")] @@ -2584,6 +3085,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("TRN2 ., ., .")] @@ -2615,6 +3117,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABA ., ., .")] @@ -2646,6 +3149,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABA ., ., .")] @@ -2677,6 +3181,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABAL{2} ., ., .")] @@ -2708,6 +3213,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABAL{2} ., ., .")] @@ -2739,6 +3245,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABD ., ., .")] @@ -2770,6 +3277,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABD ., ., .")] @@ -2801,6 +3309,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABDL{2} ., ., .")] @@ -2832,6 +3341,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABDL{2} ., ., .")] @@ -2858,6 +3368,69 @@ namespace Ryujinx.Tests.Cpu AArch64.Vpart(2, 1, new Bits(B)); SimdFp.Uabdl_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())); + }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDL{2} ., ., .")] + public void Uaddl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x2E200000; // UADDL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Uaddl_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("UADDL{2} ., ., .")] + public void Uaddl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x6E200000; // UADDL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Uaddl_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())); @@ -2870,8 +3443,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -2894,6 +3467,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UADDW{2} ., ., .")] @@ -2901,8 +3475,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -2920,6 +3494,255 @@ namespace Ryujinx.Tests.Cpu 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())); + }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHADD ., ., .")] + public void Uhadd_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 = 0x2E200400; // UHADD 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Uhadd_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("UHADD ., ., .")] + public void Uhadd_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x6E200400; // UHADD 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); + + 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); + + 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)); + SimdFp.Uhadd_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("UHSUB ., ., .")] + public void Uhsub_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 = 0x2E202400; // UHSUB 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Uhsub_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("UHSUB ., ., .")] + public void Uhsub_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x6E202400; // UHSUB 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); + + 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); + + 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)); + SimdFp.Uhsub_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("UMLAL{2} ., ., .")] + public void Umlal_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x2E208000; // UMLAL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Umlal_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("UMLAL{2} ., ., .")] + public void Umlal_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x6E208000; // UMLAL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Umlal_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("UMLSL{2} ., ., .")] + public void Umlsl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x2E20A000; // UMLSL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Umlsl_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("UMLSL{2} ., ., .")] + public void Umlsl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x6E20A000; // UMLSL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Umlsl_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())); @@ -2936,22 +3759,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -2959,7 +3781,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("UQADD ., ., .")] @@ -2971,22 +3794,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -2994,7 +3816,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("UQADD ., ., .")] @@ -3006,22 +3829,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); 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(() => @@ -3029,7 +3851,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("UQSUB , , ")] @@ -3041,22 +3864,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -3064,7 +3886,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("UQSUB ., ., .")] @@ -3076,22 +3899,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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.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(() => @@ -3099,7 +3921,8 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); } [Test, Pairwise, Description("UQSUB ., ., .")] @@ -3111,22 +3934,21 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong B, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { + const int QCFlagBit = 27; // Cumulative saturation bit. + 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); + 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)); 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(() => @@ -3134,7 +3956,132 @@ 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, Is.EqualTo((int)Shared.FPSR.ToUInt32())); + + Assert.That(((ThreadState.Fpsr >> QCFlagBit) & 1) != 0, Is.EqualTo(Shared.FPSR[QCFlagBit])); + } + + [Test, Pairwise, Description("URHADD ., ., .")] + public void Urhadd_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 = 0x2E201400; // URHADD 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); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(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.V(1, new Bits(A)); + AArch64.V(2, new Bits(B)); + SimdFp.Urhadd_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("URHADD ., ., .")] + public void Urhadd_V_16B_8H_4S([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) // <16B, 8H, 4S> + { + uint Opcode = 0x6E201400; // URHADD 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); + + 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); + + 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)); + SimdFp.Urhadd_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("USUBL{2} ., ., .")] + public void Usubl_V_8B8H_4H4S_2S2D([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) // <8B8H, 4H4S, 2S2D> + { + uint Opcode = 0x2E202000; // USUBL V0.8H, V0.8B, 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 = MakeVectorE0(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(2, 0, new Bits(B)); + SimdFp.Usubl_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("USUBL{2} ., ., .")] + public void Usubl_V_16B8H_8H4S_4S2D([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) // <16B8H, 8H4S, 4S2D> + { + uint Opcode = 0x6E202000; // USUBL2 V0.8H, V0.16B, 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 = MakeVectorE1(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, 1, new Bits(A)); + AArch64.Vpart(2, 1, new Bits(B)); + SimdFp.Usubl_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} ., ., .")] @@ -3142,8 +4089,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -3166,6 +4113,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("USUBW{2} ., ., .")] @@ -3173,8 +4121,8 @@ namespace Ryujinx.Tests.Cpu [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, + [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 @@ -3197,6 +4145,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UZP1 ., ., .")] @@ -3228,6 +4177,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UZP1 ., ., .")] @@ -3259,6 +4209,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UZP2 ., ., .")] @@ -3290,6 +4241,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UZP2 ., ., .")] @@ -3321,6 +4273,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ZIP1 ., ., .")] @@ -3352,6 +4305,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ZIP1 ., ., .")] @@ -3383,6 +4337,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ZIP2 ., ., .")] @@ -3414,6 +4369,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())); }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ZIP2 ., ., .")] @@ -3445,6 +4401,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())); }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/Tester/Instructions.cs b/Ryujinx.Tests/Cpu/Tester/Instructions.cs index b0eff5880..e3aa4e40b 100644 --- a/Ryujinx.Tests/Cpu/Tester/Instructions.cs +++ b/Ryujinx.Tests/Cpu/Tester/Instructions.cs @@ -2685,6 +2685,154 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // fcvtns_advsimd.html#FCVTNS_asisdmisc_R + public static void Fcvtns_S(Bits sz, Bits Rn, Bits Rd) + { + const bool U = false; + const bool o2 = false; + const bool o1 = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 32 << (int)UInt(sz); + int datasize = esize; + int elements = 1; + + FPRounding rounding = FPDecodeRounding(Bits.Concat(o1, o2)); + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits element; + + for (int e = 0; e <= elements - 1; e++) + { + element = Elem(operand, e, esize); + + Elem(result, e, esize, FPToFixed(esize, element, 0, unsigned, FPCR, rounding)); + } + + V(d, result); + } + + // fcvtns_advsimd.html#FCVTNS_asimdmisc_R + public static void Fcvtns_V(bool Q, Bits sz, Bits Rn, Bits Rd) + { + const bool U = false; + const bool o2 = false; + const bool o1 = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if sz:Q == '10' then ReservedValue(); */ + + int esize = 32 << (int)UInt(sz); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + FPRounding rounding = FPDecodeRounding(Bits.Concat(o1, o2)); + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits element; + + for (int e = 0; e <= elements - 1; e++) + { + element = Elem(operand, e, esize); + + Elem(result, e, esize, FPToFixed(esize, element, 0, unsigned, FPCR, rounding)); + } + + V(d, result); + } + + // fcvtnu_advsimd.html#FCVTNU_asisdmisc_R + public static void Fcvtnu_S(Bits sz, Bits Rn, Bits Rd) + { + const bool U = true; + const bool o2 = false; + const bool o1 = false; + + /* Decode Scalar */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + int esize = 32 << (int)UInt(sz); + int datasize = esize; + int elements = 1; + + FPRounding rounding = FPDecodeRounding(Bits.Concat(o1, o2)); + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits element; + + for (int e = 0; e <= elements - 1; e++) + { + element = Elem(operand, e, esize); + + Elem(result, e, esize, FPToFixed(esize, element, 0, unsigned, FPCR, rounding)); + } + + V(d, result); + } + + // fcvtnu_advsimd.html#FCVTNU_asimdmisc_R + public static void Fcvtnu_V(bool Q, Bits sz, Bits Rn, Bits Rd) + { + const bool U = true; + const bool o2 = false; + const bool o1 = false; + + /* Decode Vector */ + int d = (int)UInt(Rd); + int n = (int)UInt(Rn); + + /* if sz:Q == '10' then ReservedValue(); */ + + int esize = 32 << (int)UInt(sz); + int datasize = (Q ? 128 : 64); + int elements = datasize / esize; + + FPRounding rounding = FPDecodeRounding(Bits.Concat(o1, o2)); + + bool unsigned = (U == true); + + /* Operation */ + /* CheckFPAdvSIMDEnabled64(); */ + + Bits result = new Bits(datasize); + Bits operand = V(datasize, n); + Bits element; + + for (int e = 0; e <= elements - 1; e++) + { + element = Elem(operand, e, esize); + + Elem(result, e, esize, FPToFixed(esize, element, 0, unsigned, FPCR, rounding)); + } + + V(d, result); + } + // neg_advsimd.html#NEG_asisdmisc_R public static void Neg_S(Bits size, Bits Rn, Bits Rd) { @@ -5122,6 +5270,57 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // saddl_advsimd.html + public static void Saddl_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 = Vpart(datasize, n, part); + 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, 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); + } + // saddw_advsimd.html public static void Saddw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -5251,6 +5450,198 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // shadd_advsimd.html + public static void Shadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + 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; + + Elem(result, e, esize, sum.SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // shsub_advsimd.html + public static void Shsub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + 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; + + Elem(result, e, esize, diff.SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // smlal_advsimd_vec.html + public static void Smlal_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 = Vpart(datasize, n, part); + Bits operand2 = Vpart(datasize, m, part); + Bits operand3 = V(2 * datasize, d); + BigInteger element1; + BigInteger element2; + Bits product; + Bits accum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + product = (element1 * element2).SubBigInteger(2 * esize - 1, 0); + + if (sub_op) + { + accum = Elem(operand3, e, 2 * esize) - product; + } + else + { + accum = Elem(operand3, e, 2 * esize) + product; + } + + Elem(result, e, 2 * esize, accum); + } + + V(d, result); + } + + // smlsl_advsimd_vec.html + public static void Smlsl_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 = Vpart(datasize, n, part); + Bits operand2 = Vpart(datasize, m, part); + Bits operand3 = V(2 * datasize, d); + BigInteger element1; + BigInteger element2; + Bits product; + Bits accum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + product = (element1 * element2).SubBigInteger(2 * esize - 1, 0); + + if (sub_op) + { + accum = Elem(operand3, e, 2 * esize) - product; + } + else + { + accum = Elem(operand3, e, 2 * esize) + product; + } + + Elem(result, e, 2 * esize, accum); + } + + V(d, result); + } + // sqadd_advsimd.html#SQADD_asisdsame_only public static void Sqadd_S(Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -5651,6 +6042,95 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // srhadd_advsimd.html + public static void Srhadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + Elem(result, e, esize, (element1 + element2 + 1).SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // ssubl_advsimd.html + public static void Ssubl_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 = Vpart(datasize, n, part); + 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, 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); + } + // ssubw_advsimd.html public static void Ssubw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -6092,6 +6572,57 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // uaddl_advsimd.html + public static void Uaddl_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 = Vpart(datasize, n, part); + 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, 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); + } + // uaddw_advsimd.html public static void Uaddw_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -6143,6 +6674,198 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // uhadd_advsimd.html + public static void Uhadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + 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; + + Elem(result, e, esize, sum.SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // uhsub_advsimd.html + public static void Uhsub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + 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; + + Elem(result, e, esize, diff.SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // umlal_advsimd_vec.html + public static void Umlal_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 = Vpart(datasize, n, part); + Bits operand2 = Vpart(datasize, m, part); + Bits operand3 = V(2 * datasize, d); + BigInteger element1; + BigInteger element2; + Bits product; + Bits accum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + product = (element1 * element2).SubBigInteger(2 * esize - 1, 0); + + if (sub_op) + { + accum = Elem(operand3, e, 2 * esize) - product; + } + else + { + accum = Elem(operand3, e, 2 * esize) + product; + } + + Elem(result, e, 2 * esize, accum); + } + + V(d, result); + } + + // umlsl_advsimd_vec.html + public static void Umlsl_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 = Vpart(datasize, n, part); + Bits operand2 = Vpart(datasize, m, part); + Bits operand3 = V(2 * datasize, d); + BigInteger element1; + BigInteger element2; + Bits product; + Bits accum; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + product = (element1 * element2).SubBigInteger(2 * esize - 1, 0); + + if (sub_op) + { + accum = Elem(operand3, e, 2 * esize) - product; + } + else + { + accum = Elem(operand3, e, 2 * esize) + product; + } + + Elem(result, e, 2 * esize, accum); + } + + V(d, result); + } + // uqadd_advsimd.html#UQADD_asisdsame_only public static void Uqadd_S(Bits size, Bits Rm, Bits Rn, Bits Rd) { @@ -6339,6 +7062,95 @@ namespace Ryujinx.Tests.Cpu.Tester V(d, result); } + // urhadd_advsimd.html + public static void Urhadd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) + { + const bool U = 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 = (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; + + for (int e = 0; e <= elements - 1; e++) + { + element1 = Int(Elem(operand1, e, esize), unsigned); + element2 = Int(Elem(operand2, e, esize), unsigned); + + Elem(result, e, esize, (element1 + element2 + 1).SubBigInteger(esize, 1)); + } + + V(d, result); + } + + // usubl_advsimd.html + public static void Usubl_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 = Vpart(datasize, n, part); + 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, 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); + } + // usubw_advsimd.html public static void Usubw_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 40bec9c54..f37774763 100644 --- a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs +++ b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs @@ -5,21 +5,19 @@ // https://alastairreid.github.io/asl-lexical-syntax/ -// | ------------------------|----------------------------------- | -// | ASL | C# | -// | ------------------------|----------------------------------- | -// | bit, bits(1); boolean | bool | -// | bits | Bits | -// | integer | BigInteger, int | -// | real | decimal | -// | ------------------------|----------------------------------- | -// | '0'; FALSE | false | -// | '1'; TRUE | true | -// | '010' | "010" | -// | bitsX IN {bitsY, bitsZ} | (bitsX == bitsY || bitsX == bitsZ) | -// | DIV | / | -// | MOD | % | -// | ------------------------|----------------------------------- | +// | ------------------------|-------------------------------- | +// | ASL | C# | +// | ------------------------|-------------------------------- | +// | bit, bits(1); boolean | bool | +// | bits | Bits | +// | integer | BigInteger, int | +// | real | decimal; double, float | +// | ------------------------|-------------------------------- | +// | '0'; FALSE | false | +// | '1'; TRUE | true | +// | '010' | "010" | +// | DIV, MOD | /, % | +// | ------------------------|-------------------------------- | using System; using System.Numerics; @@ -107,6 +105,7 @@ namespace Ryujinx.Tests.Cpu.Tester /* SP_EL1 = bits(64) UNKNOWN; */ SP_EL1.SetAll(false); + FPCR.SetAll(false); // TODO: Add named fields. FPSR.SetAll(false); // TODO: Add named fields. } @@ -458,6 +457,7 @@ namespace Ryujinx.Tests.Cpu.Tester #endregion #region "instrs/vector/reduce/reduceop/" + // shared_pseudocode.html#impl-aarch64.Reduce.3 public static Bits Reduce(ReduceOp op, Bits input, int esize) { int N = input.Count; @@ -528,6 +528,7 @@ namespace Ryujinx.Tests.Cpu.Tester SP_EL0 = new Bits(64, false); SP_EL1 = new Bits(64, false); + FPCR = new Bits(32, false); // TODO: Add named fields. FPSR = new Bits(32, false); // TODO: Add named fields. PSTATE.N = false; @@ -817,6 +818,36 @@ namespace Ryujinx.Tests.Cpu.Tester return (decimal)value; } + /* */ + public static float Real_32(BigInteger value) + { + if (value == BigInteger.Pow((BigInteger)2.0f, 1000)) + { + return float.PositiveInfinity; + } + if (value == -BigInteger.Pow((BigInteger)2.0f, 1000)) + { + return float.NegativeInfinity; + } + + return (float)value; + } + + /* */ + public static double Real_64(BigInteger value) + { + if (value == BigInteger.Pow((BigInteger)2.0, 10000)) + { + return double.PositiveInfinity; + } + if (value == -BigInteger.Pow((BigInteger)2.0, 10000)) + { + return double.NegativeInfinity; + } + + return (double)value; + } + // shared_pseudocode.html#impl-shared.ROR.2 public static Bits ROR(Bits x, int shift) { @@ -881,6 +912,36 @@ namespace Ryujinx.Tests.Cpu.Tester return (BigInteger)Decimal.Floor(x); } + /* */ + public static BigInteger RoundDown_32(float x) + { + if (float.IsPositiveInfinity(x)) + { + return BigInteger.Pow((BigInteger)2.0f, 1000); + } + if (float.IsNegativeInfinity(x)) + { + return -BigInteger.Pow((BigInteger)2.0f, 1000); + } + + return (BigInteger)MathF.Floor(x); + } + + /* */ + public static BigInteger RoundDown_64(double x) + { + if (double.IsPositiveInfinity(x)) + { + return BigInteger.Pow((BigInteger)2.0, 10000); + } + if (double.IsNegativeInfinity(x)) + { + return -BigInteger.Pow((BigInteger)2.0, 10000); + } + + return (BigInteger)Math.Floor(x); + } + // shared_pseudocode.html#impl-shared.RoundTowardsZero.1 public static BigInteger RoundTowardsZero(decimal x) { @@ -1091,6 +1152,398 @@ namespace Ryujinx.Tests.Cpu.Tester } #endregion +#region "functions/float/fpdecoderounding/" + /* shared_pseudocode.html#impl-shared.FPDecodeRounding.1 */ + public static FPRounding FPDecodeRounding(Bits rmode) + { + switch (rmode) + { + default: + case Bits bits when bits == "00": + return FPRounding.FPRounding_TIEEVEN; // N + case Bits bits when bits == "01": + return FPRounding.FPRounding_POSINF; // P + case Bits bits when bits == "10": + return FPRounding.FPRounding_NEGINF; // M + case Bits bits when bits == "11": + return FPRounding.FPRounding_ZERO; // Z + } + } +#endregion + +#region "functions/float/fpexc/" + // shared_pseudocode.html#FPExc + public enum FPExc {FPExc_InvalidOp, FPExc_DivideByZero, FPExc_Overflow, + FPExc_Underflow, FPExc_Inexact, FPExc_InputDenorm}; +#endregion + +#region "functions/float/fpprocessexception/" + // shared_pseudocode.html#impl-shared.FPProcessException.2 + public static void FPProcessException(FPExc exception, Bits _fpcr) + { + Bits fpcr = new Bits(_fpcr); // Clone. + + int cumul; + + // Determine the cumulative exception bit number + switch (exception) + { + default: + case FPExc.FPExc_InvalidOp: cumul = 0; break; + case FPExc.FPExc_DivideByZero: cumul = 1; break; + case FPExc.FPExc_Overflow: cumul = 2; break; + case FPExc.FPExc_Underflow: cumul = 3; break; + case FPExc.FPExc_Inexact: cumul = 4; break; + case FPExc.FPExc_InputDenorm: cumul = 7; break; + } + + int enable = cumul + 8; + + if (fpcr[enable]) + { + // Trapping of the exception enabled. + // It is IMPLEMENTATION DEFINED whether the enable bit may be set at all, and + // if so then how exceptions may be accumulated before calling FPTrapException() + /* IMPLEMENTATION_DEFINED "floating-point trap handling"; */ + + throw new NotImplementedException(); + }/* + else if (UsingAArch32()) + { + // Set the cumulative exception bit + FPSCR = '1'; + }*/ + else + { + // Set the cumulative exception bit + FPSR[cumul] = true; + } + } +#endregion + +#region "functions/float/fprounding/" + // shared_pseudocode.html#FPRounding + public enum FPRounding {FPRounding_TIEEVEN, FPRounding_POSINF, + FPRounding_NEGINF, FPRounding_ZERO, + FPRounding_TIEAWAY, FPRounding_ODD}; +#endregion + +#region "functions/float/fptofixed/" + /* shared_pseudocode.html#impl-shared.FPToFixed.5 */ + public static Bits FPToFixed(int M, Bits op, int fbits, bool unsigned, Bits _fpcr, FPRounding rounding) + { + int N = op.Count; + + /* assert N IN {16,32,64}; */ + /* assert M IN {16,32,64}; */ + /* assert fbits >= 0; */ + /* assert rounding != FPRounding_ODD; */ + + Bits fpcr = new Bits(_fpcr); // Clone. + + if (N == 16) + { + throw new NotImplementedException(); + } + else if (N == 32) + { + // Unpack using fpcr to determine if subnormals are flushed-to-zero + (FPType type, bool sign, float value) = FPUnpack_32(op, fpcr); + + // If NaN, set cumulative flag or take exception + if (type == FPType.FPType_SNaN || type == FPType.FPType_QNaN) + { + FPProcessException(FPExc.FPExc_InvalidOp, fpcr); + } + + // Scale by fractional bits and produce integer rounded towards minus-infinity + value = value * MathF.Pow(2.0f, fbits); + BigInteger int_result = RoundDown_32(value); + float error = value - Real_32(int_result); + + if (float.IsNaN(error)) + { + error = 0.0f; + } + + // Determine whether supplied rounding mode requires an increment + bool round_up; + + switch (rounding) + { + default: + case FPRounding.FPRounding_TIEEVEN: + round_up = (error > 0.5f || (error == 0.5f && int_result.SubBigInteger(0))); + break; + case FPRounding.FPRounding_POSINF: + round_up = (error != 0.0f); + break; + case FPRounding.FPRounding_NEGINF: + round_up = false; + break; + case FPRounding.FPRounding_ZERO: + round_up = (error != 0.0f && int_result < (BigInteger)0); + break; + case FPRounding.FPRounding_TIEAWAY: + round_up = (error > 0.5f || (error == 0.5f && int_result >= (BigInteger)0)); + break; + } + + if (round_up) + { + int_result = int_result + 1; + } + + // Generate saturated result and exceptions + (Bits result, bool overflow) = SatQ(int_result, M, unsigned); + + if (overflow) + { + FPProcessException(FPExc.FPExc_InvalidOp, fpcr); + } + else if (error != 0.0f) + { + FPProcessException(FPExc.FPExc_Inexact, fpcr); + } + + return result; + } + else /* if (N == 64) */ + { + // Unpack using fpcr to determine if subnormals are flushed-to-zero + (FPType type, bool sign, double value) = FPUnpack_64(op, fpcr); + + // If NaN, set cumulative flag or take exception + if (type == FPType.FPType_SNaN || type == FPType.FPType_QNaN) + { + FPProcessException(FPExc.FPExc_InvalidOp, fpcr); + } + + // Scale by fractional bits and produce integer rounded towards minus-infinity + value = value * Math.Pow(2.0, fbits); + BigInteger int_result = RoundDown_64(value); + double error = value - Real_64(int_result); + + if (double.IsNaN(error)) + { + error = 0.0; + } + + // Determine whether supplied rounding mode requires an increment + bool round_up; + + switch (rounding) + { + default: + case FPRounding.FPRounding_TIEEVEN: + round_up = (error > 0.5 || (error == 0.5 && int_result.SubBigInteger(0))); + break; + case FPRounding.FPRounding_POSINF: + round_up = (error != 0.0); + break; + case FPRounding.FPRounding_NEGINF: + round_up = false; + break; + case FPRounding.FPRounding_ZERO: + round_up = (error != 0.0 && int_result < (BigInteger)0); + break; + case FPRounding.FPRounding_TIEAWAY: + round_up = (error > 0.5 || (error == 0.5 && int_result >= (BigInteger)0)); + break; + } + + if (round_up) + { + int_result = int_result + 1; + } + + // Generate saturated result and exceptions + (Bits result, bool overflow) = SatQ(int_result, M, unsigned); + + if (overflow) + { + FPProcessException(FPExc.FPExc_InvalidOp, fpcr); + } + else if (error != 0.0) + { + FPProcessException(FPExc.FPExc_Inexact, fpcr); + } + + return result; + } + } +#endregion + +#region "functions/float/fptype/" + // shared_pseudocode.html#FPType + public enum FPType {FPType_Nonzero, FPType_Zero, FPType_Infinity, + FPType_QNaN, FPType_SNaN}; +#endregion + +#region "functions/float/fpunpack/" + /* shared_pseudocode.html#impl-shared.FPUnpack.2 */ + /* shared_pseudocode.html#impl-shared.FPUnpackBase.2 */ + /*public static (FPType, bool, real) FPUnpack_16(Bits fpval, Bits _fpcr) + { + int N = fpval.Count; + + // assert N == 16; + + Bits fpcr = new Bits(_fpcr); // Clone. + + fpcr[26] = false; + + return FPUnpackBase_16(fpval, fpcr); + }*/ + public static (FPType, bool, float) FPUnpack_32(Bits fpval, Bits _fpcr) + { + int N = fpval.Count; + + /* assert N == 32; */ + + Bits fpcr = new Bits(_fpcr); // Clone. + + FPType type; + float value; + + bool sign = fpval[31]; + Bits exp32 = fpval[30, 23]; + Bits frac32 = fpval[22, 0]; + + if (IsZero(exp32)) + { + // Produce zero if value is zero or flush-to-zero is selected. + if (IsZero(frac32) || fpcr[24]) + { + type = FPType.FPType_Zero; + value = 0.0f; + + // Denormalized input flushed to zero + if (!IsZero(frac32)) + { + FPProcessException(FPExc.FPExc_InputDenorm, fpcr); + } + } + else + { + type = FPType.FPType_Nonzero; + value = MathF.Pow(2.0f, -126) * (Real_32(UInt(frac32)) * MathF.Pow(2.0f, -23)); + } + } + else if (IsOnes(exp32)) + { + if (IsZero(frac32)) + { + type = FPType.FPType_Infinity; + /* value = 2.0^1000000; */ + value = MathF.Pow(2.0f, 1000); + } + else + { + type = frac32[22] ? FPType.FPType_QNaN : FPType.FPType_SNaN; + value = 0.0f; + } + } + else + { + type = FPType.FPType_Nonzero; + value = MathF.Pow(2.0f, (int)UInt(exp32) - 127) * (1.0f + Real_32(UInt(frac32)) * MathF.Pow(2.0f, -23)); + } + + if (sign) + { + value = -value; + } + + return (type, sign, value); + } + public static (FPType, bool, double) FPUnpack_64(Bits fpval, Bits _fpcr) + { + int N = fpval.Count; + + /* assert N == 64; */ + + Bits fpcr = new Bits(_fpcr); // Clone. + + FPType type; + double value; + + bool sign = fpval[63]; + Bits exp64 = fpval[62, 52]; + Bits frac64 = fpval[51, 0]; + + if (IsZero(exp64)) + { + // Produce zero if value is zero or flush-to-zero is selected. + if (IsZero(frac64) || fpcr[24]) + { + type = FPType.FPType_Zero; + value = 0.0; + + // Denormalized input flushed to zero + if (!IsZero(frac64)) + { + FPProcessException(FPExc.FPExc_InputDenorm, fpcr); + } + } + else + { + type = FPType.FPType_Nonzero; + value = Math.Pow(2.0, -1022) * (Real_64(UInt(frac64)) * Math.Pow(2.0, -52)); + } + } + else if (IsOnes(exp64)) + { + if (IsZero(frac64)) + { + type = FPType.FPType_Infinity; + /* value = 2.0^1000000; */ + value = Math.Pow(2.0, 10000); + } + else + { + type = frac64[51] ? FPType.FPType_QNaN : FPType.FPType_SNaN; + value = 0.0; + } + } + else + { + type = FPType.FPType_Nonzero; + value = Math.Pow(2.0, (int)UInt(exp64) - 1023) * (1.0 + Real_64(UInt(frac64)) * Math.Pow(2.0, -52)); + } + + if (sign) + { + value = -value; + } + + return (type, sign, value); + } + + /* shared_pseudocode.html#impl-shared.FPUnpackCV.2 */ + /* shared_pseudocode.html#impl-shared.FPUnpackBase.2 */ + /*public static (FPType, bool, real) FPUnpackCV_16(Bits fpval, Bits _fpcr) + { + int N = fpval.Count; + + // assert N == 16; + + Bits fpcr = new Bits(_fpcr); // Clone. + + fpcr[19] = false; + + return FPUnpackBase_16(fpval, fpcr); + }*/ + public static (FPType, bool, float) FPUnpackCV_32(Bits fpval, Bits _fpcr) + { + return FPUnpack_32(fpval, _fpcr); + } + public static (FPType, bool, double) FPUnpackCV_64(Bits fpval, Bits _fpcr) + { + return FPUnpack_64(fpval, _fpcr); + } +#endregion + #region "functions/integer/" /* shared_pseudocode.html#impl-shared.AddWithCarry.3 */ public static (Bits, Bits) AddWithCarry(int N, Bits x, Bits y, bool carry_in) @@ -1117,7 +1570,12 @@ namespace Ryujinx.Tests.Cpu.Tester public static Bits SP_EL0; public static Bits SP_EL1; + public static Bits FPCR; // TODO: Add named fields. + // [ 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 22 | 21 20 | 19 | 18 17 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ] + // [ 0 | 0 | 0 | 0 | 0 | AHP | DN | FZ | RMode | Stride | FZ16 | Len | IDE | 0 | 0 | IXE | UFE | OFE | DZE | IOE | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 ] public static Bits FPSR; // TODO: Add named fields. + // [ 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ] + // [ N | Z | C | V | QC | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | IDC | 0 | 0 | IXC | UFC | OFC | DZC | IOC ] #endregion #region "functions/system/" @@ -1178,6 +1636,8 @@ namespace Ryujinx.Tests.Cpu.Tester /* shared_pseudocode.html#impl-shared.HaveEL.1 */ public static bool HaveEL(Bits el) { + // TODO: Implement ASL: "IN" as C#: "Bits.In()". + /* if el IN {EL1,EL0} then */ if (el == EL1 || el == EL0) { return true; // EL1 and EL0 must exist diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index a68b34e94..27c2bf786 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -5,6 +5,10 @@ win10-x64;osx-x64;linux-x64 Exe false + + windows + osx + linux @@ -20,6 +24,13 @@ + + + + + + + diff --git a/Ryujinx/Ui/ConsoleLog.cs b/Ryujinx/Ui/ConsoleLog.cs index 1a2899946..6fb92e333 100644 --- a/Ryujinx/Ui/ConsoleLog.cs +++ b/Ryujinx/Ui/ConsoleLog.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -7,6 +8,10 @@ namespace Ryujinx { static class ConsoleLog { + private static Thread MessageThread; + + private static BlockingCollection MessageQueue; + private static Dictionary LogColors; private static object ConsoleLock; @@ -21,15 +26,39 @@ namespace Ryujinx { LogLevel.Error, ConsoleColor.Red } }; + MessageQueue = new BlockingCollection(); + ConsoleLock = new object(); + + MessageThread = new Thread(() => + { + while (!MessageQueue.IsCompleted) + { + try + { + PrintLog(MessageQueue.Take()); + } + catch (InvalidOperationException) + { + // IOE means that Take() was called on a completed collection. + // Some other thread can call CompleteAdding after we pass the + // IsCompleted check but before we call Take. + // We can simply catch the exception since the loop will break + // on the next iteration. + } + } + }); + + MessageThread.IsBackground = true; + MessageThread.Start(); } - public static void PrintLog(object sender, LogEventArgs e) + private static void PrintLog(LogEventArgs e) { string FormattedTime = e.Time.ToString(@"hh\:mm\:ss\.fff"); string CurrentThread = Thread.CurrentThread.ManagedThreadId.ToString("d4"); - + string Message = FormattedTime + " | " + CurrentThread + " " + e.Message; if (LogColors.TryGetValue(e.Level, out ConsoleColor Color)) @@ -47,5 +76,13 @@ namespace Ryujinx Console.WriteLine(Message); } } + + public static void Log(object sender, LogEventArgs e) + { + if (!MessageQueue.IsAddingCompleted) + { + MessageQueue.Add(e); + } + } } } \ No newline at end of file diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs index 07027b901..503125872 100644 --- a/Ryujinx/Ui/Program.cs +++ b/Ryujinx/Ui/Program.cs @@ -21,10 +21,10 @@ namespace Ryujinx Switch Device = new Switch(Renderer, AudioOut); Config.Read(Device); - + Device.Hid.InitializeJoycons(); - - Device.Log.Updated += ConsoleLog.PrintLog; + + Device.Log.Updated += ConsoleLog.Log; if (args.Length == 1) {