diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs index fe3dce41df..44493298bc 100644 --- a/ChocolArm64/AOpCodeTable.cs +++ b/ChocolArm64/AOpCodeTable.cs @@ -278,15 +278,18 @@ namespace ChocolArm64 SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", AInstEmit.Fmax_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg)); SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", AInstEmit.Fmaxnm_V, typeof(AOpCodeSimdReg)); + SetA64("0>1011100<1xxxxx111101xxxxxxxxxx", AInstEmit.Fmaxp_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg)); SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", AInstEmit.Fmin_V, typeof(AOpCodeSimdReg)); SetA64("000111100x1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg)); SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", AInstEmit.Fminnm_V, typeof(AOpCodeSimdReg)); - SetA64("010111111<1011101<1xxxxx111101xxxxxxxxxx", AInstEmit.Fminp_V, typeof(AOpCodeSimdReg)); + SetA64("010111111xxxxxxx0001x0xxxxxxxxxx", AInstEmit.Fmla_Se, typeof(AOpCodeSimdRegElemF)); SetA64("0>0011100<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<00111110011101<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmls_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<00111111011100<1xxxxx110111xxxxxxxxxx", AInstEmit.Fmul_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<00111110011100<1xxxxx110111xxxxxxxxxx", AInstEmit.Fmulx_V, typeof(AOpCodeSimdReg)); + SetA64("0>10111111011101<100000111110xxxxxxxxxx", AInstEmit.Fneg_V, typeof(AOpCodeSimd)); SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fnmadd_S, typeof(AOpCodeSimdReg)); @@ -308,6 +315,7 @@ namespace ChocolArm64 SetA64("0>0011101<100001110110xxxxxxxxxx", AInstEmit.Frecpe_V, typeof(AOpCodeSimd)); SetA64("010111100x1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_S, typeof(AOpCodeSimdReg)); SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_V, typeof(AOpCodeSimdReg)); + SetA64("010111101x100001111110xxxxxxxxxx", AInstEmit.Frecpx_S, typeof(AOpCodeSimd)); SetA64("000111100x100110010000xxxxxxxxxx", AInstEmit.Frinta_S, typeof(AOpCodeSimd)); SetA64("0>1011100<100001100010xxxxxxxxxx", AInstEmit.Frinta_V, typeof(AOpCodeSimd)); SetA64("000111100x100111110000xxxxxxxxxx", AInstEmit.Frinti_S, typeof(AOpCodeSimd)); @@ -325,6 +333,7 @@ namespace ChocolArm64 SetA64("010111101x1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_S, typeof(AOpCodeSimdReg)); SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_V, typeof(AOpCodeSimdReg)); SetA64("000111100x100001110000xxxxxxxxxx", AInstEmit.Fsqrt_S, typeof(AOpCodeSimd)); + SetA64("0>1011101<100001111110xxxxxxxxxx", AInstEmit.Fsqrt_V, typeof(AOpCodeSimd)); SetA64("000111100x1xxxxx001110xxxxxxxxxx", AInstEmit.Fsub_S, typeof(AOpCodeSimdReg)); SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", AInstEmit.Fsub_V, typeof(AOpCodeSimdReg)); SetA64("01001110000xxxxx000111xxxxxxxxxx", AInstEmit.Ins_Gp, typeof(AOpCodeSimdIns)); @@ -395,6 +404,7 @@ namespace ChocolArm64 SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", AInstEmit.Sminp_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", AInstEmit.Smlal_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx101000xxxxxxxxxx", AInstEmit.Smlsl_V, typeof(AOpCodeSimdReg)); + SetA64("0x001110000xxxxx001011xxxxxxxxxx", AInstEmit.Smov_S, typeof(AOpCodeSimdIns)); SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Smull_V, typeof(AOpCodeSimdReg)); SetA64("01011110xx100000011110xxxxxxxxxx", AInstEmit.Sqabs_S, typeof(AOpCodeSimd)); SetA64("0>001110<<100000011110xxxxxxxxxx", AInstEmit.Sqabs_V, typeof(AOpCodeSimd)); diff --git a/ChocolArm64/AOptimizations.cs b/ChocolArm64/AOptimizations.cs index 40e1674a41..17205489a6 100644 --- a/ChocolArm64/AOptimizations.cs +++ b/ChocolArm64/AOptimizations.cs @@ -2,6 +2,8 @@ using System.Runtime.Intrinsics.X86; public static class AOptimizations { + internal static bool FastFP = true; + private static bool UseAllSseIfAvailable = true; private static bool UseSseIfAvailable = true; @@ -13,4 +15,4 @@ public static class AOptimizations internal static bool UseSse2 = (UseAllSseIfAvailable && UseSse2IfAvailable) && Sse2.IsSupported; internal static bool UseSse41 = (UseAllSseIfAvailable && UseSse41IfAvailable) && Sse41.IsSupported; internal static bool UseSse42 = (UseAllSseIfAvailable && UseSse42IfAvailable) && Sse42.IsSupported; -} \ No newline at end of file +} diff --git a/ChocolArm64/ATranslatorCache.cs b/ChocolArm64/ATranslatorCache.cs index e34cc397b6..3e3c5ab654 100644 --- a/ChocolArm64/ATranslatorCache.cs +++ b/ChocolArm64/ATranslatorCache.cs @@ -8,9 +8,14 @@ namespace ChocolArm64 { class ATranslatorCache { - private const int MaxTotalSize = 2 * 1024 * 256; - private const int MaxTimeDelta = 30000; - private const int MinCallCountForUpdate = 1000; + //Maximum size of the cache, in bytes, measured in ARM code size. + private const int MaxTotalSize = 4 * 1024 * 256; + + //Minimum time required in milliseconds for a method to be eligible for deletion. + private const int MinTimeDelta = 2 * 60000; + + //Minimum number of calls required to update the timestamp. + private const int MinCallCountForUpdate = 250; private class CacheBucket { @@ -134,7 +139,7 @@ namespace ChocolArm64 int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp); - if ((uint)TimeDelta <= (uint)MaxTimeDelta) + if ((uint)TimeDelta <= (uint)MinTimeDelta) { break; } diff --git a/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs b/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs index e61d7093a7..e0670def45 100644 --- a/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs +++ b/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs @@ -8,15 +8,26 @@ namespace ChocolArm64.Decoder public AOpCodeSimdRegElemF(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { - if ((Size & 1) != 0) + switch ((OpCode >> 21) & 3) // sz:L { - Index = (OpCode >> 11) & 1; - } - else - { - Index = (OpCode >> 21) & 1 | - (OpCode >> 10) & 2; + case 0: // H:0 + Index = (OpCode >> 10) & 2; // 0, 2 + + break; + + case 1: // H:1 + Index = (OpCode >> 10) & 2; + Index++; // 1, 3 + + break; + + case 2: // H + Index = (OpCode >> 11) & 1; // 0, 1 + + break; + + default: Emitter = AInstEmit.Und; return; } } } -} \ No newline at end of file +} diff --git a/ChocolArm64/Instruction/AInstEmitAlu.cs b/ChocolArm64/Instruction/AInstEmitAlu.cs index 490387e129..4551346bd9 100644 --- a/ChocolArm64/Instruction/AInstEmitAlu.cs +++ b/ChocolArm64/Instruction/AInstEmitAlu.cs @@ -4,6 +4,7 @@ using ChocolArm64.Translation; using System; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.Intrinsics.X86; using static ChocolArm64.Instruction.AInstEmitAluHelper; @@ -117,9 +118,18 @@ namespace ChocolArm64.Instruction Context.EmitLdintzr(Op.Rn); - Context.EmitLdc_I4(Op.RegisterSize == ARegisterSize.Int32 ? 32 : 64); + if (Lzcnt.IsSupported) + { + Type TValue = Op.RegisterSize == ARegisterSize.Int32 ? typeof(uint) : typeof(ulong); - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros)); + Context.EmitCall(typeof(Lzcnt).GetMethod(nameof(Lzcnt.LeadingZeroCount), new Type[] { TValue })); + } + else + { + Context.EmitLdc_I4(Op.RegisterSize == ARegisterSize.Int32 ? 32 : 64); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros)); + } Context.EmitStintzr(Op.Rd); } diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs index 27a86d84c2..7ba08f5e22 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs @@ -4,6 +4,7 @@ using ChocolArm64.Translation; using System; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static ChocolArm64.Instruction.AInstEmitSimdHelper; @@ -31,7 +32,7 @@ namespace ChocolArm64.Instruction { if (AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.Add)); + EmitSse2Op(Context, nameof(Sse2.Add)); } else { @@ -81,20 +82,6 @@ namespace ChocolArm64.Instruction } public static void Cls_V(AILEmitterCtx Context) - { - MethodInfo MthdInfo = typeof(ASoftFallback).GetMethod(nameof(ASoftFallback.CountLeadingSigns)); - - EmitCountLeadingBits(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Clz_V(AILEmitterCtx Context) - { - MethodInfo MthdInfo = typeof(ASoftFallback).GetMethod(nameof(ASoftFallback.CountLeadingZeros)); - - EmitCountLeadingBits(Context, () => Context.EmitCall(MthdInfo)); - } - - private static void EmitCountLeadingBits(AILEmitterCtx Context, Action Emit) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -109,7 +96,44 @@ namespace ChocolArm64.Instruction Context.EmitLdc_I4(ESize); - Emit(); + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingSigns)); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + public static void Clz_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> Op.Size; + + int ESize = 8 << Op.Size; + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); + + if (Lzcnt.IsSupported && ESize == 32) + { + Context.Emit(OpCodes.Conv_U4); + + Context.EmitCall(typeof(Lzcnt).GetMethod(nameof(Lzcnt.LeadingZeroCount), new Type[] { typeof(uint) })); + + Context.Emit(OpCodes.Conv_U8); + } + else + { + Context.EmitLdc_I4(ESize); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros)); + } EmitVectorInsert(Context, Op.Rd, Index, Op.Size); } @@ -130,11 +154,14 @@ namespace ChocolArm64.Instruction { EmitVectorExtractZx(Context, Op.Rn, Index, 0); - Context.Emit(OpCodes.Conv_U4); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountSetBits8)); - - Context.Emit(OpCodes.Conv_U8); + if (Popcnt.IsSupported) + { + Context.EmitCall(typeof(Popcnt).GetMethod(nameof(Popcnt.PopCount), new Type[] { typeof(ulong) })); + } + else + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountSetBits8)); + } EmitVectorInsert(Context, Op.Rd, Index, 0); } @@ -145,136 +172,6 @@ namespace ChocolArm64.Instruction } } - private static void EmitAbs(AILEmitterCtx Context) - { - AILLabel LblTrue = new AILLabel(); - - Context.Emit(OpCodes.Dup); - Context.Emit(OpCodes.Ldc_I4_0); - Context.Emit(OpCodes.Bge_S, LblTrue); - - Context.Emit(OpCodes.Neg); - - Context.MarkLabel(LblTrue); - } - - private static void EmitAddLongPairwise(AILEmitterCtx Context, bool Signed, bool Accumulate) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Words = Op.GetBitsCount() >> 4; - int Pairs = Words >> Op.Size; - - for (int Index = 0; Index < Pairs; Index++) - { - int Idx = Index << 1; - - EmitVectorExtract(Context, Op.Rn, Idx, Op.Size, Signed); - EmitVectorExtract(Context, Op.Rn, Idx + 1, Op.Size, Signed); - - Context.Emit(OpCodes.Add); - - if (Accumulate) - { - EmitVectorExtract(Context, Op.Rd, Index, Op.Size + 1, Signed); - - Context.Emit(OpCodes.Add); - } - - EmitVectorInsertTmp(Context, Index, Op.Size + 1); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitDoublingMultiplyHighHalf(AILEmitterCtx Context, bool Round) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int ESize = 8 << Op.Size; - - Context.Emit(OpCodes.Mul); - - if (!Round) - { - Context.EmitAsr(ESize - 1); - } - else - { - long RoundConst = 1L << (ESize - 1); - - AILLabel LblTrue = new AILLabel(); - - Context.EmitLsl(1); - - Context.EmitLdc_I8(RoundConst); - - Context.Emit(OpCodes.Add); - - Context.EmitAsr(ESize); - - Context.Emit(OpCodes.Dup); - Context.EmitLdc_I8((long)int.MinValue); - Context.Emit(OpCodes.Bne_Un_S, LblTrue); - - Context.Emit(OpCodes.Neg); - - Context.MarkLabel(LblTrue); - } - } - - private static void EmitHighNarrow(AILEmitterCtx Context, Action Emit, bool Round) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Elems = 8 >> Op.Size; - - int ESize = 8 << Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - long RoundConst = 1L << (ESize - 1); - - if (Part != 0) - { - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - } - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); - EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size + 1); - - Emit(); - - if (Round) - { - Context.EmitLdc_I8(RoundConst); - - Context.Emit(OpCodes.Add); - } - - Context.EmitLsr(ESize); - - EmitVectorInsertTmp(Context, Part + Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - public static void Fabd_S(AILEmitterCtx Context) { EmitScalarBinaryOpF(Context, () => @@ -303,25 +200,33 @@ namespace ChocolArm64.Instruction public static void Fadd_S(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.AddScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.AddScalar)); } else { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Add)); + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPAdd)); + }); } } public static void Fadd_V(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.Add)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Add)); } else { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Add)); + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPAdd)); + }); } } @@ -341,90 +246,124 @@ namespace ChocolArm64.Instruction public static void Faddp_V(AILEmitterCtx Context) { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Op.GetBitsCount() >> 3; - - int Elems = Bytes >> SizeF + 2; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = (Index & (Half - 1)) << 1; - - EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 0, SizeF); - EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 1, SizeF); - - Context.Emit(OpCodes.Add); - - EmitVectorInsertTmpF(Context, Index, SizeF); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } + EmitVectorPairwiseOpF(Context, () => Context.Emit(OpCodes.Add)); } public static void Fdiv_S(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.DivideScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.DivideScalar)); } else { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Div)); + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPDiv)); + }); } } public static void Fdiv_V(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.Divide)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Divide)); } else { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Div)); + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPDiv)); + }); } } public static void Fmadd_S(AILEmitterCtx Context) { - EmitScalarTernaryRaOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse2) { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + if (Op.Size == 0) + { + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Ra); + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.AddScalar), Types)); + + Context.EmitStvec(Op.Rd); + + EmitVectorZero32_128(Context, Op.Rd); + } + else /* if (Op.Size == 1) */ + { + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Ra); + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.AddScalar), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitScalarTernaryRaOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulAdd)); + }); + } } public static void Fmax_S(AILEmitterCtx Context) { - EmitScalarBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Max)); - }); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.MaxScalar)); + } + else + { + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMax)); + }); + } } public static void Fmax_V(AILEmitterCtx Context) { - EmitVectorBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Max)); - }); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Max)); + } + else + { + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMax)); + }); + } } public static void Fmaxnm_S(AILEmitterCtx Context) { EmitScalarBinaryOpF(Context, () => { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MaxNum)); + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMaxNum)); }); } @@ -432,31 +371,55 @@ namespace ChocolArm64.Instruction { EmitVectorBinaryOpF(Context, () => { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MaxNum)); + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMaxNum)); + }); + } + + public static void Fmaxp_V(AILEmitterCtx Context) + { + EmitVectorPairwiseOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMax)); }); } public static void Fmin_S(AILEmitterCtx Context) { - EmitScalarBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Min)); - }); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.MinScalar)); + } + else + { + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMin)); + }); + } } public static void Fmin_V(AILEmitterCtx Context) { - EmitVectorBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.Min)); - }); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Min)); + } + else + { + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMin)); + }); + } } public static void Fminnm_S(AILEmitterCtx Context) { EmitScalarBinaryOpF(Context, () => { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MinNum)); + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMinNum)); }); } @@ -464,7 +427,15 @@ namespace ChocolArm64.Instruction { EmitVectorBinaryOpF(Context, () => { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.MinNum)); + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMinNum)); + }); + } + + public static void Fminp_V(AILEmitterCtx Context) + { + EmitVectorPairwiseOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMin)); }); } @@ -495,6 +466,15 @@ namespace ChocolArm64.Instruction }); } + public static void Fmls_Se(AILEmitterCtx Context) + { + EmitScalarTernaryOpByElemF(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Sub); + }); + } + public static void Fmls_V(AILEmitterCtx Context) { EmitVectorTernaryOpF(Context, () => @@ -515,22 +495,63 @@ namespace ChocolArm64.Instruction public static void Fmsub_S(AILEmitterCtx Context) { - EmitScalarTernaryRaOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse2) { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - }); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + if (Op.Size == 0) + { + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Ra); + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SubtractScalar), Types)); + + Context.EmitStvec(Op.Rd); + + EmitVectorZero32_128(Context, Op.Rd); + } + else /* if (Op.Size == 1) */ + { + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Ra); + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SubtractScalar), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitScalarTernaryRaOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulSub)); + }); + } } public static void Fmul_S(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.MultiplyScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.MultiplyScalar)); } else { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMul)); + }); } } @@ -541,13 +562,17 @@ namespace ChocolArm64.Instruction public static void Fmul_V(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.Multiply)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Multiply)); } else { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMul)); + }); } } @@ -556,6 +581,38 @@ namespace ChocolArm64.Instruction EmitVectorBinaryOpByElemF(Context, () => Context.Emit(OpCodes.Mul)); } + public static void Fmulx_S(AILEmitterCtx Context) + { + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulX)); + }); + } + + public static void Fmulx_Se(AILEmitterCtx Context) + { + EmitScalarBinaryOpByElemF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulX)); + }); + } + + public static void Fmulx_V(AILEmitterCtx Context) + { + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulX)); + }); + } + + public static void Fmulx_Ve(AILEmitterCtx Context) + { + EmitVectorBinaryOpByElemF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPMulX)); + }); + } + public static void Fneg_S(AILEmitterCtx Context) { EmitScalarUnaryOpF(Context, () => Context.Emit(OpCodes.Neg)); @@ -632,17 +689,122 @@ namespace ChocolArm64.Instruction public static void Frecps_S(AILEmitterCtx Context) { - EmitScalarBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.RecipStep)); - }); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Type[] Types = new Type[] { typeof(float) }; + + Context.EmitLdc_R4(2f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetScalarVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SubtractScalar), Types)); + + Context.EmitStvec(Op.Rd); + + EmitVectorZero32_128(Context, Op.Rd); + } + else /* if (SizeF == 1) */ + { + Type[] Types = new Type[] { typeof(double) }; + + Context.EmitLdc_R8(2d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetScalarVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SubtractScalar), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPRecipStepFused)); + }); + } } public static void Frecps_V(AILEmitterCtx Context) { - EmitVectorBinaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse2) { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.RecipStep)); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Type[] Types = new Type[] { typeof(float) }; + + Context.EmitLdc_R4(2f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetAllVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.Multiply), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.Subtract), Types)); + + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else /* if (SizeF == 1) */ + { + Type[] Types = new Type[] { typeof(double) }; + + Context.EmitLdc_R8(2d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetAllVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Multiply), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Subtract), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + } + } + else + { + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPRecipStepFused)); + }); + } + } + + public static void Frecpx_S(AILEmitterCtx Context) + { + EmitScalarUnaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPRecpX)); }); } @@ -836,97 +998,194 @@ namespace ChocolArm64.Instruction public static void Frsqrts_S(AILEmitterCtx Context) { - EmitFrsqrts(Context, 0, Scalar: true); + if (AOptimizations.FastFP && AOptimizations.UseSse2) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Type[] Types = new Type[] { typeof(float) }; + + Context.EmitLdc_R4(0.5f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetScalarVector128), Types)); + + Context.EmitLdc_R4(3f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetScalarVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SubtractScalar), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.MultiplyScalar), Types)); + + Context.EmitStvec(Op.Rd); + + EmitVectorZero32_128(Context, Op.Rd); + } + else /* if (SizeF == 1) */ + { + Type[] Types = new Type[] { typeof(double) }; + + Context.EmitLdc_R8(0.5d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetScalarVector128), Types)); + + Context.EmitLdc_R8(3d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetScalarVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MultiplyScalar), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SubtractScalar), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MultiplyScalar), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPRSqrtStepFused)); + }); + } } public static void Frsqrts_V(AILEmitterCtx Context) { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Op.GetBitsCount() >> 3; - - for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + if (AOptimizations.FastFP && AOptimizations.UseSse2) { - EmitFrsqrts(Context, Index, Scalar: false); - } + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - if (Op.RegisterSize == ARegisterSize.SIMD64) + int SizeF = Op.Size & 1; + + if (SizeF == 0) + { + Type[] Types = new Type[] { typeof(float) }; + + Context.EmitLdc_R4(0.5f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetAllVector128), Types)); + + Context.EmitLdc_R4(3f); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.SetAllVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.Multiply), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.Subtract), Types)); + Context.EmitCall(typeof(Sse).GetMethod(nameof(Sse.Multiply), Types)); + + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else /* if (SizeF == 1) */ + { + Type[] Types = new Type[] { typeof(double) }; + + Context.EmitLdc_R8(0.5d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetAllVector128), Types)); + + Context.EmitLdc_R8(3d); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetAllVector128), Types)); + + Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + EmitLdvecWithCastToDouble(Context, Op.Rn); + EmitLdvecWithCastToDouble(Context, Op.Rm); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Multiply), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Subtract), Types)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Multiply), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + } + } + else { - EmitVectorZeroUpper(Context, Op.Rd); + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPRSqrtStepFused)); + }); } } - private static void EmitFrsqrts(AILEmitterCtx Context, int Index, bool Scalar) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - if (SizeF == 0) - { - Context.EmitLdc_R4(3); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(3); - } - - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - EmitVectorExtractF(Context, Op.Rm, Index, SizeF); - - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - - if (SizeF == 0) - { - Context.EmitLdc_R4(0.5f); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(0.5); - } - - Context.Emit(OpCodes.Mul); - - if (Scalar) - { - EmitVectorZeroAll(Context, Op.Rd); - } - - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); - } - public static void Fsqrt_S(AILEmitterCtx Context) { - EmitScalarUnaryOpF(Context, () => + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitUnaryMathCall(Context, nameof(Math.Sqrt)); - }); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.SqrtScalar)); + } + else + { + EmitScalarUnaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPSqrt)); + }); + } + } + + public static void Fsqrt_V(AILEmitterCtx Context) + { + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) + { + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Sqrt)); + } + else + { + EmitVectorUnaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPSqrt)); + }); + } } public static void Fsub_S(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.SubtractScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.SubtractScalar)); } else { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Sub)); + EmitScalarBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPSub)); + }); } } public static void Fsub_V(AILEmitterCtx Context) { - if (AOptimizations.UseSse && AOptimizations.UseSse2) + if (AOptimizations.FastFP && AOptimizations.UseSse + && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.Subtract)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.Subtract)); } else { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Sub)); + EmitVectorBinaryOpF(Context, () => + { + EmitSoftFloatCall(Context, nameof(ASoftFloat_32.FPSub)); + }); } } @@ -1250,7 +1509,7 @@ namespace ChocolArm64.Instruction { if (AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.Subtract)); + EmitSse2Op(Context, nameof(Sse2.Subtract)); } else { @@ -1496,5 +1755,135 @@ namespace ChocolArm64.Instruction { EmitVectorWidenRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); } + + private static void EmitAbs(AILEmitterCtx Context) + { + AILLabel LblTrue = new AILLabel(); + + Context.Emit(OpCodes.Dup); + Context.Emit(OpCodes.Ldc_I4_0); + Context.Emit(OpCodes.Bge_S, LblTrue); + + Context.Emit(OpCodes.Neg); + + Context.MarkLabel(LblTrue); + } + + private static void EmitAddLongPairwise(AILEmitterCtx Context, bool Signed, bool Accumulate) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int Words = Op.GetBitsCount() >> 4; + int Pairs = Words >> Op.Size; + + for (int Index = 0; Index < Pairs; Index++) + { + int Idx = Index << 1; + + EmitVectorExtract(Context, Op.Rn, Idx, Op.Size, Signed); + EmitVectorExtract(Context, Op.Rn, Idx + 1, Op.Size, Signed); + + Context.Emit(OpCodes.Add); + + if (Accumulate) + { + EmitVectorExtract(Context, Op.Rd, Index, Op.Size + 1, Signed); + + Context.Emit(OpCodes.Add); + } + + EmitVectorInsertTmp(Context, Index, Op.Size + 1); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitDoublingMultiplyHighHalf(AILEmitterCtx Context, bool Round) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int ESize = 8 << Op.Size; + + Context.Emit(OpCodes.Mul); + + if (!Round) + { + Context.EmitAsr(ESize - 1); + } + else + { + long RoundConst = 1L << (ESize - 1); + + AILLabel LblTrue = new AILLabel(); + + Context.EmitLsl(1); + + Context.EmitLdc_I8(RoundConst); + + Context.Emit(OpCodes.Add); + + Context.EmitAsr(ESize); + + Context.Emit(OpCodes.Dup); + Context.EmitLdc_I8((long)int.MinValue); + Context.Emit(OpCodes.Bne_Un_S, LblTrue); + + Context.Emit(OpCodes.Neg); + + Context.MarkLabel(LblTrue); + } + } + + private static void EmitHighNarrow(AILEmitterCtx Context, Action Emit, bool Round) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int Elems = 8 >> Op.Size; + + int ESize = 8 << Op.Size; + + int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + + long RoundConst = 1L << (ESize - 1); + + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); + EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size + 1); + + Emit(); + + if (Round) + { + Context.EmitLdc_I8(RoundConst); + + Context.Emit(OpCodes.Add); + } + + Context.EmitLsr(ESize); + + EmitVectorInsertTmp(Context, Part + Index, Op.Size); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } } } diff --git a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs index 6357396d3c..97f7623fa3 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs @@ -23,11 +23,11 @@ namespace ChocolArm64.Instruction { if (Op.Size < 3 && AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.CompareEqual)); + EmitSse2Op(Context, nameof(Sse2.CompareEqual)); } else if (Op.Size == 3 && AOptimizations.UseSse41) { - EmitSse41Call(Context, nameof(Sse41.CompareEqual)); + EmitSse41Op(Context, nameof(Sse41.CompareEqual)); } else { @@ -61,11 +61,11 @@ namespace ChocolArm64.Instruction { if (Op.Size < 3 && AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.CompareGreaterThan)); + EmitSse2Op(Context, nameof(Sse2.CompareGreaterThan)); } else if (Op.Size == 3 && AOptimizations.UseSse42) { - EmitSse42Call(Context, nameof(Sse42.CompareGreaterThan)); + EmitSse42Op(Context, nameof(Sse42.CompareGreaterThan)); } else { @@ -158,7 +158,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.CompareEqualScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.CompareEqualScalar)); } else { @@ -171,7 +171,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.CompareEqual)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.CompareEqual)); } else { @@ -184,7 +184,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanOrEqualScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.CompareGreaterThanOrEqualScalar)); } else { @@ -197,7 +197,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanOrEqual)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.CompareGreaterThanOrEqual)); } else { @@ -210,7 +210,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitScalarSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanScalar)); + EmitScalarSseOrSse2OpF(Context, nameof(Sse.CompareGreaterThanScalar)); } else { @@ -223,7 +223,7 @@ namespace ChocolArm64.Instruction if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse && AOptimizations.UseSse2) { - EmitVectorSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThan)); + EmitVectorSseOrSse2OpF(Context, nameof(Sse.CompareGreaterThan)); } else { diff --git a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs index 231de0aff7..76d984a23b 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs @@ -3,6 +3,8 @@ using ChocolArm64.State; using ChocolArm64.Translation; using System; using System.Reflection.Emit; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using static ChocolArm64.Instruction.AInstEmitSimdHelper; @@ -14,11 +16,48 @@ namespace ChocolArm64.Instruction { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); + if (AOptimizations.UseSse2) + { + if (Op.Size == 1 && Op.Opc == 0) + { + //Double -> Single. + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleZero)); - EmitFloatCast(Context, Op.Opc); + EmitLdvecWithCastToDouble(Context, Op.Rn); - EmitScalarSetF(Context, Op.Rd, Op.Opc); + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ConvertScalarToVector128Single), Types)); + + Context.EmitStvec(Op.Rd); + } + else if (Op.Size == 0 && Op.Opc == 1) + { + //Single -> Double. + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorDoubleZero)); + + Context.EmitLdvec(Op.Rn); + + Type[] Types = new Type[] { typeof(Vector128), typeof(Vector128) }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ConvertScalarToVector128Double), Types)); + + EmitStvecWithCastFromDouble(Context, Op.Rd); + } + else + { + //Invalid encoding. + throw new InvalidOperationException(); + } + } + else + { + EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); + + EmitFloatCast(Context, Op.Opc); + + EmitScalarSetF(Context, Op.Rd, Op.Opc); + } } public static void Fcvtas_Gp(AILEmitterCtx Context) diff --git a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs index 75a5a0d092..dd39f52d50 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs @@ -4,7 +4,6 @@ using ChocolArm64.Translation; using System; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -12,6 +11,38 @@ namespace ChocolArm64.Instruction { static class AInstEmitSimdHelper { + public static readonly Type[] IntTypesPerSizeLog2 = new Type[] + { + typeof(sbyte), + typeof(short), + typeof(int), + typeof(long) + }; + + public static readonly Type[] UIntTypesPerSizeLog2 = new Type[] + { + typeof(byte), + typeof(ushort), + typeof(uint), + typeof(ulong) + }; + + public static readonly Type[] VectorIntTypesPerSizeLog2 = new Type[] + { + typeof(Vector128), + typeof(Vector128), + typeof(Vector128), + typeof(Vector128) + }; + + public static readonly Type[] VectorUIntTypesPerSizeLog2 = new Type[] + { + typeof(Vector128), + typeof(Vector128), + typeof(Vector128), + typeof(Vector128) + }; + [Flags] public enum OperFlags { @@ -36,56 +67,32 @@ namespace ChocolArm64.Instruction return (8 << (Op.Size + 1)) - Op.Imm; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse2Call(AILEmitterCtx Context, string Name) + public static void EmitSse2Op(AILEmitterCtx Context, string Name) { - EmitSseCall(Context, Name, typeof(Sse2)); + EmitSseOp(Context, Name, typeof(Sse2)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse41Call(AILEmitterCtx Context, string Name) + public static void EmitSse41Op(AILEmitterCtx Context, string Name) { - EmitSseCall(Context, Name, typeof(Sse41)); + EmitSseOp(Context, Name, typeof(Sse41)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse42Call(AILEmitterCtx Context, string Name) + public static void EmitSse42Op(AILEmitterCtx Context, string Name) { - EmitSseCall(Context, Name, typeof(Sse42)); + EmitSseOp(Context, Name, typeof(Sse42)); } - private static void EmitSseCall(AILEmitterCtx Context, string Name, Type Type) + private static void EmitSseOp(AILEmitterCtx Context, string Name, Type Type) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - void Ldvec(int Reg) - { - Context.EmitLdvec(Reg); + EmitLdvecWithSignedCast(Context, Op.Rn, Op.Size); - switch (Op.Size) - { - case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToSByte)); break; - case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt16)); break; - case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt32)); break; - case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt64)); break; - } - } - - Ldvec(Op.Rn); - - Type BaseType = null; - - switch (Op.Size) - { - case 0: BaseType = typeof(Vector128); break; - case 1: BaseType = typeof(Vector128); break; - case 2: BaseType = typeof(Vector128); break; - case 3: BaseType = typeof(Vector128); break; - } + Type BaseType = VectorIntTypesPerSizeLog2[Op.Size]; if (Op is AOpCodeSimdReg BinOp) { - Ldvec(BinOp.Rm); + EmitLdvecWithSignedCast(Context, BinOp.Rm, Op.Size); Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType, BaseType })); } @@ -94,15 +101,7 @@ namespace ChocolArm64.Instruction Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType })); } - switch (Op.Size) - { - case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSByteToSingle)); break; - case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt16ToSingle)); break; - case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt32ToSingle)); break; - case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt64ToSingle)); break; - } - - Context.EmitStvec(Op.Rd); + EmitStvecWithSignedCast(Context, Op.Rd, Op.Size); if (Op.RegisterSize == ARegisterSize.SIMD64) { @@ -110,17 +109,91 @@ namespace ChocolArm64.Instruction } } - public static void EmitScalarSseOrSse2CallF(AILEmitterCtx Context, string Name) + public static void EmitLdvecWithSignedCast(AILEmitterCtx Context, int Reg, int Size) { - EmitSseOrSse2CallF(Context, Name, true); + Context.EmitLdvec(Reg); + + switch (Size) + { + case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToSByte)); break; + case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt16)); break; + case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt32)); break; + case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt64)); break; + + default: throw new ArgumentOutOfRangeException(nameof(Size)); + } } - public static void EmitVectorSseOrSse2CallF(AILEmitterCtx Context, string Name) + public static void EmitLdvecWithCastToDouble(AILEmitterCtx Context, int Reg) { - EmitSseOrSse2CallF(Context, Name, false); + Context.EmitLdvec(Reg); + + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToDouble)); } - public static void EmitSseOrSse2CallF(AILEmitterCtx Context, string Name, bool Scalar) + public static void EmitStvecWithCastFromDouble(AILEmitterCtx Context, int Reg) + { + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorDoubleToSingle)); + + Context.EmitStvec(Reg); + } + + public static void EmitLdvecWithUnsignedCast(AILEmitterCtx Context, int Reg, int Size) + { + Context.EmitLdvec(Reg); + + switch (Size) + { + case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToByte)); break; + case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToUInt16)); break; + case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToUInt32)); break; + case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToUInt64)); break; + + default: throw new ArgumentOutOfRangeException(nameof(Size)); + } + } + + public static void EmitStvecWithSignedCast(AILEmitterCtx Context, int Reg, int Size) + { + switch (Size) + { + case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSByteToSingle)); break; + case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt16ToSingle)); break; + case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt32ToSingle)); break; + case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt64ToSingle)); break; + + default: throw new ArgumentOutOfRangeException(nameof(Size)); + } + + Context.EmitStvec(Reg); + } + + public static void EmitStvecWithUnsignedCast(AILEmitterCtx Context, int Reg, int Size) + { + switch (Size) + { + case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorByteToSingle)); break; + case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorUInt16ToSingle)); break; + case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorUInt32ToSingle)); break; + case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorUInt64ToSingle)); break; + + default: throw new ArgumentOutOfRangeException(nameof(Size)); + } + + Context.EmitStvec(Reg); + } + + public static void EmitScalarSseOrSse2OpF(AILEmitterCtx Context, string Name) + { + EmitSseOrSse2OpF(Context, Name, true); + } + + public static void EmitVectorSseOrSse2OpF(AILEmitterCtx Context, string Name) + { + EmitSseOrSse2OpF(Context, Name, false); + } + + public static void EmitSseOrSse2OpF(AILEmitterCtx Context, string Name, bool Scalar) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -233,25 +306,19 @@ namespace ChocolArm64.Instruction int SizeF = Op.Size & 1; - Context.EmitLdc_I4((int)RoundMode); - MethodInfo MthdInfo; - Type[] Types = new Type[] { null, typeof(MidpointRounding) }; - - Types[0] = SizeF == 0 - ? typeof(float) - : typeof(double); - if (SizeF == 0) { - MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Round), Types); + MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Round), new Type[] { typeof(float), typeof(MidpointRounding) }); } else /* if (SizeF == 1) */ { - MthdInfo = typeof(Math).GetMethod(nameof(Math.Round), Types); + MthdInfo = typeof(Math).GetMethod(nameof(Math.Round), new Type[] { typeof(double), typeof(MidpointRounding) }); } + Context.EmitLdc_I4((int)RoundMode); + Context.EmitCall(MthdInfo); } @@ -275,24 +342,17 @@ namespace ChocolArm64.Instruction Context.EmitCall(MthdInfo); } - public static void EmitBinarySoftFloatCall(AILEmitterCtx Context, string Name) + public static void EmitSoftFloatCall(AILEmitterCtx Context, string Name) { IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - int SizeF = Op.Size & 1; + Type Type = (Op.Size & 1) == 0 + ? typeof(ASoftFloat_32) + : typeof(ASoftFloat_64); - MethodInfo MthdInfo; + Context.EmitLdarg(ATranslatedSub.StateArgIdx); - if (SizeF == 0) - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(float), typeof(float) }); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(double), typeof(double) }); - } - - Context.EmitCall(MthdInfo); + Context.EmitCall(Type, Name); } public static void EmitScalarBinaryOpByElemF(AILEmitterCtx Context, Action Emit) @@ -813,6 +873,42 @@ namespace ChocolArm64.Instruction } } + public static void EmitVectorPairwiseOpF(AILEmitterCtx Context, Action Emit) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + int SizeF = Op.Size & 1; + + int Words = Op.GetBitsCount() >> 4; + int Pairs = Words >> SizeF + 2; + + for (int Index = 0; Index < Pairs; Index++) + { + int Idx = Index << 1; + + EmitVectorExtractF(Context, Op.Rn, Idx, SizeF); + EmitVectorExtractF(Context, Op.Rn, Idx + 1, SizeF); + + Emit(); + + EmitVectorExtractF(Context, Op.Rm, Idx, SizeF); + EmitVectorExtractF(Context, Op.Rm, Idx + 1, SizeF); + + Emit(); + + EmitVectorInsertTmpF(Context, Pairs + Index, SizeF); + EmitVectorInsertTmpF(Context, Index, SizeF); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + [Flags] public enum SaturatingFlags { @@ -1147,8 +1243,21 @@ namespace ChocolArm64.Instruction public static void EmitScalarSetF(AILEmitterCtx Context, int Reg, int Size) { - EmitVectorZeroAll(Context, Reg); - EmitVectorInsertF(Context, Reg, 0, Size); + if (AOptimizations.UseSse41 && Size == 0) + { + //If the type is float, we can perform insertion and + //zero the upper bits with a single instruction (INSERTPS); + Context.EmitLdvec(Reg); + + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Sse41VectorInsertScalarSingle)); + + Context.EmitStvec(Reg); + } + else + { + EmitVectorZeroAll(Context, Reg); + EmitVectorInsertF(Context, Reg, 0, Size); + } } public static void EmitVectorExtractSx(AILEmitterCtx Context, int Reg, int Index, int Size) @@ -1199,8 +1308,17 @@ namespace ChocolArm64.Instruction public static void EmitVectorZeroAll(AILEmitterCtx Context, int Rd) { - EmitVectorZeroLower(Context, Rd); - EmitVectorZeroUpper(Context, Rd); + if (AOptimizations.UseSse2) + { + AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleZero)); + + Context.EmitStvec(Rd); + } + else + { + EmitVectorZeroLower(Context, Rd); + EmitVectorZeroUpper(Context, Rd); + } } public static void EmitVectorZeroLower(AILEmitterCtx Context, int Rd) @@ -1213,9 +1331,32 @@ namespace ChocolArm64.Instruction EmitVectorInsertTmp(Context, 0, 3, 0); } - public static void EmitVectorZeroUpper(AILEmitterCtx Context, int Rd) + public static void EmitVectorZeroUpper(AILEmitterCtx Context, int Reg) { - EmitVectorInsert(Context, Rd, 1, 3, 0); + if (AOptimizations.UseSse2) + { + //TODO: Use MoveScalar once it is fixed, as of the + //time of writing it just crashes the JIT. + EmitLdvecWithUnsignedCast(Context, Reg, 3); + + Type[] Types = new Type[] { typeof(Vector128), typeof(byte) }; + + //Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.MoveScalar), Types)); + + Context.EmitLdc_I4(8); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftLeftLogical128BitLane), Types)); + + Context.EmitLdc_I4(8); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightLogical128BitLane), Types)); + + EmitStvecWithUnsignedCast(Context, Reg, 3); + } + else + { + EmitVectorInsert(Context, Reg, 1, 3, 0); + } } public static void EmitVectorZero32_128(AILEmitterCtx Context, int Reg) diff --git a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs b/ChocolArm64/Instruction/AInstEmitSimdLogical.cs index 9f5af96cb4..1aa8981f54 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdLogical.cs @@ -15,7 +15,7 @@ namespace ChocolArm64.Instruction { if (AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.And)); + EmitSse2Op(Context, nameof(Sse2.And)); } else { @@ -25,11 +25,36 @@ namespace ChocolArm64.Instruction public static void Bic_V(AILEmitterCtx Context) { - EmitVectorBinaryOpZx(Context, () => + if (AOptimizations.UseSse2) { - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.And); - }); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + EmitLdvecWithUnsignedCast(Context, Op.Rm, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + + Type[] Types = new Type[] + { + VectorUIntTypesPerSizeLog2[Op.Size], + VectorUIntTypesPerSizeLog2[Op.Size] + }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.AndNot), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitVectorBinaryOpZx(Context, () => + { + Context.Emit(OpCodes.Not); + Context.Emit(OpCodes.And); + }); + } } public static void Bic_Vi(AILEmitterCtx Context) @@ -55,59 +80,124 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - int Bytes = Op.GetBitsCount() >> 3; - int Elems = Bytes >> Op.Size; - - for (int Index = 0; Index < Elems; Index++) + if (AOptimizations.UseSse2) { - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.Emit(OpCodes.Xor); - - EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size); - - if (NotRm) + Type[] Types = new Type[] { - Context.Emit(OpCodes.Not); + VectorUIntTypesPerSizeLog2[Op.Size], + VectorUIntTypesPerSizeLog2[Op.Size] + }; + + EmitLdvecWithUnsignedCast(Context, Op.Rm, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rd, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Xor), Types)); + + string Name = NotRm ? nameof(Sse2.AndNot) : nameof(Sse2.And); + + Context.EmitCall(typeof(Sse2).GetMethod(Name, Types)); + + EmitLdvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Xor), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> Op.Size; + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); + + Context.Emit(OpCodes.Xor); + + EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size); + + if (NotRm) + { + Context.Emit(OpCodes.Not); + } + + Context.Emit(OpCodes.And); + + EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); + + Context.Emit(OpCodes.Xor); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size); } - Context.Emit(OpCodes.And); - - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - - Context.Emit(OpCodes.Xor); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } } } public static void Bsl_V(AILEmitterCtx Context) { - EmitVectorTernaryOpZx(Context, () => + if (AOptimizations.UseSse2) { - Context.EmitSttmp(); - Context.EmitLdtmp(); + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - Context.Emit(OpCodes.Xor); - Context.Emit(OpCodes.And); + Type[] Types = new Type[] + { + VectorUIntTypesPerSizeLog2[Op.Size], + VectorUIntTypesPerSizeLog2[Op.Size] + }; - Context.EmitLdtmp(); + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rm, Op.Size); - Context.Emit(OpCodes.Xor); - }); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Xor), Types)); + + EmitLdvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.And), Types)); + + EmitLdvecWithUnsignedCast(Context, Op.Rm, Op.Size); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Xor), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitVectorTernaryOpZx(Context, () => + { + Context.EmitSttmp(); + Context.EmitLdtmp(); + + Context.Emit(OpCodes.Xor); + Context.Emit(OpCodes.And); + + Context.EmitLdtmp(); + + Context.Emit(OpCodes.Xor); + }); + } } public static void Eor_V(AILEmitterCtx Context) { if (AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.Xor)); + EmitSse2Op(Context, nameof(Sse2.Xor)); } else { @@ -133,7 +223,7 @@ namespace ChocolArm64.Instruction { if (AOptimizations.UseSse2) { - EmitSse2Call(Context, nameof(Sse2.Or)); + EmitSse2Op(Context, nameof(Sse2.Or)); } else { diff --git a/ChocolArm64/Instruction/AInstEmitSimdMove.cs b/ChocolArm64/Instruction/AInstEmitSimdMove.cs index 3bf1e4635b..6001f48caf 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdMove.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdMove.cs @@ -3,6 +3,7 @@ using ChocolArm64.State; using ChocolArm64.Translation; using System; using System.Reflection.Emit; +using System.Runtime.Intrinsics.X86; using static ChocolArm64.Instruction.AInstEmitSimdHelper; @@ -14,19 +15,44 @@ namespace ChocolArm64.Instruction { AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - int Bytes = Op.GetBitsCount() >> 3; - int Elems = Bytes >> Op.Size; - - for (int Index = 0; Index < Elems; Index++) + if (AOptimizations.UseSse2) { Context.EmitLdintzr(Op.Rn); - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } + switch (Op.Size) + { + case 0: Context.Emit(OpCodes.Conv_U1); break; + case 1: Context.Emit(OpCodes.Conv_U2); break; + case 2: Context.Emit(OpCodes.Conv_U4); break; + } - if (Op.RegisterSize == ARegisterSize.SIMD64) + Type[] Types = new Type[] { UIntTypesPerSizeLog2[Op.Size] }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetAllVector128), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else { - EmitVectorZeroUpper(Context, Op.Rd); + int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> Op.Size; + + for (int Index = 0; Index < Elems; Index++) + { + Context.EmitLdintzr(Op.Rn); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } } } @@ -223,6 +249,17 @@ namespace ChocolArm64.Instruction EmitVectorImmUnaryOp(Context, () => Context.Emit(OpCodes.Not)); } + public static void Smov_S(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + EmitVectorExtractSx(Context, Op.Rn, Op.DstIndex, Op.Size); + + EmitIntZeroUpperIfNeeded(Context); + + Context.EmitStintzr(Op.Rd); + } + public static void Tbl_V(AILEmitterCtx Context) { AOpCodeSimdTbl Op = (AOpCodeSimdTbl)Context.CurrOp; @@ -295,25 +332,91 @@ namespace ChocolArm64.Instruction int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - if (Part != 0) + if (AOptimizations.UseSse41 && Op.Size < 2) { - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); + void EmitZeroVector() + { + switch (Op.Size) + { + case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt16Zero)); break; + case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt32Zero)); break; + } + } + + //For XTN, first operand is source, second operand is 0. + //For XTN2, first operand is 0, second operand is source. + if (Part != 0) + { + EmitZeroVector(); + } + + EmitLdvecWithSignedCast(Context, Op.Rn, Op.Size + 1); + + //Set mask to discard the upper half of the wide elements. + switch (Op.Size) + { + case 0: Context.EmitLdc_I4(0x00ff); break; + case 1: Context.EmitLdc_I4(0x0000ffff); break; + } + + Type WideType = IntTypesPerSizeLog2[Op.Size + 1]; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.SetAllVector128), new Type[] { WideType })); + + WideType = VectorIntTypesPerSizeLog2[Op.Size + 1]; + + Type[] WideTypes = new Type[] { WideType, WideType }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.And), WideTypes)); + + if (Part == 0) + { + EmitZeroVector(); + } + + //Pack values with signed saturation, the signed saturation shouldn't + //saturate anything since the upper bits were masked off. + Type SseType = Op.Size == 0 ? typeof(Sse2) : typeof(Sse41); + + Context.EmitCall(SseType.GetMethod(nameof(Sse2.PackUnsignedSaturate), WideTypes)); + + if (Part != 0) + { + //For XTN2, we additionally need to discard the upper bits + //of the target register and OR the result with it. + EmitVectorZeroUpper(Context, Op.Rd); + + EmitLdvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + Type NarrowType = VectorUIntTypesPerSizeLog2[Op.Size]; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Or), new Type[] { NarrowType, NarrowType })); + } + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); } - - for (int Index = 0; Index < Elems; Index++) + else { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } - EmitVectorInsertTmp(Context, Part + Index, Op.Size); - } + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); + EmitVectorInsertTmp(Context, Part + Index, Op.Size); + } - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } } } @@ -329,7 +432,8 @@ namespace ChocolArm64.Instruction private static void EmitIntZeroUpperIfNeeded(AILEmitterCtx Context) { - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) + if (Context.CurrOp.RegisterSize == ARegisterSize.Int32 || + Context.CurrOp.RegisterSize == ARegisterSize.SIMD64) { Context.Emit(OpCodes.Conv_U4); Context.Emit(OpCodes.Conv_U8); @@ -394,28 +498,64 @@ namespace ChocolArm64.Instruction { AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - int Words = Op.GetBitsCount() >> 4; - int Pairs = Words >> Op.Size; - - int Base = Part != 0 ? Pairs : 0; - - for (int Index = 0; Index < Pairs; Index++) + if (AOptimizations.UseSse2) { - int Idx = Index << 1; + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rm, Op.Size); - EmitVectorExtractZx(Context, Op.Rn, Base + Index, Op.Size); - EmitVectorExtractZx(Context, Op.Rm, Base + Index, Op.Size); + Type[] Types = new Type[] + { + VectorUIntTypesPerSizeLog2[Op.Size], + VectorUIntTypesPerSizeLog2[Op.Size] + }; - EmitVectorInsertTmp(Context, Idx + 1, Op.Size); - EmitVectorInsertTmp(Context, Idx, Op.Size); + string Name = Part == 0 || (Part != 0 && Op.RegisterSize == ARegisterSize.SIMD64) + ? nameof(Sse2.UnpackLow) + : nameof(Sse2.UnpackHigh); + + Context.EmitCall(typeof(Sse2).GetMethod(Name, Types)); + + if (Op.RegisterSize == ARegisterSize.SIMD64 && Part != 0) + { + Context.EmitLdc_I4(8); + + Type[] ShTypes = new Type[] { VectorUIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightLogical128BitLane), ShTypes)); + } + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64 && Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) + else { - EmitVectorZeroUpper(Context, Op.Rd); + int Words = Op.GetBitsCount() >> 4; + int Pairs = Words >> Op.Size; + + int Base = Part != 0 ? Pairs : 0; + + for (int Index = 0; Index < Pairs; Index++) + { + int Idx = Index << 1; + + EmitVectorExtractZx(Context, Op.Rn, Base + Index, Op.Size); + EmitVectorExtractZx(Context, Op.Rm, Base + Index, Op.Size); + + EmitVectorInsertTmp(Context, Idx + 1, Op.Size); + EmitVectorInsertTmp(Context, Idx, Op.Size); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } } } } diff --git a/ChocolArm64/Instruction/AInstEmitSimdShift.cs b/ChocolArm64/Instruction/AInstEmitSimdShift.cs index 127abf1df8..8918c0e1ba 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdShift.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdShift.cs @@ -3,6 +3,7 @@ using ChocolArm64.State; using ChocolArm64.Translation; using System; using System.Reflection.Emit; +using System.Runtime.Intrinsics.X86; using static ChocolArm64.Instruction.AInstEmitSimdHelper; @@ -31,12 +32,32 @@ namespace ChocolArm64.Instruction { AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - EmitVectorUnaryOpZx(Context, () => + if (AOptimizations.UseSse2 && Op.Size > 0) { + Type[] Types = new Type[] { VectorUIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + Context.EmitLdc_I4(GetImmShl(Op)); - Context.Emit(OpCodes.Shl); - }); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftLeftLogical), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitVectorUnaryOpZx(Context, () => + { + Context.EmitLdc_I4(GetImmShl(Op)); + + Context.Emit(OpCodes.Shl); + }); + } } public static void Shll_V(AILEmitterCtx Context) @@ -167,7 +188,30 @@ namespace ChocolArm64.Instruction public static void Sshr_V(AILEmitterCtx Context) { - EmitShrImmOp(Context, ShrImmFlags.VectorSx); + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + if (AOptimizations.UseSse2 && Op.Size > 0 + && Op.Size < 3) + { + Type[] Types = new Type[] { VectorIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + + EmitLdvecWithSignedCast(Context, Op.Rn, Op.Size); + + Context.EmitLdc_I4(GetImmShr(Op)); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightArithmetic), Types)); + + EmitStvecWithSignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitShrImmOp(Context, ShrImmFlags.VectorSx); + } } public static void Ssra_S(AILEmitterCtx Context) @@ -177,7 +221,33 @@ namespace ChocolArm64.Instruction public static void Ssra_V(AILEmitterCtx Context) { - EmitVectorShrImmOpSx(Context, ShrImmFlags.Accumulate); + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + if (AOptimizations.UseSse2 && Op.Size > 0 + && Op.Size < 3) + { + Type[] TypesSra = new Type[] { VectorIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + Type[] TypesAdd = new Type[] { VectorIntTypesPerSizeLog2[Op.Size], VectorIntTypesPerSizeLog2[Op.Size] }; + + EmitLdvecWithSignedCast(Context, Op.Rd, Op.Size); + EmitLdvecWithSignedCast(Context, Op.Rn, Op.Size); + + Context.EmitLdc_I4(GetImmShr(Op)); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightArithmetic), TypesSra)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Add), TypesAdd)); + + EmitStvecWithSignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitVectorShrImmOpSx(Context, ShrImmFlags.Accumulate); + } } public static void Uqrshrn_S(AILEmitterCtx Context) @@ -239,7 +309,29 @@ namespace ChocolArm64.Instruction public static void Ushr_V(AILEmitterCtx Context) { - EmitShrImmOp(Context, ShrImmFlags.VectorZx); + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + if (AOptimizations.UseSse2 && Op.Size > 0) + { + Type[] Types = new Type[] { VectorUIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + + Context.EmitLdc_I4(GetImmShr(Op)); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightLogical), Types)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitShrImmOp(Context, ShrImmFlags.VectorZx); + } } public static void Usra_S(AILEmitterCtx Context) @@ -249,7 +341,32 @@ namespace ChocolArm64.Instruction public static void Usra_V(AILEmitterCtx Context) { - EmitVectorShrImmOpZx(Context, ShrImmFlags.Accumulate); + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + if (AOptimizations.UseSse2 && Op.Size > 0) + { + Type[] TypesSrl = new Type[] { VectorUIntTypesPerSizeLog2[Op.Size], typeof(byte) }; + Type[] TypesAdd = new Type[] { VectorUIntTypesPerSizeLog2[Op.Size], VectorUIntTypesPerSizeLog2[Op.Size] }; + + EmitLdvecWithUnsignedCast(Context, Op.Rd, Op.Size); + EmitLdvecWithUnsignedCast(Context, Op.Rn, Op.Size); + + Context.EmitLdc_I4(GetImmShr(Op)); + + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.ShiftRightLogical), TypesSrl)); + Context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.Add), TypesAdd)); + + EmitStvecWithUnsignedCast(Context, Op.Rd, Op.Size); + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + else + { + EmitVectorShrImmOpZx(Context, ShrImmFlags.Accumulate); + } } private static void EmitVectorShl(AILEmitterCtx Context, bool Signed) diff --git a/ChocolArm64/Instruction/ASoftFallback.cs b/ChocolArm64/Instruction/ASoftFallback.cs index a7bc108591..3c5c5c4d9e 100644 --- a/ChocolArm64/Instruction/ASoftFallback.cs +++ b/ChocolArm64/Instruction/ASoftFallback.cs @@ -386,7 +386,7 @@ namespace ChocolArm64.Instruction #endregion #region "Count" - public static ulong CountLeadingSigns(ulong Value, int Size) + public static ulong CountLeadingSigns(ulong Value, int Size) // Size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). { Value ^= Value >> 1; @@ -405,9 +405,9 @@ namespace ChocolArm64.Instruction private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; - public static ulong CountLeadingZeros(ulong Value, int Size) + public static ulong CountLeadingZeros(ulong Value, int Size) // Size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). { - if (Value == 0) + if (Value == 0ul) { return (ulong)Size; } @@ -426,12 +426,17 @@ namespace ChocolArm64.Instruction return (ulong)Count; } - public static uint CountSetBits8(uint Value) + public static ulong CountSetBits8(ulong Value) // "Size" is 8 (SIMD&FP Inst.). { - Value = ((Value >> 1) & 0x55) + (Value & 0x55); - Value = ((Value >> 2) & 0x33) + (Value & 0x33); + if (Value == 0xfful) + { + return 8ul; + } - return (Value >> 4) + (Value & 0x0f); + Value = ((Value >> 1) & 0x55ul) + (Value & 0x55ul); + Value = ((Value >> 2) & 0x33ul) + (Value & 0x33ul); + + return (Value >> 4) + (Value & 0x0ful); } #endregion diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs index e3f067ed50..2d9a9f0ebe 100644 --- a/ChocolArm64/Instruction/ASoftFloat.cs +++ b/ChocolArm64/Instruction/ASoftFloat.cs @@ -1,4 +1,7 @@ +using ChocolArm64.State; using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace ChocolArm64.Instruction { @@ -6,13 +9,29 @@ namespace ChocolArm64.Instruction { static ASoftFloat() { + RecipEstimateTable = BuildRecipEstimateTable(); InvSqrtEstimateTable = BuildInvSqrtEstimateTable(); - RecipEstimateTable = BuildRecipEstimateTable(); } private static readonly byte[] RecipEstimateTable; private static readonly byte[] InvSqrtEstimateTable; + private static byte[] BuildRecipEstimateTable() + { + byte[] Table = new byte[256]; + for (ulong index = 0; index < 256; index++) + { + ulong a = index | 0x100; + + a = (a << 1) + 1; + ulong b = 0x80000 / a; + b = (b + 1) >> 1; + + Table[index] = (byte)(b & 0xFF); + } + return Table; + } + private static byte[] BuildInvSqrtEstimateTable() { byte[] Table = new byte[512]; @@ -40,22 +59,75 @@ namespace ChocolArm64.Instruction return Table; } - private static byte[] BuildRecipEstimateTable() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RecipEstimate(float x) { - byte[] Table = new byte[256]; - for (ulong index = 0; index < 256; index++) - { - ulong a = index | 0x100; - - a = (a << 1) + 1; - ulong b = 0x80000 / a; - b = (b + 1) >> 1; - - Table[index] = (byte)(b & 0xFF); - } - return Table; + return (float)RecipEstimate((double)x); } + public static double RecipEstimate(double x) + { + ulong x_bits = (ulong)BitConverter.DoubleToInt64Bits(x); + ulong x_sign = x_bits & 0x8000000000000000; + ulong x_exp = (x_bits >> 52) & 0x7FF; + ulong scaled = x_bits & ((1ul << 52) - 1); + + if (x_exp >= 2045) + { + if (x_exp == 0x7ff && scaled != 0) + { + // NaN + return BitConverter.Int64BitsToDouble((long)(x_bits | 0x0008000000000000)); + } + + // Infinity, or Out of range -> Zero + return BitConverter.Int64BitsToDouble((long)x_sign); + } + + if (x_exp == 0) + { + if (scaled == 0) + { + // Zero -> Infinity + return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7FF0000000000000)); + } + + // Denormal + if ((scaled & (1ul << 51)) == 0) + { + x_exp = ~0ul; + scaled <<= 2; + } + else + { + scaled <<= 1; + } + } + + scaled >>= 44; + scaled &= 0xFF; + + ulong result_exp = (2045 - x_exp) & 0x7FF; + ulong estimate = (ulong)RecipEstimateTable[scaled]; + ulong fraction = estimate << 44; + + if (result_exp == 0) + { + fraction >>= 1; + fraction |= 1ul << 51; + } + else if (result_exp == 0x7FF) + { + result_exp = 0; + fraction >>= 2; + fraction |= 1ul << 50; + } + + ulong result = x_sign | (result_exp << 52) | fraction; + return BitConverter.Int64BitsToDouble((long)result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float InvSqrtEstimate(float x) { return (float)InvSqrtEstimate((double)x); @@ -124,108 +196,6 @@ namespace ChocolArm64.Instruction return BitConverter.Int64BitsToDouble((long)result); } - public static float RecipEstimate(float x) - { - return (float)RecipEstimate((double)x); - } - - public static double RecipEstimate(double x) - { - ulong x_bits = (ulong)BitConverter.DoubleToInt64Bits(x); - ulong x_sign = x_bits & 0x8000000000000000; - ulong x_exp = (x_bits >> 52) & 0x7FF; - ulong scaled = x_bits & ((1ul << 52) - 1); - - if (x_exp >= 2045) - { - if (x_exp == 0x7ff && scaled != 0) - { - // NaN - return BitConverter.Int64BitsToDouble((long)(x_bits | 0x0008000000000000)); - } - - // Infinity, or Out of range -> Zero - return BitConverter.Int64BitsToDouble((long)x_sign); - } - - if (x_exp == 0) - { - if (scaled == 0) - { - // Zero -> Infinity - return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7FF0000000000000)); - } - - // Denormal - if ((scaled & (1ul << 51)) == 0) - { - x_exp = ~0ul; - scaled <<= 2; - } - else - { - scaled <<= 1; - } - } - - scaled >>= 44; - scaled &= 0xFF; - - ulong result_exp = (2045 - x_exp) & 0x7FF; - ulong estimate = (ulong)RecipEstimateTable[scaled]; - ulong fraction = estimate << 44; - - if (result_exp == 0) - { - fraction >>= 1; - fraction |= 1ul << 51; - } - else if (result_exp == 0x7FF) - { - result_exp = 0; - fraction >>= 2; - fraction |= 1ul << 50; - } - - ulong result = x_sign | (result_exp << 52) | fraction; - return BitConverter.Int64BitsToDouble((long)result); - } - - public static float RecipStep(float op1, float op2) - { - return (float)RecipStep((double)op1, (double)op2); - } - - public static double RecipStep(double op1, double op2) - { - op1 = -op1; - - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); - - ulong op1_sign = op1_bits & 0x8000000000000000; - ulong op2_sign = op2_bits & 0x8000000000000000; - ulong op1_other = op1_bits & 0x7FFFFFFFFFFFFFFF; - ulong op2_other = op2_bits & 0x7FFFFFFFFFFFFFFF; - - bool inf1 = op1_other == 0x7FF0000000000000; - bool inf2 = op2_other == 0x7FF0000000000000; - bool zero1 = op1_other == 0; - bool zero2 = op2_other == 0; - - if ((inf1 && zero2) || (zero1 && inf2)) - { - return 2.0; - } - else if (inf1 || inf2) - { - // Infinity - return BitConverter.Int64BitsToDouble((long)(0x7FF0000000000000 | (op1_sign ^ op2_sign))); - } - - return 2.0 + op1 * op2; - } - public static float ConvertHalfToSingle(ushort x) { uint x_sign = (uint)(x >> 15) & 0x0001; @@ -261,277 +231,1457 @@ namespace ChocolArm64.Instruction uint new_exp = (uint)((exponent + 127) & 0xFF) << 23; return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | new_exp | (x_mantissa << 13))); } + } - public static float MaxNum(float op1, float op2) + static class ASoftFloat_32 + { + public static float FPAdd(float Value1, float Value2, AThreadState State) { - uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); - uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPAdd: State.Fpcr = 0x{State.Fpcr:X8}"); - if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op1 = float.NegativeInfinity; - } - else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) - { - op2 = float.NegativeInfinity; + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if (Inf1 && Inf2 && Sign1 == !Sign2) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((Inf1 && !Sign1) || (Inf2 && !Sign2)) + { + Result = FPInfinity(false); + } + else if ((Inf1 && Sign1) || (Inf2 && Sign2)) + { + Result = FPInfinity(true); + } + else if (Zero1 && Zero2 && Sign1 == Sign2) + { + Result = FPZero(Sign1); + } + else + { + Result = Value1 + Value2; + } } - return Max(op1, op2); + return Result; } - public static double MaxNum(double op1, double op2) + public static float FPDiv(float Value1, float Value2, AThreadState State) { - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPDiv: State.Fpcr = 0x{State.Fpcr:X8}"); - if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op1 = double.NegativeInfinity; - } - else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) - { - op2 = double.NegativeInfinity; + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Inf2) || (Zero1 && Zero2)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if (Inf1 || Zero2) + { + Result = FPInfinity(Sign1 ^ Sign2); + + if (!Inf1) FPProcessException(FPExc.DivideByZero, State); + } + else if (Zero1 || Inf2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 / Value2; + } } - return Max(op1, op2); + return Result; } - public static float Max(float op1, float op2) + public static float FPMax(float Value1, float Value2, AThreadState State) { - // Fast path - if (op1 > op2) + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPMax: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - return op1; + if (Value1 > Value2) + { + if (Type1 == FPType.Infinity) + { + Result = FPInfinity(Sign1); + } + else if (Type1 == FPType.Zero) + { + Result = FPZero(Sign1 && Sign2); + } + else + { + Result = Value1; + } + } + else + { + if (Type2 == FPType.Infinity) + { + Result = FPInfinity(Sign2); + } + else if (Type2 == FPType.Zero) + { + Result = FPZero(Sign1 && Sign2); + } + else + { + Result = Value2; + } + } } - if (op1 < op2 || (op1 == op2 && op2 != 0)) - { - return op2; - } - - uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); - uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); - - // Handle NaN cases - if (ProcessNaNs(op1_bits, op2_bits, out uint op_bits)) - { - return BitConverter.Int32BitsToSingle((int)op_bits); - } - - // Return the most positive zero - if ((op1_bits & op2_bits) == 0x80000000u) - { - return BitConverter.Int32BitsToSingle(int.MinValue); - } - - return 0; + return Result; } - public static double Max(double op1, double op2) + public static float FPMaxNum(float Value1, float Value2, AThreadState State) { - // Fast path - if (op1 > op2) + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_32.FPMaxNum: "); + + Value1.FPUnpack(out FPType Type1, out _, out _); + Value2.FPUnpack(out FPType Type2, out _, out _); + + if (Type1 == FPType.QNaN && Type2 != FPType.QNaN) { - return op1; + Value1 = FPInfinity(true); + } + else if (Type1 != FPType.QNaN && Type2 == FPType.QNaN) + { + Value2 = FPInfinity(true); } - if (op1 < op2 || (op1 == op2 && op2 != 0)) - { - return op2; - } - - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); - - // Handle NaN cases - if (ProcessNaNs(op1_bits, op2_bits, out ulong op_bits)) - { - return BitConverter.Int64BitsToDouble((long)op_bits); - } - - // Return the most positive zero - if ((op1_bits & op2_bits) == 0x8000000000000000ul) - { - return BitConverter.Int64BitsToDouble(long.MinValue); - } - - return 0; + return FPMax(Value1, Value2, State); } - public static float MinNum(float op1, float op2) + public static float FPMin(float Value1, float Value2, AThreadState State) { - uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); - uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPMin: State.Fpcr = 0x{State.Fpcr:X8}"); - if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op1 = float.PositiveInfinity; - } - else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) - { - op2 = float.PositiveInfinity; + if (Value1 < Value2) + { + if (Type1 == FPType.Infinity) + { + Result = FPInfinity(Sign1); + } + else if (Type1 == FPType.Zero) + { + Result = FPZero(Sign1 || Sign2); + } + else + { + Result = Value1; + } + } + else + { + if (Type2 == FPType.Infinity) + { + Result = FPInfinity(Sign2); + } + else if (Type2 == FPType.Zero) + { + Result = FPZero(Sign1 || Sign2); + } + else + { + Result = Value2; + } + } } - return Min(op1, op2); + return Result; } - public static double MinNum(double op1, double op2) + public static float FPMinNum(float Value1, float Value2, AThreadState State) { - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_32.FPMinNum: "); - if (IsQNaN(op1_bits) && !IsQNaN(op2_bits)) + Value1.FPUnpack(out FPType Type1, out _, out _); + Value2.FPUnpack(out FPType Type2, out _, out _); + + if (Type1 == FPType.QNaN && Type2 != FPType.QNaN) { - op1 = double.PositiveInfinity; + Value1 = FPInfinity(false); } - else if (!IsQNaN(op1_bits) && IsQNaN(op2_bits)) + else if (Type1 != FPType.QNaN && Type2 == FPType.QNaN) { - op2 = double.PositiveInfinity; + Value2 = FPInfinity(false); } - return Min(op1, op2); + return FPMin(Value1, Value2, State); } - public static float Min(float op1, float op2) + public static float FPMul(float Value1, float Value2, AThreadState State) { - // Fast path - if (op1 < op2) + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPMul: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - return op1; + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else if (Zero1 || Zero2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 * Value2; + } } - if (op1 > op2 || (op1 == op2 && op2 != 0)) - { - return op2; - } - - uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); - uint op2_bits = (uint)BitConverter.SingleToInt32Bits(op2); - - // Handle NaN cases - if (ProcessNaNs(op1_bits, op2_bits, out uint op_bits)) - { - return BitConverter.Int32BitsToSingle((int)op_bits); - } - - // Return the most negative zero - if ((op1_bits | op2_bits) == 0x80000000u) - { - return BitConverter.Int32BitsToSingle(int.MinValue); - } - - return 0; + return Result; } - public static double Min(double op1, double op2) + public static float FPMulAdd(float ValueA, float Value1, float Value2, AThreadState State) { - // Fast path - if (op1 < op2) + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPMulAdd: State.Fpcr = 0x{State.Fpcr:X8}"); + + ValueA = ValueA.FPUnpack(out FPType TypeA, out bool SignA, out uint Addend); + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + float Result = FPProcessNaNs3(TypeA, Type1, Type2, Addend, Op1, Op2, State, out bool Done); + + if (TypeA == FPType.QNaN && ((Inf1 && Zero2) || (Zero1 && Inf2))) { - return op1; + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); } - if (op1 > op2 || (op1 == op2 && op2 != 0)) + if (!Done) { - return op2; + bool InfA = TypeA == FPType.Infinity; bool ZeroA = TypeA == FPType.Zero; + + bool SignP = Sign1 ^ Sign2; + bool InfP = Inf1 || Inf2; + bool ZeroP = Zero1 || Zero2; + + if ((Inf1 && Zero2) || (Zero1 && Inf2) || (InfA && InfP && SignA != SignP)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((InfA && !SignA) || (InfP && !SignP)) + { + Result = FPInfinity(false); + } + else if ((InfA && SignA) || (InfP && SignP)) + { + Result = FPInfinity(true); + } + else if (ZeroA && ZeroP && SignA == SignP) + { + Result = FPZero(SignA); + } + else + { + // TODO: When available, use: T MathF.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = ValueA + (Value1 * Value2); + } } - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); - - // Handle NaN cases - if (ProcessNaNs(op1_bits, op2_bits, out ulong op_bits)) - { - return BitConverter.Int64BitsToDouble((long)op_bits); - } - - // Return the most negative zero - if ((op1_bits | op2_bits) == 0x8000000000000000ul) - { - return BitConverter.Int64BitsToDouble(long.MinValue); - } - - return 0; + return Result; } - private static bool ProcessNaNs(uint op1_bits, uint op2_bits, out uint op_bits) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float FPMulSub(float ValueA, float Value1, float Value2, AThreadState State) { - if (IsSNaN(op1_bits)) + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_32.FPMulSub: "); + + Value1 = Value1.FPNeg(); + + return FPMulAdd(ValueA, Value1, Value2, State); + } + + public static float FPMulX(float Value1, float Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPMulX: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op_bits = op1_bits | (1u << 22); // op1 is SNaN, return QNaN op1 + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPTwo(Sign1 ^ Sign2); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else if (Zero1 || Zero2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 * Value2; + } } - else if (IsSNaN(op2_bits)) + + return Result; + } + + public static float FPRecipStepFused(float Value1, float Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPRecipStepFused: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPNeg(); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op_bits = op2_bits | (1u << 22); // op2 is SNaN, return QNaN op2 + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPTwo(false); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else + { + // TODO: When available, use: T MathF.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = 2f + (Value1 * Value2); + } } - else if (IsQNaN(op1_bits)) + + return Result; + } + + public static float FPRecpX(float Value, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPRecpX: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value.FPUnpack(out FPType Type, out bool Sign, out uint Op); + + float Result; + + if (Type == FPType.SNaN || Type == FPType.QNaN) { - op_bits = op1_bits; // op1 is QNaN, return QNaN op1 - } - else if (IsQNaN(op2_bits)) - { - op_bits = op2_bits; // op2 is QNaN, return QNaN op2 + Result = FPProcessNaN(Type, Op, State); } else { - op_bits = 0; + uint NotExp = (~Op >> 23) & 0xFFu; + uint MaxExp = 0xFEu; - return false; + Result = BitConverter.Int32BitsToSingle( + (int)((Sign ? 1u : 0u) << 31 | (NotExp == 0xFFu ? MaxExp : NotExp) << 23)); } - return true; + return Result; } - private static bool ProcessNaNs(ulong op1_bits, ulong op2_bits, out ulong op_bits) + public static float FPRSqrtStepFused(float Value1, float Value2, AThreadState State) { - if (IsSNaN(op1_bits)) + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPRSqrtStepFused: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPNeg(); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) { - op_bits = op1_bits | (1ul << 51); // op1 is SNaN, return QNaN op1 + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPOnePointFive(false); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else + { + // TODO: When available, use: T MathF.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = (3f + (Value1 * Value2)) / 2f; + } } - else if (IsSNaN(op2_bits)) + + return Result; + } + + public static float FPSqrt(float Value, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPSqrt: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value = Value.FPUnpack(out FPType Type, out bool Sign, out uint Op); + + float Result; + + if (Type == FPType.SNaN || Type == FPType.QNaN) { - op_bits = op2_bits | (1ul << 51); // op2 is SNaN, return QNaN op2 + Result = FPProcessNaN(Type, Op, State); } - else if (IsQNaN(op1_bits)) + else if (Type == FPType.Zero) { - op_bits = op1_bits; // op1 is QNaN, return QNaN op1 + Result = FPZero(Sign); } - else if (IsQNaN(op2_bits)) + else if (Type == FPType.Infinity && !Sign) { - op_bits = op2_bits; // op2 is QNaN, return QNaN op2 + Result = FPInfinity(Sign); + } + else if (Sign) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); } else { - op_bits = 0; - - return false; + Result = MathF.Sqrt(Value); } - return true; + return Result; } - private static bool IsQNaN(uint op_bits) + public static float FPSub(float Value1, float Value2, AThreadState State) { - return (op_bits & 0x007FFFFF) != 0 && - (op_bits & 0x7FC00000) == 0x7FC00000; + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_32.FPSub: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out uint Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out uint Op2); + + float Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if (Inf1 && Inf2 && Sign1 == Sign2) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((Inf1 && !Sign1) || (Inf2 && Sign2)) + { + Result = FPInfinity(false); + } + else if ((Inf1 && Sign1) || (Inf2 && !Sign2)) + { + Result = FPInfinity(true); + } + else if (Zero1 && Zero2 && Sign1 == !Sign2) + { + Result = FPZero(Sign1); + } + else + { + Result = Value1 - Value2; + } + } + + return Result; } - private static bool IsQNaN(ulong op_bits) + private enum FPType { - return (op_bits & 0x000FFFFFFFFFFFFF) != 0 && - (op_bits & 0x7FF8000000000000) == 0x7FF8000000000000; + Nonzero, + Zero, + Infinity, + QNaN, + SNaN } - private static bool IsSNaN(uint op_bits) + private enum FPExc { - return (op_bits & 0x007FFFFF) != 0 && - (op_bits & 0x7FC00000) == 0x7F800000; + InvalidOp, + DivideByZero, + Overflow, + Underflow, + Inexact, + InputDenorm = 7 } - private static bool IsSNaN(ulong op_bits) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPDefaultNaN() { - return (op_bits & 0x000FFFFFFFFFFFFF) != 0 && - (op_bits & 0x7FF8000000000000) == 0x7FF0000000000000; + return -float.NaN; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPInfinity(bool Sign) + { + return Sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPZero(bool Sign) + { + return Sign ? -0f : +0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPTwo(bool Sign) + { + return Sign ? -2f : +2f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPOnePointFive(bool Sign) + { + return Sign ? -1.5f : +1.5f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float FPNeg(this float Value) + { + return -Value; + } + + private static float FPUnpack(this float Value, out FPType Type, out bool Sign, out uint ValueBits) + { + ValueBits = (uint)BitConverter.SingleToInt32Bits(Value); + + Sign = (~ValueBits & 0x80000000u) == 0u; + + if ((ValueBits & 0x7F800000u) == 0u) + { + if ((ValueBits & 0x007FFFFFu) == 0u) + { + Type = FPType.Zero; + } + else + { + Type = FPType.Nonzero; + } + } + else if ((~ValueBits & 0x7F800000u) == 0u) + { + if ((ValueBits & 0x007FFFFFu) == 0u) + { + Type = FPType.Infinity; + } + else + { + Type = (~ValueBits & 0x00400000u) == 0u + ? FPType.QNaN + : FPType.SNaN; + + return FPZero(Sign); + } + } + else + { + Type = FPType.Nonzero; + } + + return Value; + } + + private static float FPProcessNaNs( + FPType Type1, + FPType Type2, + uint Op1, + uint Op2, + AThreadState State, + out bool Done) + { + Done = true; + + if (Type1 == FPType.SNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.SNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type1 == FPType.QNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.QNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + + Done = false; + + return FPZero(false); + } + + private static float FPProcessNaNs3( + FPType Type1, + FPType Type2, + FPType Type3, + uint Op1, + uint Op2, + uint Op3, + AThreadState State, + out bool Done) + { + Done = true; + + if (Type1 == FPType.SNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.SNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type3 == FPType.SNaN) + { + return FPProcessNaN(Type3, Op3, State); + } + else if (Type1 == FPType.QNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.QNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type3 == FPType.QNaN) + { + return FPProcessNaN(Type3, Op3, State); + } + + Done = false; + + return FPZero(false); + } + + private static float FPProcessNaN(FPType Type, uint Op, AThreadState State) + { + const int DNBit = 25; // Default NaN mode control bit. + + if (Type == FPType.SNaN) + { + Op |= 1u << 22; + + FPProcessException(FPExc.InvalidOp, State); + } + + if ((State.Fpcr & (1 << DNBit)) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int32BitsToSingle((int)Op); + } + + private static void FPProcessException(FPExc Exc, AThreadState State) + { + int Enable = (int)Exc + 8; + + if ((State.Fpcr & (1 << Enable)) != 0) + { + throw new NotImplementedException("floating-point trap handling"); + } + else + { + State.Fpsr |= 1 << (int)Exc; + } } } -} \ No newline at end of file + + static class ASoftFloat_64 + { + public static double FPAdd(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPAdd: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if (Inf1 && Inf2 && Sign1 == !Sign2) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((Inf1 && !Sign1) || (Inf2 && !Sign2)) + { + Result = FPInfinity(false); + } + else if ((Inf1 && Sign1) || (Inf2 && Sign2)) + { + Result = FPInfinity(true); + } + else if (Zero1 && Zero2 && Sign1 == Sign2) + { + Result = FPZero(Sign1); + } + else + { + Result = Value1 + Value2; + } + } + + return Result; + } + + public static double FPDiv(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPDiv: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Inf2) || (Zero1 && Zero2)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if (Inf1 || Zero2) + { + Result = FPInfinity(Sign1 ^ Sign2); + + if (!Inf1) FPProcessException(FPExc.DivideByZero, State); + } + else if (Zero1 || Inf2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 / Value2; + } + } + + return Result; + } + + public static double FPMax(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPMax: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + if (Value1 > Value2) + { + if (Type1 == FPType.Infinity) + { + Result = FPInfinity(Sign1); + } + else if (Type1 == FPType.Zero) + { + Result = FPZero(Sign1 && Sign2); + } + else + { + Result = Value1; + } + } + else + { + if (Type2 == FPType.Infinity) + { + Result = FPInfinity(Sign2); + } + else if (Type2 == FPType.Zero) + { + Result = FPZero(Sign1 && Sign2); + } + else + { + Result = Value2; + } + } + } + + return Result; + } + + public static double FPMaxNum(double Value1, double Value2, AThreadState State) + { + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_64.FPMaxNum: "); + + Value1.FPUnpack(out FPType Type1, out _, out _); + Value2.FPUnpack(out FPType Type2, out _, out _); + + if (Type1 == FPType.QNaN && Type2 != FPType.QNaN) + { + Value1 = FPInfinity(true); + } + else if (Type1 != FPType.QNaN && Type2 == FPType.QNaN) + { + Value2 = FPInfinity(true); + } + + return FPMax(Value1, Value2, State); + } + + public static double FPMin(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPMin: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + if (Value1 < Value2) + { + if (Type1 == FPType.Infinity) + { + Result = FPInfinity(Sign1); + } + else if (Type1 == FPType.Zero) + { + Result = FPZero(Sign1 || Sign2); + } + else + { + Result = Value1; + } + } + else + { + if (Type2 == FPType.Infinity) + { + Result = FPInfinity(Sign2); + } + else if (Type2 == FPType.Zero) + { + Result = FPZero(Sign1 || Sign2); + } + else + { + Result = Value2; + } + } + } + + return Result; + } + + public static double FPMinNum(double Value1, double Value2, AThreadState State) + { + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_64.FPMinNum: "); + + Value1.FPUnpack(out FPType Type1, out _, out _); + Value2.FPUnpack(out FPType Type2, out _, out _); + + if (Type1 == FPType.QNaN && Type2 != FPType.QNaN) + { + Value1 = FPInfinity(false); + } + else if (Type1 != FPType.QNaN && Type2 == FPType.QNaN) + { + Value2 = FPInfinity(false); + } + + return FPMin(Value1, Value2, State); + } + + public static double FPMul(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPMul: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else if (Zero1 || Zero2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 * Value2; + } + } + + return Result; + } + + public static double FPMulAdd(double ValueA, double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPMulAdd: State.Fpcr = 0x{State.Fpcr:X8}"); + + ValueA = ValueA.FPUnpack(out FPType TypeA, out bool SignA, out ulong Addend); + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + double Result = FPProcessNaNs3(TypeA, Type1, Type2, Addend, Op1, Op2, State, out bool Done); + + if (TypeA == FPType.QNaN && ((Inf1 && Zero2) || (Zero1 && Inf2))) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + + if (!Done) + { + bool InfA = TypeA == FPType.Infinity; bool ZeroA = TypeA == FPType.Zero; + + bool SignP = Sign1 ^ Sign2; + bool InfP = Inf1 || Inf2; + bool ZeroP = Zero1 || Zero2; + + if ((Inf1 && Zero2) || (Zero1 && Inf2) || (InfA && InfP && SignA != SignP)) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((InfA && !SignA) || (InfP && !SignP)) + { + Result = FPInfinity(false); + } + else if ((InfA && SignA) || (InfP && SignP)) + { + Result = FPInfinity(true); + } + else if (ZeroA && ZeroP && SignA == SignP) + { + Result = FPZero(SignA); + } + else + { + // TODO: When available, use: T Math.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = ValueA + (Value1 * Value2); + } + } + + return Result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double FPMulSub(double ValueA, double Value1, double Value2, AThreadState State) + { + Debug.WriteIf(State.Fpcr != 0, "ASoftFloat_64.FPMulSub: "); + + Value1 = Value1.FPNeg(); + + return FPMulAdd(ValueA, Value1, Value2, State); + } + + public static double FPMulX(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPMulX: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPTwo(Sign1 ^ Sign2); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else if (Zero1 || Zero2) + { + Result = FPZero(Sign1 ^ Sign2); + } + else + { + Result = Value1 * Value2; + } + } + + return Result; + } + + public static double FPRecipStepFused(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPRecipStepFused: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPNeg(); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPTwo(false); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else + { + // TODO: When available, use: T Math.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = 2d + (Value1 * Value2); + } + } + + return Result; + } + + public static double FPRecpX(double Value, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPRecpX: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value.FPUnpack(out FPType Type, out bool Sign, out ulong Op); + + double Result; + + if (Type == FPType.SNaN || Type == FPType.QNaN) + { + Result = FPProcessNaN(Type, Op, State); + } + else + { + ulong NotExp = (~Op >> 52) & 0x7FFul; + ulong MaxExp = 0x7FEul; + + Result = BitConverter.Int64BitsToDouble( + (long)((Sign ? 1ul : 0ul) << 63 | (NotExp == 0x7FFul ? MaxExp : NotExp) << 52)); + } + + return Result; + } + + public static double FPRSqrtStepFused(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPRSqrtStepFused: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPNeg(); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if ((Inf1 && Zero2) || (Zero1 && Inf2)) + { + Result = FPOnePointFive(false); + } + else if (Inf1 || Inf2) + { + Result = FPInfinity(Sign1 ^ Sign2); + } + else + { + // TODO: When available, use: T Math.FusedMultiplyAdd(T, T, T); + // https://github.com/dotnet/corefx/issues/31903 + + Result = (3d + (Value1 * Value2)) / 2d; + } + } + + return Result; + } + + public static double FPSqrt(double Value, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPSqrt: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value = Value.FPUnpack(out FPType Type, out bool Sign, out ulong Op); + + double Result; + + if (Type == FPType.SNaN || Type == FPType.QNaN) + { + Result = FPProcessNaN(Type, Op, State); + } + else if (Type == FPType.Zero) + { + Result = FPZero(Sign); + } + else if (Type == FPType.Infinity && !Sign) + { + Result = FPInfinity(Sign); + } + else if (Sign) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else + { + Result = Math.Sqrt(Value); + } + + return Result; + } + + public static double FPSub(double Value1, double Value2, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat_64.FPSub: State.Fpcr = 0x{State.Fpcr:X8}"); + + Value1 = Value1.FPUnpack(out FPType Type1, out bool Sign1, out ulong Op1); + Value2 = Value2.FPUnpack(out FPType Type2, out bool Sign2, out ulong Op2); + + double Result = FPProcessNaNs(Type1, Type2, Op1, Op2, State, out bool Done); + + if (!Done) + { + bool Inf1 = Type1 == FPType.Infinity; bool Zero1 = Type1 == FPType.Zero; + bool Inf2 = Type2 == FPType.Infinity; bool Zero2 = Type2 == FPType.Zero; + + if (Inf1 && Inf2 && Sign1 == Sign2) + { + Result = FPDefaultNaN(); + + FPProcessException(FPExc.InvalidOp, State); + } + else if ((Inf1 && !Sign1) || (Inf2 && Sign2)) + { + Result = FPInfinity(false); + } + else if ((Inf1 && Sign1) || (Inf2 && !Sign2)) + { + Result = FPInfinity(true); + } + else if (Zero1 && Zero2 && Sign1 == !Sign2) + { + Result = FPZero(Sign1); + } + else + { + Result = Value1 - Value2; + } + } + + return Result; + } + + private enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN + } + + private enum FPExc + { + InvalidOp, + DivideByZero, + Overflow, + Underflow, + Inexact, + InputDenorm = 7 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPDefaultNaN() + { + return -double.NaN; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPInfinity(bool Sign) + { + return Sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPZero(bool Sign) + { + return Sign ? -0d : +0d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPTwo(bool Sign) + { + return Sign ? -2d : +2d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPOnePointFive(bool Sign) + { + return Sign ? -1.5d : +1.5d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double FPNeg(this double Value) + { + return -Value; + } + + private static double FPUnpack(this double Value, out FPType Type, out bool Sign, out ulong ValueBits) + { + ValueBits = (ulong)BitConverter.DoubleToInt64Bits(Value); + + Sign = (~ValueBits & 0x8000000000000000ul) == 0ul; + + if ((ValueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((ValueBits & 0x000FFFFFFFFFFFFFul) == 0ul) + { + Type = FPType.Zero; + } + else + { + Type = FPType.Nonzero; + } + } + else if ((~ValueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((ValueBits & 0x000FFFFFFFFFFFFFul) == 0ul) + { + Type = FPType.Infinity; + } + else + { + Type = (~ValueBits & 0x0008000000000000ul) == 0ul + ? FPType.QNaN + : FPType.SNaN; + + return FPZero(Sign); + } + } + else + { + Type = FPType.Nonzero; + } + + return Value; + } + + private static double FPProcessNaNs( + FPType Type1, + FPType Type2, + ulong Op1, + ulong Op2, + AThreadState State, + out bool Done) + { + Done = true; + + if (Type1 == FPType.SNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.SNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type1 == FPType.QNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.QNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + + Done = false; + + return FPZero(false); + } + + private static double FPProcessNaNs3( + FPType Type1, + FPType Type2, + FPType Type3, + ulong Op1, + ulong Op2, + ulong Op3, + AThreadState State, + out bool Done) + { + Done = true; + + if (Type1 == FPType.SNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.SNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type3 == FPType.SNaN) + { + return FPProcessNaN(Type3, Op3, State); + } + else if (Type1 == FPType.QNaN) + { + return FPProcessNaN(Type1, Op1, State); + } + else if (Type2 == FPType.QNaN) + { + return FPProcessNaN(Type2, Op2, State); + } + else if (Type3 == FPType.QNaN) + { + return FPProcessNaN(Type3, Op3, State); + } + + Done = false; + + return FPZero(false); + } + + private static double FPProcessNaN(FPType Type, ulong Op, AThreadState State) + { + const int DNBit = 25; // Default NaN mode control bit. + + if (Type == FPType.SNaN) + { + Op |= 1ul << 51; + + FPProcessException(FPExc.InvalidOp, State); + } + + if ((State.Fpcr & (1 << DNBit)) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int64BitsToDouble((long)Op); + } + + private static void FPProcessException(FPExc Exc, AThreadState State) + { + int Enable = (int)Exc + 8; + + if ((State.Fpcr & (1 << Enable)) != 0) + { + throw new NotImplementedException("floating-point trap handling"); + } + else + { + State.Fpsr |= 1 << (int)Exc; + } + } + } +} diff --git a/ChocolArm64/Instruction/AVectorHelper.cs b/ChocolArm64/Instruction/AVectorHelper.cs index 3e4452abbc..7f9d98cd83 100644 --- a/ChocolArm64/Instruction/AVectorHelper.cs +++ b/ChocolArm64/Instruction/AVectorHelper.cs @@ -227,7 +227,16 @@ namespace ChocolArm64.Instruction [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double VectorExtractDouble(Vector128 Vector, byte Index) { - return BitConverter.Int64BitsToDouble(VectorExtractIntSx(Vector, Index, 3)); + if (Sse41.IsSupported) + { + return BitConverter.Int64BitsToDouble(Sse41.Extract(Sse.StaticCast(Vector), Index)); + } + else if (Sse2.IsSupported) + { + return BitConverter.Int64BitsToDouble((long)VectorExtractIntZx(Vector, Index, 3)); + } + + throw new PlatformNotSupportedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -235,41 +244,49 @@ namespace ChocolArm64.Instruction { if (Sse41.IsSupported) { - switch (Size) + if (Size == 0) { - case 0: - return (sbyte)Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 1: - return (short)Sse2.Extract(Sse.StaticCast(Vector), Index); - - case 2: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 3: - return Sse41.Extract(Sse.StaticCast(Vector), Index); + return (sbyte)Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 1) + { + return (short)Sse2.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 2) + { + return Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 3) + { + return Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); } - - throw new ArgumentOutOfRangeException(nameof(Size)); } else if (Sse2.IsSupported) { - switch (Size) + if (Size == 0) { - case 0: - return (sbyte)VectorExtractIntZx(Vector, Index, Size); - - case 1: - return (short)VectorExtractIntZx(Vector, Index, Size); - - case 2: - return (int)VectorExtractIntZx(Vector, Index, Size); - - case 3: - return (long)VectorExtractIntZx(Vector, Index, Size); + return (sbyte)VectorExtractIntZx(Vector, Index, Size); + } + else if (Size == 1) + { + return (short)VectorExtractIntZx(Vector, Index, Size); + } + else if (Size == 2) + { + return (int)VectorExtractIntZx(Vector, Index, Size); + } + else if (Size == 3) + { + return (long)VectorExtractIntZx(Vector, Index, Size); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); } - - throw new ArgumentOutOfRangeException(nameof(Size)); } throw new PlatformNotSupportedException(); @@ -280,22 +297,26 @@ namespace ChocolArm64.Instruction { if (Sse41.IsSupported) { - switch (Size) + if (Size == 0) { - case 0: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 1: - return Sse2.Extract(Sse.StaticCast(Vector), Index); - - case 2: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 3: - return Sse41.Extract(Sse.StaticCast(Vector), Index); + return Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 1) + { + return Sse2.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 2) + { + return Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else if (Size == 3) + { + return Sse41.Extract(Sse.StaticCast(Vector), Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); } - - throw new ArgumentOutOfRangeException(nameof(Size)); } else if (Sse2.IsSupported) { @@ -305,35 +326,35 @@ namespace ChocolArm64.Instruction ushort Value = Sse2.Extract(Sse.StaticCast(Vector), (byte)ShortIdx); - switch (Size) + if (Size == 0) { - case 0: - return (byte)(Value >> (Index & 1) * 8); - - case 1: - return Value; - - case 2: - case 3: - { - ushort Value1 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 1)); - - if (Size == 2) - { - return (uint)(Value | (Value1 << 16)); - } - - ushort Value2 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 2)); - ushort Value3 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 3)); - - return ((ulong)Value << 0) | - ((ulong)Value1 << 16) | - ((ulong)Value2 << 32) | - ((ulong)Value3 << 48); - } + return (byte)(Value >> (Index & 1) * 8); } + else if (Size == 1) + { + return Value; + } + else if (Size == 2 || Size == 3) + { + ushort Value1 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 1)); - throw new ArgumentOutOfRangeException(nameof(Size)); + if (Size == 2) + { + return (uint)(Value | (Value1 << 16)); + } + + ushort Value2 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 2)); + ushort Value3 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 3)); + + return ((ulong)Value << 0) | + ((ulong)Value1 << 16) | + ((ulong)Value2 << 32) | + ((ulong)Value3 << 48); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } } throw new PlatformNotSupportedException(); @@ -370,22 +391,26 @@ namespace ChocolArm64.Instruction { if (Sse41.IsSupported) { - switch (Size) + if (Size == 0) { - case 0: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (byte)Value, Index)); - - case 1: - return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); - - case 2: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (uint)Value, Index)); - - case 3: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), Value, Index)); + return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (byte)Value, Index)); + } + else if (Size == 1) + { + return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); + } + else if (Size == 2) + { + return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (uint)Value, Index)); + } + else if (Size == 3) + { + return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), Value, Index)); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); } - - throw new ArgumentOutOfRangeException(nameof(Size)); } else if (Sse2.IsSupported) { @@ -395,41 +420,39 @@ namespace ChocolArm64.Instruction ? Index >> 1 : Index << (Size - 1); - switch (Size) + if (Size == 0) { - case 0: - { - ushort ShortVal = Sse2.Extract(Sse.StaticCast(Vector), (byte)ShortIdx); + ushort ShortVal = Sse2.Extract(Sse.StaticCast(Vector), (byte)ShortIdx); - int Shift = (Index & 1) * 8; + int Shift = (Index & 1) * 8; - ShortVal &= (ushort)(0xff00 >> Shift); + ShortVal &= (ushort)(0xff00 >> Shift); - ShortVal |= (ushort)((byte)Value << Shift); + ShortVal |= (ushort)((byte)Value << Shift); - return Sse.StaticCast(Sse2.Insert(ShortVector, ShortVal, (byte)ShortIdx)); - } - - case 1: - return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); - - case 2: - case 3: - { - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 0), (byte)(ShortIdx + 0)); - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 16), (byte)(ShortIdx + 1)); - - if (Size == 3) - { - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 32), (byte)(ShortIdx + 2)); - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 48), (byte)(ShortIdx + 3)); - } - - return Sse.StaticCast(ShortVector); - } + return Sse.StaticCast(Sse2.Insert(ShortVector, ShortVal, (byte)ShortIdx)); } + else if (Size == 1) + { + return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); + } + else if (Size == 2 || Size == 3) + { + ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 0), (byte)(ShortIdx + 0)); + ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 16), (byte)(ShortIdx + 1)); - throw new ArgumentOutOfRangeException(nameof(Size)); + if (Size == 3) + { + ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 32), (byte)(ShortIdx + 2)); + ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 48), (byte)(ShortIdx + 3)); + } + + return Sse.StaticCast(ShortVector); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } } throw new PlatformNotSupportedException(); @@ -440,7 +463,29 @@ namespace ChocolArm64.Instruction { if (Sse41.IsSupported) { - return Sse41.Insert(Vector, Value, (byte)(Index << 4)); + //Note: The if/else if is necessary to enable the JIT to + //produce a single INSERTPS instruction instead of the + //jump table fallback. + if (Index == 0) + { + return Sse41.Insert(Vector, Value, 0x00); + } + else if (Index == 1) + { + return Sse41.Insert(Vector, Value, 0x10); + } + else if (Index == 2) + { + return Sse41.Insert(Vector, Value, 0x20); + } + else if (Index == 3) + { + return Sse41.Insert(Vector, Value, 0x30); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } } else if (Sse2.IsSupported) { @@ -460,6 +505,79 @@ namespace ChocolArm64.Instruction throw new PlatformNotSupportedException(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Sse41VectorInsertScalarSingle(float Value, Vector128 Vector) + { + //Note: 0b1110 is the mask to zero the upper bits. + return Sse41.Insert(Vector, Value, 0b1110); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSByteZero() + { + if (Sse2.IsSupported) + { + return Sse2.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorInt16Zero() + { + if (Sse2.IsSupported) + { + return Sse2.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorInt32Zero() + { + if (Sse2.IsSupported) + { + return Sse2.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorInt64Zero() + { + if (Sse2.IsSupported) + { + return Sse2.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSingleZero() + { + if (Sse.IsSupported) + { + return Sse.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorDoubleZero() + { + if (Sse2.IsSupported) + { + return Sse2.SetZeroVector128(); + } + + throw new PlatformNotSupportedException(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 VectorZero32_128(Vector128 Vector) { @@ -515,6 +633,50 @@ namespace ChocolArm64.Instruction throw new PlatformNotSupportedException(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSingleToByte(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSingleToUInt16(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSingleToUInt32(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorSingleToUInt64(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 VectorSingleToDouble(Vector128 Vector) { @@ -570,6 +732,50 @@ namespace ChocolArm64.Instruction throw new PlatformNotSupportedException(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorByteToSingle(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorUInt16ToSingle(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorUInt32ToSingle(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 VectorUInt64ToSingle(Vector128 Vector) + { + if (Sse.IsSupported) + { + return Sse.StaticCast(Vector); + } + + throw new PlatformNotSupportedException(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 VectorDoubleToSingle(Vector128 Vector) { diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs index 2cb9b16c26..bb6a2b549c 100644 --- a/ChocolArm64/Memory/AMemory.cs +++ b/ChocolArm64/Memory/AMemory.cs @@ -232,7 +232,7 @@ namespace ChocolArm64.Memory } } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector32(long Position) { if (Sse.IsSupported) @@ -245,7 +245,7 @@ namespace ChocolArm64.Memory } } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector64(long Position) { if (Sse2.IsSupported) @@ -365,7 +365,7 @@ namespace ChocolArm64.Memory } } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVector32(long Position, Vector128 Value) { if (Sse.IsSupported) @@ -378,7 +378,7 @@ namespace ChocolArm64.Memory } } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVector64(long Position, Vector128 Value) { if (Sse2.IsSupported) diff --git a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs index d7a2a77751..9a75c5685e 100644 --- a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs @@ -182,7 +182,10 @@ namespace Ryujinx.Audio.OpenAL { foreach (Track Td in Tracks.Values) { - Td.CallReleaseCallbackIfNeeded(); + lock (Td) + { + Td.CallReleaseCallbackIfNeeded(); + } } //If it's not slept it will waste cycles. diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj index 2cd38add9b..3fc6117096 100644 --- a/Ryujinx.Audio/Ryujinx.Audio.csproj +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/Ryujinx.HLE/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs similarity index 90% rename from Ryujinx.HLE/Logging/LogClass.cs rename to Ryujinx.Common/Logging/LogClass.cs index 95cae7e0ab..97ffb31477 100644 --- a/Ryujinx.HLE/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { public enum LogClass { @@ -22,6 +22,8 @@ namespace Ryujinx.HLE.Logging ServiceFriend, ServiceFs, ServiceHid, + ServiceIrs, + ServiceLdr, ServiceLm, ServiceMm, ServiceNfp, diff --git a/Ryujinx.HLE/Logging/LogEventArgs.cs b/Ryujinx.Common/Logging/LogEventArgs.cs similarity index 92% rename from Ryujinx.HLE/Logging/LogEventArgs.cs rename to Ryujinx.Common/Logging/LogEventArgs.cs index 647cf71319..7a479b71c7 100644 --- a/Ryujinx.HLE/Logging/LogEventArgs.cs +++ b/Ryujinx.Common/Logging/LogEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { public class LogEventArgs : EventArgs { diff --git a/Ryujinx.HLE/Logging/LogLevel.cs b/Ryujinx.Common/Logging/LogLevel.cs similarity index 77% rename from Ryujinx.HLE/Logging/LogLevel.cs rename to Ryujinx.Common/Logging/LogLevel.cs index 971333e60a..ba3fa99f4d 100644 --- a/Ryujinx.HLE/Logging/LogLevel.cs +++ b/Ryujinx.Common/Logging/LogLevel.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { public enum LogLevel { diff --git a/Ryujinx.HLE/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs similarity index 57% rename from Ryujinx.HLE/Logging/Logger.cs rename to Ryujinx.Common/Logging/Logger.cs index 5376b253a3..6422f11313 100644 --- a/Ryujinx.HLE/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -2,18 +2,18 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { - public class Logger + public static class Logger { - private bool[] EnabledLevels; - private bool[] EnabledClasses; + private static bool[] EnabledLevels; + private static bool[] EnabledClasses; - public event EventHandler Updated; + public static event EventHandler Updated; - private Stopwatch Time; + private static Stopwatch Time; - public Logger() + static Logger() { EnabledLevels = new bool[Enum.GetNames(typeof(LogLevel)).Length]; EnabledClasses = new bool[Enum.GetNames(typeof(LogClass)).Length]; @@ -33,50 +33,50 @@ namespace Ryujinx.HLE.Logging Time.Start(); } - public void SetEnable(LogLevel Level, bool Enabled) + public static void SetEnable(LogLevel Level, bool Enabled) { EnabledLevels[(int)Level] = Enabled; } - public void SetEnable(LogClass Class, bool Enabled) + public static void SetEnable(LogClass Class, bool Enabled) { EnabledClasses[(int)Class] = Enabled; } - internal void PrintDebug(LogClass Class, string Message, [CallerMemberName] string Caller = "") + public static void PrintDebug(LogClass Class, string Message, [CallerMemberName] string Caller = "") { Print(LogLevel.Debug, Class, GetFormattedMessage(Class, Message, Caller)); } - internal void PrintStub(LogClass Class, string Message, [CallerMemberName] string Caller = "") + public static void PrintStub(LogClass Class, string Message, [CallerMemberName] string Caller = "") { Print(LogLevel.Stub, Class, GetFormattedMessage(Class, Message, Caller)); } - internal void PrintInfo(LogClass Class, string Message, [CallerMemberName] string Caller = "") + public static void PrintInfo(LogClass Class, string Message, [CallerMemberName] string Caller = "") { Print(LogLevel.Info, Class, GetFormattedMessage(Class, Message, Caller)); } - internal void PrintWarning(LogClass Class, string Message, [CallerMemberName] string Caller = "") + public static void PrintWarning(LogClass Class, string Message, [CallerMemberName] string Caller = "") { Print(LogLevel.Warning, Class, GetFormattedMessage(Class, Message, Caller)); } - internal void PrintError(LogClass Class, string Message, [CallerMemberName] string Caller = "") + public static void PrintError(LogClass Class, string Message, [CallerMemberName] string Caller = "") { Print(LogLevel.Error, Class, GetFormattedMessage(Class, Message, Caller)); } - private void Print(LogLevel Level, LogClass Class, string Message) + private static void Print(LogLevel Level, LogClass Class, string Message) { if (EnabledLevels[(int)Level] && EnabledClasses[(int)Class]) { - Updated?.Invoke(this, new LogEventArgs(Level, Time.Elapsed, Message)); + Updated?.Invoke(null, new LogEventArgs(Level, Time.Elapsed, Message)); } } - private string GetFormattedMessage(LogClass Class, string Message, string Caller) + private static string GetFormattedMessage(LogClass Class, string Message, string Caller) { return $"{Class} {Caller}: {Message}"; } diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj new file mode 100644 index 0000000000..5c9293b707 --- /dev/null +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + win10-x64;osx-x64;linux-x64 + + + + true + + + + true + + + diff --git a/Ryujinx.Graphics/Gal/GalImageFormat.cs b/Ryujinx.Graphics/Gal/GalImageFormat.cs index ba555684f2..2712cbc253 100644 --- a/Ryujinx.Graphics/Gal/GalImageFormat.cs +++ b/Ryujinx.Graphics/Gal/GalImageFormat.cs @@ -5,89 +5,61 @@ namespace Ryujinx.Graphics.Gal [Flags] public enum GalImageFormat { - Snorm = 1 << 27, - Unorm = 1 << 28, - Sint = 1 << 29, - Uint = 1 << 30, - Sfloat = 1 << 31, + Astc2DStart, + Astc2D4x4, + Astc2D5x4, + Astc2D5x5, + Astc2D6x5, + Astc2D6x6, + Astc2D8x5, + Astc2D8x6, + Astc2D8x8, + Astc2D10x5, + Astc2D10x6, + Astc2D10x8, + Astc2D10x10, + Astc2D12x10, + Astc2D12x12, + Astc2DEnd, - TypeMask = Snorm | Unorm | Sint | Uint | Sfloat, - - FormatMask = ~TypeMask, - - ASTC_BEGIN = ASTC_4x4, - - ASTC_4x4 = 1, - ASTC_5x4, - ASTC_5x5, - ASTC_6x5, - ASTC_6x6, - ASTC_8x5, - ASTC_8x6, - ASTC_8x8, - ASTC_10x5, - ASTC_10x6, - ASTC_10x8, - ASTC_10x10, - ASTC_12x10, - ASTC_12x12, - - ASTC_END = ASTC_12x12, - - R4G4, - R4G4B4A4, - B4G4R4A4, - A4B4G4R4, - R5G6B5, - B5G6R5, - R5G5B5A1, - B5G5R5A1, - A1R5G5B5, + RGBA4, + RGB565, + BGR5A1, + RGB5A1, R8, - R8G8, - G8R8, - R8G8B8, - B8G8R8, - R8G8B8A8, - B8G8R8A8, - A8B8G8R8, - A8B8G8R8_SRGB, - A2R10G10B10, - A2B10G10R10, + RG8, + RGBA8, + BGRA8, + RGB10A2, R16, - R16G16, - R16G16B16, - R16G16B16A16, + RG16, + RGBA16, R32, - R32G32, - R32G32B32, - R32G32B32A32, - R64, - R64G64, - R64G64B64, - R64G64B64A64, - B10G11R11, - E5B9G9R9, + RG32, + RGBA32, + R11G11B10, D16, - X8_D24, D32, - S8, - D16_S8, - D24_S8, - D32_S8, - BC1_RGB, - BC1_RGBA, + D24S8, + D32S8, + BC1, BC2, BC3, BC4, BC5, - BC6H_SF16, - BC6H_UF16, - BC7, - ETC2_R8G8B8, - ETC2_R8G8B8A1, - ETC2_R8G8B8A8, - EAC_R11, - EAC_R11G11, + BptcSfloat, + BptcUfloat, + BptcUnorm, + + Snorm = 1 << 26, + Unorm = 1 << 27, + Sint = 1 << 28, + Uint = 1 << 39, + Float = 1 << 30, + Srgb = 1 << 31, + + TypeMask = Snorm | Unorm | Sint | Uint | Float | Srgb, + + FormatMask = ~TypeMask } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPipelineState.cs b/Ryujinx.Graphics/Gal/GalPipelineState.cs index 7b0f17d177..56b0fbde33 100644 --- a/Ryujinx.Graphics/Gal/GalPipelineState.cs +++ b/Ryujinx.Graphics/Gal/GalPipelineState.cs @@ -1,21 +1,28 @@ namespace Ryujinx.Graphics.Gal { - public struct GalVertexBinding + public struct ColorMaskRgba { - //VboKey shouldn't be here, but ARB_vertex_attrib_binding is core since 4.3 + private static readonly ColorMaskRgba _Default = new ColorMaskRgba() + { + Red = true, + Green = true, + Blue = true, + Alpha = true + }; - public bool Enabled; - public int Stride; - public long VboKey; - public bool Instanced; - public int Divisor; - public GalVertexAttrib[] Attribs; + public static ColorMaskRgba Default => _Default; + + public bool Red; + public bool Green; + public bool Blue; + public bool Alpha; } public class GalPipelineState { - public const int Stages = 5; + public const int Stages = 5; public const int ConstBuffersPerStage = 18; + public const int RenderTargetsCount = 8; public long[][] ConstBufferKeys; @@ -38,6 +45,7 @@ public GalComparisonOp DepthFunc; public bool StencilTestEnabled; + public bool StencilTwoSideEnabled; public GalComparisonOp StencilBackFuncFunc; public int StencilBackFuncRef; @@ -64,6 +72,9 @@ public GalBlendFactor BlendFuncSrcAlpha; public GalBlendFactor BlendFuncDstAlpha; + public ColorMaskRgba ColorMask; + public ColorMaskRgba[] ColorMasks; + public bool PrimitiveRestartEnabled; public uint PrimitiveRestartIndex; @@ -75,6 +86,8 @@ { ConstBufferKeys[Stage] = new long[ConstBuffersPerStage]; } + + ColorMasks = new ColorMaskRgba[RenderTargetsCount]; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs index 5ad769437c..51ce577970 100644 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -2,45 +2,45 @@ namespace Ryujinx.Graphics.Gal { public enum GalTextureFormat { - R32G32B32A32 = 0x1, - R16G16B16A16 = 0x3, - R32G32 = 0x4, - A8B8G8R8 = 0x8, - A2B10G10R10 = 0x9, - R16G16 = 0xc, - R32 = 0xf, - BC6H_SF16 = 0x10, - BC6H_UF16 = 0x11, - A4B4G4R4 = 0x12, - A1B5G5R5 = 0x14, - B5G6R5 = 0x15, - BC7U = 0x17, - G8R8 = 0x18, - R16 = 0x1b, - R8 = 0x1d, - BF10GF11RF11 = 0x21, - BC1 = 0x24, - BC2 = 0x25, - BC3 = 0x26, - BC4 = 0x27, - BC5 = 0x28, - Z24S8 = 0x29, - ZF32 = 0x2f, - ZF32_X24S8 = 0x30, - Z16 = 0x3a, - Astc2D4x4 = 0x40, - Astc2D5x5 = 0x41, - Astc2D6x6 = 0x42, - Astc2D8x8 = 0x44, - Astc2D10x10 = 0x45, - Astc2D12x12 = 0x46, - Astc2D5x4 = 0x50, - Astc2D6x5 = 0x51, - Astc2D8x6 = 0x52, - Astc2D10x8 = 0x53, - Astc2D12x10 = 0x54, - Astc2D8x5 = 0x55, - Astc2D10x5 = 0x56, - Astc2D10x6 = 0x57 + RGBA32 = 0x1, + RGBA16 = 0x3, + RG32 = 0x4, + RGBA8 = 0x8, + RGB10A2 = 0x9, + RG16 = 0xc, + R32 = 0xf, + BptcSfloat = 0x10, + BptcUfloat = 0x11, + RGBA4 = 0x12, + RGB5A1 = 0x14, + RGB565 = 0x15, + BptcUnorm = 0x17, + RG8 = 0x18, + R16 = 0x1b, + R8 = 0x1d, + R11G11B10F = 0x21, + BC1 = 0x24, + BC2 = 0x25, + BC3 = 0x26, + BC4 = 0x27, + BC5 = 0x28, + D24S8 = 0x29, + D32F = 0x2f, + D32FX24S8 = 0x30, + D16 = 0x3a, + Astc2D4x4 = 0x40, + Astc2D5x5 = 0x41, + Astc2D6x6 = 0x42, + Astc2D8x8 = 0x44, + Astc2D10x10 = 0x45, + Astc2D12x12 = 0x46, + Astc2D5x4 = 0x50, + Astc2D6x5 = 0x51, + Astc2D8x6 = 0x52, + Astc2D10x8 = 0x53, + Astc2D12x10 = 0x54, + Astc2D8x5 = 0x55, + Astc2D10x5 = 0x56, + Astc2D10x6 = 0x57 } } diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs index dd04006025..fa9a391f71 100644 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs @@ -1,10 +1,13 @@ +using System; + namespace Ryujinx.Graphics.Gal { public struct GalVertexAttrib { - public int Index { get; private set; } - public bool IsConst { get; private set; } - public int Offset { get; private set; } + public int Index { get; private set; } + public bool IsConst { get; private set; } + public int Offset { get; private set; } + public IntPtr Pointer { get; private set; } public GalVertexAttribSize Size { get; private set; } public GalVertexAttribType Type { get; private set; } @@ -15,12 +18,14 @@ namespace Ryujinx.Graphics.Gal int Index, bool IsConst, int Offset, + IntPtr Pointer, GalVertexAttribSize Size, GalVertexAttribType Type, bool IsBgra) { this.Index = Index; this.IsConst = IsConst; + this.Pointer = Pointer; this.Offset = Offset; this.Size = Size; this.Type = Type; diff --git a/Ryujinx.Graphics/Gal/GalVertexBinding.cs b/Ryujinx.Graphics/Gal/GalVertexBinding.cs new file mode 100644 index 0000000000..0c3c845d4f --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalVertexBinding.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalVertexBinding + { + //VboKey shouldn't be here, but ARB_vertex_attrib_binding is core since 4.3 + + public bool Enabled; + public int Stride; + public long VboKey; + public bool Instanced; + public int Divisor; + public GalVertexAttrib[] Attribs; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalZetaFormat.cs b/Ryujinx.Graphics/Gal/GalZetaFormat.cs index 759e312170..2429249e54 100644 --- a/Ryujinx.Graphics/Gal/GalZetaFormat.cs +++ b/Ryujinx.Graphics/Gal/GalZetaFormat.cs @@ -2,15 +2,15 @@ { public enum GalZetaFormat { - Z32Float = 0x0a, - Z16Unorm = 0x13, - S8Z24Unorm = 0x14, - Z24X8Unorm = 0x15, - Z24S8Unorm = 0x16, - Z24C8Unorm = 0x18, - Z32S8X24Float = 0x19, - Z24X8S8C8X16Unorm = 0x1d, - Z32X8C8X16Float = 0x1e, - Z32S8C8X16Float = 0x1f + D32Float = 0x0a, + D16Unorm = 0x13, + S8D24Unorm = 0x14, + D24X8Unorm = 0x15, + D24S8Unorm = 0x16, + D24C8Unorm = 0x18, + D32S8X24Float = 0x19, + D24X8S8C8X16Unorm = 0x1d, + D32X8C8X16Float = 0x1e, + D32S8C8X16Float = 0x1f } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalPipeline.cs b/Ryujinx.Graphics/Gal/IGalPipeline.cs index d8cf266af4..cba4e7dcc6 100644 --- a/Ryujinx.Graphics/Gal/IGalPipeline.cs +++ b/Ryujinx.Graphics/Gal/IGalPipeline.cs @@ -3,5 +3,8 @@ public interface IGalPipeline { void Bind(GalPipelineState State); + + void ResetDepthMask(); + void ResetColorMask(int Index); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs index 1ee630e23b..052e3f35f0 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -10,7 +10,10 @@ namespace Ryujinx.Graphics.Gal void ClearBuffers( GalClearBufferFlags Flags, int Attachment, - float Red, float Green, float Blue, float Alpha, + float Red, + float Green, + float Blue, + float Alpha, float Depth, int Stencil); @@ -21,6 +24,7 @@ namespace Ryujinx.Graphics.Gal void CreateVbo(long Key, int DataSize, IntPtr HostAddress); void CreateIbo(long Key, int DataSize, IntPtr HostAddress); + void CreateIbo(long Key, int DataSize, byte[] Buffer); void SetIndexArray(int Size, GalIndexFormat Format); diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs index 7ccf0981d1..f941ccd584 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs @@ -2,15 +2,17 @@ namespace Ryujinx.Graphics.Gal { public interface IGalRenderTarget { - void BindColor(long Key, int Attachment, GalImage Image); + void Bind(); + + void BindColor(long Key, int Attachment); void UnbindColor(int Attachment); - void BindZeta(long Key, GalImage Image); + void BindZeta(long Key); void UnbindZeta(); - void Set(long Key); + void Present(long Key); void SetMap(int[] Map); @@ -18,7 +20,7 @@ namespace Ryujinx.Graphics.Gal void SetWindowSize(int Width, int Height); - void SetViewport(int X, int Y, int Width, int Height); + void SetViewport(int Attachment, int X, int Y, int Width, int Height); void Render(); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index 187d1eece7..6b3d4fd691 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalFrontFace.CCW: return FrontFaceDirection.Ccw; } - throw new ArgumentException(nameof(FrontFace)); + throw new ArgumentException(nameof(FrontFace) + " \"" + FrontFace + "\" is not valid!"); } public static CullFaceMode GetCullFace(GalCullFace CullFace) @@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalCullFace.FrontAndBack: return CullFaceMode.FrontAndBack; } - throw new ArgumentException(nameof(CullFace)); + throw new ArgumentException(nameof(CullFace) + " \"" + CullFace + "\" is not valid!"); } public static StencilOp GetStencilOp(GalStencilOp Op) @@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalStencilOp.DecrWrap: return StencilOp.DecrWrap; } - throw new ArgumentException(nameof(Op)); + throw new ArgumentException(nameof(Op) + " \"" + Op + "\" is not valid!"); } public static DepthFunction GetDepthFunc(GalComparisonOp Func) @@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalComparisonOp.Always: return DepthFunction.Always; } - throw new ArgumentException(nameof(Func)); + throw new ArgumentException(nameof(Func) + " \"" + Func + "\" is not valid!"); } public static StencilFunction GetStencilFunc(GalComparisonOp Func) @@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt; } - throw new ArgumentException(nameof(Format)); + throw new ArgumentException(nameof(Format) + " \"" + Format + "\" is not valid!"); } public static PrimitiveType GetPrimitiveType(GalPrimitiveType Type) @@ -98,8 +98,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalPrimitiveType.Triangles: return PrimitiveType.Triangles; case GalPrimitiveType.TriangleStrip: return PrimitiveType.TriangleStrip; case GalPrimitiveType.TriangleFan: return PrimitiveType.TriangleFan; - case GalPrimitiveType.Quads: return PrimitiveType.Quads; - case GalPrimitiveType.QuadStrip: return PrimitiveType.QuadStrip; case GalPrimitiveType.Polygon: return PrimitiveType.Polygon; case GalPrimitiveType.LinesAdjacency: return PrimitiveType.LinesAdjacency; case GalPrimitiveType.LineStripAdjacency: return PrimitiveType.LineStripAdjacency; @@ -108,7 +106,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalPrimitiveType.Patches: return PrimitiveType.Patches; } - throw new ArgumentException(nameof(Type)); + throw new ArgumentException(nameof(Type) + " \"" + Type + "\" is not valid!"); } public static ShaderType GetShaderType(GalShaderType Type) @@ -122,58 +120,61 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalShaderType.Fragment: return ShaderType.FragmentShader; } - throw new ArgumentException(nameof(Type)); + throw new ArgumentException(nameof(Type) + " \"" + Type + "\" is not valid!"); } public static (PixelInternalFormat, PixelFormat, PixelType) GetImageFormat(GalImageFormat Format) { switch (Format) { - case GalImageFormat.R32G32B32A32 | GalImageFormat.Sfloat: return (PixelInternalFormat.Rgba32f, PixelFormat.Rgba, PixelType.Float); - case GalImageFormat.R32G32B32A32 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int); - case GalImageFormat.R32G32B32A32 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt); - case GalImageFormat.R16G16B16A16 | GalImageFormat.Sfloat: return (PixelInternalFormat.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat); - case GalImageFormat.R16G16B16A16 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short); - case GalImageFormat.R16G16B16A16 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort); - case GalImageFormat.R32G32 | GalImageFormat.Sfloat: return (PixelInternalFormat.Rg32f, PixelFormat.Rg, PixelType.Float); - case GalImageFormat.R32G32 | GalImageFormat.Sint: return (PixelInternalFormat.Rg32i, PixelFormat.RgInteger, PixelType.Int); - case GalImageFormat.R32G32 | GalImageFormat.Uint: return (PixelInternalFormat.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt); - case GalImageFormat.A8B8G8R8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte); - case GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte); - case GalImageFormat.A8B8G8R8 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte); - case GalImageFormat.A8B8G8R8 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte); - case GalImageFormat.A8B8G8R8_SRGB: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte); - case GalImageFormat.A4B4G4R4 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed); - case GalImageFormat.A2B10G10R10 | GalImageFormat.Uint: return (PixelInternalFormat.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed); - case GalImageFormat.A2B10G10R10 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed); - case GalImageFormat.R32 | GalImageFormat.Sfloat: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float); - case GalImageFormat.R32 | GalImageFormat.Sint: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int); - case GalImageFormat.R32 | GalImageFormat.Uint: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt); - case GalImageFormat.A1R5G5B5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed); - case GalImageFormat.B5G6R5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed); - case GalImageFormat.R16G16 | GalImageFormat.Sfloat: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat); - case GalImageFormat.R16G16 | GalImageFormat.Sint: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short); - case GalImageFormat.R16G16 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Byte); - case GalImageFormat.R16G16 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg16, PixelFormat.Rg, PixelType.UnsignedShort); - case GalImageFormat.R8G8 | GalImageFormat.Sint: return (PixelInternalFormat.Rg8i, PixelFormat.RgInteger, PixelType.Byte); - case GalImageFormat.R8G8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg8Snorm, PixelFormat.Rg, PixelType.Byte); - case GalImageFormat.R8G8 | GalImageFormat.Uint: return (PixelInternalFormat.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte); - case GalImageFormat.R8G8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg8, PixelFormat.Rg, PixelType.UnsignedByte); - case GalImageFormat.R16 | GalImageFormat.Sfloat: return (PixelInternalFormat.R16f, PixelFormat.Red, PixelType.HalfFloat); - case GalImageFormat.R16 | GalImageFormat.Sint: return (PixelInternalFormat.R16i, PixelFormat.RedInteger, PixelType.Short); - case GalImageFormat.R16 | GalImageFormat.Snorm: return (PixelInternalFormat.R16Snorm, PixelFormat.Red, PixelType.Byte); - case GalImageFormat.R16 | GalImageFormat.Uint: return (PixelInternalFormat.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort); - case GalImageFormat.R16 | GalImageFormat.Unorm: return (PixelInternalFormat.R16, PixelFormat.Red, PixelType.UnsignedShort); - case GalImageFormat.R8 | GalImageFormat.Sint: return (PixelInternalFormat.R8i, PixelFormat.RedInteger, PixelType.Byte); - case GalImageFormat.R8 | GalImageFormat.Snorm: return (PixelInternalFormat.R8Snorm, PixelFormat.Red, PixelType.Byte); - case GalImageFormat.R8 | GalImageFormat.Uint: return (PixelInternalFormat.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte); - case GalImageFormat.R8 | GalImageFormat.Unorm: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte); - case GalImageFormat.B10G11R11 | GalImageFormat.Sfloat: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev); + case GalImageFormat.RGBA32 | GalImageFormat.Float: return (PixelInternalFormat.Rgba32f, PixelFormat.Rgba, PixelType.Float); + case GalImageFormat.RGBA32 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int); + case GalImageFormat.RGBA32 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt); + case GalImageFormat.RGBA16 | GalImageFormat.Float: return (PixelInternalFormat.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat); + case GalImageFormat.RGBA16 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short); + case GalImageFormat.RGBA16 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort); + case GalImageFormat.RG32 | GalImageFormat.Float: return (PixelInternalFormat.Rg32f, PixelFormat.Rg, PixelType.Float); + case GalImageFormat.RG32 | GalImageFormat.Sint: return (PixelInternalFormat.Rg32i, PixelFormat.RgInteger, PixelType.Int); + case GalImageFormat.RG32 | GalImageFormat.Uint: return (PixelInternalFormat.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt); + case GalImageFormat.RGBA8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte); + case GalImageFormat.RGBA8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte); + case GalImageFormat.RGBA8 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte); + case GalImageFormat.RGBA8 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte); + case GalImageFormat.RGBA8 | GalImageFormat.Srgb: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte); + case GalImageFormat.BGRA8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte); + case GalImageFormat.RGBA4 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed); + case GalImageFormat.RGB10A2 | GalImageFormat.Uint: return (PixelInternalFormat.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed); + case GalImageFormat.RGB10A2 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed); + case GalImageFormat.R32 | GalImageFormat.Float: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float); + case GalImageFormat.R32 | GalImageFormat.Sint: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int); + case GalImageFormat.R32 | GalImageFormat.Uint: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt); + case GalImageFormat.RGB5A1 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed); + case GalImageFormat.RGB565 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed); + case GalImageFormat.RG16 | GalImageFormat.Float: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat); + case GalImageFormat.RG16 | GalImageFormat.Sint: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short); + case GalImageFormat.RG16 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Short); + case GalImageFormat.RG16 | GalImageFormat.Uint: return (PixelInternalFormat.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort); + case GalImageFormat.RG16 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg16, PixelFormat.Rg, PixelType.UnsignedShort); + case GalImageFormat.RG8 | GalImageFormat.Sint: return (PixelInternalFormat.Rg8i, PixelFormat.RgInteger, PixelType.Byte); + case GalImageFormat.RG8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg8Snorm, PixelFormat.Rg, PixelType.Byte); + case GalImageFormat.RG8 | GalImageFormat.Uint: return (PixelInternalFormat.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte); + case GalImageFormat.RG8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg8, PixelFormat.Rg, PixelType.UnsignedByte); + case GalImageFormat.R16 | GalImageFormat.Float: return (PixelInternalFormat.R16f, PixelFormat.Red, PixelType.HalfFloat); + case GalImageFormat.R16 | GalImageFormat.Sint: return (PixelInternalFormat.R16i, PixelFormat.RedInteger, PixelType.Short); + case GalImageFormat.R16 | GalImageFormat.Snorm: return (PixelInternalFormat.R16Snorm, PixelFormat.Red, PixelType.Short); + case GalImageFormat.R16 | GalImageFormat.Uint: return (PixelInternalFormat.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort); + case GalImageFormat.R16 | GalImageFormat.Unorm: return (PixelInternalFormat.R16, PixelFormat.Red, PixelType.UnsignedShort); + case GalImageFormat.R8 | GalImageFormat.Sint: return (PixelInternalFormat.R8i, PixelFormat.RedInteger, PixelType.Byte); + case GalImageFormat.R8 | GalImageFormat.Snorm: return (PixelInternalFormat.R8Snorm, PixelFormat.Red, PixelType.Byte); + case GalImageFormat.R8 | GalImageFormat.Uint: return (PixelInternalFormat.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte); + case GalImageFormat.R8 | GalImageFormat.Unorm: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte); + case GalImageFormat.R11G11B10 | GalImageFormat.Float: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev); - case GalImageFormat.D24_S8 | GalImageFormat.Unorm: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); - case GalImageFormat.D32 | GalImageFormat.Sfloat: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float); - case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort); - case GalImageFormat.D32_S8 | GalImageFormat.Sfloat: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); + case GalImageFormat.D24S8 | GalImageFormat.Uint: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); + case GalImageFormat.D24S8 | GalImageFormat.Unorm: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); + case GalImageFormat.D32 | GalImageFormat.Float: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float); + case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort); + case GalImageFormat.D32S8 | GalImageFormat.Float: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); } throw new NotImplementedException($"{Format & GalImageFormat.FormatMask} {Format & GalImageFormat.TypeMask}"); @@ -183,16 +184,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL { switch (Format) { - case GalImageFormat.BC6H_UF16 | GalImageFormat.Sfloat: return InternalFormat.CompressedRgbBptcUnsignedFloat; - case GalImageFormat.BC6H_SF16 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbBptcSignedFloat; - case GalImageFormat.BC7 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaBptcUnorm; - case GalImageFormat.BC1_RGBA | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt1Ext; - case GalImageFormat.BC2 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt3Ext; - case GalImageFormat.BC3 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt5Ext; - case GalImageFormat.BC4 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRedRgtc1; - case GalImageFormat.BC4 | GalImageFormat.Unorm: return InternalFormat.CompressedRedRgtc1; - case GalImageFormat.BC5 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRgRgtc2; - case GalImageFormat.BC5 | GalImageFormat.Unorm: return InternalFormat.CompressedRgRgtc2; + case GalImageFormat.BptcSfloat | GalImageFormat.Float: return InternalFormat.CompressedRgbBptcSignedFloat; + case GalImageFormat.BptcUfloat | GalImageFormat.Float: return InternalFormat.CompressedRgbBptcUnsignedFloat; + case GalImageFormat.BptcUnorm | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaBptcUnorm; + case GalImageFormat.BptcUnorm | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaBptcUnorm; + case GalImageFormat.BC1 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt1Ext; + case GalImageFormat.BC1 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt1Ext; + case GalImageFormat.BC2 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt3Ext; + case GalImageFormat.BC2 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt3Ext; + case GalImageFormat.BC3 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt5Ext; + case GalImageFormat.BC3 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt5Ext; + case GalImageFormat.BC4 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRedRgtc1; + case GalImageFormat.BC4 | GalImageFormat.Unorm: return InternalFormat.CompressedRedRgtc1; + case GalImageFormat.BC5 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRgRgtc2; + case GalImageFormat.BC5 | GalImageFormat.Unorm: return InternalFormat.CompressedRgRgtc2; } throw new NotImplementedException($"{Format & GalImageFormat.FormatMask} {Format & GalImageFormat.TypeMask}"); @@ -211,7 +216,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureSource.OneFloat: return All.One; } - throw new ArgumentException(nameof(Source)); + throw new ArgumentException(nameof(Source) + " \"" + Source + "\" is not valid!"); } public static TextureWrapMode GetTextureWrapMode(GalTextureWrap Wrap) @@ -225,7 +230,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; } - if (OGLExtension.HasTextureMirrorClamp()) + if (OGLExtension.TextureMirrorClamp) { switch (Wrap) { @@ -245,7 +250,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } - throw new ArgumentException(nameof(Wrap)); + throw new ArgumentException(nameof(Wrap) + " \"" + Wrap + "\" is not valid!"); } public static TextureMinFilter GetTextureMinFilter( @@ -259,7 +264,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureFilter.Linear: return TextureMinFilter.Linear; } - throw new ArgumentException(nameof(MinFilter)); + throw new ArgumentException(nameof(MinFilter) + " \"" + MinFilter + "\" is not valid!"); } public static TextureMagFilter GetTextureMagFilter(GalTextureFilter Filter) @@ -270,7 +275,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalTextureFilter.Linear: return TextureMagFilter.Linear; } - throw new ArgumentException(nameof(Filter)); + throw new ArgumentException(nameof(Filter) + " \"" + Filter + "\" is not valid!"); } public static BlendEquationMode GetBlendEquation(GalBlendEquation BlendEquation) @@ -284,7 +289,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalBlendEquation.Max: return BlendEquationMode.Max; } - throw new ArgumentException(nameof(BlendEquation)); + throw new ArgumentException(nameof(BlendEquation) + " \"" + BlendEquation + "\" is not valid!"); } public static BlendingFactor GetBlendFactor(GalBlendFactor BlendFactor) @@ -315,7 +320,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL return BlendingFactor.ConstantColor; } - throw new ArgumentException(nameof(BlendFactor)); + throw new ArgumentException(nameof(BlendFactor) + " \"" + BlendFactor + "\" is not valid!"); } } } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs index 5ad422980c..11daeb593c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs @@ -1,40 +1,17 @@ using OpenTK.Graphics.OpenGL; +using System; namespace Ryujinx.Graphics.Gal.OpenGL { static class OGLExtension { - private static bool Initialized = false; + private static Lazy s_EnhancedLayouts = new Lazy(() => HasExtension("GL_ARB_enhanced_layouts")); + private static Lazy s_TextureMirrorClamp = new Lazy(() => HasExtension("GL_EXT_texture_mirror_clamp")); + private static Lazy s_ViewportArray = new Lazy(() => HasExtension("GL_ARB_viewport_array")); - private static bool EnhancedLayouts; - - private static bool TextureMirrorClamp; - - public static bool HasEnhancedLayouts() - { - EnsureInitialized(); - - return EnhancedLayouts; - } - - public static bool HasTextureMirrorClamp() - { - EnsureInitialized(); - - return TextureMirrorClamp; - } - - private static void EnsureInitialized() - { - if (Initialized) - { - return; - } - - EnhancedLayouts = HasExtension("GL_ARB_enhanced_layouts"); - - TextureMirrorClamp = HasExtension("GL_EXT_texture_mirror_clamp"); - } + public static bool EnhancedLayouts => s_EnhancedLayouts.Value; + public static bool TextureMirrorClamp => s_TextureMirrorClamp.Value; + public static bool ViewportArray => s_ViewportArray.Value; private static bool HasExtension(string Name) { diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index 20e92ff2c6..00699641fd 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -25,6 +25,19 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GalVertexAttribSize._11_11_10, 3 } }; + private static Dictionary FloatAttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16, VertexAttribPointerType.HalfFloat } + }; + private static Dictionary SignedAttribTypes = new Dictionary() { @@ -116,9 +129,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL BlendFuncSrcAlpha = GalBlendFactor.One, BlendFuncDstAlpha = GalBlendFactor.Zero, + ColorMask = ColorMaskRgba.Default, + PrimitiveRestartEnabled = false, PrimitiveRestartIndex = 0 }; + + for (int Index = 0; Index < GalPipelineState.RenderTargetsCount; Index++) + { + Old.ColorMasks[Index] = ColorMaskRgba.Default; + } } public void Bind(GalPipelineState New) @@ -164,8 +184,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL if (New.DepthWriteEnabled != Old.DepthWriteEnabled) { - Rasterizer.DepthWriteEnabled = New.DepthWriteEnabled; - GL.DepthMask(New.DepthWriteEnabled); } @@ -182,6 +200,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL Enable(EnableCap.StencilTest, New.StencilTestEnabled); } + if (New.StencilTwoSideEnabled != Old.StencilTwoSideEnabled) + { + Enable((EnableCap)All.StencilTestTwoSideExt, New.StencilTwoSideEnabled); + } + if (New.StencilTestEnabled) { if (New.StencilBackFuncFunc != Old.StencilBackFuncFunc || @@ -285,6 +308,19 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + for (int Index = 0; Index < GalPipelineState.RenderTargetsCount; Index++) + { + if (!New.ColorMasks[Index].Equals(Old.ColorMasks[Index])) + { + GL.ColorMask( + Index, + New.ColorMasks[Index].Red, + New.ColorMasks[Index].Green, + New.ColorMasks[Index].Blue, + New.ColorMasks[Index].Alpha); + } + } + if (New.PrimitiveRestartEnabled != Old.PrimitiveRestartEnabled) { Enable(EnableCap.PrimitiveRestart, New.PrimitiveRestartEnabled); @@ -356,8 +392,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL continue; } - GL.EnableVertexAttribArray(Attrib.Index); - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); bool Unsigned = @@ -373,35 +407,50 @@ namespace Ryujinx.Graphics.Gal.OpenGL if (Attrib.Type == GalVertexAttribType.Float) { - Type = VertexAttribPointerType.Float; + Type = GetType(FloatAttribTypes, Attrib); } else { if (Unsigned) { - Type = UnsignedAttribTypes[Attrib.Size]; + Type = GetType(UnsignedAttribTypes, Attrib); } else { - Type = SignedAttribTypes[Attrib.Size]; + Type = GetType(SignedAttribTypes, Attrib); } } - int Size = AttribElements[Attrib.Size]; + if (!AttribElements.TryGetValue(Attrib.Size, out int Size)) + { + throw new InvalidOperationException("Invalid attribute size \"" + Attrib.Size + "\"!"); + } + int Offset = Attrib.Offset; - if (Attrib.Type == GalVertexAttribType.Sint || - Attrib.Type == GalVertexAttribType.Uint) + if (Binding.Stride != 0) { - IntPtr Pointer = new IntPtr(Offset); + GL.EnableVertexAttribArray(Attrib.Index); - VertexAttribIntegerType IType = (VertexAttribIntegerType)Type; + if (Attrib.Type == GalVertexAttribType.Sint || + Attrib.Type == GalVertexAttribType.Uint) + { + IntPtr Pointer = new IntPtr(Offset); - GL.VertexAttribIPointer(Attrib.Index, Size, IType, Binding.Stride, Pointer); + VertexAttribIntegerType IType = (VertexAttribIntegerType)Type; + + GL.VertexAttribIPointer(Attrib.Index, Size, IType, Binding.Stride, Pointer); + } + else + { + GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Binding.Stride, Offset); + } } else { - GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Binding.Stride, Offset); + GL.DisableVertexAttribArray(Attrib.Index); + + SetConstAttrib(Attrib); } if (Binding.Instanced && Binding.Divisor != 0) @@ -416,6 +465,149 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + private static VertexAttribPointerType GetType(Dictionary Dict, GalVertexAttrib Attrib) + { + if (!Dict.TryGetValue(Attrib.Size, out VertexAttribPointerType Type)) + { + throw new NotImplementedException("Unsupported size \"" + Attrib.Size + "\" on type \"" + Attrib.Type + "\"!"); + } + + return Type; + } + + private unsafe static void SetConstAttrib(GalVertexAttrib Attrib) + { + void Unsupported() + { + throw new NotImplementedException("Constant attribute " + Attrib.Size + " not implemented!"); + } + + if (Attrib.Size == GalVertexAttribSize._10_10_10_2 || + Attrib.Size == GalVertexAttribSize._11_11_10) + { + Unsupported(); + } + + if (Attrib.Type == GalVertexAttribType.Unorm) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N((uint)Attrib.Index, (byte*)Attrib.Pointer); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N((uint)Attrib.Index, (ushort*)Attrib.Pointer); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N((uint)Attrib.Index, (uint*)Attrib.Pointer); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Snorm) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N((uint)Attrib.Index, (sbyte*)Attrib.Pointer); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N((uint)Attrib.Index, (short*)Attrib.Pointer); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N((uint)Attrib.Index, (int*)Attrib.Pointer); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Uint) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4((uint)Attrib.Index, (byte*)Attrib.Pointer); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4((uint)Attrib.Index, (ushort*)Attrib.Pointer); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4((uint)Attrib.Index, (uint*)Attrib.Pointer); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Sint) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4((uint)Attrib.Index, (sbyte*)Attrib.Pointer); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4((uint)Attrib.Index, (short*)Attrib.Pointer); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4((uint)Attrib.Index, (int*)Attrib.Pointer); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Float) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4(Attrib.Index, (float*)Attrib.Pointer); + break; + + default: Unsupported(); break; + } + } + } + private void Enable(EnableCap Cap, bool Enabled) { if (Enabled) @@ -427,5 +619,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.Disable(Cap); } } + + public void ResetDepthMask() + { + Old.DepthWriteEnabled = true; + } + + public void ResetColorMask(int Index) + { + Old.ColorMasks[Index] = ColorMaskRgba.Default; + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index a74aee0773..cefbb2d2a1 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -5,8 +5,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLRasterizer : IGalRasterizer { - public bool DepthWriteEnabled { set; private get; } - private int[] VertexBuffers; private OGLCachedResource VboCache; @@ -30,8 +28,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL IboCache = new OGLCachedResource(GL.DeleteBuffer); IndexBuffer = new IbInfo(); - - DepthWriteEnabled = true; } public void LockCaches() @@ -49,17 +45,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void ClearBuffers( GalClearBufferFlags Flags, int Attachment, - float Red, float Green, float Blue, float Alpha, + float Red, + float Green, + float Blue, + float Alpha, float Depth, int Stencil) { - //OpenGL needs glDepthMask to be enabled to clear it - if (!DepthWriteEnabled) - { - GL.DepthMask(true); - } - GL.ColorMask( + Attachment, Flags.HasFlag(GalClearBufferFlags.ColorRed), Flags.HasFlag(GalClearBufferFlags.ColorGreen), Flags.HasFlag(GalClearBufferFlags.ColorBlue), @@ -67,6 +61,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.ClearBuffer(ClearBuffer.Color, Attachment, new float[] { Red, Green, Blue, Alpha }); + GL.ColorMask(Attachment, true, true, true, true); + GL.DepthMask(true); + if (Flags.HasFlag(GalClearBufferFlags.Depth)) { GL.ClearBuffer(ClearBuffer.Depth, 0, ref Depth); @@ -76,13 +73,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GL.ClearBuffer(ClearBuffer.Stencil, 0, ref Stencil); } - - GL.ColorMask(true, true, true, true); - - if (!DepthWriteEnabled) - { - GL.DepthMask(false); - } } public bool IsVboCached(long Key, long DataSize) @@ -119,6 +109,18 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); } + public void CreateIbo(long Key, int DataSize, byte[] Buffer) + { + int Handle = GL.GenBuffer(); + + IboCache.AddOrUpdate(Key, Handle, (uint)DataSize); + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); + GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + } + public void SetIndexArray(int Size, GalIndexFormat Format) { IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format); @@ -135,7 +137,26 @@ namespace Ryujinx.Graphics.Gal.OpenGL return; } - GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, Count); + if (PrimType == GalPrimitiveType.Quads) + { + for (int Offset = 0; Offset < Count; Offset += 4) + { + GL.DrawArrays(PrimitiveType.TriangleFan, First + Offset, 4); + } + } + else if (PrimType == GalPrimitiveType.QuadStrip) + { + GL.DrawArrays(PrimitiveType.TriangleFan, First, 4); + + for (int Offset = 2; Offset < Count; Offset += 2) + { + GL.DrawArrays(PrimitiveType.TriangleFan, First + Offset, 4); + } + } + else + { + GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, Count); + } } public void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index ff5dc1b895..e0c4854ed2 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -22,16 +22,50 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + private class FrameBufferAttachments + { + public long[] Colors; + public long Zeta; + + public int MapCount; + public DrawBuffersEnum[] Map; + + public FrameBufferAttachments() + { + Colors = new long[RenderTargetsCount]; + + Map = new DrawBuffersEnum[RenderTargetsCount]; + } + + public void SetAndClear(FrameBufferAttachments Source) + { + Zeta = Source.Zeta; + MapCount = Source.MapCount; + + Source.Zeta = 0; + Source.MapCount = 0; + + for (int i = 0; i < RenderTargetsCount; i++) + { + Colors[i] = Source.Colors[i]; + Map[i] = Source.Map[i]; + + Source.Colors[i] = 0; + Source.Map[i] = 0; + } + } + } + private const int NativeWidth = 1280; private const int NativeHeight = 720; - private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm; + private const int RenderTargetsCount = GalPipelineState.RenderTargetsCount; private OGLTexture Texture; private ImageHandler ReadTex; - private Rect Viewport; + private float[] Viewports; private Rect Window; private bool FlipX; @@ -50,138 +84,165 @@ namespace Ryujinx.Graphics.Gal.OpenGL 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; + private FrameBufferAttachments Attachments; + private FrameBufferAttachments OldAttachments; private int CopyPBO; public OGLRenderTarget(OGLTexture Texture) { - ColorAttachments = new int[8]; + Attachments = new FrameBufferAttachments(); + + OldAttachments = new FrameBufferAttachments(); + + Viewports = new float[RenderTargetsCount * 4]; this.Texture = Texture; } - public void BindColor(long Key, int Attachment, GalImage Image) + public void Bind() { - if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) + if (DummyFrameBuffer == 0) { - EnsureFrameBuffer(); - - Attach(ref ColorAttachments[Attachment], CachedImage.Handle, FramebufferAttachment.ColorAttachment0 + Attachment); + DummyFrameBuffer = GL.GenFramebuffer(); } - else + + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer); + + ImageHandler CachedImage; + + for (int Attachment = 0; Attachment < RenderTargetsCount; Attachment++) { - UnbindColor(Attachment); - } - } + long Key = Attachments.Colors[Attachment]; - public void UnbindColor(int Attachment) - { - EnsureFrameBuffer(); - - Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment); - } - - public void BindZeta(long Key, GalImage Image) - { - if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) - { - EnsureFrameBuffer(); - - if (CachedImage.HasDepth && CachedImage.HasStencil) + if (Key == OldAttachments.Colors[Attachment]) { - if (DepthAttachment != CachedImage.Handle || - StencilAttachment != CachedImage.Handle) + continue; + } + + int Handle = 0; + + if (Key != 0 && Texture.TryGetImageHandler(Key, out CachedImage)) + { + Handle = CachedImage.Handle; + } + + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.ColorAttachment0 + Attachment, + Handle, + 0); + } + + if (Attachments.Zeta != OldAttachments.Zeta) + { + if (Attachments.Zeta != 0 && Texture.TryGetImageHandler(Attachments.Zeta, out CachedImage)) + { + if (CachedImage.HasDepth && CachedImage.HasStencil) { GL.FramebufferTexture( FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthStencilAttachment, CachedImage.Handle, 0); - - DepthAttachment = CachedImage.Handle; - StencilAttachment = CachedImage.Handle; } - } - else if (CachedImage.HasDepth) - { - Attach(ref DepthAttachment, CachedImage.Handle, FramebufferAttachment.DepthAttachment); + else if (CachedImage.HasDepth) + { + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.DepthAttachment, + CachedImage.Handle, + 0); - Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment); - } - else if (CachedImage.HasStencil) - { - Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment); - - Attach(ref StencilAttachment, CachedImage.Handle, FramebufferAttachment.StencilAttachment); + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.StencilAttachment, + 0, + 0); + } + else + { + throw new NotImplementedException(); + } } else { - throw new InvalidOperationException(); + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FramebufferAttachment.DepthStencilAttachment, + 0, + 0); } } + + if (OGLExtension.ViewportArray) + { + GL.ViewportArray(0, RenderTargetsCount, Viewports); + } else { - UnbindZeta(); + GL.Viewport( + (int)Viewports[0], + (int)Viewports[1], + (int)Viewports[2], + (int)Viewports[3]); } + + if (Attachments.MapCount > 1) + { + GL.DrawBuffers(Attachments.MapCount, Attachments.Map); + } + else if (Attachments.MapCount == 1) + { + GL.DrawBuffer((DrawBufferMode)Attachments.Map[0]); + } + else + { + GL.DrawBuffer(DrawBufferMode.None); + } + + OldAttachments.SetAndClear(Attachments); } - private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment) + public void BindColor(long Key, int Attachment) { - if (OldHandle != NewHandle) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FbAttachment, - NewHandle, - 0); + Attachments.Colors[Attachment] = Key; + } - OldHandle = NewHandle; - } + public void UnbindColor(int Attachment) + { + Attachments.Colors[Attachment] = 0; + } + + public void BindZeta(long Key) + { + Attachments.Zeta = Key; } public void UnbindZeta() { - EnsureFrameBuffer(); - - if (DepthAttachment != 0 || StencilAttachment != 0) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.DepthStencilAttachment, - 0, - 0); - - DepthAttachment = 0; - StencilAttachment = 0; - } + Attachments.Zeta = 0; } - public void Set(long Key) + public void Present(long Key) { Texture.TryGetImageHandler(Key, out ReadTex); } public void SetMap(int[] Map) { - if (Map != null && Map.Length > 0) + if (Map != null) { - DrawBuffersEnum[] Mode = new DrawBuffersEnum[Map.Length]; + Attachments.MapCount = Map.Length; - for (int i = 0; i < Map.Length; i++) + for (int Attachment = 0; Attachment < Attachments.MapCount; Attachment++) { - Mode[i] = DrawBuffersEnum.ColorAttachment0 + Map[i]; + Attachments.Map[Attachment] = DrawBuffersEnum.ColorAttachment0 + Map[Attachment]; } - - GL.DrawBuffers(Mode.Length, Mode); } else { - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + Attachments.MapCount = 0; } } @@ -201,20 +262,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL Window = new Rect(0, 0, Width, Height); } - public void SetViewport(int X, int Y, int Width, int Height) + public void SetViewport(int Attachment, int X, int Y, int Width, int Height) { - Viewport = new Rect(X, Y, Width, Height); + int Offset = Attachment * 4; - SetViewport(Viewport); - } - - private void SetViewport(Rect Viewport) - { - GL.Viewport( - Viewport.X, - Viewport.Y, - Viewport.Width, - Viewport.Height); + Viewports[Offset + 0] = X; + Viewports[Offset + 1] = Y; + Viewports[Offset + 2] = Width; + Viewports[Offset + 3] = Height; } public void Render() @@ -276,7 +331,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0); GL.ReadBuffer(ReadBufferMode.ColorAttachment0); - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); GL.Clear(ClearBufferMask.ColorBufferBit); @@ -285,8 +339,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL DstX0, DstY0, DstX1, DstY1, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear); - - EnsureFrameBuffer(); } public void Copy( @@ -343,8 +395,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.Clear(Mask); GL.BlitFramebuffer(SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, Mask, Filter); - - EnsureFrameBuffer(); } } @@ -419,15 +469,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL (CachedImage.HasDepth ? ClearBufferMask.DepthBufferBit : 0) | (CachedImage.HasStencil ? ClearBufferMask.StencilBufferBit : 0); } - - private void EnsureFrameBuffer() - { - if (DummyFrameBuffer == 0) - { - DummyFrameBuffer = GL.GenFramebuffer(); - } - - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer); - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs index 73d37b8791..b45a3a3a5a 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -55,16 +55,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL GlslDecompiler Decompiler = new GlslDecompiler(); + int ShaderDumpIndex = ShaderDumper.DumpIndex; + if (IsDualVp) { ShaderDumper.Dump(Memory, Position, Type, "a"); ShaderDumper.Dump(Memory, PositionB, Type, "b"); - Program = Decompiler.Decompile( - Memory, - Position, - PositionB, - Type); + Program = Decompiler.Decompile(Memory, Position, PositionB, Type); } else { @@ -73,11 +71,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL Program = Decompiler.Decompile(Memory, Position, Type); } - return new OGLShaderStage( - Type, - Program.Code, - Program.Uniforms, - Program.Textures); + string Code = Program.Code; + + if (ShaderDumper.IsDumpEnabled()) + { + Code = "//Shader " + ShaderDumpIndex + Environment.NewLine + Code; + } + + return new OGLShaderStage(Type, Code, Program.Uniforms, Program.Textures); } public IEnumerable GetConstBufferUsage(long Key) @@ -133,7 +134,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { //Enhanced layouts are required for Geometry shaders //skip this stage if current driver has no ARB_enhanced_layouts - if (!OGLExtension.HasEnhancedLayouts()) + if (!OGLExtension.EnhancedLayouts) { return; } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index 3347afbd1c..6c60852801 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -39,41 +39,25 @@ namespace Ryujinx.Graphics.Gal.OpenGL TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Size); - GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask; - - bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END; - - if (ImageUtils.IsCompressed(Image.Format) && !IsASTC) + if (ImageUtils.IsCompressed(Image.Format)) { - InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format); - - GL.CompressedTexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Image.Width, - Image.Height, - Border, - Size, - IntPtr.Zero); + throw new InvalidOperationException("Surfaces with compressed formats are not supported!"); } - else - { - (PixelInternalFormat InternalFmt, - PixelFormat Format, - PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Image.Width, - Image.Height, - Border, - Format, - Type, - IntPtr.Zero); - } + (PixelInternalFormat InternalFmt, + PixelFormat Format, + PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Image.Width, + Image.Height, + Border, + Format, + Type, + IntPtr.Zero); } public void Create(long Key, byte[] Data, GalImage Image) @@ -87,11 +71,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length); - GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask; - - bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END; - - if (ImageUtils.IsCompressed(Image.Format) && !IsASTC) + if (ImageUtils.IsCompressed(Image.Format) && !IsAstc(Image.Format)) { InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format); @@ -108,7 +88,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL else { //TODO: Use KHR_texture_compression_astc_hdr when available - if (IsASTC) + if (IsAstc(Image.Format)) { int TextureBlockWidth = ImageUtils.GetBlockWidth(Image.Format); int TextureBlockHeight = ImageUtils.GetBlockHeight(Image.Format); @@ -120,17 +100,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL Image.Width, Image.Height, 1); - Image.Format = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm; - } - else if (TypeLess == GalImageFormat.G8R8) - { - Data = ImageConverter.G8R8ToR8G8( - Data, - Image.Width, - Image.Height, - 1); - - Image.Format = GalImageFormat.R8G8 | (Image.Format & GalImageFormat.TypeMask); + Image.Format = GalImageFormat.RGBA8 | GalImageFormat.Unorm; } (PixelInternalFormat InternalFmt, @@ -150,6 +120,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + private static bool IsAstc(GalImageFormat Format) + { + Format &= GalImageFormat.FormatMask; + + return Format > GalImageFormat.Astc2DStart && Format < GalImageFormat.Astc2DEnd; + } + public bool TryGetImage(long Key, out GalImage Image) { if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 11f0444948..d0f9223b91 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -360,7 +360,7 @@ namespace Ryujinx.Graphics.Gal.Shader private void PrintDeclSsy() { - SB.AppendLine("uint " + GlslDecl.SsyCursorName + "= 0;"); + SB.AppendLine("uint " + GlslDecl.SsyCursorName + " = 0;"); SB.AppendLine("uint " + GlslDecl.SsyStackName + "[" + GlslDecl.SsyStackSize + "];" + Environment.NewLine); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs index 0a3c0da98a..d4a76bc936 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -585,6 +585,7 @@ namespace Ryujinx.Graphics.Gal.Shader bool AbsA = OpCode.Read(46); bool NegA = OpCode.Read(48); bool AbsB = OpCode.Read(49); + bool Sat = OpCode.Read(50); ShaderIrNode OperA = OpCode.Gpr8(), OperB; @@ -603,12 +604,13 @@ namespace Ryujinx.Graphics.Gal.Shader ShaderIrNode Op = new ShaderIrOp(ShaderIrInst.Fadd, OperA, OperB); - Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); + Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), GetAluFsat(Op, Sat)))); } private static void EmitFmul(ShaderIrBlock Block, long OpCode, ShaderOper Oper) { bool NegB = OpCode.Read(48); + bool Sat = OpCode.Read(50); ShaderIrNode OperA = OpCode.Gpr8(), OperB; @@ -625,13 +627,14 @@ namespace Ryujinx.Graphics.Gal.Shader ShaderIrNode Op = new ShaderIrOp(ShaderIrInst.Fmul, OperA, OperB); - Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); + Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), GetAluFsat(Op, Sat)))); } private static void EmitFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper) { bool NegB = OpCode.Read(48); bool NegC = OpCode.Read(49); + bool Sat = OpCode.Read(50); ShaderIrNode OperA = OpCode.Gpr8(), OperB, OperC; @@ -658,7 +661,7 @@ namespace Ryujinx.Graphics.Gal.Shader ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC); - Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); + Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), GetAluFsat(Op, Sat)))); } private static void EmitIadd(ShaderIrBlock Block, long OpCode, ShaderOper Oper) diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs index ebacd53ab4..d07bcd9171 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -2,6 +2,9 @@ namespace Ryujinx.Graphics.Gal.Shader { static class ShaderDecodeHelper { + private static readonly ShaderIrOperImmf ImmfZero = new ShaderIrOperImmf(0); + private static readonly ShaderIrOperImmf ImmfOne = new ShaderIrOperImmf(1); + public static ShaderIrNode GetAluFabsFneg(ShaderIrNode Node, bool Abs, bool Neg) { return GetAluFneg(GetAluFabs(Node, Abs), Neg); @@ -17,6 +20,11 @@ namespace Ryujinx.Graphics.Gal.Shader return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node; } + public static ShaderIrNode GetAluFsat(ShaderIrNode Node, bool Sat) + { + return Sat ? new ShaderIrOp(ShaderIrInst.Fclamp, Node, ImmfZero, ImmfOne) : Node; + } + public static ShaderIrNode GetAluIabsIneg(ShaderIrNode Node, bool Abs, bool Neg) { return GetAluIneg(GetAluIabs(Node, Abs), Neg); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs index ac6ae8d5d1..f8c07f31ab 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gal.Shader { int Target = ((ShaderIrOperImm)CurrOp.OperandA).Value; - Current.Branch = Enqueue(Target, Current); + Enqueue(Target, Current); } } @@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Gal.Shader DbgOpCode += (Decode?.Method.Name ?? "???"); - if (Decode == ShaderDecode.Bra) + if (Decode == ShaderDecode.Bra || Decode == ShaderDecode.Ssy) { int Offset = ((int)(OpCode >> 20) << 8) >> 8; diff --git a/Ryujinx.Graphics/Gal/ShaderDumper.cs b/Ryujinx.Graphics/Gal/ShaderDumper.cs index 541368e89b..d3bcbf0d87 100644 --- a/Ryujinx.Graphics/Gal/ShaderDumper.cs +++ b/Ryujinx.Graphics/Gal/ShaderDumper.cs @@ -7,11 +7,11 @@ namespace Ryujinx.Graphics.Gal { private static string RuntimeDir; - private static int DumpIndex = 1; + public static int DumpIndex { get; private set; } = 1; public static void Dump(IGalMemory Memory, long Position, GalShaderType Type, string ExtSuffix = "") { - if (string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath)) + if (!IsDumpEnabled()) { return; } @@ -25,9 +25,10 @@ namespace Ryujinx.Graphics.Gal using (FileStream FullFile = File.Create(FullPath)) using (FileStream CodeFile = File.Create(CodePath)) - using (BinaryWriter FullWriter = new BinaryWriter(FullFile)) - using (BinaryWriter CodeWriter = new BinaryWriter(CodeFile)) { + BinaryWriter FullWriter = new BinaryWriter(FullFile); + BinaryWriter CodeWriter = new BinaryWriter(CodeFile); + for (long i = 0; i < 0x50; i += 4) { FullWriter.Write(Memory.ReadInt32(Position + i)); @@ -69,6 +70,11 @@ namespace Ryujinx.Graphics.Gal } } + public static bool IsDumpEnabled() + { + return !string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath); + } + private static string FullDir() { return CreateAndReturn(Path.Combine(DumpDir(), "Full")); diff --git a/Ryujinx.Graphics/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs index 0a8d201452..71390a83ac 100644 --- a/Ryujinx.Graphics/GpuResourceManager.cs +++ b/Ryujinx.Graphics/GpuResourceManager.cs @@ -46,7 +46,7 @@ namespace Ryujinx.Graphics Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); } - Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage); + Gpu.Renderer.RenderTarget.BindColor(Position, Attachment); } public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage) @@ -60,7 +60,7 @@ namespace Ryujinx.Graphics Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); } - Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage); + Gpu.Renderer.RenderTarget.BindZeta(Position); } public void SendTexture(NvGpuVmm Vmm, long Position, GalImage NewImage, int TexIndex = -1) diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs index b19f3063bf..fd15d0b6a2 100644 --- a/Ryujinx.Graphics/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/NvGpuEngine3d.cs @@ -97,10 +97,14 @@ namespace Ryujinx.Graphics SetCullFace(State); SetDepth(State); SetStencil(State); - SetAlphaBlending(State); + SetBlending(State); + SetColorMask(State); SetPrimitiveRestart(State); - SetFrameBuffer(Vmm, 0); + for (int FbIndex = 0; FbIndex < 8; FbIndex++) + { + SetFrameBuffer(Vmm, FbIndex); + } SetZeta(Vmm); @@ -137,7 +141,7 @@ namespace Ryujinx.Graphics { int Arg0 = PBEntry.Arguments[0]; - int FbIndex = (Arg0 >> 6) & 0xf; + int Attachment = (Arg0 >> 6) & 0xf; GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f); @@ -150,16 +154,18 @@ namespace Ryujinx.Graphics int Stencil = ReadRegister(NvGpuEngine3dReg.ClearStencil); - SetFrameBuffer(Vmm, FbIndex); + SetFrameBuffer(Vmm, Attachment); SetZeta(Vmm); - Gpu.Renderer.Rasterizer.ClearBuffers( - Flags, - FbIndex, - Red, Green, Blue, Alpha, - Depth, - Stencil); + SetRenderTargets(); + + Gpu.Renderer.RenderTarget.Bind(); + + Gpu.Renderer.Rasterizer.ClearBuffers(Flags, Attachment, Red, Green, Blue, Alpha, Depth, Stencil); + + Gpu.Renderer.Pipeline.ResetDepthMask(); + Gpu.Renderer.Pipeline.ResetColorMask(Attachment); } private void SetFrameBuffer(NvGpuVmm Vmm, int FbIndex) @@ -204,7 +210,7 @@ namespace Ryujinx.Graphics Gpu.ResourceManager.SendColorBuffer(Vmm, Key, FbIndex, Image); - Gpu.Renderer.RenderTarget.SetViewport(VpX, VpY, VpW, VpH); + Gpu.Renderer.RenderTarget.SetViewport(FbIndex, VpX, VpY, VpW, VpH); } private void SetFrameBuffer(GalPipelineState State) @@ -337,13 +343,8 @@ namespace Ryujinx.Graphics { switch (FrontFace) { - case GalFrontFace.CW: - FrontFace = GalFrontFace.CCW; - break; - - case GalFrontFace.CCW: - FrontFace = GalFrontFace.CW; - break; + case GalFrontFace.CW: FrontFace = GalFrontFace.CCW; break; + case GalFrontFace.CCW: FrontFace = GalFrontFace.CW; break; } } @@ -396,7 +397,7 @@ namespace Ryujinx.Graphics } } - private void SetAlphaBlending(GalPipelineState State) + private void SetBlending(GalPipelineState State) { //TODO: Support independent blend properly. State.BlendEnabled = ReadRegisterBool(NvGpuEngine3dReg.IBlendNEnable); @@ -414,6 +415,26 @@ namespace Ryujinx.Graphics } } + private void SetColorMask(GalPipelineState State) + { + int ColorMask = ReadRegister(NvGpuEngine3dReg.ColorMask); + + State.ColorMask.Red = ((ColorMask >> 0) & 0xf) != 0; + State.ColorMask.Green = ((ColorMask >> 4) & 0xf) != 0; + State.ColorMask.Blue = ((ColorMask >> 8) & 0xf) != 0; + State.ColorMask.Alpha = ((ColorMask >> 12) & 0xf) != 0; + + for (int Index = 0; Index < GalPipelineState.RenderTargetsCount; Index++) + { + ColorMask = ReadRegister(NvGpuEngine3dReg.ColorMaskN + Index); + + State.ColorMasks[Index].Red = ((ColorMask >> 0) & 0xf) != 0; + State.ColorMasks[Index].Green = ((ColorMask >> 4) & 0xf) != 0; + State.ColorMasks[Index].Blue = ((ColorMask >> 8) & 0xf) != 0; + State.ColorMasks[Index].Alpha = ((ColorMask >> 12) & 0xf) != 0; + } + } + private void SetPrimitiveRestart(GalPipelineState State) { State.PrimitiveRestartEnabled = ReadRegisterBool(NvGpuEngine3dReg.PrimRestartEnable); @@ -426,21 +447,22 @@ namespace Ryujinx.Graphics private void SetRenderTargets() { - bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData); + //Commercial games do not seem to + //bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData); - if (SeparateFragData) + uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl)); + + uint Count = Control & 0xf; + + if (Count > 0) { - uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl)); - - uint Count = Control & 0xf; - int[] Map = new int[Count]; - for (int i = 0; i < Count; i++) + for (int Index = 0; Index < Count; Index++) { - int Shift = 4 + i * 3; + int Shift = 4 + Index * 3; - Map[i] = (int)((Control >> Shift) & 7); + Map[Index] = (int)((Control >> Shift) & 7); } Gpu.Renderer.RenderTarget.SetMap(Map); @@ -560,12 +582,15 @@ namespace Ryujinx.Graphics private void UploadVertexArrays(NvGpuVmm Vmm, GalPipelineState State) { - long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); + long IbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); - long IboKey = Vmm.GetPhysicalAddress(IndexPosition); + long IboKey = Vmm.GetPhysicalAddress(IbPosition); int IndexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); + int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); + + GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); GalIndexFormat IndexFormat = (GalIndexFormat)IndexEntryFmt; @@ -582,14 +607,50 @@ namespace Ryujinx.Graphics bool IboCached = Gpu.Renderer.Rasterizer.IsIboCached(IboKey, (uint)IbSize); + bool UsesLegacyQuads = + PrimType == GalPrimitiveType.Quads || + PrimType == GalPrimitiveType.QuadStrip; + if (!IboCached || QueryKeyUpload(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index)) { - IntPtr DataAddress = Vmm.GetHostAddress(IndexPosition, IbSize); + if (!UsesLegacyQuads) + { + IntPtr DataAddress = Vmm.GetHostAddress(IbPosition, IbSize); - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, DataAddress); + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, DataAddress); + } + else + { + byte[] Buffer = Vmm.ReadBytes(IbPosition, IbSize); + + if (PrimType == GalPrimitiveType.Quads) + { + Buffer = QuadHelper.ConvertIbQuadsToTris(Buffer, IndexEntrySize, IndexCount); + } + else /* if (PrimType == GalPrimitiveType.QuadStrip) */ + { + Buffer = QuadHelper.ConvertIbQuadStripToTris(Buffer, IndexEntrySize, IndexCount); + } + + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, Buffer); + } } - Gpu.Renderer.Rasterizer.SetIndexArray(IbSize, IndexFormat); + if (!UsesLegacyQuads) + { + Gpu.Renderer.Rasterizer.SetIndexArray(IbSize, IndexFormat); + } + else + { + if (PrimType == GalPrimitiveType.Quads) + { + Gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertIbSizeQuadsToTris(IbSize), IndexFormat); + } + else /* if (PrimType == GalPrimitiveType.QuadStrip) */ + { + Gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertIbSizeQuadStripToTris(IbSize), IndexFormat); + } + } } List[] Attribs = new List[32]; @@ -605,10 +666,19 @@ namespace Ryujinx.Graphics Attribs[ArrayIndex] = new List(); } + long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + ArrayIndex * 4); + + int Offset = (Packed >> 7) & 0x3fff; + + //Note: 16 is the maximum size of an attribute, + //having a component size of 32-bits with 4 elements (a vec4). + IntPtr Pointer = Vmm.GetHostAddress(VertexPosition + Offset, 16); + Attribs[ArrayIndex].Add(new GalVertexAttrib( Attr, ((Packed >> 6) & 0x1) != 0, - (Packed >> 7) & 0x3fff, + Offset, + Pointer, (GalVertexAttribSize)((Packed >> 21) & 0x3f), (GalVertexAttribType)((Packed >> 27) & 0x7), ((Packed >> 31) & 0x1) != 0)); @@ -702,6 +772,8 @@ namespace Ryujinx.Graphics Gpu.Renderer.Pipeline.Bind(State); + Gpu.Renderer.RenderTarget.Bind(); + if (IndexCount != 0) { int IndexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); @@ -712,6 +784,27 @@ namespace Ryujinx.Graphics long IboKey = Vmm.GetPhysicalAddress(IndexPosition); + //Quad primitive types were deprecated on OpenGL 3.x, + //they are converted to a triangles index buffer on IB creation, + //so we should use the triangles type here too. + if (PrimType == GalPrimitiveType.Quads || + PrimType == GalPrimitiveType.QuadStrip) + { + PrimType = GalPrimitiveType.Triangles; + + //Note: We assume that index first points to the first + //vertex of a quad, if it points to the middle of a + //quad (First % 4 != 0 for Quads) then it will not work properly. + if (PrimType == GalPrimitiveType.Quads) + { + IndexFirst = QuadHelper.ConvertIbSizeQuadsToTris(IndexFirst); + } + else /* if (PrimType == GalPrimitiveType.QuadStrip) */ + { + IndexFirst = QuadHelper.ConvertIbSizeQuadStripToTris(IndexFirst); + } + } + Gpu.Renderer.Rasterizer.DrawElements(IboKey, IndexFirst, VertexBase, PrimType); } else diff --git a/Ryujinx.Graphics/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/NvGpuEngine3dReg.cs index 418e5b6b40..ef74e4f675 100644 --- a/Ryujinx.Graphics/NvGpuEngine3dReg.cs +++ b/Ryujinx.Graphics/NvGpuEngine3dReg.cs @@ -23,6 +23,7 @@ namespace Ryujinx.Graphics StencilBackFuncRef = 0x3d5, StencilBackMask = 0x3d6, StencilBackFuncMask = 0x3d7, + ColorMask = 0x3e4, RTSeparateFragData = 0x3eb, ZetaAddress = 0x3f8, ZetaFormat = 0x3fa, @@ -78,6 +79,7 @@ namespace Ryujinx.Graphics CullFaceEnable = 0x646, FrontFace = 0x647, CullFace = 0x648, + ColorMaskN = 0x680, QueryAddress = 0x6c0, QuerySequence = 0x6c2, QueryControl = 0x6c3, diff --git a/Ryujinx.Graphics/QuadHelper.cs b/Ryujinx.Graphics/QuadHelper.cs new file mode 100644 index 0000000000..0dfffce0bc --- /dev/null +++ b/Ryujinx.Graphics/QuadHelper.cs @@ -0,0 +1,81 @@ +using System; + +namespace Ryujinx.Graphics +{ + static class QuadHelper + { + public static int ConvertIbSizeQuadsToTris(int Size) + { + return Size <= 0 ? 0 : (Size / 4) * 6; + } + + public static int ConvertIbSizeQuadStripToTris(int Size) + { + return Size <= 1 ? 0 : ((Size - 2) / 2) * 6; + } + + public static byte[] ConvertIbQuadsToTris(byte[] Data, int EntrySize, int Count) + { + int PrimitivesCount = Count / 4; + + int QuadPrimSize = 4 * EntrySize; + int TrisPrimSize = 6 * EntrySize; + + byte[] Output = new byte[PrimitivesCount * 6 * EntrySize]; + + for (int Prim = 0; Prim < PrimitivesCount; Prim++) + { + void AssignIndex(int Src, int Dst, int CopyCount = 1) + { + Src = Prim * QuadPrimSize + Src * EntrySize; + Dst = Prim * TrisPrimSize + Dst * EntrySize; + + Buffer.BlockCopy(Data, Src, Output, Dst, CopyCount * EntrySize); + } + + //0 1 2 -> 0 1 2. + AssignIndex(0, 0, 3); + + //2 3 -> 3 4. + AssignIndex(2, 3, 2); + + //0 -> 5. + AssignIndex(0, 5); + } + + return Output; + } + + public static byte[] ConvertIbQuadStripToTris(byte[] Data, int EntrySize, int Count) + { + int PrimitivesCount = (Count - 2) / 2; + + int QuadPrimSize = 2 * EntrySize; + int TrisPrimSize = 6 * EntrySize; + + byte[] Output = new byte[PrimitivesCount * 6 * EntrySize]; + + for (int Prim = 0; Prim < PrimitivesCount; Prim++) + { + void AssignIndex(int Src, int Dst, int CopyCount = 1) + { + Src = Prim * QuadPrimSize + Src * EntrySize + 2 * EntrySize; + Dst = Prim * TrisPrimSize + Dst * EntrySize; + + Buffer.BlockCopy(Data, Src, Output, Dst, CopyCount * EntrySize); + } + + //-2 -1 0 -> 0 1 2. + AssignIndex(-2, 0, 3); + + //0 1 -> 3 4. + AssignIndex(0, 3, 2); + + //-2 -> 5. + AssignIndex(-2, 5); + } + + return Output; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Ryujinx.Graphics.csproj b/Ryujinx.Graphics/Ryujinx.Graphics.csproj index 7d86cbe134..be113a21bf 100644 --- a/Ryujinx.Graphics/Ryujinx.Graphics.csproj +++ b/Ryujinx.Graphics/Ryujinx.Graphics.csproj @@ -19,6 +19,7 @@ + diff --git a/Ryujinx.Graphics/Texture/ImageConverter.cs b/Ryujinx.Graphics/Texture/ImageConverter.cs deleted file mode 100644 index 89529061fd..0000000000 --- a/Ryujinx.Graphics/Texture/ImageConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Ryujinx.Graphics.Texture -{ - static class ImageConverter - { - public static byte[] G8R8ToR8G8( - byte[] Data, - int Width, - int Height, - int Depth) - { - int Texels = Width * Height * Depth; - - byte[] Output = new byte[Texels * 2]; - - for (int Texel = 0; Texel < Texels; Texel++) - { - Output[Texel * 2 + 0] = Data[Texel * 2 + 1]; - Output[Texel * 2 + 1] = Data[Texel * 2 + 0]; - } - - return Output; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Texture/ImageUtils.cs b/Ryujinx.Graphics/Texture/ImageUtils.cs index 18a179fbfa..d2172c2ef8 100644 --- a/Ryujinx.Graphics/Texture/ImageUtils.cs +++ b/Ryujinx.Graphics/Texture/ImageUtils.cs @@ -35,105 +35,105 @@ namespace Ryujinx.Graphics.Texture } } - private const GalImageFormat Snorm = GalImageFormat.Snorm; - private const GalImageFormat Unorm = GalImageFormat.Unorm; - private const GalImageFormat Sint = GalImageFormat.Sint; - private const GalImageFormat Uint = GalImageFormat.Uint; - private const GalImageFormat Sfloat = GalImageFormat.Sfloat; + private const GalImageFormat Snorm = GalImageFormat.Snorm; + private const GalImageFormat Unorm = GalImageFormat.Unorm; + private const GalImageFormat Sint = GalImageFormat.Sint; + private const GalImageFormat Uint = GalImageFormat.Uint; + private const GalImageFormat Float = GalImageFormat.Float; + private const GalImageFormat Srgb = GalImageFormat.Srgb; private static readonly Dictionary s_TextureTable = new Dictionary() - { - { GalTextureFormat.R32G32B32A32, GalImageFormat.R32G32B32A32 | Sint | Uint | Sfloat }, - { GalTextureFormat.R16G16B16A16, GalImageFormat.R16G16B16A16 | Snorm | Unorm | Sint | Uint | Sfloat }, - { GalTextureFormat.R32G32, GalImageFormat.R32G32 | Sint | Uint | Sfloat }, - { GalTextureFormat.A8B8G8R8, GalImageFormat.A8B8G8R8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.A2B10G10R10, GalImageFormat.A2B10G10R10 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.G8R8, GalImageFormat.G8R8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Sfloat }, - { GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.R16G16, GalImageFormat.R16G16 | Snorm | Sfloat }, - { GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Sfloat }, - { GalTextureFormat.A4B4G4R4, GalImageFormat.A4B4G4R4 | Unorm }, - { GalTextureFormat.A1B5G5R5, GalImageFormat.A1R5G5B5 | Unorm }, - { GalTextureFormat.B5G6R5, GalImageFormat.B5G6R5 | Unorm }, - { GalTextureFormat.BF10GF11RF11, GalImageFormat.B10G11R11 | Sfloat }, - { GalTextureFormat.Z24S8, GalImageFormat.D24_S8 | Unorm }, - { GalTextureFormat.ZF32, GalImageFormat.D32 | Sfloat }, - { GalTextureFormat.ZF32_X24S8, GalImageFormat.D32_S8 | Unorm }, - { GalTextureFormat.Z16, GalImageFormat.D16 | Unorm }, + { + { GalTextureFormat.RGBA32, GalImageFormat.RGBA32 | Sint | Uint | Float }, + { GalTextureFormat.RGBA16, GalImageFormat.RGBA16 | Snorm | Unorm | Sint | Uint | Float }, + { GalTextureFormat.RG32, GalImageFormat.RG32 | Sint | Uint | Float }, + { GalTextureFormat.RGBA8, GalImageFormat.RGBA8 | Snorm | Unorm | Sint | Uint | Srgb }, + { GalTextureFormat.RGB10A2, GalImageFormat.RGB10A2 | Snorm | Unorm | Sint | Uint }, + { GalTextureFormat.RG8, GalImageFormat.RG8 | Snorm | Unorm | Sint | Uint }, + { GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Float }, + { GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint }, + { GalTextureFormat.RG16, GalImageFormat.RG16 | Snorm | Unorm | Float }, + { GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Float }, + { GalTextureFormat.RGBA4, GalImageFormat.RGBA4 | Unorm }, + { GalTextureFormat.RGB5A1, GalImageFormat.RGB5A1 | Unorm }, + { GalTextureFormat.RGB565, GalImageFormat.RGB565 | Unorm }, + { GalTextureFormat.R11G11B10F, GalImageFormat.R11G11B10 | Float }, + { GalTextureFormat.D24S8, GalImageFormat.D24S8 | Unorm | Uint }, + { GalTextureFormat.D32F, GalImageFormat.D32 | Float }, + { GalTextureFormat.D32FX24S8, GalImageFormat.D32S8 | Unorm }, + { GalTextureFormat.D16, GalImageFormat.D16 | Unorm }, - //Compressed formats - { GalTextureFormat.BC6H_SF16, GalImageFormat.BC6H_SF16 | Unorm }, - { GalTextureFormat.BC6H_UF16, GalImageFormat.BC6H_UF16 | Sfloat }, - { GalTextureFormat.BC7U, GalImageFormat.BC7 | Unorm }, - { GalTextureFormat.BC1, GalImageFormat.BC1_RGBA | Unorm }, - { GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm }, - { GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm }, - { GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm }, - { GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm }, - { GalTextureFormat.Astc2D4x4, GalImageFormat.ASTC_4x4 | Unorm }, - { GalTextureFormat.Astc2D5x5, GalImageFormat.ASTC_5x5 | Unorm }, - { GalTextureFormat.Astc2D6x6, GalImageFormat.ASTC_6x6 | Unorm }, - { GalTextureFormat.Astc2D8x8, GalImageFormat.ASTC_8x8 | Unorm }, - { GalTextureFormat.Astc2D10x10, GalImageFormat.ASTC_10x10 | Unorm }, - { GalTextureFormat.Astc2D12x12, GalImageFormat.ASTC_12x12 | Unorm }, - { GalTextureFormat.Astc2D5x4, GalImageFormat.ASTC_5x4 | Unorm }, - { GalTextureFormat.Astc2D6x5, GalImageFormat.ASTC_6x5 | Unorm }, - { GalTextureFormat.Astc2D8x6, GalImageFormat.ASTC_8x6 | Unorm }, - { GalTextureFormat.Astc2D10x8, GalImageFormat.ASTC_10x8 | Unorm }, - { GalTextureFormat.Astc2D12x10, GalImageFormat.ASTC_12x10 | Unorm }, - { GalTextureFormat.Astc2D8x5, GalImageFormat.ASTC_8x5 | Unorm }, - { GalTextureFormat.Astc2D10x5, GalImageFormat.ASTC_10x5 | Unorm }, - { GalTextureFormat.Astc2D10x6, GalImageFormat.ASTC_10x6 | Unorm } - }; + //Compressed formats + { GalTextureFormat.BptcSfloat, GalImageFormat.BptcSfloat | Float }, + { GalTextureFormat.BptcUfloat, GalImageFormat.BptcUfloat | Float }, + { GalTextureFormat.BptcUnorm, GalImageFormat.BptcUnorm | Unorm | Srgb }, + { GalTextureFormat.BC1, GalImageFormat.BC1 | Unorm | Srgb }, + { GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm | Srgb }, + { GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm | Srgb }, + { GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm }, + { GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm }, + { GalTextureFormat.Astc2D4x4, GalImageFormat.Astc2D4x4 | Unorm | Srgb }, + { GalTextureFormat.Astc2D5x5, GalImageFormat.Astc2D5x5 | Unorm | Srgb }, + { GalTextureFormat.Astc2D6x6, GalImageFormat.Astc2D6x6 | Unorm | Srgb }, + { GalTextureFormat.Astc2D8x8, GalImageFormat.Astc2D8x8 | Unorm | Srgb }, + { GalTextureFormat.Astc2D10x10, GalImageFormat.Astc2D10x10 | Unorm | Srgb }, + { GalTextureFormat.Astc2D12x12, GalImageFormat.Astc2D12x12 | Unorm | Srgb }, + { GalTextureFormat.Astc2D5x4, GalImageFormat.Astc2D5x4 | Unorm | Srgb }, + { GalTextureFormat.Astc2D6x5, GalImageFormat.Astc2D6x5 | Unorm | Srgb }, + { GalTextureFormat.Astc2D8x6, GalImageFormat.Astc2D8x6 | Unorm | Srgb }, + { GalTextureFormat.Astc2D10x8, GalImageFormat.Astc2D10x8 | Unorm | Srgb }, + { GalTextureFormat.Astc2D12x10, GalImageFormat.Astc2D12x10 | Unorm | Srgb }, + { GalTextureFormat.Astc2D8x5, GalImageFormat.Astc2D8x5 | Unorm | Srgb }, + { GalTextureFormat.Astc2D10x5, GalImageFormat.Astc2D10x5 | Unorm | Srgb }, + { GalTextureFormat.Astc2D10x6, GalImageFormat.Astc2D10x6 | Unorm | Srgb } + }; private static readonly Dictionary s_ImageTable = new Dictionary() { - { GalImageFormat.R32G32B32A32, new ImageDescriptor(16, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R16G16B16A16, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R32G32, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.A8B8G8R8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.A2B10G10R10, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R32, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.A4B4G4R4, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BC6H_SF16, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.BC6H_UF16, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.A1R5G5B5, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.B5G6R5, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BC7, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.R16G16, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R8G8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.G8R8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R16, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R8, new ImageDescriptor(1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.B10G11R11, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.A8B8G8R8_SRGB, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BC1_RGBA, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.BC2, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.BC3, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.BC4, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.BC5, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.ASTC_4x4, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, - { GalImageFormat.ASTC_5x5, new ImageDescriptor(16, 5, 5, TargetBuffer.Color) }, - { GalImageFormat.ASTC_6x6, new ImageDescriptor(16, 6, 6, TargetBuffer.Color) }, - { GalImageFormat.ASTC_8x8, new ImageDescriptor(16, 8, 8, TargetBuffer.Color) }, - { GalImageFormat.ASTC_10x10, new ImageDescriptor(16, 10, 10, TargetBuffer.Color) }, - { GalImageFormat.ASTC_12x12, new ImageDescriptor(16, 12, 12, TargetBuffer.Color) }, - { GalImageFormat.ASTC_5x4, new ImageDescriptor(16, 5, 4, TargetBuffer.Color) }, - { GalImageFormat.ASTC_6x5, new ImageDescriptor(16, 6, 5, TargetBuffer.Color) }, - { GalImageFormat.ASTC_8x6, new ImageDescriptor(16, 8, 6, TargetBuffer.Color) }, - { GalImageFormat.ASTC_10x8, new ImageDescriptor(16, 10, 8, TargetBuffer.Color) }, - { GalImageFormat.ASTC_12x10, new ImageDescriptor(16, 12, 10, TargetBuffer.Color) }, - { GalImageFormat.ASTC_8x5, new ImageDescriptor(16, 8, 5, TargetBuffer.Color) }, - { GalImageFormat.ASTC_10x5, new ImageDescriptor(16, 10, 5, TargetBuffer.Color) }, - { GalImageFormat.ASTC_10x6, new ImageDescriptor(16, 10, 6, TargetBuffer.Color) }, + { GalImageFormat.RGBA32, new ImageDescriptor(16, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RGBA16, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RG32, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RGBA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BGRA8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RGB10A2, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R32, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RGBA4, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BptcSfloat, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BptcUfloat, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.RGB5A1, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RGB565, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BptcUnorm, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.RG16, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.RG8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R16, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R8, new ImageDescriptor(1, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R11G11B10, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BC1, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC2, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC3, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC4, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC5, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.Astc2D4x4, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.Astc2D5x5, new ImageDescriptor(16, 5, 5, TargetBuffer.Color) }, + { GalImageFormat.Astc2D6x6, new ImageDescriptor(16, 6, 6, TargetBuffer.Color) }, + { GalImageFormat.Astc2D8x8, new ImageDescriptor(16, 8, 8, TargetBuffer.Color) }, + { GalImageFormat.Astc2D10x10, new ImageDescriptor(16, 10, 10, TargetBuffer.Color) }, + { GalImageFormat.Astc2D12x12, new ImageDescriptor(16, 12, 12, TargetBuffer.Color) }, + { GalImageFormat.Astc2D5x4, new ImageDescriptor(16, 5, 4, TargetBuffer.Color) }, + { GalImageFormat.Astc2D6x5, new ImageDescriptor(16, 6, 5, TargetBuffer.Color) }, + { GalImageFormat.Astc2D8x6, new ImageDescriptor(16, 8, 6, TargetBuffer.Color) }, + { GalImageFormat.Astc2D10x8, new ImageDescriptor(16, 10, 8, TargetBuffer.Color) }, + { GalImageFormat.Astc2D12x10, new ImageDescriptor(16, 12, 10, TargetBuffer.Color) }, + { GalImageFormat.Astc2D8x5, new ImageDescriptor(16, 8, 5, TargetBuffer.Color) }, + { GalImageFormat.Astc2D10x5, new ImageDescriptor(16, 10, 5, TargetBuffer.Color) }, + { GalImageFormat.Astc2D10x6, new ImageDescriptor(16, 10, 6, TargetBuffer.Color) }, - { GalImageFormat.D24_S8, new ImageDescriptor(4, 1, 1, TargetBuffer.DepthStencil) }, - { GalImageFormat.D32, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) }, - { GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) }, - { GalImageFormat.D32_S8, new ImageDescriptor(8, 1, 1, TargetBuffer.DepthStencil) }, + { GalImageFormat.D24S8, new ImageDescriptor(4, 1, 1, TargetBuffer.DepthStencil) }, + { GalImageFormat.D32, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) }, + { GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) }, + { GalImageFormat.D32S8, new ImageDescriptor(8, 1, 1, TargetBuffer.DepthStencil) } }; public static GalImageFormat ConvertTexture( @@ -141,60 +141,62 @@ namespace Ryujinx.Graphics.Texture GalTextureType RType, GalTextureType GType, GalTextureType BType, - GalTextureType AType) + GalTextureType AType, + bool ConvSrgb) { if (RType != GType || RType != BType || RType != AType) { - throw new NotImplementedException("Per component types are not implemented"); + throw new NotImplementedException("Per component types are not implemented!"); } if (!s_TextureTable.TryGetValue(Format, out GalImageFormat ImageFormat)) { - throw new NotImplementedException("Texture with format " + ((int)Format).ToString("x2") + " not implemented"); + throw new NotImplementedException($"Format 0x{((int)Format):x} not implemented!"); } - GalTextureType Type = RType; + GalImageFormat FormatType = ConvSrgb ? Srgb : GetFormatType(RType); - GalImageFormat FormatType = GetFormatType(RType); + GalImageFormat CombinedFormat = (ImageFormat & GalImageFormat.FormatMask) | FormatType; - if (ImageFormat.HasFlag(FormatType)) + if (!ImageFormat.HasFlag(FormatType)) { - return (ImageFormat & GalImageFormat.FormatMask) | FormatType; - } - else - { - throw new NotImplementedException("Texture with format " + Format + - " and component type " + Type + " is not implemented"); + throw new NotImplementedException($"Format \"{CombinedFormat}\" not implemented!"); } + + return CombinedFormat; } public static GalImageFormat ConvertSurface(GalSurfaceFormat Format) { switch (Format) { - case GalSurfaceFormat.RGBA32Float: return GalImageFormat.R32G32B32A32 | Sfloat; - case GalSurfaceFormat.RGBA32Uint: return GalImageFormat.R32G32B32A32 | Uint; - case GalSurfaceFormat.RGBA16Float: return GalImageFormat.R16G16B16A16 | Sfloat; - case GalSurfaceFormat.RG32Float: return GalImageFormat.R32G32 | Sfloat; - case GalSurfaceFormat.RG32Sint: return GalImageFormat.R32G32 | Sint; - case GalSurfaceFormat.RG32Uint: return GalImageFormat.R32G32 | Uint; - case GalSurfaceFormat.BGRA8Unorm: return GalImageFormat.R8G8B8A8 | Unorm; //Is this right? - case GalSurfaceFormat.BGRA8Srgb: return GalImageFormat.A8B8G8R8_SRGB; //This one might be wrong - case GalSurfaceFormat.RGB10A2Unorm: return GalImageFormat.A2B10G10R10 | Unorm; - case GalSurfaceFormat.RGBA8Unorm: return GalImageFormat.A8B8G8R8 | Unorm; - case GalSurfaceFormat.RGBA8Srgb: return GalImageFormat.A8B8G8R8_SRGB; - case GalSurfaceFormat.RGBA8Snorm: return GalImageFormat.A8B8G8R8 | Snorm; - case GalSurfaceFormat.RG16Snorm: return GalImageFormat.R16G16 | Snorm; - case GalSurfaceFormat.RG16Float: return GalImageFormat.R16G16 | Sfloat; - case GalSurfaceFormat.R11G11B10Float: return GalImageFormat.B10G11R11 | Sfloat; - case GalSurfaceFormat.R32Float: return GalImageFormat.R32 | Sfloat; - case GalSurfaceFormat.RG8Unorm: return GalImageFormat.R8G8 | Unorm; - case GalSurfaceFormat.RG8Snorm: return GalImageFormat.R8 | Snorm; - case GalSurfaceFormat.R16Float: return GalImageFormat.R16 | Sfloat; - case GalSurfaceFormat.R16Unorm: return GalImageFormat.R16 | Unorm; - case GalSurfaceFormat.R8Unorm: return GalImageFormat.R8 | Unorm; - case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.B5G6R5 | Unorm; - case GalSurfaceFormat.BGR5A1Unorm: return GalImageFormat.A1R5G5B5 | Unorm; + case GalSurfaceFormat.RGBA32Float: return GalImageFormat.RGBA32 | Float; + case GalSurfaceFormat.RGBA32Uint: return GalImageFormat.RGBA32 | Uint; + case GalSurfaceFormat.RGBA16Float: return GalImageFormat.RGBA16 | Float; + case GalSurfaceFormat.RG32Float: return GalImageFormat.RG32 | Float; + case GalSurfaceFormat.RG32Sint: return GalImageFormat.RG32 | Sint; + case GalSurfaceFormat.RG32Uint: return GalImageFormat.RG32 | Uint; + case GalSurfaceFormat.BGRA8Unorm: return GalImageFormat.BGRA8 | Unorm; + case GalSurfaceFormat.BGRA8Srgb: return GalImageFormat.BGRA8 | Srgb; + case GalSurfaceFormat.RGB10A2Unorm: return GalImageFormat.RGB10A2 | Unorm; + case GalSurfaceFormat.RGBA8Unorm: return GalImageFormat.RGBA8 | Unorm; + case GalSurfaceFormat.RGBA8Srgb: return GalImageFormat.RGBA8 | Srgb; + case GalSurfaceFormat.RGBA8Snorm: return GalImageFormat.RGBA8 | Snorm; + case GalSurfaceFormat.RG16Snorm: return GalImageFormat.RG16 | Snorm; + case GalSurfaceFormat.RG16Unorm: return GalImageFormat.RG16 | Unorm; + case GalSurfaceFormat.RG16Float: return GalImageFormat.RG16 | Float; + case GalSurfaceFormat.R11G11B10Float: return GalImageFormat.R11G11B10 | Float; + case GalSurfaceFormat.R32Float: return GalImageFormat.R32 | Float; + case GalSurfaceFormat.R32Uint: return GalImageFormat.R32 | Uint; + case GalSurfaceFormat.RG8Unorm: return GalImageFormat.RG8 | Unorm; + case GalSurfaceFormat.RG8Snorm: return GalImageFormat.RG8 | Snorm; + case GalSurfaceFormat.R16Float: return GalImageFormat.R16 | Float; + case GalSurfaceFormat.R16Unorm: return GalImageFormat.R16 | Unorm; + case GalSurfaceFormat.R16Uint: return GalImageFormat.R16 | Uint; + case GalSurfaceFormat.R8Unorm: return GalImageFormat.R8 | Unorm; + case GalSurfaceFormat.R8Uint: return GalImageFormat.R8 | Uint; + case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.RGB565 | Unorm; + case GalSurfaceFormat.BGR5A1Unorm: return GalImageFormat.BGR5A1 | Unorm; } throw new NotImplementedException(Format.ToString()); @@ -204,11 +206,11 @@ namespace Ryujinx.Graphics.Texture { switch (Format) { - case GalZetaFormat.Z32Float: return GalImageFormat.D32 | Sfloat; - case GalZetaFormat.S8Z24Unorm: return GalImageFormat.D24_S8 | Unorm; - case GalZetaFormat.Z16Unorm: return GalImageFormat.D16 | Unorm; - case GalZetaFormat.Z24S8Unorm: return GalImageFormat.D24_S8 | Unorm; - case GalZetaFormat.Z32S8X24Float: return GalImageFormat.D32_S8 | Sfloat; + case GalZetaFormat.D32Float: return GalImageFormat.D32 | Float; + case GalZetaFormat.S8D24Unorm: return GalImageFormat.D24S8 | Unorm; + case GalZetaFormat.D16Unorm: return GalImageFormat.D16 | Unorm; + case GalZetaFormat.D24S8Unorm: return GalImageFormat.D24S8 | Unorm; + case GalZetaFormat.D32S8X24Float: return GalImageFormat.D32S8 | Float; } throw new NotImplementedException(Format.ToString()); @@ -235,18 +237,23 @@ namespace Ryujinx.Graphics.Texture int BytesPerPixel = Desc.BytesPerPixel; - int OutOffs = 0; + //Note: Each row of the texture needs to be aligned to 4 bytes. + int Pitch = (Width * BytesPerPixel + 3) & ~3; - byte[] Data = new byte[Width * Height * BytesPerPixel]; + byte[] Data = new byte[Height * Pitch]; for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + int OutOffs = Y * Pitch; - CpuMemory.ReadBytes(Position + Offset, Data, OutOffs, BytesPerPixel); + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - OutOffs += BytesPerPixel; + CpuMemory.ReadBytes(Position + Offset, Data, OutOffs, BytesPerPixel); + + OutOffs += BytesPerPixel; + } } return Data; @@ -289,7 +296,11 @@ namespace Ryujinx.Graphics.Texture { ImageDescriptor Desc = GetImageDescriptor(Format); - return Desc.BytesPerPixel * DivRoundUp(Width, Desc.BlockWidth); + int Pitch = Desc.BytesPerPixel * DivRoundUp(Width, Desc.BlockWidth); + + Pitch = (Pitch + 0x1f) & ~0x1f; + + return Pitch; } public static int GetBlockWidth(GalImageFormat Format) @@ -362,14 +373,14 @@ namespace Ryujinx.Graphics.Texture private static ImageDescriptor GetImageDescriptor(GalImageFormat Format) { - GalImageFormat TypeLess = (Format & GalImageFormat.FormatMask); + GalImageFormat PixelFormat = Format & GalImageFormat.FormatMask; - if (s_ImageTable.TryGetValue(TypeLess, out ImageDescriptor Descriptor)) + if (s_ImageTable.TryGetValue(PixelFormat, out ImageDescriptor Descriptor)) { return Descriptor; } - throw new NotImplementedException("Image with format " + TypeLess.ToString() + " not implemented"); + throw new NotImplementedException($"Format \"{PixelFormat}\" not implemented!"); } private static GalImageFormat GetFormatType(GalTextureType Type) @@ -380,7 +391,7 @@ namespace Ryujinx.Graphics.Texture case GalTextureType.Unorm: return Unorm; case GalTextureType.Sint: return Sint; case GalTextureType.Uint: return Uint; - case GalTextureType.Float: return Sfloat; + case GalTextureType.Float: return Float; default: throw new NotImplementedException(((int)Type).ToString()); } diff --git a/Ryujinx.Graphics/Texture/TextureFactory.cs b/Ryujinx.Graphics/Texture/TextureFactory.cs index 766c53da46..1f2d625ec4 100644 --- a/Ryujinx.Graphics/Texture/TextureFactory.cs +++ b/Ryujinx.Graphics/Texture/TextureFactory.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Texture int Width = (Tic[4] & 0xffff) + 1; int Height = (Tic[5] & 0xffff) + 1; - return new GalImage( + GalImage Image = new GalImage( Width, Height, TileWidth, @@ -51,6 +51,13 @@ namespace Ryujinx.Graphics.Texture YSource, ZSource, WSource); + + if (Layout == GalMemoryLayout.Pitch) + { + Image.Pitch = (Tic[3] & 0xffff) << 5; + } + + return Image; } public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition) @@ -90,7 +97,9 @@ namespace Ryujinx.Graphics.Texture GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); - return ImageUtils.ConvertTexture(Format, RType, GType, BType, AType); + bool ConvSrgb = ((Tic[4] >> 22) & 1) != 0; + + return ImageUtils.ConvertTexture(Format, RType, GType, BType, AType, ConvSrgb); } private static int[] ReadWords(NvGpuVmm Vmm, long Position, int Count) diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs index 67f010169c..b74d853c79 100644 --- a/Ryujinx.HLE/FileSystem/SaveHelper.cs +++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs @@ -35,9 +35,11 @@ namespace Ryujinx.HLE.FileSystem } } + string SaveAccount = SaveMetaData.UserId.IsZero() ? "savecommon" : SaveMetaData.UserId.ToString(); + string SavePath = Path.Combine(BaseSavePath, SaveMetaData.SaveId.ToString("x16"), - SaveMetaData.UserId.ToString(), + SaveAccount, SaveMetaData.SaveDataType == SaveDataType.SaveData ? CurrentTitleId.ToString("x16") : string.Empty); return SavePath; diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs index f3790ec787..3acd33fdae 100644 --- a/Ryujinx.HLE/FileSystem/SaveInfo.cs +++ b/Ryujinx.HLE/FileSystem/SaveInfo.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Utilities; namespace Ryujinx.HLE.FileSystem { @@ -6,7 +6,7 @@ namespace Ryujinx.HLE.FileSystem { public long TitleId { get; private set; } public long SaveId { get; private set; } - public UserId UserId { get; private set; } + public UInt128 UserId { get; private set; } public SaveDataType SaveDataType { get; private set; } public SaveSpaceId SaveSpaceId { get; private set; } @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.FileSystem long TitleId, long SaveId, SaveDataType SaveDataType, - UserId UserId, + UInt128 UserId, SaveSpaceId SaveSpaceId) { this.TitleId = TitleId; diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs index 7e59ab09df..ae43fcdb21 100644 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs similarity index 92% rename from Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs rename to Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs index 2415c6c000..c02e9e6555 100644 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs @@ -1,7 +1,5 @@ -using System; using System.IO; - namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast { public class CastExpression : BaseNode @@ -24,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast To.PrintLeft(Writer); Writer.Write(">("); From.PrintLeft(Writer); - Writer.Write(")"); + Writer.Write(")"); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs index cd534590b9..2ed4daa46e 100644 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs index 9720a8e49d..f7bfa194fb 100644 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs index 4de66e000b..d6efbd0fc3 100644 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index f8ec89140e..1cb419b984 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -1,10 +1,10 @@ using LibHac; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Font; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Npdm; -using Ryujinx.HLE.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -51,6 +51,8 @@ namespace Ryujinx.HLE.HOS public string CurrentTitle { get; private set; } + public bool EnableFsIntegrityChecks { get; set; } + public Horizon(Switch Device) { this.Device = Device; @@ -71,6 +73,8 @@ namespace Ryujinx.HLE.HOS Withholders = new LinkedList(); + Scheduler.StartAutoPreemptionThread(); + if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) || !Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA)) { @@ -100,7 +104,7 @@ namespace Ryujinx.HLE.HOS if (File.Exists(NpdmFileName)) { - Device.Log.PrintInfo(LogClass.Loader, $"Loading main.npdm..."); + Logger.PrintInfo(LogClass.Loader, $"Loading main.npdm..."); using (FileStream Input = new FileStream(NpdmFileName, FileMode.Open)) { @@ -109,7 +113,7 @@ namespace Ryujinx.HLE.HOS } else { - Device.Log.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); + Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); } Process MainProcess = MakeProcess(MetaData); @@ -123,7 +127,7 @@ namespace Ryujinx.HLE.HOS continue; } - Device.Log.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}..."); + Logger.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}..."); using (FileStream Input = new FileStream(File, FileMode.Open)) { @@ -164,7 +168,7 @@ namespace Ryujinx.HLE.HOS if (MainNca == null) { - Device.Log.PrintError(LogClass.Loader, "Unable to load XCI"); + Logger.PrintError(LogClass.Loader, "Unable to load XCI"); return; } @@ -208,22 +212,29 @@ namespace Ryujinx.HLE.HOS if (MainNca == null) { - Device.Log.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file"); + Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file"); } MainNca.SetBaseNca(PatchNca); - + if (ControlNca != null) { ReadControlData(ControlNca); } + if (PatchNca != null) + { + PatchNca.SetBaseNca(MainNca); + + return (PatchNca, ControlNca); + } + return (MainNca, ControlNca); } public void ReadControlData(Nca ControlNca) { - Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false, EnableFsIntegrityChecks)); byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); @@ -252,8 +263,7 @@ namespace Ryujinx.HLE.HOS // Load title key from the NSP's ticket in case the user doesn't have a title key file if (TicketFile != null) { - // todo Change when Ticket(Stream) overload is added - Ticket Ticket = new Ticket(new BinaryReader(Nsp.OpenFile(TicketFile))); + Ticket Ticket = new Ticket(Nsp.OpenFile(TicketFile)); KeySet.TitleKeys[Ticket.RightsId] = Ticket.GetTitleKey(KeySet); } @@ -282,33 +292,33 @@ namespace Ryujinx.HLE.HOS return; } - Device.Log.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided NSP file"); + Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided NSP file"); } public void LoadNca(Nca MainNca, Nca ControlNca) { - NcaSection RomfsSection = MainNca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); + NcaSection RomfsSection = MainNca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); NcaSection ExefsSection = MainNca.Sections.FirstOrDefault(x => x?.IsExefs == true); if (ExefsSection == null) { - Device.Log.PrintError(LogClass.Loader, "No ExeFS found in NCA"); + Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA"); return; } if (RomfsSection == null) { - Device.Log.PrintError(LogClass.Loader, "No RomFS found in NCA"); + Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA"); + } + else + { + Stream RomfsStream = MainNca.OpenSection(RomfsSection.SectionNum, false, EnableFsIntegrityChecks); - return; + Device.FileSystem.SetRomFs(RomfsStream); } - Stream RomfsStream = MainNca.OpenSection(RomfsSection.SectionNum, false); - - Device.FileSystem.SetRomFs(RomfsStream); - - Stream ExefsStream = MainNca.OpenSection(ExefsSection.SectionNum, false); + Stream ExefsStream = MainNca.OpenSection(ExefsSection.SectionNum, false, EnableFsIntegrityChecks); Pfs Exefs = new Pfs(ExefsStream); @@ -316,13 +326,13 @@ namespace Ryujinx.HLE.HOS if (Exefs.FileExists("main.npdm")) { - Device.Log.PrintInfo(LogClass.Loader, "Loading main.npdm..."); + Logger.PrintInfo(LogClass.Loader, "Loading main.npdm..."); MetaData = new Npdm(Exefs.OpenFile("main.npdm")); } else { - Device.Log.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); + Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); } Process MainProcess = MakeProcess(MetaData); @@ -336,7 +346,7 @@ namespace Ryujinx.HLE.HOS continue; } - Device.Log.PrintInfo(LogClass.Loader, $"Loading {Filename}..."); + Logger.PrintInfo(LogClass.Loader, $"Loading {Filename}..."); string Name = Path.GetFileNameWithoutExtension(File.Name); @@ -348,7 +358,7 @@ namespace Ryujinx.HLE.HOS Nacp ReadControlData() { - Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false, EnableFsIntegrityChecks)); byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); @@ -466,7 +476,7 @@ namespace Ryujinx.HLE.HOS public void SignalVsync() { - VsyncEvent.Signal(); + VsyncEvent.ReadableEvent.Signal(); } private Process MakeProcess(Npdm MetaData = null) diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs index 08a4cdb5d0..fca995c08a 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs @@ -73,7 +73,10 @@ namespace Ryujinx.HLE.HOS.Ipc { int Unknown = ReqReader.ReadInt32(); - int Handle = Process.HandleTable.OpenHandle(Session); + if (Process.HandleTable.GenerateHandle(Session, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); diff --git a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs index 42caeca2d5..e0cb158c98 100644 --- a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs @@ -15,6 +15,15 @@ namespace Ryujinx.HLE.HOS.Kernel private bool KeepPreempting; + public void StartAutoPreemptionThread() + { + Thread PreemptionThread = new Thread(PreemptCurrentThread); + + KeepPreempting = true; + + PreemptionThread.Start(); + } + public void ContextSwitch() { lock (CoreContexts) diff --git a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs index f2156a5c20..73309e1e26 100644 --- a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs +++ b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs @@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS.Kernel return 0; } - KThread MutexOwner = Process.HandleTable.GetData(OwnerHandle); + KThread MutexOwner = Process.HandleTable.GetObject(OwnerHandle); if (MutexOwner == null) { @@ -282,7 +282,7 @@ namespace Ryujinx.HLE.HOS.Kernel MutexValue &= ~HasListenersMask; - KThread MutexOwner = Process.HandleTable.GetData(MutexValue); + KThread MutexOwner = Process.HandleTable.GetObject(MutexValue); if (MutexOwner != null) { diff --git a/Ryujinx.HLE/HOS/Kernel/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/KEvent.cs index 1a865aa202..106d1b4092 100644 --- a/Ryujinx.HLE/HOS/Kernel/KEvent.cs +++ b/Ryujinx.HLE/HOS/Kernel/KEvent.cs @@ -1,38 +1,14 @@ namespace Ryujinx.HLE.HOS.Kernel { - class KEvent : KSynchronizationObject + class KEvent { - private bool Signaled; + public KReadableEvent ReadableEvent { get; private set; } + public KWritableEvent WritableEvent { get; private set; } - public string Name { get; private set; } - - public KEvent(Horizon System, string Name = "") : base(System) + public KEvent(Horizon System) { - this.Name = Name; - } - - public override void Signal() - { - System.CriticalSectionLock.Lock(); - - if (!Signaled) - { - Signaled = true; - - base.Signal(); - } - - System.CriticalSectionLock.Unlock(); - } - - public void Reset() - { - Signaled = false; - } - - public override bool IsSignaled() - { - return Signaled; + ReadableEvent = new KReadableEvent(System, this); + WritableEvent = new KWritableEvent(this); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs b/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs new file mode 100644 index 0000000000..9863a374bd --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KHandleEntry + { + public KHandleEntry Next { get; set; } + + public int Index { get; private set; } + + public ushort HandleId { get; set; } + public object Obj { get; set; } + + public KHandleEntry(int Index) + { + this.Index = Index; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs index 3afdb57022..8871cbe7d6 100644 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs @@ -148,6 +148,92 @@ namespace Ryujinx.HLE.HOS.Kernel } } + public long MapProcessCodeMemory(long Dst, long Src, long Size) + { + lock (Blocks) + { + long PagesCount = Size / PageSize; + + bool Success = IsUnmapped(Dst, Size); + + Success &= CheckRange( + Src, + Size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + if (Success) + { + long PA = CpuMemory.GetPhysicalAddress(Src); + + InsertBlock(Dst, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute); + InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.None); + + CpuMemory.Map(Dst, PA, Size); + + return 0; + } + } + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + public long UnmapProcessCodeMemory(long Dst, long Src, long Size) + { + lock (Blocks) + { + long PagesCount = Size / PageSize; + + bool Success = CheckRange( + Dst, + Size, + MemoryState.Mask, + MemoryState.CodeStatic, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + Success &= CheckRange( + Src, + Size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + if (Success) + { + InsertBlock(Dst, PagesCount, MemoryState.Unmapped); + InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + + CpuMemory.Unmap(Dst, Size); + + return 0; + } + } + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + public void HleMapCustom(long Position, long Size, MemoryState State, MemoryPermission Permission) { long PagesCount = Size / PageSize; @@ -755,6 +841,18 @@ namespace Ryujinx.HLE.HOS.Kernel } } + public bool HleIsUnmapped(long Position, long Size) + { + bool Result = false; + + lock (Blocks) + { + Result = IsUnmapped(Position, Size); + } + + return Result; + } + private bool IsUnmapped(long Position, long Size) { return CheckRange( diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs index db0eaa44f9..682f08d4ff 100644 --- a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs @@ -1,34 +1,183 @@ -using System.Collections.Generic; +using System; namespace Ryujinx.HLE.HOS.Kernel { class KProcessHandleTable { - private IdDictionary Handles; + private const int SelfThreadHandle = (0x1ffff << 15) | 0; + private const int SelfProcessHandle = (0x1ffff << 15) | 1; - public KProcessHandleTable() + private Horizon System; + + private KHandleEntry[] Table; + + private KHandleEntry TableHead; + private KHandleEntry NextFreeEntry; + + private int ActiveSlotsCount; + + private int Size; + + private ushort IdCounter; + + private object LockObj; + + public KProcessHandleTable(Horizon System, int Size = 1024) { - Handles = new IdDictionary(); + this.System = System; + this.Size = Size; + + IdCounter = 1; + + Table = new KHandleEntry[Size]; + + TableHead = new KHandleEntry(0); + + KHandleEntry Entry = TableHead; + + for (int Index = 0; Index < Size; Index++) + { + Table[Index] = Entry; + + Entry.Next = new KHandleEntry(Index + 1); + + Entry = Entry.Next; + } + + Table[Size - 1].Next = null; + + NextFreeEntry = TableHead; + + LockObj = new object(); } - public int OpenHandle(object Obj) + public KernelResult GenerateHandle(object Obj, out int Handle) { - return Handles.Add(Obj); + Handle = 0; + + lock (LockObj) + { + if (ActiveSlotsCount >= Size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry Entry = NextFreeEntry; + + NextFreeEntry = Entry.Next; + + Entry.Obj = Obj; + Entry.HandleId = IdCounter; + + ActiveSlotsCount++; + + Handle = (int)((IdCounter << 15) & (uint)0xffff8000) | Entry.Index; + + if ((short)(IdCounter + 1) >= 0) + { + IdCounter++; + } + else + { + IdCounter = 1; + } + } + + return KernelResult.Success; } - public T GetData(int Handle) + public bool CloseHandle(int Handle) { - return Handles.GetData(Handle); + if ((Handle >> 30) != 0 || + Handle == SelfThreadHandle || + Handle == SelfProcessHandle) + { + return false; + } + + int Index = (Handle >> 0) & 0x7fff; + int HandleId = (Handle >> 15); + + bool Result = false; + + lock (LockObj) + { + if (HandleId != 0 && Index < Size) + { + KHandleEntry Entry = Table[Index]; + + if (Entry.Obj != null && Entry.HandleId == HandleId) + { + Entry.Obj = null; + Entry.Next = NextFreeEntry; + + NextFreeEntry = Entry; + + ActiveSlotsCount--; + + Result = true; + } + } + } + + return Result; } - public object CloseHandle(int Handle) + public T GetObject(int Handle) { - return Handles.Delete(Handle); + int Index = (Handle >> 0) & 0x7fff; + int HandleId = (Handle >> 15); + + lock (LockObj) + { + if ((Handle >> 30) == 0 && HandleId != 0) + { + KHandleEntry Entry = Table[Index]; + + if (Entry.HandleId == HandleId && Entry.Obj is T Obj) + { + return Obj; + } + } + } + + return default(T); } - public ICollection Clear() + public KThread GetKThread(int Handle) { - return Handles.Clear(); + if (Handle == SelfThreadHandle) + { + return System.Scheduler.GetCurrentThread(); + } + else + { + return GetObject(Handle); + } + } + + public void Destroy() + { + lock (LockObj) + { + for (int Index = 0; Index < Size; Index++) + { + KHandleEntry Entry = Table[Index]; + + if (Entry.Obj != null) + { + if (Entry.Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + Entry.Obj = null; + Entry.Next = NextFreeEntry; + + NextFreeEntry = Entry; + } + } + } } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs b/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs new file mode 100644 index 0000000000..d43fe8249e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs @@ -0,0 +1,62 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KReadableEvent : KSynchronizationObject + { + private KEvent Parent; + + private bool Signaled; + + public KReadableEvent(Horizon System, KEvent Parent) : base(System) + { + this.Parent = Parent; + } + + public override void Signal() + { + System.CriticalSectionLock.Lock(); + + if (!Signaled) + { + Signaled = true; + + base.Signal(); + } + + System.CriticalSectionLock.Unlock(); + } + + public KernelResult Clear() + { + Signaled = false; + + return KernelResult.Success; + } + + public KernelResult ClearIfSignaled() + { + KernelResult Result; + + System.CriticalSectionLock.Lock(); + + if (Signaled) + { + Signaled = false; + + Result = KernelResult.Success; + } + else + { + Result = KernelResult.InvalidState; + } + + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public override bool IsSignaled() + { + return Signaled; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs index f6382d05d6..3cfda4197f 100644 --- a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Ryujinx.HLE.HOS.Kernel { @@ -35,12 +34,6 @@ namespace Ryujinx.HLE.HOS.Kernel { CoreContexts[Core] = new KCoreContext(this, CoreManager); } - - Thread PreemptionThread = new Thread(PreemptCurrentThread); - - KeepPreempting = true; - - PreemptionThread.Start(); } private void PreemptThreads() diff --git a/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs new file mode 100644 index 0000000000..1721ed0011 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KWritableEvent + { + private KEvent Parent; + + public KWritableEvent(KEvent Parent) + { + this.Parent = Parent; + } + + public void Signal() + { + Parent.ReadableEvent.Signal(); + } + + public KernelResult Clear() + { + return Parent.ReadableEvent.Clear(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs index 0749f3fd08..e0b196f412 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs @@ -6,6 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel public const int InvalidSize = 101; public const int InvalidAddress = 102; public const int OutOfMemory = 104; + public const int HandleTableFull = 105; public const int NoAccessPerm = 106; public const int InvalidPermission = 108; public const int InvalidMemRange = 110; diff --git a/Ryujinx.HLE/HOS/Kernel/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/KernelResult.cs new file mode 100644 index 0000000000..d9cbfc6735 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KernelResult.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + enum KernelResult + { + Success = 0, + HandleTableFull = 0xd201, + InvalidHandle = 0xe401, + InvalidState = 0xfa01 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs index a12a0ba0f3..d098aab0e5 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs @@ -1,8 +1,8 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; @@ -39,9 +39,6 @@ namespace Ryujinx.HLE.HOS.Kernel } } - private const uint SelfThreadHandle = 0xffff8000; - private const uint SelfProcessHandle = 0xffff8001; - private static Random Rng; public SvcHandler(Switch Device, Process Process) @@ -63,12 +60,13 @@ namespace Ryujinx.HLE.HOS.Kernel { 0x0e, SvcGetThreadCoreMask }, { 0x0f, SvcSetThreadCoreMask }, { 0x10, SvcGetCurrentProcessorNumber }, - { 0x12, SvcClearEvent }, + { 0x11, SignalEvent64 }, + { 0x12, ClearEvent64 }, { 0x13, SvcMapSharedMemory }, { 0x14, SvcUnmapSharedMemory }, { 0x15, SvcCreateTransferMemory }, { 0x16, SvcCloseHandle }, - { 0x17, SvcResetSignal }, + { 0x17, ResetSignal64 }, { 0x18, SvcWaitSynchronization }, { 0x19, SvcCancelSynchronization }, { 0x1a, SvcArbitrateLock }, @@ -88,7 +86,8 @@ namespace Ryujinx.HLE.HOS.Kernel { 0x32, SvcSetThreadActivity }, { 0x33, SvcGetThreadContext3 }, { 0x34, SvcWaitForAddress }, - { 0x35, SvcSignalToAddress } + { 0x35, SvcSignalToAddress }, + { 0x45, CreateEvent64 } }; this.Device = Device; @@ -110,11 +109,11 @@ namespace Ryujinx.HLE.HOS.Kernel if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) { - Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called."); + Logger.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called."); Func(ThreadState); - Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended."); + Logger.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended."); } else { @@ -123,17 +122,5 @@ namespace Ryujinx.HLE.HOS.Kernel throw new NotImplementedException($"0x{e.Id:x4}"); } } - - private KThread GetThread(long Tpidr, int Handle) - { - if ((uint)Handle == SelfThreadHandle) - { - return Process.GetThread(Tpidr); - } - else - { - return Process.HandleTable.GetData(Handle); - } - } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs index b9e71b183c..07bc474b80 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs @@ -1,5 +1,5 @@ using ChocolArm64.State; -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; using static Ryujinx.HLE.HOS.ErrorCode; @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((Size & 0xFFFFFFFE001FFFFF) != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } } @@ -41,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Attributes != AttributeMask || (Attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached) { - Device.Log.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!"); + Logger.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); @@ -80,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } else { @@ -98,7 +98,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Src | Dst)) { - Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -116,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst) { - Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); + Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -125,7 +125,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Src, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -134,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideNewMapRegion(Dst, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -145,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -159,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Src | Dst)) { - Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -168,7 +168,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -177,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst) { - Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); + Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -186,7 +186,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Src, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -195,7 +195,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideNewMapRegion(Dst, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); @@ -206,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -240,7 +240,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -249,7 +249,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -258,7 +258,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Position + Size) <= (ulong)Position) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -269,18 +269,18 @@ namespace Ryujinx.HLE.HOS.Kernel if ((Permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission); return; } - KSharedMemory SharedMemory = Process.HandleTable.GetData(Handle); + KSharedMemory SharedMemory = Process.HandleTable.GetObject(Handle); if (SharedMemory == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -289,7 +289,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -298,7 +298,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (SharedMemory.Size != Size) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -309,7 +309,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -323,7 +323,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -332,7 +332,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -341,18 +341,18 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Position + Size) <= (ulong)Position) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - KSharedMemory SharedMemory = Process.HandleTable.GetData(Handle); + KSharedMemory SharedMemory = Process.HandleTable.GetObject(Handle); if (SharedMemory == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -361,7 +361,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -372,7 +372,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -385,7 +385,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -394,7 +394,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -403,7 +403,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Position + Size) <= (ulong)Position) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -414,7 +414,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Permission > MemoryPermission.ReadAndWrite || Permission == MemoryPermission.Write) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission); @@ -425,9 +425,9 @@ namespace Ryujinx.HLE.HOS.Kernel KTransferMemory TransferMemory = new KTransferMemory(Position, Size); - int Handle = Process.HandleTable.OpenHandle(TransferMemory); + KernelResult Result = Process.HandleTable.GenerateHandle(TransferMemory, out int Handle); - ThreadState.X0 = 0; + ThreadState.X0 = (uint)Result; ThreadState.X1 = (ulong)Handle; } @@ -438,7 +438,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -447,7 +447,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -456,7 +456,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Position + Size) <= (ulong)Position) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -465,7 +465,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Position, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -476,7 +476,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -489,7 +489,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Position)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -498,7 +498,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!PageAligned(Size) || Size == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); @@ -507,7 +507,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((ulong)(Position + Size) <= (ulong)Position) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -516,7 +516,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (!InsideAddrSpace(Position, Size)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -527,7 +527,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs index 60ccf7f7f8..6eb8419cb2 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs @@ -1,9 +1,9 @@ using ChocolArm64.Memory; using ChocolArm64.State; +using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Services; -using Ryujinx.HLE.Logging; using System; using System.Threading; @@ -22,24 +22,77 @@ namespace Ryujinx.HLE.HOS.Kernel Device.System.ExitProcess(Process.ProcessId); } - private void SvcClearEvent(AThreadState ThreadState) + private void SignalEvent64(AThreadState ThreadState) { - int Handle = (int)ThreadState.X0; + ThreadState.X0 = (ulong)SignalEvent((int)ThreadState.X0); + } - //TODO: Implement events. + private KernelResult SignalEvent(int Handle) + { + KWritableEvent WritableEvent = Process.HandleTable.GetObject(Handle); - ThreadState.X0 = 0; + KernelResult Result; + + if (WritableEvent != null) + { + WritableEvent.Signal(); + + Result = KernelResult.Success; + } + else + { + Result = KernelResult.InvalidHandle; + } + + if (Result != KernelResult.Success) + { + Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + Result + "!"); + } + + return Result; + } + + private void ClearEvent64(AThreadState ThreadState) + { + ThreadState.X0 = (ulong)ClearEvent((int)ThreadState.X0); + } + + private KernelResult ClearEvent(int Handle) + { + KernelResult Result; + + KWritableEvent WritableEvent = Process.HandleTable.GetObject(Handle); + + if (WritableEvent == null) + { + KReadableEvent ReadableEvent = Process.HandleTable.GetObject(Handle); + + Result = ReadableEvent?.Clear() ?? KernelResult.InvalidHandle; + } + else + { + Result = WritableEvent.Clear(); + } + + if (Result != KernelResult.Success) + { + Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + Result + "!"); + } + + return Result; } private void SvcCloseHandle(AThreadState ThreadState) { int Handle = (int)ThreadState.X0; - object Obj = Process.HandleTable.CloseHandle(Handle); + object Obj = Process.HandleTable.GetObject(Handle); + + Process.HandleTable.CloseHandle(Handle); if (Obj == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -60,24 +113,37 @@ namespace Ryujinx.HLE.HOS.Kernel ThreadState.X0 = 0; } - private void SvcResetSignal(AThreadState ThreadState) + private void ResetSignal64(AThreadState ThreadState) { - int Handle = (int)ThreadState.X0; + ThreadState.X0 = (ulong)ResetSignal((int)ThreadState.X0); + } - KEvent Event = Process.HandleTable.GetData(Handle); + private KernelResult ResetSignal(int Handle) + { + KReadableEvent ReadableEvent = Process.HandleTable.GetObject(Handle); - if (Event != null) + KernelResult Result; + + //TODO: KProcess support. + if (ReadableEvent != null) { - Event.Reset(); - - ThreadState.X0 = 0; + Result = ReadableEvent.ClearIfSignaled(); } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + Result = KernelResult.InvalidHandle; } + + if (Result == KernelResult.InvalidState) + { + Logger.PrintDebug(LogClass.KernelSvc, "Operation failed with error: " + Result + "!"); + } + else if (Result != KernelResult.Success) + { + Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + Result + "!"); + } + + return Result; } private void SvcGetSystemTick(AThreadState ThreadState) @@ -96,10 +162,13 @@ namespace Ryujinx.HLE.HOS.Kernel //actually exists, return error codes otherwise. KSession Session = new KSession(ServiceFactory.MakeService(System, Name), Name); - ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session); + if (Process.HandleTable.GenerateHandle(Session, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } ThreadState.X0 = 0; - ThreadState.X1 = Handle; + ThreadState.X1 = (uint)Handle; } private void SvcSendSyncRequest(AThreadState ThreadState) @@ -122,7 +191,7 @@ namespace Ryujinx.HLE.HOS.Kernel byte[] MessageData = Memory.ReadBytes(MessagePtr, Size); - KSession Session = Process.HandleTable.GetData(Handle); + KSession Session = Process.HandleTable.GetObject(Handle); if (Session != null) { @@ -151,7 +220,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -178,9 +247,17 @@ namespace Ryujinx.HLE.HOS.Kernel long Unknown = (long)ThreadState.X1; long Info = (long)ThreadState.X2; - Process.PrintStackTrace(ThreadState); + if ((Reason & (1 << 31)) == 0) + { + Process.PrintStackTrace(ThreadState); - throw new GuestBrokeExecutionException(); + throw new GuestBrokeExecutionException(); + } + else + { + Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered"); + Process.PrintStackTrace(ThreadState); + } } private void SvcOutputDebugString(AThreadState ThreadState) @@ -190,7 +267,7 @@ namespace Ryujinx.HLE.HOS.Kernel string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size); - Device.Log.PrintWarning(LogClass.KernelSvc, Str); + Logger.PrintWarning(LogClass.KernelSvc, Str); ThreadState.X0 = 0; } @@ -206,7 +283,8 @@ namespace Ryujinx.HLE.HOS.Kernel if (InfoType == 18 || InfoType == 19 || InfoType == 20 || - InfoType == 21) + InfoType == 21 || + InfoType == 22) { ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); @@ -287,5 +365,37 @@ namespace Ryujinx.HLE.HOS.Kernel ThreadState.X0 = 0; } + + private void CreateEvent64(AThreadState State) + { + KernelResult Result = CreateEvent(out int WEventHandle, out int REventHandle); + + State.X0 = (ulong)Result; + State.X1 = (ulong)WEventHandle; + State.X2 = (ulong)REventHandle; + } + + private KernelResult CreateEvent(out int WEventHandle, out int REventHandle) + { + KEvent Event = new KEvent(System); + + KernelResult Result = Process.HandleTable.GenerateHandle(Event.WritableEvent, out WEventHandle); + + if (Result == KernelResult.Success) + { + Result = Process.HandleTable.GenerateHandle(Event.ReadableEvent, out REventHandle); + + if (Result != KernelResult.Success) + { + Process.HandleTable.CloseHandle(WEventHandle); + } + } + else + { + REventHandle = 0; + } + + return Result; + } } } diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs index dc296b060e..4c1744e6e3 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs @@ -1,5 +1,5 @@ using ChocolArm64.State; -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; using static Ryujinx.HLE.HOS.ErrorCode; @@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel if ((uint)Priority > 0x3f) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority); @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else if ((uint)ProcessorId > 3) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); @@ -53,7 +53,7 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = (int)ThreadState.X0; - KThread Thread = Process.HandleTable.GetData(Handle); + KThread Thread = Process.HandleTable.GetObject(Handle); if (Thread != null) { @@ -61,14 +61,14 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -87,7 +87,7 @@ namespace Ryujinx.HLE.HOS.Kernel { long Timeout = (long)ThreadState.X0; - Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + Timeout.ToString("x16")); + Logger.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + Timeout.ToString("x16")); KThread CurrentThread = System.Scheduler.GetCurrentThread(); @@ -112,7 +112,7 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = (int)ThreadState.X1; - KThread Thread = GetThread(ThreadState.Tpidr, Handle); + KThread Thread = Process.HandleTable.GetKThread(Handle); if (Thread != null) { @@ -121,7 +121,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -132,17 +132,17 @@ namespace Ryujinx.HLE.HOS.Kernel int Handle = (int)ThreadState.X0; int Priority = (int)ThreadState.X1; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle .ToString("x8") + ", " + "Priority = 0x" + Priority.ToString("x8")); //TODO: NPDM check. - KThread Thread = GetThread(ThreadState.Tpidr, Handle); + KThread Thread = Process.HandleTable.GetKThread(Handle); if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -158,9 +158,9 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = (int)ThreadState.X2; - Device.Log.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle.ToString("x8")); + Logger.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle.ToString("x8")); - KThread Thread = GetThread(ThreadState.Tpidr, Handle); + KThread Thread = Process.HandleTable.GetKThread(Handle); if (Thread != null) { @@ -170,7 +170,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -178,12 +178,12 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcSetThreadCoreMask(AThreadState ThreadState) { - int ThreadHandle = (int)ThreadState.X0; + int Handle = (int)ThreadState.X0; int PrefferedCore = (int)ThreadState.X1; long AffinityMask = (long)ThreadState.X2; - Device.Log.PrintDebug(LogClass.KernelSvc, - "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " + + Logger.PrintDebug(LogClass.KernelSvc, + "Handle = 0x" + Handle .ToString("x8") + ", " + "PrefferedCore = 0x" + PrefferedCore.ToString("x8") + ", " + "AffinityMask = 0x" + AffinityMask .ToString("x16")); @@ -202,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Kernel { if ((PrefferedCore | 2) != -1) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); @@ -211,7 +211,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else if ((AffinityMask & (1 << PrefferedCore)) == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{AffinityMask:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{AffinityMask:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); @@ -219,11 +219,11 @@ namespace Ryujinx.HLE.HOS.Kernel } } - KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle); + KThread Thread = Process.HandleTable.GetKThread(Handle); if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -234,7 +234,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -249,7 +249,7 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = (int)ThreadState.X1; - KThread Thread = GetThread(ThreadState.Tpidr, Handle); + KThread Thread = Process.HandleTable.GetKThread(Handle); if (Thread != null) { @@ -258,7 +258,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -269,11 +269,11 @@ namespace Ryujinx.HLE.HOS.Kernel int Handle = (int)ThreadState.X0; bool Pause = (int)ThreadState.X1 == 1; - KThread Thread = Process.HandleTable.GetData(Handle); + KThread Thread = Process.HandleTable.GetObject(Handle); if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -282,7 +282,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread.Owner != Process) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread owner process!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread owner process!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -293,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -304,11 +304,11 @@ namespace Ryujinx.HLE.HOS.Kernel long Position = (long)ThreadState.X0; int Handle = (int)ThreadState.X1; - KThread Thread = Process.HandleTable.GetData(Handle); + KThread Thread = Process.HandleTable.GetObject(Handle); if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -317,7 +317,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Process.GetThread(ThreadState.Tpidr) == Thread) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread); diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs index 868e017292..73719c19f7 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs @@ -1,5 +1,6 @@ using ChocolArm64.State; -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; +using System.Collections.Generic; using static Ryujinx.HLE.HOS.ErrorCode; @@ -13,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Kernel int HandlesCount = (int)ThreadState.X2; long Timeout = (long)ThreadState.X3; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " + "HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " + "Timeout = 0x" + Timeout .ToString("x16")); @@ -25,33 +26,38 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - KSynchronizationObject[] SyncObjs = new KSynchronizationObject[HandlesCount]; + List SyncObjs = new List(); for (int Index = 0; Index < HandlesCount; Index++) { int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); - KSynchronizationObject SyncObj = Process.HandleTable.GetData(Handle); + KSynchronizationObject SyncObj = Process.HandleTable.GetObject(Handle); - SyncObjs[Index] = SyncObj; + if (SyncObj == null) + { + break; + } + + SyncObjs.Add(SyncObj); } int HndIndex = (int)ThreadState.X1; ulong High = ThreadState.X1 & (0xffffffffUL << 32); - long Result = System.Synchronization.WaitFor(SyncObjs, Timeout, ref HndIndex); + long Result = System.Synchronization.WaitFor(SyncObjs.ToArray(), Timeout, ref HndIndex); if (Result != 0) { if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout) || Result == MakeError(ErrorModule.Kernel, KernelErr.Cancelled)) { - Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } } @@ -63,13 +69,13 @@ namespace Ryujinx.HLE.HOS.Kernel { int ThreadHandle = (int)ThreadState.X0; - Device.Log.PrintDebug(LogClass.KernelSvc, "ThreadHandle = 0x" + ThreadHandle.ToString("x8")); + Logger.PrintDebug(LogClass.KernelSvc, "ThreadHandle = 0x" + ThreadHandle.ToString("x8")); - KThread Thread = Process.HandleTable.GetData(ThreadHandle); + KThread Thread = Process.HandleTable.GetKThread(ThreadHandle); if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); @@ -87,14 +93,14 @@ namespace Ryujinx.HLE.HOS.Kernel long MutexAddress = (long)ThreadState.X1; int RequesterHandle = (int)ThreadState.X2; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "OwnerHandle = 0x" + OwnerHandle .ToString("x8") + ", " + "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " + "RequesterHandle = 0x" + RequesterHandle.ToString("x8")); if (IsPointingInsideKernel(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -103,7 +109,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsAddressNotWordAligned(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -119,7 +125,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -129,11 +135,11 @@ namespace Ryujinx.HLE.HOS.Kernel { long MutexAddress = (long)ThreadState.X0; - Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16")); + Logger.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16")); if (IsPointingInsideKernel(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -142,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsAddressNotWordAligned(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -153,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -166,7 +172,7 @@ namespace Ryujinx.HLE.HOS.Kernel int ThreadHandle = (int)ThreadState.X2; long Timeout = (long)ThreadState.X3; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " + "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " + "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " + @@ -174,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsPointingInsideKernel(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -183,7 +189,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsAddressNotWordAligned(MutexAddress)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -201,11 +207,11 @@ namespace Ryujinx.HLE.HOS.Kernel { if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout)) { - Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } else { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } } @@ -217,7 +223,7 @@ namespace Ryujinx.HLE.HOS.Kernel long Address = (long)ThreadState.X0; int Count = (int)ThreadState.X1; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "Address = 0x" + Address.ToString("x16") + ", " + "Count = 0x" + Count .ToString("x8")); @@ -233,7 +239,7 @@ namespace Ryujinx.HLE.HOS.Kernel int Value = (int)ThreadState.X2; long Timeout = (long)ThreadState.X3; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "Address = 0x" + Address.ToString("x16") + ", " + "Type = " + Type .ToString() + ", " + "Value = 0x" + Value .ToString("x8") + ", " + @@ -241,7 +247,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsPointingInsideKernel(Address)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -250,7 +256,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsAddressNotWordAligned(Address)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -280,7 +286,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; @@ -293,7 +299,7 @@ namespace Ryujinx.HLE.HOS.Kernel int Value = (int)ThreadState.X2; int Count = (int)ThreadState.X3; - Device.Log.PrintDebug(LogClass.KernelSvc, + Logger.PrintDebug(LogClass.KernelSvc, "Address = 0x" + Address.ToString("x16") + ", " + "Type = " + Type .ToString() + ", " + "Value = 0x" + Value .ToString("x8") + ", " + @@ -301,7 +307,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsPointingInsideKernel(Address)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -310,7 +316,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (IsAddressNotWordAligned(Address)) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -340,7 +346,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } ThreadState.X0 = (ulong)Result; diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index f7ec2604cc..448f1afac8 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -3,6 +3,7 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; using LibHac; +using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Diagnostics.Demangler; using Ryujinx.HLE.HOS.Kernel; @@ -11,7 +12,6 @@ using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Npdm; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; using System; using System.Collections.Concurrent; @@ -71,7 +71,22 @@ namespace Ryujinx.HLE.HOS TlsPages = new List(); - HandleTable = new KProcessHandleTable(); + int HandleTableSize = 1024; + + if (MetaData != null) + { + foreach (KernelAccessControlItem Item in MetaData.ACI0.KernelAccessControl.Items) + { + if (Item.HasHandleTableSize) + { + HandleTableSize = Item.HandleTableSize; + + break; + } + } + } + + HandleTable = new KProcessHandleTable(Device.System, HandleTableSize); AppletState = new AppletStateMgr(Device.System); @@ -91,13 +106,37 @@ namespace Ryujinx.HLE.HOS throw new ObjectDisposedException(nameof(Process)); } - Device.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}."); + long ImageEnd = LoadProgram(Program, ImageBase); - Executable Executable = new Executable(Program, MemoryManager, Memory, ImageBase); + ImageBase = IntUtils.AlignUp(ImageEnd, KMemoryManager.PageSize); + } + + public long LoadProgram(IExecutable Program, long ExecutableBase) + { + if (Disposed) + { + throw new ObjectDisposedException(nameof(Process)); + } + + Logger.PrintInfo(LogClass.Loader, $"Image base at 0x{ExecutableBase:x16}."); + + Executable Executable = new Executable(Program, MemoryManager, Memory, ExecutableBase); Executables.Add(Executable); - ImageBase = IntUtils.AlignUp(Executable.ImageEnd, KMemoryManager.PageSize); + return Executable.ImageEnd; + } + + public void RemoveProgram(long ExecutableBase) + { + foreach (Executable Executable in Executables) + { + if (Executable.ImageBase == ExecutableBase) + { + Executables.Remove(Executable); + break; + } + } } public void SetEmptyArgs() @@ -139,7 +178,7 @@ namespace Ryujinx.HLE.HOS return false; } - KThread MainThread = HandleTable.GetData(Handle); + KThread MainThread = HandleTable.GetKThread(Handle); if (NeedsHbAbi) { @@ -190,7 +229,7 @@ namespace Ryujinx.HLE.HOS Thread.LastPc = EntryPoint; - int Handle = HandleTable.OpenHandle(Thread); + HandleTable.GenerateHandle(Thread, out int Handle); CpuThread.ThreadState.CntfrqEl0 = TickFreq; CpuThread.ThreadState.Tpidr = Tpidr; @@ -280,7 +319,7 @@ namespace Ryujinx.HLE.HOS string ExeNameWithAddr = $"{Exe.Name}:0x{Offset:x8}"; - Device.Log.PrintDebug(LogClass.Cpu, ExeNameWithAddr + " " + SubName); + Logger.PrintDebug(LogClass.Cpu, ExeNameWithAddr + " " + SubName); } private ATranslator GetTranslator() @@ -335,7 +374,7 @@ namespace Ryujinx.HLE.HOS FramePointer = Memory.ReadInt64(FramePointer); } - Device.Log.PrintInfo(LogClass.Cpu, Trace.ToString()); + Logger.PrintInfo(LogClass.Cpu, Trace.ToString()); } private bool TryGetSubName(Executable Exe, long Position, out string Name) @@ -427,13 +466,7 @@ namespace Ryujinx.HLE.HOS Disposed = true; - foreach (object Obj in HandleTable.Clear()) - { - if (Obj is KSession Session) - { - Session.Dispose(); - } - } + HandleTable.Destroy(); INvDrvServices.UnloadProcess(this); @@ -442,7 +475,7 @@ namespace Ryujinx.HLE.HOS File.Delete(Executables[0].FilePath); } - Device.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting..."); + Logger.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting..."); } public void Dispose() diff --git a/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs b/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs index 8fd7bfeafd..f920c00ba0 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IAccountService.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Logging; +using Ryujinx.HLE.Utilities; using System.Collections.Generic; using static Ryujinx.HLE.HOS.ErrorCode; @@ -23,11 +24,14 @@ namespace Ryujinx.HLE.HOS.Services.Acc { 3, ListOpenUsers }, { 4, GetLastOpenedUser }, { 5, GetProfile }, + { 50, IsUserRegistrationRequestPermitted }, + { 51, TrySelectUserWithoutInteraction }, { 100, InitializeApplicationInfo }, { 101, GetBaasAccountManagerForApplication } }; } + // GetUserCount() -> i32 public long GetUserCount(ServiceCtx Context) { Context.ResponseData.Write(Context.Device.System.State.GetUserCount()); @@ -35,22 +39,25 @@ namespace Ryujinx.HLE.HOS.Services.Acc return 0; } + // GetUserExistence(nn::account::Uid) -> bool public long GetUserExistence(ServiceCtx Context) { - UserId Uuid = new UserId( + UInt128 Uuid = new UInt128( Context.RequestData.ReadInt64(), Context.RequestData.ReadInt64()); - Context.ResponseData.Write(Context.Device.System.State.TryGetUser(Uuid, out _) ? 1 : 0); + Context.ResponseData.Write(Context.Device.System.State.TryGetUser(Uuid, out _)); return 0; } + // ListAllUsers() -> array public long ListAllUsers(ServiceCtx Context) { return WriteUserList(Context, Context.Device.System.State.GetAllUsers()); } + // ListOpenUsers() -> array public long ListOpenUsers(ServiceCtx Context) { return WriteUserList(Context, Context.Device.System.State.GetOpenUsers()); @@ -70,17 +77,14 @@ namespace Ryujinx.HLE.HOS.Services.Acc break; } - byte[] Uuid = Profile.Uuid.Bytes; - - for (int Index = Uuid.Length - 1; Index >= 0; Index--) - { - Context.Memory.WriteByte(OutputPosition + Offset++, Uuid[Index]); - } + Context.Memory.WriteInt64(OutputPosition, Profile.Uuid.Low); + Context.Memory.WriteInt64(OutputPosition + 8, Profile.Uuid.High); } return 0; } + // GetLastOpenedUser() -> nn::account::Uid public long GetLastOpenedUser(ServiceCtx Context) { UserProfile LastOpened = Context.Device.System.State.LastOpenUser; @@ -90,15 +94,16 @@ namespace Ryujinx.HLE.HOS.Services.Acc return 0; } + // GetProfile(nn::account::Uid) -> object public long GetProfile(ServiceCtx Context) { - UserId Uuid = new UserId( + UInt128 Uuid = new UInt128( Context.RequestData.ReadInt64(), Context.RequestData.ReadInt64()); if (!Context.Device.System.State.TryGetUser(Uuid, out UserProfile Profile)) { - Context.Device.Log.PrintWarning(LogClass.ServiceAcc, $"User 0x{Uuid} not found!"); + Logger.PrintWarning(LogClass.ServiceAcc, $"User 0x{Uuid} not found!"); return MakeError(ErrorModule.Account, AccErr.UserNotFound); } @@ -108,16 +113,50 @@ namespace Ryujinx.HLE.HOS.Services.Acc return 0; } - public long InitializeApplicationInfo(ServiceCtx Context) + // IsUserRegistrationRequestPermitted(u64, pid) -> bool + public long IsUserRegistrationRequestPermitted(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); + long Unknown = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceAcc, $"Stubbed. Unknown: {Unknown}"); + + Context.ResponseData.Write(false); return 0; } + // TrySelectUserWithoutInteraction(bool) -> nn::account::Uid + public long TrySelectUserWithoutInteraction(ServiceCtx Context) + { + bool Unknown = Context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceAcc, $"Stubbed. Unknown: {Unknown}"); + + UserProfile Profile = Context.Device.System.State.LastOpenUser; + + Profile.Uuid.Write(Context.ResponseData); + + return 0; + } + + // InitializeApplicationInfo(u64, pid) + public long InitializeApplicationInfo(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceAcc, $"Stubbed. Unknown: {Unknown}"); + + return 0; + } + + // GetBaasAccountManagerForApplication(nn::account::Uid) -> object public long GetBaasAccountManagerForApplication(ServiceCtx Context) { - MakeObject(Context, new IManagerForApplication()); + UInt128 Uuid = new UInt128( + Context.RequestData.ReadInt64(), + Context.RequestData.ReadInt64()); + + MakeObject(Context, new IManagerForApplication(Uuid)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs b/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs index 813a1b17e5..9312b2bc82 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs @@ -1,36 +1,45 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; +using Ryujinx.HLE.Utilities; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Acc { class IManagerForApplication : IpcService { + private UInt128 Uuid; + private Dictionary m_Commands; public override IReadOnlyDictionary Commands => m_Commands; - public IManagerForApplication() + public IManagerForApplication(UInt128 Uuid) { m_Commands = new Dictionary() { { 0, CheckAvailability }, { 1, GetAccountId } }; + + this.Uuid = Uuid; } + // CheckAvailability() public long CheckAvailability(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAcc, "Stubbed."); return 0; } + // GetAccountId() -> nn::account::NetworkServiceAccountId public long GetAccountId(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); + long NetworkServiceAccountId = 0xcafe; - Context.ResponseData.Write(0xcafeL); + Logger.PrintStub(LogClass.ServiceAcc, $"Stubbed. NetworkServiceAccountId: {NetworkServiceAccountId}"); + + Context.ResponseData.Write(NetworkServiceAccountId); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs index f68c819178..1776b37b2c 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; using System.Collections.Generic; using System.IO; @@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Services.Acc public long Get(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAcc, "Stubbed."); long Position = Context.Request.ReceiveBuff[0].Position; diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs index 0a10d2a66c..1934798b2d 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -39,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Am long UIdLow = Context.RequestData.ReadInt64(); long UIdHigh = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); Context.ResponseData.Write(0L); @@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Am string Result = GetFormattedErrorCode(ErrorCode); - Context.Device.Log.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result})."); + Logger.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result})."); return 0; } @@ -90,7 +90,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetPseudoDeviceId(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); Context.ResponseData.Write(0L); Context.ResponseData.Write(0L); @@ -100,7 +100,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long InitializeGamePlayRecording(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -109,7 +109,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { int State = Context.RequestData.ReadInt32(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs b/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs index 8968ad72c3..062f2d8651 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Am float AppletVolume = Context.RequestData.ReadSingle(); float LibraryAppletVolume = Context.RequestData.ReadSingle(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { Context.ResponseData.Write(1f); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { Context.ResponseData.Write(1f); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Am float Unknown0 = Context.RequestData.ReadSingle(); long Unknown1 = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { float Unknown0 = Context.RequestData.ReadSingle(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs index 72049d6f44..6b012689c4 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; using static Ryujinx.HLE.HOS.ErrorCode; @@ -36,7 +37,10 @@ namespace Ryujinx.HLE.HOS.Services.Am { KEvent Event = Context.Process.AppletState.MessageEvent; - int Handle = Context.Process.HandleTable.OpenHandle(Event); + if (Context.Process.HandleTable.GenerateHandle(Event.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); @@ -81,7 +85,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { Context.ResponseData.Write((byte)0); //Unknown value. - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -103,11 +107,14 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetDefaultDisplayResolutionChangeEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(DisplayResolutionChangeEvent); + if (Context.Process.HandleTable.GenerateHandle(DisplayResolutionChangeEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs index 0c271796e8..3f026e2fe6 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -27,18 +28,21 @@ namespace Ryujinx.HLE.HOS.Services.Am public long RequestToGetForeground(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long GetPopFromGeneralChannelEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent); + if (Context.Process.HandleTable.GenerateHandle(ChannelEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs index a9de3ebd45..9e0d0e7075 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs @@ -1,6 +1,7 @@ -using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -29,34 +30,37 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetAppletStateChangedEvent(ServiceCtx Context) { - StateChangedEvent.Signal(); + StateChangedEvent.ReadableEvent.Signal(); - int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent); + if (Context.Process.HandleTable.GenerateHandle(StateChangedEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long Start(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long GetResult(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long PushInData(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs index fe8822735b..2abaee2e7b 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -13,6 +14,8 @@ namespace Ryujinx.HLE.HOS.Services.Am private KEvent LaunchableEvent; + private int IdleTimeDetectionExtension; + public ISelfController(Horizon System) { m_Commands = new Dictionary() @@ -28,7 +31,9 @@ namespace Ryujinx.HLE.HOS.Services.Am { 14, SetRestartMessageEnabled }, { 16, SetOutOfFocusSuspendingEnabled }, { 19, SetScreenShotImageOrientation }, - { 50, SetHandlesRequestToDisplay } + { 50, SetHandlesRequestToDisplay }, + { 62, SetIdleTimeDetectionExtension }, + { 63, GetIdleTimeDetectionExtension } }; LaunchableEvent = new KEvent(System); @@ -36,34 +41,37 @@ namespace Ryujinx.HLE.HOS.Services.Am public long Exit(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long LockExit(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long UnlockExit(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } public long GetLibraryAppletLaunchableEvent(ServiceCtx Context) { - LaunchableEvent.Signal(); + LaunchableEvent.ReadableEvent.Signal(); - int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent); + if (Context.Process.HandleTable.GenerateHandle(LaunchableEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -72,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -81,7 +89,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -90,7 +98,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -101,7 +109,7 @@ namespace Ryujinx.HLE.HOS.Services.Am bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -110,7 +118,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -119,7 +127,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -128,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { int Orientation = Context.RequestData.ReadInt32(); - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } @@ -137,7 +145,27 @@ namespace Ryujinx.HLE.HOS.Services.Am { bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + // SetIdleTimeDetectionExtension(u32) + public long SetIdleTimeDetectionExtension(ServiceCtx Context) + { + IdleTimeDetectionExtension = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceAm, $"Stubbed. IdleTimeDetectionExtension: {IdleTimeDetectionExtension}"); + + return 0; + } + + // GetIdleTimeDetectionExtension() -> u32 + public long GetIdleTimeDetectionExtension(ServiceCtx Context) + { + Context.ResponseData.Write(IdleTimeDetectionExtension); + + Logger.PrintStub(LogClass.ServiceAm, $"Stubbed. IdleTimeDetectionExtension: {IdleTimeDetectionExtension}"); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs b/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs index 1a5a716f9a..de5137d120 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Am @@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetAppletResourceUserId(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); Context.ResponseData.Write(0L); @@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long AcquireForegroundRights(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Apm/ISession.cs b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs index 739e264d58..d04bcfc973 100644 --- a/Ryujinx.HLE/HOS/Services/Apm/ISession.cs +++ b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Apm @@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Apm Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1); - Context.Device.Log.PrintStub(LogClass.ServiceApm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceApm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs index 2b0b5293ed..cd3d6e4902 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs @@ -67,7 +67,10 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut public long RegisterBufferEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); + if (Context.Process.HandleTable.GenerateHandle(ReleaseEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs index ae85bf0189..7963cbb47d 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs @@ -1,9 +1,9 @@ using ChocolArm64.Memory; using Ryujinx.Audio; using Ryujinx.Audio.Adpcm; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; using System; using System.Collections.Generic; @@ -38,6 +38,8 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer private int Track; + private PlayState PlayState; + public IAudioRenderer( Horizon System, AMemory Memory, @@ -46,6 +48,10 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer { m_Commands = new Dictionary() { + { 0, GetSampleRate }, + { 1, GetSampleCount }, + { 2, GetMixBufferCount }, + { 3, GetState }, { 4, RequestUpdateAudioRenderer }, { 5, StartAudioRenderer }, { 6, StopAudioRenderer }, @@ -68,11 +74,47 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer Voices = CreateArray(Params.VoiceCount); InitializeAudioOut(); + + PlayState = PlayState.Stopped; + } + + // GetSampleRate() -> u32 + public long GetSampleRate(ServiceCtx Context) + { + Context.ResponseData.Write(Params.SampleRate); + + return 0; + } + + // GetSampleCount() -> u32 + public long GetSampleCount(ServiceCtx Context) + { + Context.ResponseData.Write(Params.SampleCount); + + return 0; + } + + // GetMixBufferCount() -> u32 + public long GetMixBufferCount(ServiceCtx Context) + { + Context.ResponseData.Write(Params.MixCount); + + return 0; + } + + // GetState() -> u32 + private long GetState(ServiceCtx Context) + { + Context.ResponseData.Write((int)PlayState); + + Logger.PrintStub(LogClass.ServiceAudio, $"Stubbed. Renderer State: {Enum.GetName(typeof(PlayState), PlayState)}"); + + return 0; } private void AudioCallback() { - UpdateEvent.Signal(); + UpdateEvent.ReadableEvent.Signal(); } private static T[] CreateArray(int Size) where T : new() @@ -204,21 +246,28 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer public long StartAudioRenderer(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + PlayState = PlayState.Playing; return 0; } public long StopAudioRenderer(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + PlayState = PlayState.Stopped; return 0; } public long QuerySystemEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent); + if (Context.Process.HandleTable.GenerateHandle(UpdateEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs index adecc7210a..f9c0d315a3 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs @@ -1,7 +1,8 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; using System.Text; @@ -35,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud SystemEvent = new KEvent(System); //TODO: We shouldn't be signaling this here. - SystemEvent.Signal(); + SystemEvent.ReadableEvent.Signal(); } public long ListAudioDeviceName(ServiceCtx Context) @@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud if ((Position - BasePosition) + Buffer.Length > Size) { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); break; } @@ -79,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud string DeviceName = Encoding.ASCII.GetString(DeviceNameBuffer); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } @@ -99,7 +100,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud } else { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); } return 0; @@ -107,11 +108,14 @@ namespace Ryujinx.HLE.HOS.Services.Aud public long QueryAudioDeviceSystemEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + if (Context.Process.HandleTable.GenerateHandle(SystemEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } @@ -120,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud { Context.ResponseData.Write(2); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } @@ -141,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud if ((Position - BasePosition) + Buffer.Length > Size) { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); break; } @@ -164,7 +168,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud string DeviceName = Encoding.UTF8.GetString(DeviceNameBuffer); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } @@ -173,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud { Context.ResponseData.Write(1f); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } @@ -192,7 +196,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud } else { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); } return 0; @@ -200,22 +204,28 @@ namespace Ryujinx.HLE.HOS.Services.Aud public long QueryAudioDeviceInputEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + if (Context.Process.HandleTable.GenerateHandle(SystemEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } public long QueryAudioDeviceOutputEvent(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + if (Context.Process.HandleTable.GenerateHandle(SystemEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + Logger.PrintStub(LogClass.ServiceAudio, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs index ef9250d926..2bc2d82068 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs @@ -1,9 +1,9 @@ using ChocolArm64.Memory; using Ryujinx.Audio; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Aud.AudioOut; -using Ryujinx.HLE.Logging; using System.Collections.Generic; using System.Text; @@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud } else { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); } Context.ResponseData.Write(NameCount); @@ -108,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud if (DeviceName != DefaultAudioOutput) { - Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid device name!"); + Logger.PrintWarning(LogClass.Audio, "Invalid device name!"); return MakeError(ErrorModule.Audio, AudErr.DeviceNotFound); } @@ -121,7 +121,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud } else { - Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {ReceiveSize} too small!"); + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {ReceiveSize} too small!"); } int SampleRate = Context.RequestData.ReadInt32(); @@ -134,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud if (SampleRate != DefaultSampleRate) { - Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid sample rate!"); + Logger.PrintWarning(LogClass.Audio, "Invalid sample rate!"); return MakeError(ErrorModule.Audio, AudErr.UnsupportedSampleRate); } @@ -150,7 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud ReleaseCallback Callback = () => { - ReleaseEvent.Signal(); + ReleaseEvent.ReadableEvent.Signal(); }; IAalOutput AudioOut = Context.Device.AudioOut; diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs index 7ebe2b5873..48a449ccc9 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs @@ -1,7 +1,7 @@ using Ryujinx.Audio; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Services.Aud.AudioRenderer; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; using System.Collections.Generic; @@ -28,9 +28,10 @@ namespace Ryujinx.HLE.HOS.Services.Aud { m_Commands = new Dictionary() { - { 0, OpenAudioRenderer }, - { 1, GetAudioRendererWorkBufferSize }, - { 2, GetAudioDevice } + { 0, OpenAudioRenderer }, + { 1, GetAudioRendererWorkBufferSize }, + { 2, GetAudioDeviceService }, + { 4, GetAudioDeviceServiceWithRevisionInfo } }; } @@ -102,7 +103,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud Context.ResponseData.Write(Size); - Context.Device.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{Size:x16}."); + Logger.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{Size:x16}."); return 0; } @@ -110,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud { Context.ResponseData.Write(0L); - Context.Device.Log.PrintWarning(LogClass.ServiceAudio, $"Library Revision 0x{Params.Revision:x8} is not supported!"); + Logger.PrintWarning(LogClass.ServiceAudio, $"Library Revision 0x{Params.Revision:x8} is not supported!"); return MakeError(ErrorModule.Audio, AudErr.UnsupportedRevision); } @@ -161,13 +162,26 @@ namespace Ryujinx.HLE.HOS.Services.Aud return Result / 8; } - public long GetAudioDevice(ServiceCtx Context) + // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object + public long GetAudioDeviceService(ServiceCtx Context) { - long UserId = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); MakeObject(Context, new IAudioDevice(Context.Device.System)); return 0; } + + // GetAudioDeviceServiceWithRevisionInfo(nn::applet::AppletResourceUserId, u32) -> object + private long GetAudioDeviceServiceWithRevisionInfo(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + int RevisionInfo = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceAudio, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"RevisionInfo: {RevisionInfo}"); + + return GetAudioDeviceService(Context); + } } } diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs index 3b06ba8afc..e20de267ff 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Logging; +using Ryujinx.HLE.Utilities; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Friend @@ -15,14 +16,53 @@ namespace Ryujinx.HLE.HOS.Services.Friend { m_Commands = new Dictionary() { + { 10101, GetFriendList }, { 10601, DeclareCloseOnlinePlaySession }, { 10610, UpdateUserPresence } }; } + // nn::friends::GetFriendListGetFriendListIds(nn::account::Uid, int Unknown0, nn::friends::detail::ipc::SizedFriendFilter, ulong Unknown1) -> int CounterIds, array + public long GetFriendList(ServiceCtx Context) + { + UInt128 Uuid = new UInt128( + Context.RequestData.ReadInt64(), + Context.RequestData.ReadInt64()); + + int Unknown0 = Context.RequestData.ReadInt32(); + + FriendFilter Filter = new FriendFilter() + { + PresenceStatus = (PresenceStatusFilter)Context.RequestData.ReadInt32(), + IsFavoriteOnly = Context.RequestData.ReadBoolean(), + IsSameAppPresenceOnly = Context.RequestData.ReadBoolean(), + IsSameAppPlayedOnly = Context.RequestData.ReadBoolean(), + IsArbitraryAppPlayedOnly = Context.RequestData.ReadBoolean(), + PresenceGroupId = Context.RequestData.ReadInt64() + }; + + long Unknown1 = Context.RequestData.ReadInt64(); + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + Context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceFriend, $"Stubbed. UserId: {Uuid.ToString()} - " + + $"Unknown0: {Unknown0} - " + + $"PresenceStatus: {Filter.PresenceStatus} - " + + $"IsFavoriteOnly: {Filter.IsFavoriteOnly} - " + + $"IsSameAppPresenceOnly: {Filter.IsSameAppPresenceOnly} - " + + $"IsSameAppPlayedOnly: {Filter.IsSameAppPlayedOnly} - " + + $"IsArbitraryAppPlayedOnly: {Filter.IsArbitraryAppPlayedOnly} - " + + $"PresenceGroupId: {Filter.PresenceGroupId} - " + + $"Unknown1: {Unknown1}"); + + return 0; + } + + // DeclareCloseOnlinePlaySession(nn::account::Uid) public long DeclareCloseOnlinePlaySession(ServiceCtx Context) { - UserId Uuid = new UserId( + UInt128 Uuid = new UInt128( Context.RequestData.ReadInt64(), Context.RequestData.ReadInt64()); @@ -31,19 +71,30 @@ namespace Ryujinx.HLE.HOS.Services.Friend Profile.OnlinePlayState = OpenCloseState.Closed; } + Logger.PrintStub(LogClass.ServiceFriend, $"Stubbed. Uuid: {Uuid.ToString()} - " + + $"OnlinePlayState: {Profile.OnlinePlayState}"); + return 0; } + // UpdateUserPresence(nn::account::Uid, ulong Unknown0) -> buffer public long UpdateUserPresence(ServiceCtx Context) { - UserId Uuid = new UserId( + UInt128 Uuid = new UInt128( Context.RequestData.ReadInt64(), Context.RequestData.ReadInt64()); - //TODO. - Context.Device.Log.PrintStub(LogClass.ServiceFriend, "Stubbed."); + long Unknown0 = Context.RequestData.ReadInt64(); + + long Position = Context.Request.PtrBuff[0].Position; + long Size = Context.Request.PtrBuff[0].Size; + + //Todo: Write the buffer content. + + Logger.PrintStub(LogClass.ServiceFriend, $"Stubbed. Uuid: {Uuid.ToString()} - " + + $"Unknown0: {Unknown0}"); return 0; } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs new file mode 100644 index 0000000000..31459f7d68 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Friend +{ + enum PresenceStatusFilter + { + None, + Online, + OnlinePlay, + OnlineOrOnlinePlay + } + + struct FriendFilter + { + public PresenceStatusFilter PresenceStatus; + public bool IsFavoriteOnly; + public bool IsSameAppPresenceOnly; + public bool IsSameAppPlayedOnly; + public bool IsArbitraryAppPlayedOnly; + public long PresenceGroupId; + } +} diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs index b77043bddb..bd249e5084 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs @@ -281,11 +281,6 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); } - if (IsPathAlreadyInUse(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - IDirectory DirInterface = new IDirectory(DirName, FilterFlags); DirInterface.Disposed += RemoveDirectoryInUse; diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs index 937ea6d6bf..daf5e0b269 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs @@ -1,6 +1,6 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Utilities; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.FspSrv @@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv long TitleId = Context.RequestData.ReadInt64(); - UserId UserId = new UserId( + UInt128 UserId = new UInt128( Context.RequestData.ReadInt64(), Context.RequestData.ReadInt64()); diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidNpad.cs b/Ryujinx.HLE/HOS/Services/Hid/HidNpad.cs new file mode 100644 index 0000000000..d273185733 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidNpad.cs @@ -0,0 +1,41 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadJoyAssignmentMode + { + Dual, + Single, + } + + public enum HidNpadHandheldActivationMode + { + Dual, + Single, + None, + } + + public enum HidNpadJoyDeviceType + { + Left, + Right, + } + + public enum HidNpadJoyHoldType + { + Vertical, + Horizontal, + } + + [Flags] + public enum HidNpadStyle + { + None, + FullKey = 1 << 0, + Handheld = 1 << 1, + Dual = 1 << 2, + Left = 1 << 3, + Right = 1 << 4, + Invalid = 1 << 5, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidSixAxis.cs b/Ryujinx.HLE/HOS/Services/Hid/HidSixAxis.cs new file mode 100644 index 0000000000..bd0647621e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidSixAxis.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidSensorFusionParameters + { + public float RevisePower; + public float ReviseRange; + } + + public struct HidAccelerometerParameters + { + public float X; + public float Y; + } + + public enum HidGyroscopeZeroDriftMode + { + Loose, + Standard, + Tight + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidVibration.cs b/Ryujinx.HLE/HOS/Services/Hid/HidVibration.cs new file mode 100644 index 0000000000..cb2427e732 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidVibration.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidVibrationDeviceType + { + None, + LinearResonantActuator + } + + public enum HidVibrationDevicePosition + { + None, + Left, + Right, + } + + public struct HidVibrationDeviceValue + { + public HidVibrationDeviceType DeviceType; + public HidVibrationDevicePosition Position; + } + + public struct HidVibrationValue + { + public float AmplitudeLow; + public float FrequencyLow; + public float AmplitudeHigh; + public float FrequencyHigh; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs index 012ccb4053..89a17acf7b 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Hid @@ -24,7 +25,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid public long GetSharedMemoryHandle(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(HidSharedMem); + if (Context.Process.HandleTable.GenerateHandle(HidSharedMem, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 2fd07ec768..e54ca8125d 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -1,7 +1,8 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Hid @@ -11,6 +12,30 @@ namespace Ryujinx.HLE.HOS.Services.Hid private Dictionary m_Commands; private KEvent NpadStyleSetUpdateEvent; + private KEvent XpadIdEvent; + private KEvent PalmaOperationCompleteEvent; + + private int XpadIdEventHandle; + + private bool SixAxisSensorFusionEnabled; + private bool UnintendedHomeButtonInputProtectionEnabled; + private bool VibrationPermitted; + private bool UsbFullKeyControllerEnabled; + + private HidNpadJoyHoldType NpadJoyHoldType; + private HidNpadStyle NpadStyleTag; + private HidNpadJoyAssignmentMode NpadJoyAssignmentMode; + private HidNpadHandheldActivationMode NpadHandheldActivationMode; + private HidGyroscopeZeroDriftMode GyroscopeZeroDriftMode; + + private long NpadCommunicationMode; + private uint AccelerometerPlayMode; + private long VibrationGcErmCommand; + private float SevenSixAxisSensorFusionStrength; + + private HidSensorFusionParameters SensorFusionParams; + private HidAccelerometerParameters AccelerometerParams; + private HidVibrationValue VibrationValue; public override IReadOnlyDictionary Commands => m_Commands; @@ -18,256 +43,992 @@ namespace Ryujinx.HLE.HOS.Services.Hid { m_Commands = new Dictionary() { - { 0, CreateAppletResource }, - { 1, ActivateDebugPad }, - { 11, ActivateTouchScreen }, - { 21, ActivateMouse }, - { 31, ActivateKeyboard }, - { 66, StartSixAxisSensor }, - { 79, SetGyroscopeZeroDriftMode }, - { 100, SetSupportedNpadStyleSet }, - { 101, GetSupportedNpadStyleSet }, - { 102, SetSupportedNpadIdType }, - { 103, ActivateNpad }, - { 106, AcquireNpadStyleSetUpdateEventHandle }, - { 108, GetPlayerLedPattern }, - { 120, SetNpadJoyHoldType }, - { 121, GetNpadJoyHoldType }, - { 122, SetNpadJoyAssignmentModeSingleByDefault }, - { 123, SetNpadJoyAssignmentModeSingle }, - { 124, SetNpadJoyAssignmentModeDual }, - { 125, MergeSingleJoyAsDualJoy }, - { 128, SetNpadHandheldActivationMode }, - { 200, GetVibrationDeviceInfo }, - { 201, SendVibrationValue }, - { 203, CreateActiveVibrationDeviceList }, - { 206, SendVibrationValues } + { 0, CreateAppletResource }, + { 1, ActivateDebugPad }, + { 11, ActivateTouchScreen }, + { 21, ActivateMouse }, + { 31, ActivateKeyboard }, + { 40, AcquireXpadIdEventHandle }, + { 41, ReleaseXpadIdEventHandle }, + { 51, ActivateXpad }, + { 55, GetXpadIds }, + { 56, ActivateJoyXpad }, + { 58, GetJoyXpadLifoHandle }, + { 59, GetJoyXpadIds }, + { 60, ActivateSixAxisSensor }, + { 61, DeactivateSixAxisSensor }, + { 62, GetSixAxisSensorLifoHandle }, + { 63, ActivateJoySixAxisSensor }, + { 64, DeactivateJoySixAxisSensor }, + { 65, GetJoySixAxisSensorLifoHandle }, + { 66, StartSixAxisSensor }, + { 67, StopSixAxisSensor }, + { 68, IsSixAxisSensorFusionEnabled }, + { 69, EnableSixAxisSensorFusion }, + { 70, SetSixAxisSensorFusionParameters }, + { 71, GetSixAxisSensorFusionParameters }, + { 72, ResetSixAxisSensorFusionParameters }, + { 73, SetAccelerometerParameters }, + { 74, GetAccelerometerParameters }, + { 75, ResetAccelerometerParameters }, + { 76, SetAccelerometerPlayMode }, + { 77, GetAccelerometerPlayMode }, + { 78, ResetAccelerometerPlayMode }, + { 79, SetGyroscopeZeroDriftMode }, + { 80, GetGyroscopeZeroDriftMode }, + { 81, ResetGyroscopeZeroDriftMode }, + { 82, IsSixAxisSensorAtRest }, + { 91, ActivateGesture }, + { 100, SetSupportedNpadStyleSet }, + { 101, GetSupportedNpadStyleSet }, + { 102, SetSupportedNpadIdType }, + { 103, ActivateNpad }, + { 104, DeactivateNpad }, + { 106, AcquireNpadStyleSetUpdateEventHandle }, + { 107, DisconnectNpad }, + { 108, GetPlayerLedPattern }, + { 109, ActivateNpadWithRevision }, + { 120, SetNpadJoyHoldType }, + { 121, GetNpadJoyHoldType }, + { 122, SetNpadJoyAssignmentModeSingleByDefault }, + { 123, SetNpadJoyAssignmentModeSingle }, + { 124, SetNpadJoyAssignmentModeDual }, + { 125, MergeSingleJoyAsDualJoy }, + { 126, StartLrAssignmentMode }, + { 127, StopLrAssignmentMode }, + { 128, SetNpadHandheldActivationMode }, + { 129, GetNpadHandheldActivationMode }, + { 130, SwapNpadAssignment }, + { 131, IsUnintendedHomeButtonInputProtectionEnabled }, + { 132, EnableUnintendedHomeButtonInputProtection }, + { 133, SetNpadJoyAssignmentModeSingleWithDestination }, + { 200, GetVibrationDeviceInfo }, + { 201, SendVibrationValue }, + { 202, GetActualVibrationValue }, + { 203, CreateActiveVibrationDeviceList }, + { 204, PermitVibration }, + { 205, IsVibrationPermitted }, + { 206, SendVibrationValues }, + { 207, SendVibrationGcErmCommand }, + { 208, GetActualVibrationGcErmCommand }, + { 209, BeginPermitVibrationSession }, + { 210, EndPermitVibrationSession }, + { 300, ActivateConsoleSixAxisSensor }, + { 301, StartConsoleSixAxisSensor }, + { 302, StopConsoleSixAxisSensor }, + { 303, ActivateSevenSixAxisSensor }, + { 304, StartSevenSixAxisSensor }, + { 305, StopSevenSixAxisSensor }, + { 306, InitializeSevenSixAxisSensor }, + { 307, FinalizeSevenSixAxisSensor }, + { 308, SetSevenSixAxisSensorFusionStrength }, + { 309, GetSevenSixAxisSensorFusionStrength }, + { 400, IsUsbFullKeyControllerEnabled }, + { 401, EnableUsbFullKeyController }, + { 402, IsUsbFullKeyControllerConnected }, + { 403, HasBattery }, + { 404, HasLeftRightBattery }, + { 405, GetNpadInterfaceType }, + { 406, GetNpadLeftRightInterfaceType }, + { 500, GetPalmaConnectionHandle }, + { 501, InitializePalma }, + { 502, AcquirePalmaOperationCompleteEvent }, + { 503, GetPalmaOperationInfo }, + { 504, PlayPalmaActivity }, + { 505, SetPalmaFrModeType }, + { 506, ReadPalmaStep }, + { 507, EnablePalmaStep }, + { 508, SuspendPalmaStep }, + { 509, ResetPalmaStep }, + { 510, ReadPalmaApplicationSection }, + { 511, WritePalmaApplicationSection }, + { 512, ReadPalmaUniqueCode }, + { 513, SetPalmaUniqueCodeInvalid }, + { 1000, SetNpadCommunicationMode }, + { 1001, GetNpadCommunicationMode }, }; - NpadStyleSetUpdateEvent = new KEvent(System); + NpadStyleSetUpdateEvent = new KEvent(System); + XpadIdEvent = new KEvent(System); + PalmaOperationCompleteEvent = new KEvent(System); + + NpadJoyHoldType = HidNpadJoyHoldType.Vertical; + NpadStyleTag = HidNpadStyle.FullKey | HidNpadStyle.Dual | HidNpadStyle.Left | HidNpadStyle.Right; + NpadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; + NpadHandheldActivationMode = HidNpadHandheldActivationMode.Dual; + GyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + + SensorFusionParams = new HidSensorFusionParameters(); + AccelerometerParams = new HidAccelerometerParameters(); + VibrationValue = new HidVibrationValue(); } + // CreateAppletResource(nn::applet::AppletResourceUserId) -> object public long CreateAppletResource(ServiceCtx Context) { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + MakeObject(Context, new IAppletResource(Context.Device.System.HidSharedMem)); return 0; } + // ActivateDebugPad(nn::applet::AppletResourceUserId) public long ActivateDebugPad(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); return 0; } + // ActivateTouchScreen(nn::applet::AppletResourceUserId) public long ActivateTouchScreen(ServiceCtx Context) { long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); return 0; } + // ActivateMouse(nn::applet::AppletResourceUserId) public long ActivateMouse(ServiceCtx Context) { long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); return 0; } + // ActivateKeyboard(nn::applet::AppletResourceUserId) public long ActivateKeyboard(ServiceCtx Context) { long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); return 0; } - public long StartSixAxisSensor(ServiceCtx Context) + // AcquireXpadIdEventHandle(ulong XpadId) -> nn::sf::NativeHandle + public long AcquireXpadIdEventHandle(ServiceCtx Context) { - int Handle = Context.RequestData.ReadInt32(); + long XpadId = Context.RequestData.ReadInt64(); + if (Context.Process.HandleTable.GenerateHandle(XpadIdEvent, out XpadIdEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(XpadIdEventHandle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. XpadId: {XpadId}"); + + return 0; + } + + // ReleaseXpadIdEventHandle(ulong XpadId) + public long ReleaseXpadIdEventHandle(ServiceCtx Context) + { + long XpadId = Context.RequestData.ReadInt64(); + + Context.Process.HandleTable.CloseHandle(XpadIdEventHandle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. XpadId: {XpadId}"); + + return 0; + } + + // ActivateXpad(nn::hid::BasicXpadId, nn::applet::AppletResourceUserId) + public long ActivateXpad(ServiceCtx Context) + { + int BasicXpadId = Context.RequestData.ReadInt32(); long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"BasicXpadId: {BasicXpadId}"); return 0; } - public long SetGyroscopeZeroDriftMode(ServiceCtx Context) + // GetXpadIds() -> long IdsCount, buffer, type: 0xa> + public long GetXpadIds(ServiceCtx Context) { - int Handle = Context.RequestData.ReadInt32(); - int Unknown = Context.RequestData.ReadInt32(); - long AppletResourceUserId = Context.RequestData.ReadInt64(); + // There is any Xpad, so we return 0 and write nothing inside the type-0xa buffer. + Context.ResponseData.Write(0L); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed."); return 0; } - public long AcquireNpadStyleSetUpdateEventHandle(ServiceCtx Context) + // ActivateJoyXpad(nn::hid::JoyXpadId) + public long ActivateJoyXpad(ServiceCtx Context) { - int Handle = Context.Process.HandleTable.OpenHandle(NpadStyleSetUpdateEvent); + int JoyXpadId = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. JoyXpadId: {JoyXpadId}"); + + return 0; + } + + // GetJoyXpadLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public long GetJoyXpadLifoHandle(ServiceCtx Context) + { + int JoyXpadId = Context.RequestData.ReadInt32(); + + int Handle = 0; Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. JoyXpadId: {JoyXpadId}"); + return 0; } - public long GetSupportedNpadStyleSet(ServiceCtx Context) + // GetJoyXpadIds() -> long IdsCount, buffer, type: 0xa> + public long GetJoyXpadIds(ServiceCtx Context) { - Context.ResponseData.Write(0); + // There is any JoyXpad, so we return 0 and write nothing inside the type-0xa buffer. + Context.ResponseData.Write(0L); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed."); return 0; } + // ActivateSixAxisSensor(nn::hid::BasicXpadId) + public long ActivateSixAxisSensor(ServiceCtx Context) + { + int BasicXpadId = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. BasicXpadId: {BasicXpadId}"); + + return 0; + } + + // DeactivateSixAxisSensor(nn::hid::BasicXpadId) + public long DeactivateSixAxisSensor(ServiceCtx Context) + { + int BasicXpadId = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. BasicXpadId: {BasicXpadId}"); + + return 0; + } + + // GetSixAxisSensorLifoHandle(nn::hid::BasicXpadId) -> nn::sf::NativeHandle + public long GetSixAxisSensorLifoHandle(ServiceCtx Context) + { + int BasicXpadId = Context.RequestData.ReadInt32(); + + int Handle = 0; + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. BasicXpadId: {BasicXpadId}"); + + return 0; + } + + // ActivateJoySixAxisSensor(nn::hid::JoyXpadId) + public long ActivateJoySixAxisSensor(ServiceCtx Context) + { + int JoyXpadId = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. JoyXpadId: {JoyXpadId}"); + + return 0; + } + + // DeactivateJoySixAxisSensor(nn::hid::JoyXpadId) + public long DeactivateJoySixAxisSensor(ServiceCtx Context) + { + int JoyXpadId = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. JoyXpadId: {JoyXpadId}"); + + return 0; + } + + // GetJoySixAxisSensorLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public long GetJoySixAxisSensorLifoHandle(ServiceCtx Context) + { + int JoyXpadId = Context.RequestData.ReadInt32(); + + int Handle = 0; + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. JoyXpadId: {JoyXpadId}"); + + return 0; + } + + // StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long StartSixAxisSensor(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle}"); + + return 0; + } + + // StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long StopSixAxisSensor(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle}"); + + return 0; + } + + // IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled + public long IsSixAxisSensorFusionEnabled(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(SixAxisSensorFusionEnabled); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"SixAxisSensorFusionEnabled: {SixAxisSensorFusionEnabled}"); + + return 0; + } + + // EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long EnableSixAxisSensorFusion(ServiceCtx Context) + { + SixAxisSensorFusionEnabled = Context.RequestData.ReadBoolean(); + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"SixAxisSensorFusionEnabled: {SixAxisSensorFusionEnabled}"); + + return 0; + } + + // SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId) + public long SetSixAxisSensorFusionParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + + SensorFusionParams = new HidSensorFusionParameters() + { + RevisePower = Context.RequestData.ReadInt32(), + ReviseRange = Context.RequestData.ReadInt32(), + }; + + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"RevisePower: {SensorFusionParams.RevisePower} - " + + $"ReviseRange: {SensorFusionParams.ReviseRange}"); + + return 0; + } + + // GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange) + public long GetSixAxisSensorFusionParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(SensorFusionParams.RevisePower); + Context.ResponseData.Write(SensorFusionParams.ReviseRange); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"RevisePower: {SensorFusionParams.RevisePower} - " + + $"ReviseRange: {SensorFusionParams.ReviseRange}"); + + return 0; + } + + // ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long ResetSixAxisSensorFusionParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + SensorFusionParams.RevisePower = 0; + SensorFusionParams.ReviseRange = 0; + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"RevisePower: {SensorFusionParams.RevisePower} - " + + $"ReviseRange: {SensorFusionParams.ReviseRange}"); + + return 0; + } + + // SetAccelerometerParameters(nn::hid::SixAxisSensorHandle, float X, float Y, nn::applet::AppletResourceUserId) + public long SetAccelerometerParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + + AccelerometerParams = new HidAccelerometerParameters() + { + X = Context.RequestData.ReadInt32(), + Y = Context.RequestData.ReadInt32(), + }; + + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"X: {AccelerometerParams.X} - " + + $"Y: {AccelerometerParams.Y}"); + + return 0; + } + + // GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y + public long GetAccelerometerParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(AccelerometerParams.X); + Context.ResponseData.Write(AccelerometerParams.Y); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"X: {AccelerometerParams.X} - " + + $"Y: {AccelerometerParams.Y}"); + + return 0; + } + + // ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long ResetAccelerometerParameters(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + AccelerometerParams.X = 0; + AccelerometerParams.Y = 0; + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"X: {AccelerometerParams.X} - " + + $"Y: {AccelerometerParams.Y}"); + + return 0; + } + + // SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId) + public long SetAccelerometerPlayMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + AccelerometerPlayMode = Context.RequestData.ReadUInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"PlayMode: {AccelerometerPlayMode}"); + + return 0; + } + + // GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode + public long GetAccelerometerPlayMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(AccelerometerPlayMode); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"PlayMode: {AccelerometerPlayMode}"); + + return 0; + } + + // ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long ResetAccelerometerPlayMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + AccelerometerPlayMode = 0; + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"PlayMode: {AccelerometerPlayMode}"); + + return 0; + } + + // SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId) + public long SetGyroscopeZeroDriftMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + GyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"GyroscopeZeroDriftMode: {GyroscopeZeroDriftMode}"); + + return 0; + } + + // GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode + public long GetGyroscopeZeroDriftMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write((int)GyroscopeZeroDriftMode); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"GyroscopeZeroDriftMode: {GyroscopeZeroDriftMode}"); + + return 0; + } + + // ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long ResetGyroscopeZeroDriftMode(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + GyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"GyroscopeZeroDriftMode: {GyroscopeZeroDriftMode}"); + + return 0; + } + + // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest + public long IsSixAxisSensorAtRest(ServiceCtx Context) + { + int SixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + bool IsAtRest = true; + + Context.ResponseData.Write(IsAtRest); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SixAxisSensorHandle: {SixAxisSensorHandle} - " + + $"IsAtRest: {IsAtRest}"); + + return 0; + } + + // ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0) + public long ActivateGesture(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + int Unknown0 = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"Unknown0: {Unknown0}"); + + return 0; + } + + + // SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag) public long SetSupportedNpadStyleSet(ServiceCtx Context) { - long Unknown0 = Context.RequestData.ReadInt64(); - long Unknown8 = Context.RequestData.ReadInt64(); + NpadStyleTag = (HidNpadStyle)Context.RequestData.ReadInt32(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadStyleTag: {NpadStyleTag}"); return 0; } + // GetSupportedNpadStyleSet(nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag + public long GetSupportedNpadStyleSet(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write((int)NpadStyleTag); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadStyleTag: {NpadStyleTag}"); + + return 0; + } + + // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array) public long SetSupportedNpadIdType(ServiceCtx Context) { - long Unknown = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + HidControllerId NpadIdType = (HidControllerId)Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadIdType: {NpadIdType}"); return 0; } + // ActivateNpad(nn::applet::AppletResourceUserId) public long ActivateNpad(ServiceCtx Context) { - long Unknown = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); return 0; } + // DeactivateNpad(nn::applet::AppletResourceUserId) + public long DeactivateNpad(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle + public long AcquireNpadStyleSetUpdateEventHandle(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + int NpadId = Context.RequestData.ReadInt32(); + long NpadStyleSet = Context.RequestData.ReadInt64(); + + if (Context.Process.HandleTable.GenerateHandle(NpadStyleSetUpdateEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadId: {NpadId} - " + + $"NpadStyleSet: {NpadStyleSet}"); + + return 0; + } + + // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType) + public long DisconnectNpad(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + int NpadIdType = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadIdType: {NpadIdType}"); + + return 0; + } + + // GetPlayerLedPattern(uint NpadId) -> ulong LedPattern public long GetPlayerLedPattern(ServiceCtx Context) { - long Unknown = Context.RequestData.ReadInt32(); + int NpadId = Context.RequestData.ReadInt32(); - Context.ResponseData.Write(0L); + long LedPattern = 0; - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Context.ResponseData.Write(LedPattern); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. NpadId: {NpadId} - Pattern: {LedPattern}"); return 0; } + // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int Unknown) + public long ActivateNpadWithRevision(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + int Unknown = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - Unknown: {Unknown}"); + + return 0; + } + + // SetNpadJoyHoldType(nn::applet::AppletResourceUserId, long NpadJoyHoldType) public long SetNpadJoyHoldType(ServiceCtx Context) { - long Unknown0 = Context.RequestData.ReadInt64(); - long Unknown8 = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + NpadJoyHoldType = (HidNpadJoyHoldType)Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadJoyHoldType: {NpadJoyHoldType}"); return 0; } + // GetNpadJoyHoldType(nn::applet::AppletResourceUserId) -> long NpadJoyHoldType public long GetNpadJoyHoldType(ServiceCtx Context) { - Context.ResponseData.Write(0L); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Context.ResponseData.Write((long)NpadJoyHoldType); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadJoyHoldTypeValue: {NpadJoyHoldType}"); return 0; } + // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context) { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - long AppletUserResourceId = Context.RequestData.ReadInt64(); + NpadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"HidControllerId: {HidControllerId} - " + + $"NpadJoyAssignmentModeValue: {NpadJoyAssignmentMode}"); return 0; } + // SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType) public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context) { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + HidNpadJoyDeviceType HidNpadJoyDeviceType = (HidNpadJoyDeviceType)Context.RequestData.ReadInt64(); - long AppletUserResourceId = Context.RequestData.ReadInt64(); - long NpadJoyDeviceType = Context.RequestData.ReadInt64(); + NpadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"HidControllerId: {HidControllerId} - " + + $"HidNpadJoyDeviceType: {HidNpadJoyDeviceType} - " + + $"NpadJoyAssignmentModeValue: {NpadJoyAssignmentMode}"); return 0; } + // SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId) public long SetNpadJoyAssignmentModeDual(ServiceCtx Context) { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - long AppletUserResourceId = Context.RequestData.ReadInt64(); + NpadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"HidControllerId: {HidControllerId} - " + + $"NpadJoyAssignmentModeValue: {NpadJoyAssignmentMode}"); return 0; } + // MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId) public long MergeSingleJoyAsDualJoy(ServiceCtx Context) { - long Unknown0 = Context.RequestData.ReadInt32(); - long Unknown8 = Context.RequestData.ReadInt32(); - long AppletUserResourceId = Context.RequestData.ReadInt64(); + long SingleJoyId0 = Context.RequestData.ReadInt32(); + long SingleJoyId1 = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SingleJoyId0: {SingleJoyId0} - " + + $"SingleJoyId1: {SingleJoyId1}"); return 0; } + // StartLrAssignmentMode(nn::applet::AppletResourceUserId) + public long StartLrAssignmentMode(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // StopLrAssignmentMode(nn::applet::AppletResourceUserId) + public long StopLrAssignmentMode(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // SetNpadHandheldActivationMode(nn::applet::AppletResourceUserId, long HidNpadHandheldActivationMode) public long SetNpadHandheldActivationMode(ServiceCtx Context) { - long AppletUserResourceId = Context.RequestData.ReadInt64(); - long Unknown = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + NpadHandheldActivationMode = (HidNpadHandheldActivationMode)Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadHandheldActivationMode: {NpadHandheldActivationMode}"); return 0; } + // GetNpadHandheldActivationMode(nn::applet::AppletResourceUserId) -> long HidNpadHandheldActivationMode + public long GetNpadHandheldActivationMode(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write((long)NpadHandheldActivationMode); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadHandheldActivationMode: {NpadHandheldActivationMode}"); + + return 0; + } + + // SwapNpadAssignment(uint OldNpadAssignment, uint NewNpadAssignment, nn::applet::AppletResourceUserId) + public long SwapNpadAssignment(ServiceCtx Context) + { + int OldNpadAssignment = Context.RequestData.ReadInt32(); + int NewNpadAssignment = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"OldNpadAssignment: {OldNpadAssignment} - " + + $"NewNpadAssignment: {NewNpadAssignment}"); + + return 0; + } + + // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled + public long IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx Context) + { + uint Unknown0 = Context.RequestData.ReadUInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(UnintendedHomeButtonInputProtectionEnabled); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"Unknown0: {Unknown0} - " + + $"UnintendedHomeButtonInputProtectionEnabled: {UnintendedHomeButtonInputProtectionEnabled}"); + + return 0; + } + + // EnableUnintendedHomeButtonInputProtection(bool Enable, uint Unknown0, nn::applet::AppletResourceUserId) + public long EnableUnintendedHomeButtonInputProtection(ServiceCtx Context) + { + UnintendedHomeButtonInputProtectionEnabled = Context.RequestData.ReadBoolean(); + uint Unknown0 = Context.RequestData.ReadUInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"Unknown0: {Unknown0} - " + + $"UnintendedHomeButtonInputProtectionEnable: {UnintendedHomeButtonInputProtectionEnabled}"); + + return 0; + } + + // SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1 + public long SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx Context) + { + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + HidNpadJoyDeviceType HidNpadJoyDeviceType = (HidNpadJoyDeviceType)Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + NpadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; + + Context.ResponseData.Write(0); //Unknown0 + Context.ResponseData.Write(0); //Unknown1 + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"HidControllerId: {HidControllerId} - " + + $"HidNpadJoyDeviceType: {HidNpadJoyDeviceType} - " + + $"NpadJoyAssignmentModeValue: {NpadJoyAssignmentMode} - " + + $"Unknown0: 0 - " + + $"Unknown1: 0"); + + return 0; + } + + // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo public long GetVibrationDeviceInfo(ServiceCtx Context) { int VibrationDeviceHandle = Context.RequestData.ReadInt32(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + HidVibrationDeviceValue DeviceInfo = new HidVibrationDeviceValue + { + DeviceType = HidVibrationDeviceType.None, + Position = HidVibrationDevicePosition.None + }; - Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc + Context.ResponseData.Write((int)DeviceInfo.DeviceType); + Context.ResponseData.Write((int)DeviceInfo.Position); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. VibrationDeviceHandle: {VibrationDeviceHandle} - " + + $"DeviceType: {DeviceInfo.DeviceType} - " + + $"Position: {DeviceInfo.Position}"); return 0; } + // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) public long SendVibrationValue(ServiceCtx Context) { int VibrationDeviceHandle = Context.RequestData.ReadInt32(); - int VibrationValue1 = Context.RequestData.ReadInt32(); - int VibrationValue2 = Context.RequestData.ReadInt32(); - int VibrationValue3 = Context.RequestData.ReadInt32(); - int VibrationValue4 = Context.RequestData.ReadInt32(); + VibrationValue = new HidVibrationValue + { + AmplitudeLow = Context.RequestData.ReadSingle(), + FrequencyLow = Context.RequestData.ReadSingle(), + AmplitudeHigh = Context.RequestData.ReadSingle(), + FrequencyHigh = Context.RequestData.ReadSingle() + }; - long AppletUserResourceId = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"VibrationDeviceHandle: {VibrationDeviceHandle} - " + + $"AmplitudeLow: {VibrationValue.AmplitudeLow} - " + + $"FrequencyLow: {VibrationValue.FrequencyLow} - " + + $"AmplitudeHigh: {VibrationValue.AmplitudeHigh} - " + + $"FrequencyHigh: {VibrationValue.FrequencyHigh}"); return 0; } + // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue + public long GetActualVibrationValue(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(VibrationValue.AmplitudeLow); + Context.ResponseData.Write(VibrationValue.FrequencyLow); + Context.ResponseData.Write(VibrationValue.AmplitudeHigh); + Context.ResponseData.Write(VibrationValue.FrequencyHigh); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"VibrationDeviceHandle: {VibrationDeviceHandle} - " + + $"AmplitudeLow: {VibrationValue.AmplitudeLow} - " + + $"FrequencyLow: {VibrationValue.FrequencyLow} - " + + $"AmplitudeHigh: {VibrationValue.AmplitudeHigh} - " + + $"FrequencyHigh: {VibrationValue.FrequencyHigh}"); + + return 0; + } + + // CreateActiveVibrationDeviceList() -> object public long CreateActiveVibrationDeviceList(ServiceCtx Context) { MakeObject(Context, new IActiveApplicationDeviceList()); @@ -275,9 +1036,486 @@ namespace Ryujinx.HLE.HOS.Services.Hid return 0; } + // PermitVibration(bool Enable) + public long PermitVibration(ServiceCtx Context) + { + VibrationPermitted = Context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. VibrationPermitted: {VibrationPermitted}"); + + return 0; + } + + // IsVibrationPermitted() -> bool IsEnabled + public long IsVibrationPermitted(ServiceCtx Context) + { + Context.ResponseData.Write(VibrationPermitted); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. VibrationPermitted: {VibrationPermitted}"); + + return 0; + } + + // SendVibrationValues(nn::applet::AppletResourceUserId, buffer, type: 9>, buffer, type: 9>) public long SendVibrationValues(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + byte[] VibrationDeviceHandleBuffer = Context.Memory.ReadBytes( + Context.Request.PtrBuff[0].Position, + Context.Request.PtrBuff[0].Size); + + byte[] VibrationValueBuffer = Context.Memory.ReadBytes( + Context.Request.PtrBuff[1].Position, + Context.Request.PtrBuff[1].Size); + + //Todo: Read all handles and values from buffer. + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"VibrationDeviceHandleBufferLength: {VibrationDeviceHandleBuffer.Length} - " + + $"VibrationValueBufferLength: {VibrationValueBuffer.Length}"); + + return 0; + } + + // SendVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::hid::VibrationGcErmCommand, nn::applet::AppletResourceUserId) + public long SendVibrationGcErmCommand(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + long VibrationGcErmCommand = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"VibrationDeviceHandle: {VibrationDeviceHandle} - " + + $"VibrationGcErmCommand: {VibrationGcErmCommand}"); + + return 0; + } + + // GetActualVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationGcErmCommand + public long GetActualVibrationGcErmCommand(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(VibrationGcErmCommand); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"VibrationDeviceHandle: {VibrationDeviceHandle} - " + + $"VibrationGcErmCommand: {VibrationGcErmCommand}"); + + return 0; + } + + // BeginPermitVibrationSession(nn::applet::AppletResourceUserId) + public long BeginPermitVibrationSession(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // EndPermitVibrationSession() + public long EndPermitVibrationSession(ServiceCtx Context) + { + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed."); + + return 0; + } + + // ActivateConsoleSixAxisSensor(nn::applet::AppletResourceUserId) + public long ActivateConsoleSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // StartConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long StartConsoleSixAxisSensor(ServiceCtx Context) + { + int ConsoleSixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"ConsoleSixAxisSensorHandle: {ConsoleSixAxisSensorHandle}"); + + return 0; + } + + // StopConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public long StopConsoleSixAxisSensor(ServiceCtx Context) + { + int ConsoleSixAxisSensorHandle = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"ConsoleSixAxisSensorHandle: {ConsoleSixAxisSensorHandle}"); + + return 0; + } + + // ActivateSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public long ActivateSevenSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // StartSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public long StartSevenSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // StopSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public long StopSevenSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // InitializeSevenSixAxisSensor(array, ulong Counter0, array, ulong Counter1, nn::applet::AppletResourceUserId) + public long InitializeSevenSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + long Counter0 = Context.RequestData.ReadInt64(); + long Counter1 = Context.RequestData.ReadInt64(); + + // Todo: Determine if array is a buffer or not... + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"Counter0: {Counter0} - " + + $"Counter1: {Counter1}"); + + return 0; + } + + // FinalizeSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public long FinalizeSevenSixAxisSensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // SetSevenSixAxisSensorFusionStrength(float Strength, nn::applet::AppletResourceUserId) + public long SetSevenSixAxisSensorFusionStrength(ServiceCtx Context) + { + SevenSixAxisSensorFusionStrength = Context.RequestData.ReadSingle(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SevenSixAxisSensorFusionStrength: {SevenSixAxisSensorFusionStrength}"); + + return 0; + } + + // GetSevenSixAxisSensorFusionStrength(nn::applet::AppletResourceUserId) -> float Strength + public long GetSevenSixAxisSensorFusionStrength(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(SevenSixAxisSensorFusionStrength); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"SevenSixAxisSensorFusionStrength: {SevenSixAxisSensorFusionStrength}"); + + return 0; + } + + // IsUsbFullKeyControllerEnabled() -> bool IsEnabled + public long IsUsbFullKeyControllerEnabled(ServiceCtx Context) + { + Context.ResponseData.Write(UsbFullKeyControllerEnabled); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. UsbFullKeyControllerEnabled: {UsbFullKeyControllerEnabled}"); + + return 0; + } + + // EnableUsbFullKeyController(bool Enable) + public long EnableUsbFullKeyController(ServiceCtx Context) + { + UsbFullKeyControllerEnabled = Context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. UsbFullKeyControllerEnabled: {UsbFullKeyControllerEnabled}"); + + return 0; + } + + // IsUsbFullKeyControllerConnected(uint Unknown0) -> bool Connected + public long IsUsbFullKeyControllerConnected(ServiceCtx Context) + { + int Unknown0 = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(true); //FullKeyController is always connected ? + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. Unknown0: {Unknown0} - Connected: true"); + + return 0; + } + + // HasBattery(uint NpadId) -> bool HasBattery + public long HasBattery(ServiceCtx Context) + { + int NpadId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(true); //Npad always got a battery ? + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. NpadId: {NpadId} - HasBattery: true"); + + return 0; + } + + // HasLeftRightBattery(uint NpadId) -> bool HasLeftBattery, bool HasRightBattery + public long HasLeftRightBattery(ServiceCtx Context) + { + int NpadId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(true); //Npad always got a left battery ? + Context.ResponseData.Write(true); //Npad always got a right battery ? + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. NpadId: {NpadId} - HasLeftBattery: true - HasRightBattery: true"); + + return 0; + } + + // GetNpadInterfaceType(uint NpadId) -> uchar InterfaceType + public long GetNpadInterfaceType(ServiceCtx Context) + { + int NpadId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write((byte)0); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. NpadId: {NpadId} - NpadInterfaceType: 0"); + + return 0; + } + + // GetNpadLeftRightInterfaceType(uint NpadId) -> uchar LeftInterfaceType, uchar RightInterfaceType + public long GetNpadLeftRightInterfaceType(ServiceCtx Context) + { + int NpadId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write((byte)0); + Context.ResponseData.Write((byte)0); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. NpadId: {NpadId} - " + + $"LeftInterfaceType: 0 - " + + $"RightInterfaceType: 0"); + + return 0; + } + + // GetPalmaConnectionHandle(uint Unknown0, nn::applet::AppletResourceUserId) -> nn::hid::PalmaConnectionHandle + public long GetPalmaConnectionHandle(ServiceCtx Context) + { + int Unknown0 = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + int PalmaConnectionHandle = 0; + + Context.ResponseData.Write(PalmaConnectionHandle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"Unknown0: {Unknown0} - " + + $"PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // InitializePalma(nn::hid::PalmaConnectionHandle) + public long InitializePalma(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // AcquirePalmaOperationCompleteEvent(nn::hid::PalmaConnectionHandle) -> nn::sf::NativeHandle + public long AcquirePalmaOperationCompleteEvent(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + if (Context.Process.HandleTable.GenerateHandle(PalmaOperationCompleteEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // GetPalmaOperationInfo(nn::hid::PalmaConnectionHandle) -> long Unknown0, buffer + public long GetPalmaOperationInfo(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + long Unknown0 = 0; //Counter? + + Context.ResponseData.Write(Unknown0); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"Unknown0: {Unknown0}"); + + return 0; + } + + // PlayPalmaActivity(nn::hid::PalmaConnectionHandle, ulong Unknown0) + public long PlayPalmaActivity(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + long Unknown0 = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"Unknown0: {Unknown0}"); + + return 0; + } + + // SetPalmaFrModeType(nn::hid::PalmaConnectionHandle, ulong FrModeType) + public long SetPalmaFrModeType(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + long FrModeType = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"FrModeType: {FrModeType}"); + + return 0; + } + + // ReadPalmaStep(nn::hid::PalmaConnectionHandle) + public long ReadPalmaStep(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // EnablePalmaStep(nn::hid::PalmaConnectionHandle, bool Enable) + public long EnablePalmaStep(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + bool EnabledPalmaStep = Context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"EnabledPalmaStep: {EnabledPalmaStep}"); + + return 0; + } + + // SuspendPalmaStep(nn::hid::PalmaConnectionHandle) + public long SuspendPalmaStep(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // ResetPalmaStep(nn::hid::PalmaConnectionHandle) + public long ResetPalmaStep(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // ReadPalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1) + public long ReadPalmaApplicationSection(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown1 = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"Unknown0: {Unknown0} - " + + $"Unknown1: {Unknown1}"); + + return 0; + } + + // WritePalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1, nn::hid::PalmaApplicationSectionAccessBuffer) + public long WritePalmaApplicationSection(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown1 = Context.RequestData.ReadInt64(); + // nn::hid::PalmaApplicationSectionAccessBuffer cast is unknown + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle} - " + + $"Unknown0: {Unknown0} - " + + $"Unknown1: {Unknown1}"); + + return 0; + } + + // ReadPalmaUniqueCode(nn::hid::PalmaConnectionHandle) + public long ReadPalmaUniqueCode(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // SetPalmaUniqueCodeInvalid(nn::hid::PalmaConnectionHandle) + public long SetPalmaUniqueCodeInvalid(ServiceCtx Context) + { + int PalmaConnectionHandle = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. PalmaConnectionHandle: {PalmaConnectionHandle}"); + + return 0; + } + + // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId) + public long SetNpadCommunicationMode(ServiceCtx Context) + { + NpadCommunicationMode = Context.RequestData.ReadInt64(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {AppletResourceUserId} - " + + $"NpadCommunicationMode: {NpadCommunicationMode}"); + + return 0; + } + + // GetNpadCommunicationMode() -> long CommunicationMode + public long GetNpadCommunicationMode(ServiceCtx Context) + { + Context.ResponseData.Write(NpadCommunicationMode); + + Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. CommunicationMode: {NpadCommunicationMode}"); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index 8e487d55c1..60a4431e9e 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -1,6 +1,6 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; using System.IO; @@ -91,7 +91,7 @@ namespace Ryujinx.HLE.HOS.Services { Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin); - Context.Device.Log.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}"); + Logger.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}"); long Result = ProcessRequest(Context); @@ -132,7 +132,10 @@ namespace Ryujinx.HLE.HOS.Services { KSession Session = new KSession(Obj, Context.Session.ServiceName); - int Handle = Context.Process.HandleTable.OpenHandle(Session); + if (Context.Process.HandleTable.GenerateHandle(Session, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); } @@ -146,7 +149,7 @@ namespace Ryujinx.HLE.HOS.Services { int Handle = Context.Request.HandleDesc.ToMove[Index]; - KSession Session = Context.Process.HandleTable.GetData(Handle); + KSession Session = Context.Process.HandleTable.GetObject(Handle); return Session?.Service is T ? (T)Session.Service : null; } diff --git a/Ryujinx.HLE/HOS/Services/Irs/IIrSensorServer.cs b/Ryujinx.HLE/HOS/Services/Irs/IIrSensorServer.cs new file mode 100644 index 0000000000..155faea3e6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Irs/IIrSensorServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Irs +{ + class IIrSensorServer : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + private bool Activated; + + public IIrSensorServer() + { + m_Commands = new Dictionary() + { + { 302, ActivateIrsensor }, + { 303, DeactivateIrsensor } + }; + } + + // ActivateIrsensor(nn::applet::AppletResourceUserId, pid) + public long ActivateIrsensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceIrs, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + + // DeactivateIrsensor(nn::applet::AppletResourceUserId, pid) + public long DeactivateIrsensor(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceIrs, $"Stubbed. AppletResourceUserId: {AppletResourceUserId}"); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs new file mode 100644 index 0000000000..4d595fde27 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs @@ -0,0 +1,457 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.HOS.Services.Ldr +{ + [StructLayout(LayoutKind.Explicit, Size = 0x350)] + unsafe struct NrrHeader + { + [FieldOffset(0)] + public uint Magic; + + [FieldOffset(0x10)] + public ulong TitleIdMask; + + [FieldOffset(0x18)] + public ulong TitleIdPattern; + + [FieldOffset(0x30)] + public fixed byte Modulus[0x100]; + + [FieldOffset(0x130)] + public fixed byte FixedKeySignature[0x100]; + + [FieldOffset(0x230)] + public fixed byte NrrSignature[0x100]; + + [FieldOffset(0x330)] + public ulong TitleIdMin; + + [FieldOffset(0x338)] + public uint NrrSize; + + [FieldOffset(0x340)] + public uint HashOffset; + + [FieldOffset(0x344)] + public uint HashCount; + } + + class NrrInfo + { + public NrrHeader Header { get; private set; } + public List Hashes { get; private set; } + public long NrrAddress { get; private set; } + + public NrrInfo(long NrrAddress, NrrHeader Header, List Hashes) + { + this.NrrAddress = NrrAddress; + this.Header = Header; + this.Hashes = Hashes; + } + } + + class NroInfo + { + public Nro Executable { get; private set; } + public byte[] Hash { get; private set; } + public long NroAddress { get; private set; } + public long TotalSize { get; private set; } + public long NroMappedAddress { get; set; } + + public NroInfo(Nro Executable, byte[] Hash, long TotalSize) + { + this.Executable = Executable; + this.Hash = Hash; + this.TotalSize = TotalSize; + } + } + + class IRoInterface : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + private const int MaxNrr = 0x40; + private const int MaxNro = 0x40; + + private const uint NrrMagic = 0x3052524E; + private const uint NroMagic = 0x304F524E; + + private List NrrInfos; + private List NroInfos; + + private bool IsInitialized; + + public IRoInterface() + { + m_Commands = new Dictionary() + { + { 0, LoadNro }, + { 1, UnloadNro }, + { 2, LoadNrr }, + { 3, UnloadNrr }, + { 4, Initialize }, + }; + + NrrInfos = new List(MaxNrr); + NroInfos = new List(MaxNro); + } + + private long ParseNrr(out NrrInfo NrrInfo, ServiceCtx Context, long NrrAddress, long NrrSize) + { + NrrInfo = null; + + if (NrrSize == 0 || NrrAddress + NrrSize <= NrrAddress || (NrrSize & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.BadSize); + } + else if ((NrrAddress & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); + } + + StructReader Reader = new StructReader(Context.Memory, NrrAddress); + NrrHeader Header = Reader.Read(); + + if (Header.Magic != NrrMagic) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidNrr); + } + else if (Header.NrrSize != NrrSize) + { + return MakeError(ErrorModule.Loader, LoaderErr.BadSize); + } + + List Hashes = new List(); + + for (int i = 0; i < Header.HashCount; i++) + { + Hashes.Add(Context.Memory.ReadBytes(NrrAddress + Header.HashOffset + (i * 0x20), 0x20)); + } + + NrrInfo = new NrrInfo(NrrAddress, Header, Hashes); + + return 0; + } + + public bool IsNroHashPresent(byte[] NroHash) + { + foreach (NrrInfo Info in NrrInfos) + { + foreach (byte[] Hash in Info.Hashes) + { + if (Hash.SequenceEqual(NroHash)) + { + return true; + } + } + } + + return false; + } + + public bool IsNroLoaded(byte[] NroHash) + { + foreach (NroInfo Info in NroInfos) + { + if (Info.Hash.SequenceEqual(NroHash)) + { + return true; + } + } + + return false; + } + + public long ParseNro(out NroInfo Res, ServiceCtx Context, long NroHeapAddress, long NroSize, long BssHeapAddress, long BssSize) + { + Res = null; + + if (NroInfos.Count >= MaxNro) + { + return MakeError(ErrorModule.Loader, LoaderErr.MaxNro); + } + else if (NroSize == 0 || NroHeapAddress + NroSize <= NroHeapAddress || (NroSize & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.BadSize); + } + else if (BssSize != 0 && (BssHeapAddress + BssSize) <= BssHeapAddress) + { + return MakeError(ErrorModule.Loader, LoaderErr.BadSize); + } + else if ((NroHeapAddress & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); + } + + uint Magic = Context.Memory.ReadUInt32(NroHeapAddress + 0x10); + uint NroFileSize = Context.Memory.ReadUInt32(NroHeapAddress + 0x18); + + if (Magic != NroMagic || NroSize != NroFileSize) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); + } + + byte[] NroData = Context.Memory.ReadBytes(NroHeapAddress, NroSize); + byte[] NroHash = null; + + MemoryStream Stream = new MemoryStream(NroData); + + using (SHA256 Hasher = SHA256.Create()) + { + NroHash = Hasher.ComputeHash(Stream); + } + + if (!IsNroHashPresent(NroHash)) + { + return MakeError(ErrorModule.Loader, LoaderErr.NroHashNotPresent); + } + + if (IsNroLoaded(NroHash)) + { + return MakeError(ErrorModule.Loader, LoaderErr.NroAlreadyLoaded); + } + + Stream.Position = 0; + + Nro Executable = new Nro(Stream, "memory", NroHeapAddress, BssHeapAddress); + + // check if everything is page align. + if ((Executable.Text.Length & 0xFFF) != 0 || (Executable.RO.Length & 0xFFF) != 0 + || (Executable.Data.Length & 0xFFF) != 0 || (Executable.BssSize & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); + } + + // check if everything is contiguous. + if (Executable.ROOffset != Executable.TextOffset + Executable.Text.Length + || Executable.DataOffset != Executable.ROOffset + Executable.RO.Length + || NroFileSize != Executable.DataOffset + Executable.Data.Length) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); + } + + // finally check the bss size match. + if (Executable.BssSize != BssSize) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); + } + + Res = new NroInfo(Executable, NroHash, Executable.Text.Length + Executable.RO.Length + Executable.Data.Length + Executable.BssSize); + + return 0; + } + + private long MapNro(ServiceCtx Context, NroInfo Info, out long NroMappedAddress) + { + NroMappedAddress = 0; + long TargetAddress = Context.Process.MemoryManager.AddrSpaceStart; + + long HeapRegionStart = Context.Process.MemoryManager.HeapRegionStart; + long HeapRegionEnd = Context.Process.MemoryManager.HeapRegionEnd; + + long MapRegionStart = Context.Process.MemoryManager.MapRegionStart; + long MapRegionEnd = Context.Process.MemoryManager.MapRegionEnd; + + while (true) + { + if (TargetAddress + Info.TotalSize >= Context.Process.MemoryManager.AddrSpaceEnd) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidMemoryState); + } + + bool IsValidAddress = !(HeapRegionStart > 0 && HeapRegionStart <= TargetAddress + Info.TotalSize - 1 + && TargetAddress <= HeapRegionEnd - 1) + && !(MapRegionStart > 0 + && MapRegionStart <= TargetAddress + Info.TotalSize - 1 + && TargetAddress <= MapRegionEnd - 1); + + if (IsValidAddress && Context.Process.MemoryManager.HleIsUnmapped(TargetAddress, Info.TotalSize)) + { + break; + } + + TargetAddress += 0x1000; + } + + Context.Process.LoadProgram(Info.Executable, TargetAddress); + + Info.NroMappedAddress = TargetAddress; + NroMappedAddress = TargetAddress; + + return 0; + } + + private long RemoveNrrInfo(long NrrAddress) + { + foreach (NrrInfo Info in NrrInfos) + { + if (Info.NrrAddress == NrrAddress) + { + NrrInfos.Remove(Info); + + return 0; + } + } + + return MakeError(ErrorModule.Loader, LoaderErr.BadNrrAddress); + } + + private long RemoveNroInfo(ServiceCtx Context, long NroMappedAddress, long NroHeapAddress) + { + foreach (NroInfo Info in NroInfos) + { + if (Info.NroMappedAddress == NroMappedAddress && Info.Executable.SourceAddress == NroHeapAddress) + { + NroInfos.Remove(Info); + + Context.Process.RemoveProgram(Info.NroMappedAddress); + + long Result = Context.Process.MemoryManager.UnmapProcessCodeMemory(Info.NroMappedAddress, Info.Executable.SourceAddress, Info.TotalSize - Info.Executable.BssSize); + + if (Result == 0 && Info.Executable.BssSize != 0) + { + Result = Context.Process.MemoryManager.UnmapProcessCodeMemory(Info.NroMappedAddress + Info.TotalSize - Info.Executable.BssSize, Info.Executable.BssAddress, Info.Executable.BssSize); + } + + return Result; + } + } + + return MakeError(ErrorModule.Loader, LoaderErr.BadNroAddress); + } + + // LoadNro(u64, u64, u64, u64, u64, pid) -> u64 + public long LoadNro(ServiceCtx Context) + { + long Result = MakeError(ErrorModule.Loader, LoaderErr.BadInitialization); + + // Zero + Context.RequestData.ReadUInt64(); + + long NroHeapAddress = Context.RequestData.ReadInt64(); + long NroSize = Context.RequestData.ReadInt64(); + long BssHeapAddress = Context.RequestData.ReadInt64(); + long BssSize = Context.RequestData.ReadInt64(); + + long NroMappedAddress = 0; + + if (IsInitialized) + { + NroInfo Info; + + Result = ParseNro(out Info, Context, NroHeapAddress, NroSize, BssHeapAddress, BssSize); + + if (Result == 0) + { + Result = MapNro(Context, Info, out NroMappedAddress); + + if (Result == 0) + { + NroInfos.Add(Info); + } + } + } + + Context.ResponseData.Write(NroMappedAddress); + + return Result; + } + + // UnloadNro(u64, u64, pid) + public long UnloadNro(ServiceCtx Context) + { + long Result = MakeError(ErrorModule.Loader, LoaderErr.BadInitialization); + + long NroMappedAddress = Context.RequestData.ReadInt64(); + long NroHeapAddress = Context.RequestData.ReadInt64(); + + if (IsInitialized) + { + if ((NroMappedAddress & 0xFFF) != 0 || (NroHeapAddress & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); + } + + Result = RemoveNroInfo(Context, NroMappedAddress, NroHeapAddress); + } + + return Result; + } + + // LoadNrr(u64, u64, u64, pid) + public long LoadNrr(ServiceCtx Context) + { + long Result = MakeError(ErrorModule.Loader, LoaderErr.BadInitialization); + + // Zero + Context.RequestData.ReadUInt64(); + + long NrrAddress = Context.RequestData.ReadInt64(); + long NrrSize = Context.RequestData.ReadInt64(); + + if (IsInitialized) + { + NrrInfo Info; + Result = ParseNrr(out Info, Context, NrrAddress, NrrSize); + + if(Result == 0) + { + if (NrrInfos.Count >= MaxNrr) + { + Result = MakeError(ErrorModule.Loader, LoaderErr.MaxNrr); + } + else + { + NrrInfos.Add(Info); + } + } + } + + return Result; + } + + // UnloadNrr(u64, u64, pid) + public long UnloadNrr(ServiceCtx Context) + { + long Result = MakeError(ErrorModule.Loader, LoaderErr.BadInitialization); + + // Zero + Context.RequestData.ReadUInt64(); + + long NrrHeapAddress = Context.RequestData.ReadInt64(); + + if (IsInitialized) + { + if ((NrrHeapAddress & 0xFFF) != 0) + { + return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); + } + + Result = RemoveNrrInfo(NrrHeapAddress); + } + + return Result; + } + + // Initialize(u64, pid, KObject) + public long Initialize(ServiceCtx Context) + { + // TODO: we actually ignore the pid and process handle receive, we will need to use them when we will have multi process support. + IsInitialized = true; + + return 0; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Ldr/LoaderErr.cs b/Ryujinx.HLE/HOS/Services/Ldr/LoaderErr.cs new file mode 100644 index 0000000000..ba77a5cc13 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldr/LoaderErr.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Ldr +{ + static class LoaderErr + { + public const int InvalidMemoryState = 51; + public const int InvalidNro = 52; + public const int InvalidNrr = 53; + public const int MaxNro = 55; + public const int MaxNrr = 56; + public const int NroAlreadyLoaded = 57; + public const int NroHashNotPresent = 54; + public const int UnalignedAddress = 81; + public const int BadSize = 82; + public const int BadNroAddress = 84; + public const int BadNrrAddress = 85; + public const int BadInitialization = 87; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs index 5d99970990..8a630ce515 100644 --- a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; using System.IO; using System.Text; @@ -88,11 +88,11 @@ namespace Ryujinx.HLE.HOS.Services.Lm switch((LmLogLevel)Level) { - case LmLogLevel.Trace: Context.Device.Log.PrintDebug (LogClass.ServiceLm, Text); break; - case LmLogLevel.Info: Context.Device.Log.PrintInfo (LogClass.ServiceLm, Text); break; - case LmLogLevel.Warning: Context.Device.Log.PrintWarning(LogClass.ServiceLm, Text); break; - case LmLogLevel.Error: Context.Device.Log.PrintError (LogClass.ServiceLm, Text); break; - case LmLogLevel.Critical: Context.Device.Log.PrintError (LogClass.ServiceLm, Text); break; + case LmLogLevel.Trace: Logger.PrintDebug (LogClass.ServiceLm, Text); break; + case LmLogLevel.Info: Logger.PrintInfo (LogClass.ServiceLm, Text); break; + case LmLogLevel.Warning: Logger.PrintWarning(LogClass.ServiceLm, Text); break; + case LmLogLevel.Error: Logger.PrintError (LogClass.ServiceLm, Text); break; + case LmLogLevel.Critical: Logger.PrintError (LogClass.ServiceLm, Text); break; } } diff --git a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs index e65ef08679..88cd57cff7 100644 --- a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs +++ b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Mm @@ -14,22 +14,35 @@ namespace Ryujinx.HLE.HOS.Services.Mm { m_Commands = new Dictionary() { - { 4, Initialize }, - { 6, SetAndWait }, - { 7, Get } + { 1, InitializeOld }, + { 4, Initialize }, + { 6, SetAndWait }, + { 7, Get } }; } + // InitializeOld(u32, u32, u32) + public long InitializeOld(ServiceCtx Context) + { + int Unknown0 = Context.RequestData.ReadInt32(); + int Unknown1 = Context.RequestData.ReadInt32(); + int Unknown2 = Context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, "Stubbed."); + + return 0; + } + public long Initialize(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceMm, "Stubbed."); return 0; } public long SetAndWait(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceMm, "Stubbed."); return 0; } @@ -38,7 +51,7 @@ namespace Ryujinx.HLE.HOS.Services.Mm { Context.ResponseData.Write(0); - Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceMm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs index 33f739677f..f0dc825b08 100644 --- a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs +++ b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs @@ -1,7 +1,8 @@ -using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Nfp @@ -44,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfp public long Initialize(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); State = State.Initialized; @@ -53,9 +54,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfp public long AttachActivateEvent(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); - int Handle = Context.Process.HandleTable.OpenHandle(ActivateEvent); + if (Context.Process.HandleTable.GenerateHandle(ActivateEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);; @@ -64,9 +68,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfp public long AttachDeactivateEvent(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); - int Handle = Context.Process.HandleTable.OpenHandle(DeactivateEvent); + if (Context.Process.HandleTable.GenerateHandle(DeactivateEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); @@ -77,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfp { Context.ResponseData.Write((int)State); - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); return 0; } @@ -86,7 +93,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfp { Context.ResponseData.Write((int)DeviceState); - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); return 0; } @@ -95,16 +102,19 @@ namespace Ryujinx.HLE.HOS.Services.Nfp { Context.ResponseData.Write((int)NpadId); - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); return 0; } public long AttachAvailabilityChangeEvent(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNfp, "Stubbed."); - int Handle = Context.Process.HandleTable.OpenHandle(AvailabilityChangeEvent); + if (Context.Process.HandleTable.GenerateHandle(AvailabilityChangeEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs index 6adbf00a15..bc23ea9100 100644 --- a/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs +++ b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm MakeObject(Context, new IRequest(Context.Device.System)); - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } @@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm Context.ResponseData.Write(BitConverter.ToUInt32(Address.GetAddressBytes())); - Context.Device.Log.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is \"{Address}\"."); + Logger.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is \"{Address}\"."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs index 3f4df719cf..9832786040 100644 --- a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs +++ b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs @@ -1,6 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Logging; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Nifm @@ -34,22 +35,29 @@ namespace Ryujinx.HLE.HOS.Services.Nifm { Context.ResponseData.Write(1); - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } public long GetResult(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } public long GetSystemEventReadableHandles(ServiceCtx Context) { - int Handle0 = Context.Process.HandleTable.OpenHandle(Event0); - int Handle1 = Context.Process.HandleTable.OpenHandle(Event1); + if (Context.Process.HandleTable.GenerateHandle(Event0.ReadableEvent, out int Handle0) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + if (Context.Process.HandleTable.GenerateHandle(Event1.ReadableEvent, out int Handle1) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle0, Handle1); @@ -58,21 +66,21 @@ namespace Ryujinx.HLE.HOS.Services.Nifm public long Cancel(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } public long Submit(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } public long SetConnectionConfirmationOption(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNifm, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs index b8455d4144..82fce6b983 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Ns @@ -23,14 +23,14 @@ namespace Ryujinx.HLE.HOS.Services.Ns { Context.ResponseData.Write(0); - Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNs, "Stubbed."); return 0; } public static long ListAddOnContent(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNs, "Stubbed."); //TODO: This is supposed to write a u32 array aswell. //It's unknown what it contains. diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 7d5589920f..4f4c8add17 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -1,4 +1,5 @@ using ChocolArm64.Memory; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; @@ -6,7 +7,6 @@ using Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu; using Ryujinx.HLE.HOS.Services.Nv.NvHostChannel; using Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl; using Ryujinx.HLE.HOS.Services.Nv.NvMap; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; @@ -123,7 +123,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv int EventId = Context.RequestData.ReadInt32(); //TODO: Use Fd/EventId, different channels have different events. - int Handle = Context.Process.HandleTable.OpenHandle(Event); + if (Context.Process.HandleTable.GenerateHandle(Event.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); @@ -143,7 +146,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv public long FinishInitialize(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return 0; } @@ -177,14 +180,14 @@ namespace Ryujinx.HLE.HOS.Services.Nv { if (CmdIn(Cmd) && Context.Request.GetBufferType0x21().Position == 0) { - Context.Device.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!"); + Logger.PrintError(LogClass.ServiceNv, "Input buffer is null!"); return NvResult.InvalidInput; } if (CmdOut(Cmd) && Context.Request.GetBufferType0x22().Position == 0) { - Context.Device.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!"); + Logger.PrintError(LogClass.ServiceNv, "Output buffer is null!"); return NvResult.InvalidInput; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs index 95eb5b9860..0ccc1949b7 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; using Ryujinx.HLE.HOS.Services.Nv.NvMap; -using Ryujinx.HLE.Logging; using System; using System.Collections.Concurrent; @@ -42,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS { Args.Offset = 0; - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {Size:x16}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {Size:x16}!"); Result = NvResult.OutOfMemory; } @@ -115,7 +115,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS } else { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, + Logger.PrintWarning(LogClass.ServiceNv, $"Failed to free offset 0x{Args.Offset:x16} size 0x{Size:x16}!"); Result = NvResult.InvalidInput; @@ -145,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS } else { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); } } @@ -167,7 +167,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); return NvResult.InvalidInput; } @@ -188,7 +188,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS { string Msg = string.Format(MapErrorMsg, VA, Args.MappingSize); - Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg); + Logger.PrintWarning(LogClass.ServiceNv, Msg); return NvResult.InvalidInput; } @@ -197,7 +197,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS } else { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Address 0x{Args.Offset:x16} not mapped!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Address 0x{Args.Offset:x16} not mapped!"); return NvResult.InvalidInput; } @@ -231,7 +231,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS { string Msg = string.Format(MapErrorMsg, Args.Offset, Size); - Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg); + Logger.PrintWarning(LogClass.ServiceNv, Msg); Result = NvResult.InvalidInput; } @@ -245,7 +245,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS { Args.Offset = 0; - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{Size:x16}!"); Result = NvResult.InvalidInput; } @@ -265,7 +265,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -275,7 +275,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -296,7 +296,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); return NvResult.InvalidInput; } @@ -306,7 +306,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS if (Result < 0) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, + Logger.PrintWarning(LogClass.ServiceNv, $"Page 0x{Args.Offset:x16} size 0x{Args.Pages:x16} not allocated!"); return NvResult.InvalidInput; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs index 5ae45c1e9d..387fe5b416 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs @@ -1,5 +1,5 @@ using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; using System; using System.Diagnostics; @@ -46,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -70,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -80,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -163,7 +163,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs index f4ed48217b..d2dd089da5 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; -using Ryujinx.HLE.Logging; using System; using System.Collections.Concurrent; @@ -57,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -67,7 +67,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -110,7 +110,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -120,7 +120,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -130,7 +130,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -140,7 +140,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -150,7 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs index 2bfe88821b..0b70928ea2 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs @@ -1,5 +1,5 @@ using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; using System; using System.Collections.Concurrent; using System.Text; @@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl { if (StringValue.Length > 0x100) { - Context.Device.Log.PrintError(Logging.LogClass.ServiceNv, $"{Domain}!{Name} String value size is too big!"); + Logger.PrintError(LogClass.ServiceNv, $"{Domain}!{Name} String value size is too big!"); } else { @@ -118,7 +118,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl Context.Memory.WriteBytes(OutputPosition + 0x82, SettingBuffer); - Context.Device.Log.PrintDebug(Logging.LogClass.ServiceNv, $"Got setting {Domain}!{Name}"); + Logger.PrintDebug(LogClass.ServiceNv, $"Got setting {Domain}!{Name}"); } return NvResult.Success; @@ -144,7 +144,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl int EventId = Context.Memory.ReadInt32(InputPosition); - Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + Logger.PrintStub(LogClass.ServiceNv, "Stubbed."); return NvResult.Success; } @@ -201,7 +201,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl } else { - Context.Device.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); + Logger.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); using (ManualResetEvent WaitEvent = new ManualResetEvent(false)) { @@ -232,7 +232,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl } } - Context.Device.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); + Logger.PrintDebug(LogClass.ServiceNv, "Resuming..."); } if (Extended) diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs index 38da2889eb..7953d66542 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs @@ -1,6 +1,6 @@ using ChocolArm64.Memory; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; using System.Collections.Concurrent; @@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap case 0x010e: return GetId (Context); } - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!"); return NvResult.NotSupported; } @@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Args.Size == 0) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!"); return NvResult.InvalidInput; } @@ -52,7 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap Args.Handle = AddNvMap(Context, new NvMapHandle(Size)); - Context.Device.Log.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!"); + Logger.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!"); AMemoryHelper.Write(Context.Memory, OutputPosition, Args); @@ -70,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); return NvResult.InvalidInput; } @@ -95,14 +95,14 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); return NvResult.InvalidInput; } if ((Args.Align & (Args.Align - 1)) != 0) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!"); return NvResult.InvalidInput; } @@ -159,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); return NvResult.InvalidInput; } @@ -168,7 +168,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap { DeleteNvMap(Context, Args.Handle); - Context.Device.Log.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!"); + Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!"); Args.Address = Map.Address; Args.Flags = 0; @@ -197,7 +197,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); return NvResult.InvalidInput; } @@ -231,7 +231,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap if (Map == null) { - Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); return NvResult.InvalidInput; } diff --git a/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs index d484d312f3..3e9d276ae2 100644 --- a/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs +++ b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Pctl @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl } else { - Context.Device.Log.PrintWarning(LogClass.ServicePctl, "Service is already initialized!"); + Logger.PrintWarning(LogClass.ServicePctl, "Service is already initialized!"); } return 0; diff --git a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs index 92821217ba..d73adc0efd 100644 --- a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs @@ -1,5 +1,7 @@ using Ryujinx.HLE.HOS.Font; using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Pl @@ -65,7 +67,10 @@ namespace Ryujinx.HLE.HOS.Services.Pl { Context.Device.System.Font.EnsureInitialized(); - int Handle = Context.Process.HandleTable.OpenHandle(Context.Device.System.FontSharedMem); + if (Context.Process.HandleTable.GenerateHandle(Context.Device.System.FontSharedMem, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs index fa39854d1d..cc96a5c920 100644 --- a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs +++ b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Prepo @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Prepo public static long SaveReportWithUser(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServicePrepo, "Stubbed."); + Logger.PrintStub(LogClass.ServicePrepo, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index 5e1e780aac..f701dd0537 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -6,6 +6,8 @@ using Ryujinx.HLE.HOS.Services.Bsd; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.FspSrv; using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Irs; +using Ryujinx.HLE.HOS.Services.Ldr; using Ryujinx.HLE.HOS.Services.Lm; using Ryujinx.HLE.HOS.Services.Mm; using Ryujinx.HLE.HOS.Services.Nfp; @@ -96,6 +98,12 @@ namespace Ryujinx.HLE.HOS.Services case "hid": return new IHidServer(System); + case "irs": + return new IIrSensorServer(); + + case "ldr:ro": + return new IRoInterface(); + case "lm": return new ILogService(); diff --git a/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs index dc1469674c..070a4d5e4d 100644 --- a/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs +++ b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; using System; @@ -115,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.Set { if (StringValue.Length + 1 > ReplySize) { - Context.Device.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} String value size is too big!"); + Logger.PrintError(LogClass.ServiceSet, $"{AskedSetting} String value size is too big!"); } else { @@ -138,11 +139,11 @@ namespace Ryujinx.HLE.HOS.Services.Set Context.Memory.WriteBytes(ReplyPos, SettingBuffer); - Context.Device.Log.PrintDebug(Logging.LogClass.ServiceSet, $"{AskedSetting} set value: {NxSetting} as {NxSetting.GetType()}"); + Logger.PrintDebug(LogClass.ServiceSet, $"{AskedSetting} set value: {NxSetting} as {NxSetting.GetType()}"); } else { - Context.Device.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} not found!"); + Logger.PrintError(LogClass.ServiceSet, $"{AskedSetting} not found!"); } return 0; diff --git a/Ryujinx.HLE/HOS/Services/Sfdnsres/GaiError.cs b/Ryujinx.HLE/HOS/Services/Sfdnsres/GaiError.cs new file mode 100644 index 0000000000..65d5457707 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sfdnsres/GaiError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sfdnsres +{ + enum GaiError + { + Success, + AddressFamily, + Again, + BadFlags, + Fail, + Family, + Memory, + NoData, + NoName, + Service, + SocketType, + System, + BadHints, + Protocol, + Overflow, + Max, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs index 26dbedf441..a351e3deca 100644 --- a/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs +++ b/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs @@ -1,5 +1,12 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; +using System; using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +using static Ryujinx.HLE.HOS.ErrorCode; namespace Ryujinx.HLE.HOS.Services.Sfdnsres { @@ -13,8 +20,380 @@ namespace Ryujinx.HLE.HOS.Services.Sfdnsres { m_Commands = new Dictionary() { - //... + { 0, SetDnsAddressesPrivate }, + { 1, GetDnsAddressesPrivate }, + { 2, GetHostByName }, + { 3, GetHostByAddress }, + { 4, GetHostStringError }, + { 5, GetGaiStringError }, + { 8, RequestCancelHandle }, + { 9, CancelSocketCall }, + { 11, ClearDnsAddresses }, }; } + + private long SerializeHostEnt(ServiceCtx Context, IPHostEntry HostEntry, List Addresses = null) + { + long OriginalBufferPosition = Context.Request.ReceiveBuff[0].Position; + long BufferPosition = OriginalBufferPosition; + long BufferSize = Context.Request.ReceiveBuff[0].Size; + + string HostName = HostEntry.HostName + '\0'; + + // h_name + Context.Memory.WriteBytes(BufferPosition, Encoding.ASCII.GetBytes(HostName)); + BufferPosition += HostName.Length; + + // h_aliases list size + Context.Memory.WriteInt32(BufferPosition, IPAddress.HostToNetworkOrder(HostEntry.Aliases.Length)); + BufferPosition += 4; + + // Actual aliases + foreach (string Alias in HostEntry.Aliases) + { + Context.Memory.WriteBytes(BufferPosition, Encoding.ASCII.GetBytes(Alias + '\0')); + BufferPosition += Alias.Length + 1; + } + + // h_addrtype but it's a short (also only support IPv4) + Context.Memory.WriteInt16(BufferPosition, IPAddress.HostToNetworkOrder((short)2)); + BufferPosition += 2; + + // h_length but it's a short + Context.Memory.WriteInt16(BufferPosition, IPAddress.HostToNetworkOrder((short)4)); + BufferPosition += 2; + + // Ip address count, we can only support ipv4 (blame Nintendo) + Context.Memory.WriteInt32(BufferPosition, Addresses != null ? IPAddress.HostToNetworkOrder(Addresses.Count) : 0); + BufferPosition += 4; + + if (Addresses != null) + { + foreach (IPAddress Ip in Addresses) + { + Context.Memory.WriteInt32(BufferPosition, IPAddress.HostToNetworkOrder(BitConverter.ToInt32(Ip.GetAddressBytes(), 0))); + BufferPosition += 4; + } + } + + return BufferPosition - OriginalBufferPosition; + } + + private string GetGaiStringErrorFromErrorCode(GaiError ErrorCode) + { + if (ErrorCode > GaiError.Max) + { + ErrorCode = GaiError.Max; + } + + switch (ErrorCode) + { + case GaiError.AddressFamily: + return "Address family for hostname not supported"; + case GaiError.Again: + return "Temporary failure in name resolution"; + case GaiError.BadFlags: + return "Invalid value for ai_flags"; + case GaiError.Fail: + return "Non-recoverable failure in name resolution"; + case GaiError.Family: + return "ai_family not supported"; + case GaiError.Memory: + return "Memory allocation failure"; + case GaiError.NoData: + return "No address associated with hostname"; + case GaiError.NoName: + return "hostname nor servname provided, or not known"; + case GaiError.Service: + return "servname not supported for ai_socktype"; + case GaiError.SocketType: + return "ai_socktype not supported"; + case GaiError.System: + return "System error returned in errno"; + case GaiError.BadHints: + return "Invalid value for hints"; + case GaiError.Protocol: + return "Resolved protocol is unknown"; + case GaiError.Overflow: + return "Argument buffer overflow"; + case GaiError.Max: + return "Unknown error"; + default: + return "Success"; + } + } + + private string GetHostStringErrorFromErrorCode(NetDBError ErrorCode) + { + if (ErrorCode <= NetDBError.Internal) + { + return "Resolver internal error"; + } + + switch (ErrorCode) + { + case NetDBError.Success: + return "Resolver Error 0 (no error)"; + case NetDBError.HostNotFound: + return "Unknown host"; + case NetDBError.TryAgain: + return "Host name lookup failure"; + case NetDBError.NoRecovery: + return "Unknown server error"; + case NetDBError.NoData: + return "No address associated with name"; + default: + return "Unknown resolver error"; + } + } + + private List GetIPV4Addresses(IPHostEntry HostEntry) + { + List Result = new List(); + foreach (IPAddress Ip in HostEntry.AddressList) + { + if (Ip.AddressFamily == AddressFamily.InterNetwork) + Result.Add(Ip); + } + return Result; + } + + // SetDnsAddressesPrivate(u32, buffer) + public long SetDnsAddressesPrivate(ServiceCtx Context) + { + uint Unknown0 = Context.RequestData.ReadUInt32(); + long BufferPosition = Context.Request.SendBuff[0].Position; + long BufferSize = Context.Request.SendBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, $"Stubbed. Unknown0: {Unknown0}"); + + return MakeError(ErrorModule.Os, 1023); + } + + // GetDnsAddressPrivate(u32) -> buffer + public long GetDnsAddressesPrivate(ServiceCtx Context) + { + uint Unknown0 = Context.RequestData.ReadUInt32(); + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, $"Stubbed. Unknown0: {Unknown0}"); + + return MakeError(ErrorModule.Os, 1023); + } + + // GetHostByName(u8, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public long GetHostByName(ServiceCtx Context) + { + byte[] RawName = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, Context.Request.SendBuff[0].Size); + string Name = Encoding.ASCII.GetString(RawName).TrimEnd('\0'); + + // TODO: use params + bool EnableNsdResolve = Context.RequestData.ReadInt32() == 1; + int TimeOut = Context.RequestData.ReadInt32(); + ulong PidPlaceholder = Context.RequestData.ReadUInt64(); + + IPHostEntry HostEntry = null; + + NetDBError NetDBErrorCode = NetDBError.Success; + GaiError Errno = GaiError.Overflow; + long SerializedSize = 0; + + if (Name.Length <= 255) + { + try + { + HostEntry = Dns.GetHostEntry(Name); + } + catch (SocketException Exception) + { + NetDBErrorCode = NetDBError.Internal; + + if (Exception.ErrorCode == 11001) + { + NetDBErrorCode = NetDBError.HostNotFound; + Errno = GaiError.NoData; + } + else if (Exception.ErrorCode == 11002) + { + NetDBErrorCode = NetDBError.TryAgain; + } + else if (Exception.ErrorCode == 11003) + { + NetDBErrorCode = NetDBError.NoRecovery; + } + else if (Exception.ErrorCode == 11004) + { + NetDBErrorCode = NetDBError.NoData; + } + else if (Exception.ErrorCode == 10060) + { + Errno = GaiError.Again; + } + } + } + else + { + NetDBErrorCode = NetDBError.HostNotFound; + } + + if (HostEntry != null) + { + Errno = GaiError.Success; + + List Addresses = GetIPV4Addresses(HostEntry); + + if (Addresses.Count == 0) + { + Errno = GaiError.NoData; + NetDBErrorCode = NetDBError.NoAddress; + } + else + { + SerializedSize = SerializeHostEnt(Context, HostEntry, Addresses); + } + } + + Context.ResponseData.Write((int)NetDBErrorCode); + Context.ResponseData.Write((int)Errno); + Context.ResponseData.Write(SerializedSize); + + return 0; + } + + // GetHostByAddr(u32, u32, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public long GetHostByAddress(ServiceCtx Context) + { + byte[] RawIp = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, Context.Request.SendBuff[0].Size); + + // TODO: use params + uint SocketLength = Context.RequestData.ReadUInt32(); + uint Type = Context.RequestData.ReadUInt32(); + int TimeOut = Context.RequestData.ReadInt32(); + ulong PidPlaceholder = Context.RequestData.ReadUInt64(); + + IPHostEntry HostEntry = null; + + NetDBError NetDBErrorCode = NetDBError.Success; + GaiError Errno = GaiError.AddressFamily; + long SerializedSize = 0; + + if (RawIp.Length == 4) + { + try + { + IPAddress Address = new IPAddress(RawIp); + + HostEntry = Dns.GetHostEntry(Address); + } + catch (SocketException Exception) + { + NetDBErrorCode = NetDBError.Internal; + if (Exception.ErrorCode == 11001) + { + NetDBErrorCode = NetDBError.HostNotFound; + Errno = GaiError.NoData; + } + else if (Exception.ErrorCode == 11002) + { + NetDBErrorCode = NetDBError.TryAgain; + } + else if (Exception.ErrorCode == 11003) + { + NetDBErrorCode = NetDBError.NoRecovery; + } + else if (Exception.ErrorCode == 11004) + { + NetDBErrorCode = NetDBError.NoData; + } + else if (Exception.ErrorCode == 10060) + { + Errno = GaiError.Again; + } + } + } + else + { + NetDBErrorCode = NetDBError.NoAddress; + } + + if (HostEntry != null) + { + Errno = GaiError.Success; + SerializedSize = SerializeHostEnt(Context, HostEntry, GetIPV4Addresses(HostEntry)); + } + + Context.ResponseData.Write((int)NetDBErrorCode); + Context.ResponseData.Write((int)Errno); + Context.ResponseData.Write(SerializedSize); + + return 0; + } + + // GetHostStringError(u32) -> buffer + public long GetHostStringError(ServiceCtx Context) + { + long ResultCode = MakeError(ErrorModule.Os, 1023); + NetDBError ErrorCode = (NetDBError)Context.RequestData.ReadInt32(); + string ErrorString = GetHostStringErrorFromErrorCode(ErrorCode); + + if (ErrorString.Length + 1 <= Context.Request.ReceiveBuff[0].Size) + { + ResultCode = 0; + Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(ErrorString + '\0')); + } + + return ResultCode; + } + + // GetGaiStringError(u32) -> buffer + public long GetGaiStringError(ServiceCtx Context) + { + long ResultCode = MakeError(ErrorModule.Os, 1023); + GaiError ErrorCode = (GaiError)Context.RequestData.ReadInt32(); + string ErrorString = GetGaiStringErrorFromErrorCode(ErrorCode); + + if (ErrorString.Length + 1 <= Context.Request.ReceiveBuff[0].Size) + { + ResultCode = 0; + Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(ErrorString + '\0')); + } + + return ResultCode; + } + + // RequestCancelHandle(u64, pid) -> u32 + public long RequestCancelHandle(ServiceCtx Context) + { + ulong Unknown0 = Context.RequestData.ReadUInt64(); + + Context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceSfdnsres, $"Stubbed. Unknown0: {Unknown0}"); + + return 0; + } + + // CancelSocketCall(u32, u64, pid) + public long CancelSocketCall(ServiceCtx Context) + { + uint Unknown0 = Context.RequestData.ReadUInt32(); + ulong Unknown1 = Context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, $"Stubbed. Unknown0: {Unknown0} - " + + $"Unknown1: {Unknown1}"); + + return 0; + } + + // ClearDnsAddresses(u32) + public long ClearDnsAddresses(ServiceCtx Context) + { + uint Unknown0 = Context.RequestData.ReadUInt32(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, $"Stubbed. Unknown0: {Unknown0}"); + + return 0; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Sfdnsres/NetDBError.cs b/Ryujinx.HLE/HOS/Services/Sfdnsres/NetDBError.cs new file mode 100644 index 0000000000..6c1b7825d2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sfdnsres/NetDBError.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sfdnsres +{ + enum NetDBError + { + Internal = -1, + Success, + HostNotFound, + TryAgain, + NoRecovery, + NoData, + NoAddress = NoData, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index c56d65dbc0..0c26b7d9dd 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; +using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Sm @@ -59,7 +60,10 @@ namespace Ryujinx.HLE.HOS.Services.Sm KSession Session = new KSession(ServiceFactory.MakeService(Context.Device.System, Name), Name); - int Handle = Context.Process.HandleTable.OpenHandle(Session); + if (Context.Process.HandleTable.GenerateHandle(Session, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Ssl/ISslContext.cs b/Ryujinx.HLE/HOS/Services/Ssl/ISslContext.cs new file mode 100644 index 0000000000..8f3a0649b2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ssl/ISslContext.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + class ISslContext : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public ISslContext() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs index b59527f707..3046aab7a3 100644 --- a/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs +++ b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Ssl @@ -14,15 +14,30 @@ namespace Ryujinx.HLE.HOS.Services.Ssl { m_Commands = new Dictionary() { + { 0, CreateContext }, { 5, SetInterfaceVersion } }; } + // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object + public long CreateContext(ServiceCtx Context) + { + int SslVersion = Context.RequestData.ReadInt32(); + long Unknown = Context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceSsl, $"Stubbed. SslVersion: {SslVersion} - Unknown: {Unknown}"); + + MakeObject(Context, new ISslContext()); + + return 0; + } + + // SetInterfaceVersion(u32) public long SetInterfaceVersion(ServiceCtx Context) { int Version = Context.RequestData.ReadInt32(); - Context.Device.Log.PrintStub(LogClass.ServiceSsl, "Stubbed."); + Logger.PrintStub(LogClass.ServiceSsl, $"Stubbed. Version: {Version}"); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs index 6df28659d1..0e321e44c2 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; using System.Text; @@ -110,7 +110,7 @@ namespace Ryujinx.HLE.HOS.Services.Time if (BufferSize != 0x4000) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); + Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); } long ResultCode = 0; @@ -132,7 +132,7 @@ namespace Ryujinx.HLE.HOS.Services.Time } catch (TimeZoneNotFoundException) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); + Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); ResultCode = MakeError(ErrorModule.Time, 0x3dd); } @@ -170,7 +170,7 @@ namespace Ryujinx.HLE.HOS.Services.Time if (BufferSize != 0x4000) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); + Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); } // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware. @@ -189,7 +189,7 @@ namespace Ryujinx.HLE.HOS.Services.Time } catch (TimeZoneNotFoundException) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); + Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); ResultCode = MakeError(ErrorModule.Time, 0x3dd); } @@ -220,7 +220,7 @@ namespace Ryujinx.HLE.HOS.Services.Time if (BufferSize != 0x4000) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); + Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); } // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware. @@ -239,7 +239,7 @@ namespace Ryujinx.HLE.HOS.Services.Time } catch (TimeZoneNotFoundException) { - Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); + Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); ResultCode = MakeError(ErrorModule.Time, 0x3dd); } diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs index 5423827932..33a1dee974 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs @@ -1,6 +1,8 @@ using ChocolArm64.Memory; using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; using System.Collections.Generic; +using System; using System.IO; using System.Text; @@ -178,7 +180,10 @@ namespace Ryujinx.HLE.HOS.Services.Vi { string Name = GetDisplayName(Context); - int Handle = Context.Process.HandleTable.OpenHandle(Context.Device.System.VsyncEvent); + if (Context.Process.HandleTable.GenerateHandle(Context.Device.System.VsyncEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs index d47fc30a7d..09a37b0f7b 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs @@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi BinderEvent = new KEvent(System); - BinderEvent.Signal(); + BinderEvent.ReadableEvent.Signal(); Flinger = new NvFlinger(Renderer, BinderEvent); } @@ -77,7 +77,10 @@ namespace Ryujinx.HLE.HOS.Services.Vi int Id = Context.RequestData.ReadInt32(); uint Unk = Context.RequestData.ReadUInt32(); - int Handle = Context.Process.HandleTable.OpenHandle(BinderEvent); + if (Context.Process.HandleTable.GenerateHandle(BinderEvent.ReadableEvent, out int Handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); diff --git a/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs index 61f0ffaa51..a78f1812f0 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Vi @@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi public static long CreateManagedLayer(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); Context.ResponseData.Write(0L); //LayerId @@ -32,21 +32,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi public long DestroyManagedLayer(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); return 0; } public static long AddToLayerStack(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); return 0; } public static long SetLayerVisibility(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs index 5657ba69f3..4545579bef 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.Logging; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Vi @@ -22,14 +22,14 @@ namespace Ryujinx.HLE.HOS.Services.Vi public static long SetLayerZ(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); return 0; } public static long SetLayerVisibility(ServiceCtx Context) { - Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Logger.PrintStub(LogClass.ServiceVi, "Stubbed."); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs index dcdf5d1747..64e0b4a96d 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs @@ -1,9 +1,9 @@ +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Memory; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using Ryujinx.HLE.HOS.Services.Nv.NvMap; -using Ryujinx.HLE.Logging; using System; using System.Collections.Generic; using System.IO; @@ -114,7 +114,7 @@ namespace Ryujinx.HLE.HOS.Services.Android if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq)) { - Context.Device.Log.PrintDebug(LogClass.ServiceVi, $"{InterfaceName} {ProcReq.Method.Name}"); + Logger.PrintDebug(LogClass.ServiceVi, $"{InterfaceName} {ProcReq.Method.Name}"); return ProcReq(Context, Reader); } @@ -321,14 +321,14 @@ namespace Ryujinx.HLE.HOS.Services.Android FbWidth, FbHeight, 1, 16, GalMemoryLayout.BlockLinear, - GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm); + GalImageFormat.RGBA8 | GalImageFormat.Unorm); } Context.Device.Gpu.ResourceManager.ClearPbCache(); Context.Device.Gpu.ResourceManager.SendTexture(Vmm, FbAddr, Image); Renderer.RenderTarget.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom); - Renderer.RenderTarget.Set(FbAddr); + Renderer.RenderTarget.Present(FbAddr); ReleaseBuffer(Slot); }); @@ -338,7 +338,7 @@ namespace Ryujinx.HLE.HOS.Services.Android { BufferQueue[Slot].State = BufferState.Free; - BinderEvent.Signal(); + BinderEvent.ReadableEvent.Signal(); WaitBufferFree.Set(); } diff --git a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs index b537b06acf..274892c08a 100644 --- a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -32,14 +32,14 @@ namespace Ryujinx.HLE.HOS.SystemState { Messages.Enqueue(Message); - MessageEvent.Signal(); + MessageEvent.ReadableEvent.Signal(); } public bool TryDequeueMessage(out MessageInfo Message) { if (Messages.Count < 2) { - MessageEvent.Reset(); + MessageEvent.ReadableEvent.Clear(); } return Messages.TryDequeue(out Message); diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs index 2a3c8288b2..3833ce9ec9 100644 --- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.Utilities; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -57,9 +58,10 @@ namespace Ryujinx.HLE.HOS.SystemState Profiles = new ConcurrentDictionary(); - UserId DefaultUuid = new UserId("00000000000000000000000000000001"); + UInt128 DefaultUuid = new UInt128("00000000000000000000000000000001"); AddUser(DefaultUuid, "Player"); + OpenUser(DefaultUuid); } @@ -85,24 +87,24 @@ namespace Ryujinx.HLE.HOS.SystemState ActiveAudioOutput = AudioOutputs[2]; } - public void AddUser(UserId Uuid, string Name) + public void AddUser(UInt128 Uuid, string Name) { UserProfile Profile = new UserProfile(Uuid, Name); - Profiles.AddOrUpdate(Uuid.UserIdHex, Profile, (Key, Old) => Profile); + Profiles.AddOrUpdate(Uuid.ToString(), Profile, (Key, Old) => Profile); } - public void OpenUser(UserId Uuid) + public void OpenUser(UInt128 Uuid) { - if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile)) + if (Profiles.TryGetValue(Uuid.ToString(), out UserProfile Profile)) { (LastOpenUser = Profile).AccountState = OpenCloseState.Open; } } - public void CloseUser(UserId Uuid) + public void CloseUser(UInt128 Uuid) { - if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile)) + if (Profiles.TryGetValue(Uuid.ToString(), out UserProfile Profile)) { Profile.AccountState = OpenCloseState.Closed; } @@ -113,9 +115,9 @@ namespace Ryujinx.HLE.HOS.SystemState return Profiles.Count; } - internal bool TryGetUser(UserId Uuid, out UserProfile Profile) + internal bool TryGetUser(UInt128 Uuid, out UserProfile Profile) { - return Profiles.TryGetValue(Uuid.UserIdHex, out Profile); + return Profiles.TryGetValue(Uuid.ToString(), out Profile); } internal IEnumerable GetAllUsers() diff --git a/Ryujinx.HLE/HOS/SystemState/UserId.cs b/Ryujinx.HLE/HOS/SystemState/UserId.cs deleted file mode 100644 index 1e7c53dd00..0000000000 --- a/Ryujinx.HLE/HOS/SystemState/UserId.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Ryujinx.HLE.Utilities; -using System; -using System.IO; -using System.Linq; - -namespace Ryujinx.HLE.HOS.SystemState -{ - public struct UserId - { - public string UserIdHex { get; private set; } - - public byte[] Bytes { get; private set; } - - public UserId(long Low, long High) - { - if ((Low | High) == 0) - { - throw new ArgumentException("Zero is not a valid user id!"); - } - - byte[] Bytes = new byte[16]; - - int Index = Bytes.Length; - - void WriteBytes(long Value) - { - for (int Byte = 0; Byte < 8; Byte++) - { - Bytes[--Index] = (byte)(Value >> Byte * 8); - } - } - - WriteBytes(Low); - WriteBytes(High); - - UserIdHex = string.Empty; - - foreach (byte Byte in Bytes) - { - UserIdHex += Byte.ToString("X2"); - } - - this.Bytes = Bytes; - } - - public UserId(string UserIdHex) - { - if (UserIdHex == null || UserIdHex.Length != 32 || !UserIdHex.All("0123456789abcdefABCDEF".Contains)) - { - throw new ArgumentException("Invalid user id!", nameof(UserIdHex)); - } - - if (UserIdHex == "00000000000000000000000000000000") - { - throw new ArgumentException("Zero is not a valid user id!", nameof(UserIdHex)); - } - - this.UserIdHex = UserIdHex.ToUpper(); - - Bytes = StringUtils.HexToBytes(UserIdHex); - } - - internal void Write(BinaryWriter Writer) - { - for (int Index = Bytes.Length - 1; Index >= 0; Index--) - { - Writer.Write(Bytes[Index]); - } - } - - public override string ToString() - { - return UserIdHex; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/SystemState/UserProfile.cs b/Ryujinx.HLE/HOS/SystemState/UserProfile.cs index 63852cdf4f..e08bc48aa3 100644 --- a/Ryujinx.HLE/HOS/SystemState/UserProfile.cs +++ b/Ryujinx.HLE/HOS/SystemState/UserProfile.cs @@ -1,4 +1,5 @@ -using System; +using Ryujinx.HLE.Utilities; +using System; namespace Ryujinx.HLE.HOS.SystemState { @@ -6,7 +7,7 @@ namespace Ryujinx.HLE.HOS.SystemState { private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public UserId Uuid { get; private set; } + public UInt128 Uuid { get; private set; } public string Name { get; private set; } @@ -15,7 +16,7 @@ namespace Ryujinx.HLE.HOS.SystemState public OpenCloseState AccountState { get; set; } public OpenCloseState OnlinePlayState { get; set; } - public UserProfile(UserId Uuid, string Name) + public UserProfile(UInt128 Uuid, string Name) { this.Uuid = Uuid; this.Name = Name; diff --git a/Ryujinx.HLE/Loaders/Executable.cs b/Ryujinx.HLE/Loaders/Executable.cs index a9850e4af9..3c63af141b 100644 --- a/Ryujinx.HLE/Loaders/Executable.cs +++ b/Ryujinx.HLE/Loaders/Executable.cs @@ -49,21 +49,49 @@ namespace Ryujinx.HLE.Loaders long DataPosition = ImageBase + (uint)Exe.DataOffset; long TextSize = (uint)IntUtils.AlignUp(Exe.Text.Length, KMemoryManager.PageSize); - long ROSize = (uint)IntUtils.AlignUp(Exe.RO.Length, KMemoryManager.PageSize); + long ROSize = (uint)IntUtils.AlignUp(Exe.RO.Length, KMemoryManager.PageSize); long DataSize = (uint)IntUtils.AlignUp(Exe.Data.Length, KMemoryManager.PageSize); + long BssSize = (uint)IntUtils.AlignUp(Exe.BssSize, KMemoryManager.PageSize); - long DataAndBssSize = (uint)IntUtils.AlignUp(Exe.BssSize, KMemoryManager.PageSize) + DataSize; + long DataAndBssSize = BssSize + DataSize; ImageEnd = DataPosition + DataAndBssSize; - MemoryManager.HleMapProcessCode(TextPosition, TextSize + ROSize + DataAndBssSize); + if (Exe.SourceAddress == 0) + { + MemoryManager.HleMapProcessCode(TextPosition, TextSize + ROSize + DataAndBssSize); - MemoryManager.SetProcessMemoryPermission(ROPosition, ROSize, MemoryPermission.Read); - MemoryManager.SetProcessMemoryPermission(DataPosition, DataAndBssSize, MemoryPermission.ReadAndWrite); + MemoryManager.SetProcessMemoryPermission(ROPosition, ROSize, MemoryPermission.Read); + MemoryManager.SetProcessMemoryPermission(DataPosition, DataAndBssSize, MemoryPermission.ReadAndWrite); - Memory.WriteBytes(TextPosition, Exe.Text); - Memory.WriteBytes(ROPosition, Exe.RO); - Memory.WriteBytes(DataPosition, Exe.Data); + Memory.WriteBytes(TextPosition, Exe.Text); + Memory.WriteBytes(ROPosition, Exe.RO); + Memory.WriteBytes(DataPosition, Exe.Data); + } + else + { + long Result = MemoryManager.MapProcessCodeMemory(TextPosition, Exe.SourceAddress, TextSize + ROSize + DataSize); + + if (Result != 0) + { + throw new InvalidOperationException(); + } + + MemoryManager.SetProcessMemoryPermission(ROPosition, ROSize, MemoryPermission.Read); + MemoryManager.SetProcessMemoryPermission(DataPosition, DataSize, MemoryPermission.ReadAndWrite); + + if (Exe.BssAddress != 0 && Exe.BssSize != 0) + { + Result = MemoryManager.MapProcessCodeMemory(DataPosition + DataSize, Exe.BssAddress, BssSize); + + if (Result != 0) + { + throw new InvalidOperationException(); + } + + MemoryManager.SetProcessMemoryPermission(DataPosition + DataSize, BssSize, MemoryPermission.ReadAndWrite); + } + } if (Exe.Mod0Offset == 0) { diff --git a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs index 44bad61497..6f0952abdb 100644 --- a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs +++ b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs @@ -8,6 +8,9 @@ namespace Ryujinx.HLE.Loaders.Executables byte[] RO { get; } byte[] Data { get; } + long SourceAddress { get; } + long BssAddress { get; } + int Mod0Offset { get; } int TextOffset { get; } int ROOffset { get; } diff --git a/Ryujinx.HLE/Loaders/Executables/Nro.cs b/Ryujinx.HLE/Loaders/Executables/Nro.cs index 0b5068d7b9..6015da2132 100644 --- a/Ryujinx.HLE/Loaders/Executables/Nro.cs +++ b/Ryujinx.HLE/Loaders/Executables/Nro.cs @@ -16,9 +16,14 @@ namespace Ryujinx.HLE.Loaders.Executables public int DataOffset { get; private set; } public int BssSize { get; private set; } - public Nro(Stream Input, string FilePath) + public long SourceAddress { get; private set; } + public long BssAddress { get; private set; } + + public Nro(Stream Input, string FilePath, long SourceAddress = 0, long BssAddress = 0) { - this.FilePath = FilePath; + this.FilePath = FilePath; + this.SourceAddress = SourceAddress; + this.BssAddress = BssAddress; BinaryReader Reader = new BinaryReader(Input); diff --git a/Ryujinx.HLE/Loaders/Executables/Nso.cs b/Ryujinx.HLE/Loaders/Executables/Nso.cs index fef9c4b853..c7b48a5f35 100644 --- a/Ryujinx.HLE/Loaders/Executables/Nso.cs +++ b/Ryujinx.HLE/Loaders/Executables/Nso.cs @@ -18,6 +18,9 @@ namespace Ryujinx.HLE.Loaders.Executables public int DataOffset { get; private set; } public int BssSize { get; private set; } + public long SourceAddress { get; private set; } + public long BssAddress { get; private set; } + [Flags] private enum NsoFlags { @@ -33,6 +36,9 @@ namespace Ryujinx.HLE.Loaders.Executables { this.FilePath = FilePath; + SourceAddress = 0; + BssAddress = 0; + BinaryReader Reader = new BinaryReader(Input); Input.Seek(0, SeekOrigin.Begin); diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index cd1bb03458..6625136a1e 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -24,8 +24,9 @@ + - + diff --git a/Ryujinx.HLE/RyujinxProfileImage.jpg b/Ryujinx.HLE/RyujinxProfileImage.jpg index fe9ec2a959..55e4c43cf2 100644 Binary files a/Ryujinx.HLE/RyujinxProfileImage.jpg and b/Ryujinx.HLE/RyujinxProfileImage.jpg differ diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index a3f874ee59..fe0be6cef6 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -4,7 +4,6 @@ using Ryujinx.Graphics.Gal; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; using Ryujinx.HLE.Memory; using System; using System.Threading; @@ -15,8 +14,6 @@ namespace Ryujinx.HLE { internal IAalOutput AudioOut { get; private set; } - public Logger Log { get; private set; } - internal DeviceMemory Memory { get; private set; } internal NvGpu Gpu { get; private set; } @@ -49,8 +46,6 @@ namespace Ryujinx.HLE this.AudioOut = AudioOut; - Log = new Logger(); - Memory = new DeviceMemory(); Gpu = new NvGpu(Renderer); diff --git a/Ryujinx.HLE/Utilities/UInt128.cs b/Ryujinx.HLE/Utilities/UInt128.cs new file mode 100644 index 0000000000..14e04e4a6a --- /dev/null +++ b/Ryujinx.HLE/Utilities/UInt128.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.HLE.Utilities +{ + public struct UInt128 + { + public long High { get; private set; } + public long Low { get; private set; } + + public UInt128(long Low, long High) + { + this.Low = Low; + this.High = High; + } + + public UInt128(string UInt128Hex) + { + if (UInt128Hex == null || UInt128Hex.Length != 32 || !UInt128Hex.All("0123456789abcdefABCDEF".Contains)) + { + throw new ArgumentException("Invalid Hex value!", nameof(UInt128Hex)); + } + + Low = Convert.ToInt64(UInt128Hex.Substring(16), 16); + High = Convert.ToInt64(UInt128Hex.Substring(0, 16), 16); + } + + public void Write(BinaryWriter BinaryWriter) + { + BinaryWriter.Write(Low); + BinaryWriter.Write(High); + } + + public override string ToString() + { + return High.ToString("x16") + Low.ToString("x16"); + } + + public bool IsZero() + { + return (Low | High) == 0; + } + } +} diff --git a/Ryujinx.Tests.Unicorn/MemoryPermission.cs b/Ryujinx.Tests.Unicorn/MemoryPermission.cs index d79b2adda1..a14c4e9cf1 100644 --- a/Ryujinx.Tests.Unicorn/MemoryPermission.cs +++ b/Ryujinx.Tests.Unicorn/MemoryPermission.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Tests.Unicorn { public enum MemoryPermission diff --git a/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs index 3554480c13..cf110598eb 100644 --- a/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs +++ b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Tests.Unicorn.Native { public enum ArmRegister diff --git a/Ryujinx.Tests.Unicorn/Native/Interface.cs b/Ryujinx.Tests.Unicorn/Native/Interface.cs index a6563220fe..b2786d1436 100644 --- a/Ryujinx.Tests.Unicorn/Native/Interface.cs +++ b/Ryujinx.Tests.Unicorn/Native/Interface.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using Ryujinx.Tests.Unicorn; namespace Ryujinx.Tests.Unicorn.Native { diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs index be088366da..73710faa89 100644 --- a/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs +++ b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Tests.Unicorn.Native { public enum UnicornArch diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs index 950583bdc0..a5040518dd 100644 --- a/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs +++ b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Tests.Unicorn.Native { public enum UnicornMode diff --git a/Ryujinx.Tests.Unicorn/UnicornAArch64.cs b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs index 1cd3671fe1..8ee4e76db9 100644 --- a/Ryujinx.Tests.Unicorn/UnicornAArch64.cs +++ b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.Contracts; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; diff --git a/Ryujinx.Tests.Unicorn/UnicornError.cs b/Ryujinx.Tests.Unicorn/UnicornError.cs index db56c615d1..85833ea008 100644 --- a/Ryujinx.Tests.Unicorn/UnicornError.cs +++ b/Ryujinx.Tests.Unicorn/UnicornError.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Tests.Unicorn { public enum UnicornError diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index c69c7a02f9..24585fe78f 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -178,8 +178,15 @@ namespace Ryujinx.Tests.Cpu return GetThreadState(); } - [Flags] - protected enum FPSR + /// Floating-point Control Register. + protected enum FPCR + { + /// Default NaN mode control bit. + DN = 25 + } + + /// Floating-point Status Register. + [Flags] protected enum FPSR { None = 0, @@ -195,32 +202,43 @@ namespace Ryujinx.Tests.Cpu IXC = 1 << 4, /// Input Denormal cumulative floating-point exception bit. IDC = 1 << 7, + /// Cumulative saturation bit. - QC = 1 << 27 + QC = 1 << 27 } - protected enum FpSkips { None, IfNaN_S, IfNaN_D }; + [Flags] protected enum FpSkips + { + None = 0, - protected enum FpUseTolerance { None, OneUlps_S, OneUlps_D }; + IfNaN_S = 1, + IfNaN_D = 2, + + IfUnderflow = 4, + IfOverflow = 8 + } + + protected enum FpTolerances + { + None, + + UpToOneUlps_S, + UpToOneUlps_D + } protected void CompareAgainstUnicorn( - FPSR FpsrMask = FPSR.None, - FpSkips FpSkips = FpSkips.None, - FpUseTolerance FpUseTolerance = FpUseTolerance.None) + FPSR FpsrMask = FPSR.None, + FpSkips FpSkips = FpSkips.None, + FpTolerances FpTolerances = FpTolerances.None) { if (!UnicornAvailable) { return; } - if (FpSkips == FpSkips.IfNaN_S && float.IsNaN(VectorExtractSingle(UnicornEmu.Q[0], (byte)0))) + if (FpSkips != FpSkips.None) { - Assert.Ignore("NaN test."); - } - - if (FpSkips == FpSkips.IfNaN_D && double.IsNaN(VectorExtractDouble(UnicornEmu.Q[0], (byte)0))) - { - Assert.Ignore("NaN test."); + ManageFpSkips(FpSkips); } Assert.That(Thread.ThreadState.X0, Is.EqualTo(UnicornEmu.X[0])); @@ -257,50 +275,13 @@ namespace Ryujinx.Tests.Cpu Assert.That(Thread.ThreadState.X31, Is.EqualTo(UnicornEmu.SP)); - if (FpUseTolerance == FpUseTolerance.None) + if (FpTolerances == FpTolerances.None) { Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); } else { - if (!Is.EqualTo(UnicornEmu.Q[0]).ApplyTo(Thread.ThreadState.V0).IsSuccess) - { - if (FpUseTolerance == FpUseTolerance.OneUlps_S) - { - if (float.IsNormal (VectorExtractSingle(UnicornEmu.Q[0], (byte)0)) || - float.IsSubnormal(VectorExtractSingle(UnicornEmu.Q[0], (byte)0))) - { - Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)0), - Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); - Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)1), - Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); - Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)2), - Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)2)).Within(1).Ulps); - Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)3), - Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)3)).Within(1).Ulps); - } - else - { - Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); - } - } - - if (FpUseTolerance == FpUseTolerance.OneUlps_D) - { - if (double.IsNormal (VectorExtractDouble(UnicornEmu.Q[0], (byte)0)) || - double.IsSubnormal(VectorExtractDouble(UnicornEmu.Q[0], (byte)0))) - { - Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)0), - Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); - Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)1), - Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); - } - else - { - Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); - } - } - } + ManageFpTolerances(FpTolerances); } Assert.That(Thread.ThreadState.V1, Is.EqualTo(UnicornEmu.Q[1])); Assert.That(Thread.ThreadState.V2, Is.EqualTo(UnicornEmu.Q[2])); @@ -344,6 +325,90 @@ namespace Ryujinx.Tests.Cpu Assert.That(Thread.ThreadState.Negative, Is.EqualTo(UnicornEmu.NegativeFlag)); } + private void ManageFpSkips(FpSkips FpSkips) + { + if (FpSkips.HasFlag(FpSkips.IfNaN_S)) + { + if (float.IsNaN(VectorExtractSingle(UnicornEmu.Q[0], (byte)0))) + { + Assert.Ignore("NaN test."); + } + } + else if (FpSkips.HasFlag(FpSkips.IfNaN_D)) + { + if (double.IsNaN(VectorExtractDouble(UnicornEmu.Q[0], (byte)0))) + { + Assert.Ignore("NaN test."); + } + } + + if (FpSkips.HasFlag(FpSkips.IfUnderflow)) + { + if ((UnicornEmu.Fpsr & (int)FPSR.UFC) != 0) + { + Assert.Ignore("Underflow test."); + } + } + + if (FpSkips.HasFlag(FpSkips.IfOverflow)) + { + if ((UnicornEmu.Fpsr & (int)FPSR.OFC) != 0) + { + Assert.Ignore("Overflow test."); + } + } + } + + private void ManageFpTolerances(FpTolerances FpTolerances) + { + if (!Is.EqualTo(UnicornEmu.Q[0]).ApplyTo(Thread.ThreadState.V0).IsSuccess) + { + if (FpTolerances == FpTolerances.UpToOneUlps_S) + { + if (IsNormalOrSubnormal_S(VectorExtractSingle(UnicornEmu.Q[0], (byte)0)) && + IsNormalOrSubnormal_S(VectorExtractSingle(Thread.ThreadState.V0, (byte)0))) + { + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)0), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)1), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)2), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)2)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)3), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)3)).Within(1).Ulps); + + Console.WriteLine(FpTolerances); + } + else + { + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + } + } + + if (FpTolerances == FpTolerances.UpToOneUlps_D) + { + if (IsNormalOrSubnormal_D(VectorExtractDouble(UnicornEmu.Q[0], (byte)0)) && + IsNormalOrSubnormal_D(VectorExtractDouble(Thread.ThreadState.V0, (byte)0))) + { + Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)0), + Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); + Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)1), + Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); + + Console.WriteLine(FpTolerances); + } + else + { + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + } + } + } + + bool IsNormalOrSubnormal_S(float f) => float.IsNormal(f) || float.IsSubnormal(f); + + bool IsNormalOrSubnormal_D(double d) => double.IsNormal(d) || double.IsSubnormal(d); + } + protected static Vector128 MakeVectorE0(double E0) { if (!Sse2.IsSupported) @@ -453,14 +518,14 @@ namespace Ryujinx.Tests.Cpu { uint Rnd; - do Rnd = TestContext.CurrentContext.Random.NextUInt(); - while ((Rnd & 0x7F800000u) == 0u || - (Rnd & 0x7F800000u) == 0x7F800000u); + do Rnd = TestContext.CurrentContext.Random.NextUInt(); + while (( Rnd & 0x7F800000u) == 0u || + (~Rnd & 0x7F800000u) == 0u); return Rnd; } - protected static uint GenSubNormal_S() + protected static uint GenSubnormal_S() { uint Rnd; @@ -474,14 +539,14 @@ namespace Ryujinx.Tests.Cpu { ulong Rnd; - do Rnd = TestContext.CurrentContext.Random.NextULong(); - while ((Rnd & 0x7FF0000000000000ul) == 0ul || - (Rnd & 0x7FF0000000000000ul) == 0x7FF0000000000000ul); + do Rnd = TestContext.CurrentContext.Random.NextULong(); + while (( Rnd & 0x7FF0000000000000ul) == 0ul || + (~Rnd & 0x7FF0000000000000ul) == 0ul); return Rnd; } - protected static ulong GenSubNormal_D() + protected static ulong GenSubnormal_D() { ulong Rnd; diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index b423b4de73..279f9f0c3e 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -81,14 +81,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _1S_F_() { - yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) yield return 0x0000000080800000ul; // -Min Normal - yield return 0x00000000807FFFFFul; // -Max SubNormal - yield return 0x0000000080000001ul; // -Min SubNormal - yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) yield return 0x0000000000800000ul; // +Min Normal - yield return 0x00000000007FFFFFul; // +Max SubNormal - yield return 0x0000000000000001ul; // +Min SubNormal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) if (!NoZeros) { @@ -104,17 +104,17 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0x00000000FFFFFFFFul; // -QNaN (all ones payload) - yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) - yield return 0x000000007FFFFFFFul; // +QNaN (all ones payload) - yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Grbg = TestContext.CurrentContext.Random.NextUInt(); ulong Rnd1 = GenNormal_S(); - ulong Rnd2 = GenSubNormal_S(); + ulong Rnd2 = GenSubnormal_S(); yield return (Grbg << 32) | Rnd1; yield return (Grbg << 32) | Rnd2; @@ -123,14 +123,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _2S_F_() { - yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) yield return 0x8080000080800000ul; // -Min Normal - yield return 0x807FFFFF807FFFFFul; // -Max SubNormal - yield return 0x8000000180000001ul; // -Min SubNormal - yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) yield return 0x0080000000800000ul; // +Min Normal - yield return 0x007FFFFF007FFFFFul; // +Max SubNormal - yield return 0x0000000100000001ul; // +Min SubNormal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) if (!NoZeros) { @@ -146,16 +146,16 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) - yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) - yield return 0x7FFFFFFF7FFFFFFFul; // +QNaN (all ones payload) - yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Rnd1 = GenNormal_S(); - ulong Rnd2 = GenSubNormal_S(); + ulong Rnd2 = GenSubnormal_S(); yield return (Rnd1 << 32) | Rnd1; yield return (Rnd2 << 32) | Rnd2; @@ -164,14 +164,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _1D_F_() { - yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) yield return 0x8010000000000000ul; // -Min Normal - yield return 0x800FFFFFFFFFFFFFul; // -Max SubNormal - yield return 0x8000000000000001ul; // -Min SubNormal - yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) yield return 0x0010000000000000ul; // +Min Normal - yield return 0x000FFFFFFFFFFFFFul; // +Max SubNormal - yield return 0x0000000000000001ul; // +Min SubNormal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) if (!NoZeros) { @@ -187,16 +187,16 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) - yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) - yield return 0x7FFFFFFFFFFFFFFFul; // +QNaN (all ones payload) - yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Rnd1 = GenNormal_D(); - ulong Rnd2 = GenSubNormal_D(); + ulong Rnd2 = GenSubnormal_D(); yield return Rnd1; yield return Rnd2; @@ -205,6 +205,22 @@ namespace Ryujinx.Tests.Cpu #endregion #region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_S_SD_() + { + return new uint[] + { + 0x1E22C020u // FCVT D0, S1 + }; + } + + private static uint[] _F_Cvt_S_DS_() + { + return new uint[] + { + 0x1E624020u // FCVT S0, D1 + }; + } + private static uint[] _F_Cvt_NZ_SU_S_S_() { return new uint[] @@ -248,6 +264,40 @@ namespace Ryujinx.Tests.Cpu 0x6EE1B800u // FCVTZU V0.2D, V0.2D }; } + + private static uint[] _F_Recpx_Sqrt_S_S_() + { + return new uint[] + { + 0x5EA1F820u, // FRECPX S0, S1 + 0x1E21C020u // FSQRT S0, S1 + }; + } + + private static uint[] _F_Recpx_Sqrt_S_D_() + { + return new uint[] + { + 0x5EE1F820u, // FRECPX D0, D1 + 0x1E61C020u // FSQRT D0, D1 + }; + } + + private static uint[] _F_Sqrt_V_2S_4S_() + { + return new uint[] + { + 0x2EA1F800u // FSQRT V0.2S, V0.2S + }; + } + + private static uint[] _F_Sqrt_V_2D_() + { + return new uint[] + { + 0x6EE1F800u // FSQRT V0.2D, V0.2D + }; + } #endregion private const int RndCnt = 2; @@ -751,58 +801,46 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Pairwise, Description("FCVT
, ")] - public void Fcvt_S_SD([ValueSource("_1S_F_")] ulong A) + [Test, Pairwise] [Explicit] + public void F_Cvt_S_SD([ValueSource("_F_Cvt_S_SD_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A) { - //const int DNFlagBit = 25; // Default NaN mode control bit. - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x1E22C020; // FCVT D0, S1 - ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE1(Z); Vector128 V1 = MakeVectorE0(A); - //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. - //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); - } - - [Test, Pairwise, Description("FCVT , ")] - public void Fcvt_S_DS([ValueSource("_1D_F_")] ulong A) - { - uint Opcode = 0x1E624020; // FCVT S0, D1 - - ulong Z = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); CompareAgainstUnicorn(); } - [Test, Pairwise] - public void F_Cvt_NZ_SU_S_S([ValueSource("_F_Cvt_NZ_SU_S_S_")] uint Opcodes, - [ValueSource("_1S_F_")] ulong A) + [Test, Pairwise] [Explicit] + public void F_Cvt_S_DS([ValueSource("_F_Cvt_S_DS_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A) { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); - AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); + CompareAgainstUnicorn(); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] + public void F_Cvt_NZ_SU_S_S([ValueSource("_F_Cvt_NZ_SU_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] public void F_Cvt_NZ_SU_S_D([ValueSource("_F_Cvt_NZ_SU_S_D_")] uint Opcodes, [ValueSource("_1D_F_")] ulong A) { @@ -815,7 +853,7 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Cvt_NZ_SU_V_2S_4S([ValueSource("_F_Cvt_NZ_SU_V_2S_4S_")] uint Opcodes, [Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -834,7 +872,7 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Cvt_NZ_SU_V_2D([ValueSource("_F_Cvt_NZ_SU_V_2D_")] uint Opcodes, [Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -851,6 +889,76 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + [Test, Pairwise] [Explicit] + public void F_Recpx_Sqrt_S_S([ValueSource("_F_Recpx_Sqrt_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Recpx_Sqrt_S_D([ValueSource("_F_Recpx_Sqrt_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Sqrt_V_2S_4S([ValueSource("_F_Sqrt_V_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Sqrt_V_2D([ValueSource("_F_Sqrt_V_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A) + { + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + [Test, Pairwise, Description("NEG , ")] public void Neg_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs index 7a67d53b91..08e2894ca9 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs @@ -3,144 +3,11 @@ using ChocolArm64.State; using NUnit.Framework; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace Ryujinx.Tests.Cpu { public class CpuTestSimdArithmetic : CpuTest { - [TestCase(0x1E224820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000000000000ul)] // FMAX S0, S1, S2 - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x000000003DCCCCCDul)] - [TestCase(0x1E224820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003DCCCCCDul)] - [TestCase(0x1E224820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x0000000000000076ul)] - [TestCase(0x1E224820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x000000007F7FFFFFul)] - [TestCase(0x1E224820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x000000007F7FFFFFul)] - [TestCase(0x1E224820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] - [TestCase(0x1E224820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E224820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul)] - [TestCase(0x1E224820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul)] - [TestCase(0x1E224820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul)] - [TestCase(0x1E624820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x0000000000000000ul)] // FMAX D0, D1, D2 - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x3FF3333333333333ul)] - public void Fmax_S(uint Opcode, ulong A, ulong B, ulong Result) - { - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(Result)); - - CompareAgainstUnicorn(); - } - - [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u)] - [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu)] - [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3DCCCCCDu, 0x3DCCCCCDu)] - [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x00000076u, 0x00000076u)] - [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u)] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u)] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au)] - public void Fmax_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) - { - uint Opcode = 0x4E22F420; // FMAX V0.4S, V1.4S, V2.4S - - Vector128 V1 = MakeVectorE0E1(A, B); - Vector128 V2 = MakeVectorE0E1(C, D); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(Result0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(Result1)); - }); - - CompareAgainstUnicorn(); - } - - [TestCase(0x1E225820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] // FMIN S0, S1, S2 - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003C9623B1ul)] - [TestCase(0x1E225820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x000000008BA98D27ul)] - [TestCase(0x1E225820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x00000000807FFFFFul)] - [TestCase(0x1E225820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x00000000807FFFFFul)] - [TestCase(0x1E225820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] - [TestCase(0x1E225820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E225820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul)] - [TestCase(0x1E225820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul)] - [TestCase(0x1E225820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul)] - [TestCase(0x1E625820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] // FMIN D0, D1, D2 - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x8000000000000000ul)] - public void Fmin_S(uint Opcode, ulong A, ulong B, ulong Result) - { - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(Result)); - - CompareAgainstUnicorn(); - } - - [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x80000000u, 0x80000000u)] - [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u)] - [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x8BA98D27u, 0x8BA98D27u)] - [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] - [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] - [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u)] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u)] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au)] - public void Fmin_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) - { - uint Opcode = 0x4EA2F420; // FMIN V0.4S, V1.4S, V2.4S - - Vector128 V1 = MakeVectorE0E1(A, B); - Vector128 V2 = MakeVectorE0E1(C, D); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(Result0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(Result1)); - }); - - CompareAgainstUnicorn(); - } - - [Test, Description("FMUL S6, S1, V0.S[2]")] - public void Fmul_Se([Random(10)] float A, [Random(10)] float B) - { - AThreadState ThreadState = SingleOpcode(0x5F809826, - V1: Sse.SetVector128(0, 0, 0, A), - V0: Sse.SetVector128(0, B, 0, 0)); - - Assert.That(Sse41.Extract(ThreadState.V6, (byte)0), Is.EqualTo(A * B)); - - CompareAgainstUnicorn(); - } - [TestCase(0x00000000u, 0x7F800000u)] [TestCase(0x80000000u, 0xFF800000u)] [TestCase(0x00FFF000u, 0x7E000000u)] @@ -161,38 +28,6 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Description("FRECPS D0, D1, D2"), Ignore("Not accurate enough.")] - public void Frecps_S([Random(10)] double A, [Random(10)] double B) - { - AThreadState ThreadState = SingleOpcode(0x5E62FC20, - V1: MakeVectorE0(A), - V2: MakeVectorE0(B)); - - Assert.That(VectorExtractDouble(ThreadState.V0, (byte)0), Is.EqualTo(2 - (A * B))); - - CompareAgainstUnicorn(); - } - - [Test, Description("FRECPS V4.4S, V2.4S, V0.4S")] - public void Frecps_V([Random(10)] float A, [Random(10)] float B) - { - AThreadState ThreadState = SingleOpcode(0x4E20FC44, - V2: Sse.SetAllVector128(A), - V0: Sse.SetAllVector128(B)); - - float Result = (float)(2 - ((double)A * (double)B)); - - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(ThreadState.V4, (byte)0), Is.EqualTo(Result)); - Assert.That(Sse41.Extract(ThreadState.V4, (byte)1), Is.EqualTo(Result)); - 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)] [TestCase(0x3F99999Au, false, 0x3F800000u)] [TestCase(0x404CCCCDu, false, 0x40400000u)] @@ -238,7 +73,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -267,7 +102,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, B); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -337,7 +172,7 @@ namespace Ryujinx.Tests.Cpu case 'M': FpcrTemp = 0x800000; break; case 'Z': FpcrTemp = 0xC00000; break; } - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp |= 1 << 25; } @@ -393,7 +228,7 @@ namespace Ryujinx.Tests.Cpu case 'M': FpcrTemp = 0x800000; break; case 'Z': FpcrTemp = 0xC00000; break; } - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp |= 1 << 25; } @@ -454,7 +289,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -479,7 +314,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, B); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -541,7 +376,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -569,7 +404,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, B); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -630,7 +465,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -655,7 +490,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, B); int FpcrTemp = 0x0; - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp = 0x2000000; } @@ -725,7 +560,7 @@ namespace Ryujinx.Tests.Cpu case 'M': FpcrTemp = 0x800000; break; case 'Z': FpcrTemp = 0xC00000; break; } - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp |= 1 << 25; } @@ -781,7 +616,7 @@ namespace Ryujinx.Tests.Cpu case 'M': FpcrTemp = 0x800000; break; case 'Z': FpcrTemp = 0xC00000; break; } - if(DefaultNaN) + if (DefaultNaN) { FpcrTemp |= 1 << 25; } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs deleted file mode 100644 index a1558b05af..0000000000 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs +++ /dev/null @@ -1,407 +0,0 @@ -using ChocolArm64.State; - -using NUnit.Framework; - -using System; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Ryujinx.Tests.Cpu -{ - public class CpuTestSimdCmp : CpuTest - { -#region "ValueSource" - private static float[] _floats_() - { - return new float[] { float.NegativeInfinity, float.MinValue, -1f, -0f, - +0f, +1f, float.MaxValue, float.PositiveInfinity }; - } - - private static double[] _doubles_() - { - return new double[] { double.NegativeInfinity, double.MinValue, -1d, -0d, - +0d, +1d, double.MaxValue, double.PositiveInfinity }; - } -#endregion - - private const int RndCnt = 2; - - [Test, Description("FCMEQ D0, D1, D2 | FCMGE D0, D1, D2 | FCMGT D0, D1, D2")] - public void Fcmeq_Fcmge_Fcmgt_Reg_S_D([ValueSource("_doubles_")] [Random(RndCnt)] double A, - [ValueSource("_doubles_")] [Random(RndCnt)] double B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x5E62E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - - Vector128 V0 = Sse.StaticCast(Sse2.SetAllVector128(TestContext.CurrentContext.Random.NextDouble())); - Vector128 V1 = Sse.StaticCast(Sse2.SetScalarVector128(A)); - Vector128 V2 = Sse.StaticCast(Sse2.SetScalarVector128(B)); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - 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")] - public void Fcmeq_Fcmge_Fcmgt_Reg_S_S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [ValueSource("_floats_")] [Random(RndCnt)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x5E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetScalarVector128(A); - Vector128 V2 = Sse.SetScalarVector128(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)1), Is.Zero); - 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")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_2D([ValueSource("_doubles_")] [Random(RndCnt)] double A, - [ValueSource("_doubles_")] [Random(RndCnt)] double B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x4E62E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - - Vector128 V1 = Sse.StaticCast(Sse2.SetAllVector128(A)); - Vector128 V2 = Sse.StaticCast(Sse2.SetAllVector128(B)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - 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")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_2S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [ValueSource("_floats_")] [Random(RndCnt)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x0E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetVector128(0, 0, A, A); - Vector128 V2 = Sse.SetVector128(0, 0, B, B); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - 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")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_4S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [ValueSource("_floats_")] [Random(RndCnt)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x4E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - - Vector128 V1 = Sse.SetAllVector128(A); - Vector128 V2 = Sse.SetAllVector128(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - 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")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_S_D([ValueSource("_doubles_")] [Random(RndCnt)] double A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x5EE0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - - Vector128 V0 = Sse.StaticCast(Sse2.SetAllVector128(TestContext.CurrentContext.Random.NextDouble())); - Vector128 V1 = Sse.StaticCast(Sse2.SetScalarVector128(A)); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - double Zero = +0d; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - 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")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_S_S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x5EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetScalarVector128(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)1), Is.Zero); - 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")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_2D([ValueSource("_doubles_")] [Random(RndCnt)] double A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x4EE0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - - Vector128 V1 = Sse.StaticCast(Sse2.SetAllVector128(A)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - double Zero = +0d; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - 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")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_2S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x0EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetVector128(0, 0, A, A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - 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")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_4S([ValueSource("_floats_")] [Random(RndCnt)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x4EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - - Vector128 V1 = Sse.SetAllVector128(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - 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/CpuTestSimdIns.cs b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs index 387cdf5dd9..0b227edbb6 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs @@ -14,6 +14,27 @@ namespace Ryujinx.Tests.Cpu #if SimdIns #region "ValueSource" + private static ulong[] _1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + private static uint[] _W_() { return new uint[] { 0x00000000u, 0x0000007Fu, @@ -39,7 +60,7 @@ namespace Ryujinx.Tests.Cpu [Values(0, 1, 2)] int Size, // Q0: <8B, 4H, 2S> [Values(0b0u, 0b1u)] uint Q) // Q1: <16B, 8H, 4S> { - uint Imm5 = (1U << Size) & 0x1F; + uint Imm5 = (1u << Size) & 0x1Fu; uint Opcode = 0x0E000C00; // RESERVED Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); @@ -69,6 +90,92 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + + [Test, Pairwise, Description("SMOV , .[]")] + public void Smov_S_W([Values(0u, 31u)] uint Rd, + [Values(1u)] uint Rn, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong A, + [Values(0, 1)] int Size, // + [Values(0u, 1u, 2u, 3u)] uint Index) + { + uint Imm5 = (Index << (Size + 1) | 1u << Size) & 0x1Fu; + + uint Opcode = 0x0E002C00; // RESERVED + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= (Imm5 << 16); + + ulong _X0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcode, X0: _X0, X31: _W31, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .[]")] + public void Smov_S_X([Values(0u, 31u)] uint Rd, + [Values(1u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0, 1, 2)] int Size, // + [Values(0u, 1u)] uint Index) + { + uint Imm5 = (Index << (Size + 1) | 1u << Size) & 0x1Fu; + + uint Opcode = 0x4E002C00; // RESERVED + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= (Imm5 << 16); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .[]")] + public void Umov_S_W([Values(0u, 31u)] uint Rd, + [Values(1u)] uint Rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong A, + [Values(0, 1, 2)] int Size, // + [Values(0u, 1u)] uint Index) + { + uint Imm5 = (Index << (Size + 1) | 1u << Size) & 0x1Fu; + + uint Opcode = 0x0E003C00; // RESERVED + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= (Imm5 << 16); + + ulong _X0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint _W31 = TestContext.CurrentContext.Random.NextUInt(); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcode, X0: _X0, X31: _W31, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .[]")] + public void Umov_S_X([Values(0u, 31u)] uint Rd, + [Values(1u)] uint Rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong A, + [Values(3)] int Size, // + [Values(0u)] uint Index) + { + uint Imm5 = (Index << (Size + 1) | 1u << Size) & 0x1Fu; + + uint Opcode = 0x4E003C00; // RESERVED + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= (Imm5 << 16); + + ulong _X31 = TestContext.CurrentContext.Random.NextULong(); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31, V1: V1); + + CompareAgainstUnicorn(); + } #endif } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs index ae409a6d85..1ea017c807 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -80,14 +80,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _1S_F_() { - yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) yield return 0x0000000080800000ul; // -Min Normal - yield return 0x00000000807FFFFFul; // -Max SubNormal - yield return 0x0000000080000001ul; // -Min SubNormal - yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) yield return 0x0000000000800000ul; // +Min Normal - yield return 0x00000000007FFFFFul; // +Max SubNormal - yield return 0x0000000000000001ul; // +Min SubNormal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) if (!NoZeros) { @@ -103,17 +103,17 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0x00000000FFFFFFFFul; // -QNaN (all ones payload) - yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) - yield return 0x000000007FFFFFFFul; // +QNaN (all ones payload) - yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Grbg = TestContext.CurrentContext.Random.NextUInt(); ulong Rnd1 = GenNormal_S(); - ulong Rnd2 = GenSubNormal_S(); + ulong Rnd2 = GenSubnormal_S(); yield return (Grbg << 32) | Rnd1; yield return (Grbg << 32) | Rnd2; @@ -122,14 +122,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _2S_F_() { - yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) yield return 0x8080000080800000ul; // -Min Normal - yield return 0x807FFFFF807FFFFFul; // -Max SubNormal - yield return 0x8000000180000001ul; // -Min SubNormal - yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) yield return 0x0080000000800000ul; // +Min Normal - yield return 0x007FFFFF007FFFFFul; // +Max SubNormal - yield return 0x0000000100000001ul; // +Min SubNormal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) if (!NoZeros) { @@ -145,16 +145,16 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) - yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) - yield return 0x7FFFFFFF7FFFFFFFul; // +QNaN (all ones payload) - yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Rnd1 = GenNormal_S(); - ulong Rnd2 = GenSubNormal_S(); + ulong Rnd2 = GenSubnormal_S(); yield return (Rnd1 << 32) | Rnd1; yield return (Rnd2 << 32) | Rnd2; @@ -163,14 +163,14 @@ namespace Ryujinx.Tests.Cpu private static IEnumerable _1D_F_() { - yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) yield return 0x8010000000000000ul; // -Min Normal - yield return 0x800FFFFFFFFFFFFFul; // -Max SubNormal - yield return 0x8000000000000001ul; // -Min SubNormal - yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) yield return 0x0010000000000000ul; // +Min Normal - yield return 0x000FFFFFFFFFFFFFul; // +Max SubNormal - yield return 0x0000000000000001ul; // +Min SubNormal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) if (!NoZeros) { @@ -186,16 +186,16 @@ namespace Ryujinx.Tests.Cpu if (!NoNaNs) { - yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) - yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) - yield return 0x7FFFFFFFFFFFFFFFul; // +QNaN (all ones payload) - yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) } for (int Cnt = 1; Cnt <= RndCnt; Cnt++) { ulong Rnd1 = GenNormal_D(); - ulong Rnd2 = GenSubNormal_D(); + ulong Rnd2 = GenSubnormal_D(); yield return Rnd1; yield return Rnd2; @@ -204,6 +204,72 @@ namespace Ryujinx.Tests.Cpu #endregion #region "ValueSource (Opcodes)" + private static uint[] _F_Add_Div_Mul_Mulx_Sub_S_S_() + { + return new uint[] + { + 0x1E222820u, // FADD S0, S1, S2 + 0x1E221820u, // FDIV S0, S1, S2 + 0x1E220820u, // FMUL S0, S1, S2 + 0x5E22DC20u, // FMULX S0, S1, S2 + 0x1E223820u // FSUB S0, S1, S2 + }; + } + + private static uint[] _F_Add_Div_Mul_Mulx_Sub_S_D_() + { + return new uint[] + { + 0x1E622820u, // FADD D0, D1, D2 + 0x1E621820u, // FDIV D0, D1, D2 + 0x1E620820u, // FMUL D0, D1, D2 + 0x5E62DC20u, // FMULX D0, D1, D2 + 0x1E623820u // FSUB D0, D1, D2 + }; + } + + private static uint[] _F_Add_Div_Mul_Mulx_Sub_V_2S_4S_() + { + return new uint[] + { + 0x0E20D400u, // FADD V0.2S, V0.2S, V0.2S + 0x2E20FC00u, // FDIV V0.2S, V0.2S, V0.2S + 0x2E20DC00u, // FMUL V0.2S, V0.2S, V0.2S + 0x0E20DC00u, // FMULX V0.2S, V0.2S, V0.2S + 0x0EA0D400u // FSUB V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Add_Div_Mul_Mulx_Sub_V_2D_() + { + return new uint[] + { + 0x4E60D400u, // FADD V0.2D, V0.2D, V0.2D + 0x6E60FC00u, // FDIV V0.2D, V0.2D, V0.2D + 0x6E60DC00u, // FMUL V0.2D, V0.2D, V0.2D + 0x4E60DC00u, // FMULX V0.2D, V0.2D, V0.2D + 0x4EE0D400u // FSUB V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Madd_Msub_S_S_() + { + return new uint[] + { + 0x1F020C20u, // FMADD S0, S1, S2, S3 + 0x1F028C20u // FMSUB S0, S1, S2, S3 + }; + } + + private static uint[] _F_Madd_Msub_S_D_() + { + return new uint[] + { + 0x1F420C20u, // FMADD D0, D1, D2, D3 + 0x1F428C20u // FMSUB D0, D1, D2, D3 + }; + } + private static uint[] _F_Max_Min_Nm_S_S_() { return new uint[] @@ -251,6 +317,42 @@ namespace Ryujinx.Tests.Cpu 0x6EE0F400u // FMINP V0.2D, V0.2D, V0.2D }; } + + private static uint[] _F_Recps_Rsqrts_S_S_() + { + return new uint[] + { + 0x5E22FC20u, // FRECPS S0, S1, S2 + 0x5EA2FC20u // FRSQRTS S0, S1, S2 + }; + } + + private static uint[] _F_Recps_Rsqrts_S_D_() + { + return new uint[] + { + 0x5E62FC20u, // FRECPS D0, D1, D2 + 0x5EE2FC20u // FRSQRTS D0, D1, D2 + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2S_4S_() + { + return new uint[] + { + 0x0E20FC00u, // FRECPS V0.2S, V0.2S, V0.2S + 0x0EA0FC00u // FRSQRTS V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2D_() + { + return new uint[] + { + 0x4E60FC00u, // FRECPS V0.2D, V0.2D, V0.2D + 0x4EE0FC00u // FRSQRTS V0.2D, V0.2D, V0.2D + }; + } #endregion private const int RndCnt = 2; @@ -1035,70 +1137,142 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Pairwise, Description("FMADD , , , ")] - public void Fmadd_S_S([ValueSource("_1S_F_")] ulong A, - [ValueSource("_1S_F_")] ulong B, - [ValueSource("_1S_F_")] ulong C) + [Test, Pairwise] [Explicit] + public void F_Add_Div_Mul_Mulx_Sub_S_S([ValueSource("_F_Add_Div_Mul_Mulx_Sub_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_1S_F_")] ulong B) { - //const int DNFlagBit = 25; // Default NaN mode control bit. - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); - uint Opcode = 0x1F020C20; // FMADD S0, S1, S2, S3 + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC | FPSR.DZC); + } + + [Test, Pairwise] [Explicit] + public void F_Add_Div_Mul_Mulx_Sub_S_D([ValueSource("_F_Add_Div_Mul_Mulx_Sub_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC | FPSR.DZC); + } + + [Test, Pairwise] [Explicit] + public void F_Add_Div_Mul_Mulx_Sub_V_2S_4S([ValueSource("_F_Add_Div_Mul_Mulx_Sub_V_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + Vector128 V2 = MakeVectorE0E1(B, B * Q); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC | FPSR.DZC); + } + + [Test, Pairwise] [Explicit] + public void F_Add_Div_Mul_Mulx_Sub_V_2D([ValueSource("_F_Add_Div_Mul_Mulx_Sub_V_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC | FPSR.DZC); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Madd_Msub_S_S([ValueSource("_F_Madd_Msub_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_1S_F_")] ulong B, + [ValueSource("_1S_F_")] ulong C) + { ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); Vector128 V2 = MakeVectorE0(B); Vector128 V3 = MakeVectorE0(C); - //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. - //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, V3: V3/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, V3: V3, Fpcr: Fpcr); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC, */FpSkips: FpSkips.IfNaN_S/*, FpUseTolerance: FpUseTolerance.OneUlps_S*/); + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_S); } - [Test, Pairwise, Description("FMADD
, , , ")] - public void Fmadd_S_D([ValueSource("_1D_F_")] ulong A, - [ValueSource("_1D_F_")] ulong B, - [ValueSource("_1D_F_")] ulong C) + [Test, Pairwise] [Explicit] // Fused. + public void F_Madd_Msub_S_D([ValueSource("_F_Madd_Msub_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [ValueSource("_1D_F_")] ulong C) { - uint Opcode = 0x1F420C20; // FMADD D0, D1, D2, D3 - ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE1(Z); Vector128 V1 = MakeVectorE0(A); Vector128 V2 = MakeVectorE0(B); Vector128 V3 = MakeVectorE0(C); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, V3: V3); + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - CompareAgainstUnicorn(FpSkips: FpSkips.IfNaN_D/*, FpUseTolerance: FpUseTolerance.OneUlps_D*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, V3: V3, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_D); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Max_Min_Nm_S_S([ValueSource("_F_Max_Min_Nm_S_S_")] uint Opcodes, [ValueSource("_1S_F_")] ulong A, [ValueSource("_1S_F_")] ulong B) { - //const int DNFlagBit = 25; // Default NaN mode control bit. - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); Vector128 V2 = MakeVectorE0(B); - //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. - //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Max_Min_Nm_S_D([ValueSource("_F_Max_Min_Nm_S_D_")] uint Opcodes, [ValueSource("_1D_F_")] ulong A, [ValueSource("_1D_F_")] ulong B) @@ -1108,12 +1282,14 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0(A); Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - CompareAgainstUnicorn(); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Max_Min_Nm_P_V_2S_4S([ValueSource("_F_Max_Min_Nm_P_V_2S_4S_")] uint Opcodes, [Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -1123,9 +1299,6 @@ namespace Ryujinx.Tests.Cpu [ValueSource("_2S_F_")] ulong B, [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> { - //const int DNFlagBit = 25; // Default NaN mode control bit. - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); Opcodes |= ((Q & 1) << 30); @@ -1133,15 +1306,14 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, A * Q); Vector128 V2 = MakeVectorE0E1(B, B * Q); - //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. - //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); } - [Test, Pairwise] + [Test, Pairwise] [Explicit] public void F_Max_Min_Nm_P_V_2D([ValueSource("_F_Max_Min_Nm_P_V_2D_")] uint Opcodes, [Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -1156,9 +1328,91 @@ namespace Ryujinx.Tests.Cpu Vector128 V1 = MakeVectorE0E1(A, A); Vector128 V2 = MakeVectorE0E1(B, B); - AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); - CompareAgainstUnicorn(); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_S_S([ValueSource("_F_Recps_Rsqrts_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_1S_F_")] ulong B) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_S); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_S_D([ValueSource("_F_Recps_Rsqrts_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_D); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_V_2S_4S([ValueSource("_F_Recps_Rsqrts_V_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + Vector128 V2 = MakeVectorE0E1(B, B * Q); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_S); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_V_2D([ValueSource("_F_Recps_Rsqrts_V_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_D); } [Test, Pairwise, Description("ORN ., ., .")] diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs b/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs index 4d14ab4859..615520625a 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Tests.Cpu { #if SimdRegElem -#region "ValueSource" +#region "ValueSource (Types)" private static ulong[] _2S_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, @@ -27,114 +27,81 @@ namespace Ryujinx.Tests.Cpu } #endregion +#region "ValueSource (Opcodes)" + private static uint[] _Mla_Mls_Mul_Ve_4H_8H_() + { + return new uint[] + { + 0x2F400000u, // MLA V0.4H, V0.4H, V0.H[0] + 0x2F404000u, // MLS V0.4H, V0.4H, V0.H[0] + 0x0F408000u // MUL V0.4H, V0.4H, V0.H[0] + }; + } + + private static uint[] _Mla_Mls_Mul_Ve_2S_4S_() + { + return new uint[] + { + 0x2F800000u, // MLA V0.2S, V0.2S, V0.S[0] + 0x2F804000u, // MLS V0.2S, V0.2S, V0.S[0] + 0x0F808000u // MUL V0.2S, V0.2S, V0.S[0] + }; + } +#endregion + private const int RndCnt = 2; - [Test, Pairwise, Description("MLA ., ., .[]")] - public void Mla_Ve_4H_8H([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [Values(2u, 0u)] uint Rm, - [ValueSource("_4H_")] [Random(RndCnt)] ulong Z, - [ValueSource("_4H_")] [Random(RndCnt)] ulong A, - [ValueSource("_4H_")] [Random(RndCnt)] ulong B, - [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint Index, - [Values(0b0u, 0b1u)] uint Q) // <4H, 8H> + [Test, Pairwise] + public void Mla_Mls_Mul_Ve_4H_8H([ValueSource("_Mla_Mls_Mul_Ve_4H_8H_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_4H_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong A, + [ValueSource("_4H_")] [Random(RndCnt)] ulong B, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint Index, + [Values(0b0u, 0b1u)] uint Q) // <4H, 8H> { - uint H = (Index & 4) >> 2; - uint L = (Index & 2) >> 1; - uint M = (Index & 1) >> 0; + uint H = (Index >> 2) & 1; + uint L = (Index >> 1) & 1; + uint M = Index & 1; - uint Opcode = 0x2F400000; // MLA V0.4H, V0.4H, V0.H[0] - Opcode |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= (L << 21) | (M << 20) | (H << 11); - Opcode |= ((Q & 1) << 30); + Opcodes |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (L << 21) | (M << 20) | (H << 11); + Opcodes |= ((Q & 1) << 30); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A * Q); Vector128 V2 = MakeVectorE0E1(B, B * H); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); CompareAgainstUnicorn(); } - [Test, Pairwise, Description("MLA ., ., .[]")] - public void Mla_Ve_2S_4S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [Values(2u, 0u)] uint Rm, - [ValueSource("_2S_")] [Random(RndCnt)] ulong Z, - [ValueSource("_2S_")] [Random(RndCnt)] ulong A, - [ValueSource("_2S_")] [Random(RndCnt)] ulong B, - [Values(0u, 1u, 2u, 3u)] uint Index, - [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + [Test, Pairwise] + public void Mla_Mls_Mul_Ve_2S_4S([ValueSource("_Mla_Mls_Mul_Ve_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong A, + [ValueSource("_2S_")] [Random(RndCnt)] ulong B, + [Values(0u, 1u, 2u, 3u)] uint Index, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> { - uint H = (Index & 2) >> 1; - uint L = (Index & 1) >> 0; + uint H = (Index >> 1) & 1; + uint L = Index & 1; - uint Opcode = 0x2F800000; // MLA V0.2S, V0.2S, V0.S[0] - Opcode |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= (L << 21) | (H << 11); - Opcode |= ((Q & 1) << 30); + Opcodes |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (L << 21) | (H << 11); + Opcodes |= ((Q & 1) << 30); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A * Q); Vector128 V2 = MakeVectorE0E1(B, B * H); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - CompareAgainstUnicorn(); - } - - [Test, Pairwise, Description("MLS ., ., .[]")] - public void Mls_Ve_4H_8H([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [Values(2u, 0u)] uint Rm, - [ValueSource("_4H_")] [Random(RndCnt)] ulong Z, - [ValueSource("_4H_")] [Random(RndCnt)] ulong A, - [ValueSource("_4H_")] [Random(RndCnt)] ulong B, - [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint Index, - [Values(0b0u, 0b1u)] uint Q) // <4H, 8H> - { - uint H = (Index & 4) >> 2; - uint L = (Index & 2) >> 1; - uint M = (Index & 1) >> 0; - - uint Opcode = 0x2F404000; // MLS V0.4H, V0.4H, V0.H[0] - Opcode |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= (L << 21) | (M << 20) | (H << 11); - Opcode |= ((Q & 1) << 30); - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0E1(A, A * Q); - Vector128 V2 = MakeVectorE0E1(B, B * H); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - CompareAgainstUnicorn(); - } - - [Test, Pairwise, Description("MLS ., ., .[]")] - public void Mls_Ve_2S_4S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [Values(2u, 0u)] uint Rm, - [ValueSource("_2S_")] [Random(RndCnt)] ulong Z, - [ValueSource("_2S_")] [Random(RndCnt)] ulong A, - [ValueSource("_2S_")] [Random(RndCnt)] ulong B, - [Values(0u, 1u, 2u, 3u)] uint Index, - [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> - { - uint H = (Index & 2) >> 1; - uint L = (Index & 1) >> 0; - - uint Opcode = 0x2F804000; // MLS V0.2S, V0.2S, V0.S[0] - Opcode |= ((Rm & 15) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= (L << 21) | (H << 11); - Opcode |= ((Q & 1) << 30); - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0E1(A, A * Q); - Vector128 V2 = MakeVectorE0E1(B, B * H); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); CompareAgainstUnicorn(); } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs b/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs new file mode 100644 index 0000000000..3945cce1a7 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs @@ -0,0 +1,424 @@ +#define SimdRegElemF + +using ChocolArm64.State; + +using NUnit.Framework; + +using System.Collections.Generic; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElemF")] // Tested: second half of 2018. + public sealed class CpuTestSimdRegElemF : CpuTest + { +#if SimdRegElemF + +#region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubnormal_S(); + + yield return (Grbg << 32) | Rnd1; + yield return (Grbg << 32) | Rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubnormal_S(); + + yield return (Rnd1 << 32) | Rnd1; + yield return (Rnd2 << 32) | Rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_D(); + ulong Rnd2 = GenSubnormal_D(); + + yield return Rnd1; + yield return Rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Mla_Mls_Se_S_() + { + return new uint[] + { + 0x5F821020u, // FMLA S0, S1, V2.S[0] + 0x5F825020u // FMLS S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Se_D_() + { + return new uint[] + { + 0x5FC21020u, // FMLA D0, D1, V2.D[0] + 0x5FC25020u // FMLS D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2S_4S_() + { + return new uint[] + { + 0x0F801000u, // FMLA V0.2S, V0.2S, V0.S[0] + 0x0F805000u // FMLS V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2D_() + { + return new uint[] + { + 0x4FC01000u, // FMLA V0.2D, V0.2D, V0.D[0] + 0x4FC05000u // FMLS V0.2D, V0.2D, V0.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_S_() + { + return new uint[] + { + 0x5F829020u, // FMUL S0, S1, V2.S[0] + 0x7F829020u // FMULX S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_D_() + { + return new uint[] + { + 0x5FC29020u, // FMUL D0, D1, V2.D[0] + 0x7FC29020u // FMULX D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2S_4S_() + { + return new uint[] + { + 0x0F809000u, // FMUL V0.2S, V0.2S, V0.S[0] + 0x2F809000u // FMULX V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2D_() + { + return new uint[] + { + 0x4FC09000u, // FMUL V0.2D, V0.2D, V0.D[0] + 0x6FC09000u // FMULX V0.2D, V0.2D, V0.D[0] + }; + } +#endregion + + private const int RndCnt = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Se_S([ValueSource("_F_Mla_Mls_Se_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong Z, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0u, 1u, 2u, 3u)] uint Index) + { + uint H = (Index >> 1) & 1; + uint L = Index & 1; + + Opcodes |= (L << 21) | (H << 11); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_S); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Se_D([ValueSource("_F_Mla_Mls_Se_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [Values(0u, 1u)] uint Index) + { + uint H = Index & 1; + + Opcodes |= H << 11; + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_D); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Ve_2S_4S([ValueSource("_F_Mla_Mls_Ve_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0u, 1u, 2u, 3u)] uint Index, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + uint H = (Index >> 1) & 1; + uint L = Index & 1; + + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (L << 21) | (H << 11); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_S); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Ve_2D([ValueSource("_F_Mla_Mls_Ve_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [Values(0u, 1u)] uint Index) + { + uint H = Index & 1; + + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= H << 11; + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FPSR.IOC, FpSkips.IfUnderflow, FpTolerances.UpToOneUlps_D); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Se_S([ValueSource("_F_Mul_Mulx_Se_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0u, 1u, 2u, 3u)] uint Index) + { + uint H = (Index >> 1) & 1; + uint L = Index & 1; + + Opcodes |= (L << 21) | (H << 11); + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Se_D([ValueSource("_F_Mul_Mulx_Se_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [Values(0u, 1u)] uint Index) + { + uint H = Index & 1; + + Opcodes |= H << 11; + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Ve_2S_4S([ValueSource("_F_Mul_Mulx_Ve_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0u, 1u, 2u, 3u)] uint Index, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + uint H = (Index >> 1) & 1; + uint L = Index & 1; + + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (L << 21) | (H << 11); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Ve_2D([ValueSource("_F_Mul_Mulx_Ve_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [Values(0u, 1u)] uint Index) + { + uint H = Index & 1; + + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= H << 11; + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B * H); + + int Fpcr = (int)TestContext.CurrentContext.Random.NextUInt() & (1 << (int)FPCR.DN); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2, Fpcr: Fpcr); + + CompareAgainstUnicorn(FpsrMask: FPSR.IOC); + } +#endif + } +} diff --git a/Ryujinx.sln b/Ryujinx.sln index 79af0ed344..148224faa4 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -17,9 +17,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics", "Ryujinx EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,6 +65,10 @@ Global {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs index f4dd77ba4e..3e4d4dd3f4 100644 --- a/Ryujinx/Config.cs +++ b/Ryujinx/Config.cs @@ -1,5 +1,5 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE; -using Ryujinx.HLE.Logging; using Ryujinx.UI.Input; using System; using System.Globalization; @@ -25,11 +25,11 @@ namespace Ryujinx GraphicsConfig.ShadersDumpPath = Parser.Value("Graphics_Shaders_Dump_Path"); - Device.Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"))); - Device.Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"))); - Device.Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info"))); - Device.Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); - Device.Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); + Logger.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"))); + Logger.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"))); + Logger.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info"))); + Logger.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); + Logger.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries); @@ -41,7 +41,7 @@ namespace Ryujinx { foreach (LogClass Class in Enum.GetValues(typeof(LogClass))) { - Device.Log.SetEnable(Class, false); + Logger.SetEnable(Class, false); } } @@ -53,7 +53,7 @@ namespace Ryujinx { if (Class.ToString().ToLower().Contains(LogClass.Trim().ToLower())) { - Device.Log.SetEnable(Class, true); + Logger.SetEnable(Class, true); } } } @@ -68,6 +68,8 @@ namespace Ryujinx Device.System.EnableMultiCoreScheduling(); } + Device.System.EnableFsIntegrityChecks = Convert.ToBoolean(Parser.Value("Enable_FS_Integrity_Checks")); + JoyConKeyboard = new JoyConKeyboard( new JoyConKeyboardLeft diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf index bf361db3e5..45f545d00e 100644 --- a/Ryujinx/Ryujinx.conf +++ b/Ryujinx/Ryujinx.conf @@ -31,6 +31,9 @@ Enable_Vsync = true #Enable or Disable Multi-core scheduling of threads Enable_MultiCore_Scheduling = false +#Enable integrity checks on Switch content files +Enable_FS_Integrity_Checks = true + #Controller Device Index GamePad_Index = 0 diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index ad132ada4a..7bc30d104e 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -15,6 +15,7 @@ + diff --git a/Ryujinx/Ui/ConsoleLog.cs b/Ryujinx/Ui/ConsoleLog.cs index 6fb92e333f..5ae13817e4 100644 --- a/Ryujinx/Ui/ConsoleLog.cs +++ b/Ryujinx/Ui/ConsoleLog.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.Logging; +using Ryujinx.Common.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs index 053cf1be4b..145ff33fd8 100644 --- a/Ryujinx/Ui/Program.cs +++ b/Ryujinx/Ui/Program.cs @@ -1,5 +1,6 @@ using Ryujinx.Audio; using Ryujinx.Audio.OpenAL; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal.OpenGL; using Ryujinx.HLE; @@ -22,7 +23,7 @@ namespace Ryujinx Config.Read(Device); - Device.Log.Updated += ConsoleLog.Log; + Logger.Updated += ConsoleLog.Log; if (args.Length == 1) {