Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
460f9faf4e | |||
552c15739c | |||
0137c9e635 | |||
23fa5f4c9c | |||
4f75e26ec7 | |||
8d8983049e | |||
7969fb6bba | |||
4a4b11871e | |||
e85ee673b1 | |||
42f22fe5d7 | |||
263eb97f79 | |||
3004902257 |
@ -13,14 +13,14 @@
|
|||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="2.2.0" />
|
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
<PackageVersion Include="DynamicData" Version="9.0.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.9" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||||
|
@ -822,6 +822,10 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
|
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
|
||||||
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
||||||
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
|
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
|
||||||
@ -1007,6 +1011,8 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
||||||
|
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
@ -1030,6 +1036,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
|
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
|
SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
||||||
@ -1054,6 +1061,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
|
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
|
||||||
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
|
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
|
||||||
|
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
|
||||||
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
||||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||||
@ -558,6 +560,46 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitHsub8(context, unsigned: true);
|
EmitHsub8(context, unsigned: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Uqadd16(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqadd(context, d, context.Add(n, m), 16);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqadd8(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqadd(context, d, context.Add(n, m), 8);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqsub16(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqsub8(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public static void Usat(ArmEmitterContext context)
|
public static void Usat(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
||||||
@ -934,6 +976,148 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
|
||||||
|
if (saturateTo == 32)
|
||||||
|
{
|
||||||
|
// No saturation possible for this case.
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (saturateTo == 0)
|
||||||
|
{
|
||||||
|
// Result is always zero if we saturate 0 bits.
|
||||||
|
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result is 0, the values are equal and we don't need saturation.
|
||||||
|
Operand lblNoSat = Label();
|
||||||
|
context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
|
||||||
|
|
||||||
|
// Saturate.
|
||||||
|
context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
|
||||||
|
|
||||||
|
Operand lblExit = Label();
|
||||||
|
context.Branch(lblExit);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNoSat);
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
context.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
|
||||||
|
if (saturateTo == 32)
|
||||||
|
{
|
||||||
|
// No saturation possible for this case.
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (saturateTo == 0)
|
||||||
|
{
|
||||||
|
// Result is always zero if we saturate 0 bits.
|
||||||
|
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result is 0, the values are equal and we don't need saturation.
|
||||||
|
Operand lblNoSat = Label();
|
||||||
|
context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
|
||||||
|
|
||||||
|
// Saturate.
|
||||||
|
// Assumes that the value can only underflow, since this is only used for unsigned subtraction.
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
Operand lblExit = Label();
|
||||||
|
context.Branch(lblExit);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNoSat);
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
context.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
Operand tempD = context.AllocateLocal(OperandType.I32);
|
||||||
|
|
||||||
|
Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
|
||||||
|
Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
|
||||||
|
|
||||||
|
tempN = context.ShiftRightUI(rn, Const(16));
|
||||||
|
tempM = context.ShiftRightUI(rm, Const(16));
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
|
||||||
|
{
|
||||||
|
Operand tempD = context.AllocateLocal(OperandType.I32);
|
||||||
|
Operand result = default;
|
||||||
|
|
||||||
|
for (int b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
|
||||||
|
Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
|
||||||
|
|
||||||
|
if (unsigned)
|
||||||
|
{
|
||||||
|
nByte = context.ZeroExtend8(OperandType.I32, nByte);
|
||||||
|
mByte = context.ZeroExtend8(OperandType.I32, mByte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nByte = context.SignExtend8(OperandType.I32, nByte);
|
||||||
|
mByte = context.SignExtend8(OperandType.I32, mByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
elementAction(tempD, nByte, mByte);
|
||||||
|
|
||||||
|
if (b == 0)
|
||||||
|
{
|
||||||
|
result = context.ZeroExtend8(OperandType.I32, tempD);
|
||||||
|
}
|
||||||
|
else if (b < 3)
|
||||||
|
{
|
||||||
|
result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static void EmitAluStore(ArmEmitterContext context, Operand value)
|
private static void EmitAluStore(ArmEmitterContext context, Operand value)
|
||||||
{
|
{
|
||||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||||
|
@ -1246,6 +1246,33 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
|
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vqrdmulh(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||||
|
int eSize = 8 << op.Size;
|
||||||
|
|
||||||
|
EmitVectorBinaryOpI32(context, (op1, op2) =>
|
||||||
|
{
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
op1 = context.SignExtend32(OperandType.I64, op1);
|
||||||
|
op2 = context.SignExtend32(OperandType.I64, op2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand res = context.Multiply(op1, op2);
|
||||||
|
res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
|
||||||
|
res = context.ShiftRightSI(res, Const(eSize - 1));
|
||||||
|
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
|
||||||
|
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
res = context.ConvertI64ToI32(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, signed: true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vqsub(ArmEmitterContext context)
|
public static void Vqsub(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||||
|
@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vswp(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
|
||||||
|
if (op.Q)
|
||||||
|
{
|
||||||
|
Operand temp = context.Copy(GetVecA32(op.Qd));
|
||||||
|
|
||||||
|
context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
|
||||||
|
context.Copy(GetVecA32(op.Qm), temp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
|
||||||
|
|
||||||
|
InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
|
||||||
|
InsertScalar(context, op.Vm, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vtbl(ArmEmitterContext context)
|
public static void Vtbl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
||||||
|
@ -130,6 +130,36 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
|
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vsli_I(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
|
int shift = op.Shift;
|
||||||
|
int eSize = 8 << op.Size;
|
||||||
|
|
||||||
|
ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
|
||||||
|
|
||||||
|
Operand res = GetVec(op.Qd);
|
||||||
|
|
||||||
|
int elems = op.GetBytesCount() >> op.Size;
|
||||||
|
|
||||||
|
for (int index = 0; index < elems; index++)
|
||||||
|
{
|
||||||
|
Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
|
||||||
|
|
||||||
|
Operand neShifted = context.ShiftLeft(me, Const(shift));
|
||||||
|
|
||||||
|
Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
|
||||||
|
|
||||||
|
Operand deMasked = context.BitwiseAnd(de, Const(mask));
|
||||||
|
|
||||||
|
Operand e = context.BitwiseOr(neShifted, deMasked);
|
||||||
|
|
||||||
|
res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVec(op.Qd), res);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vsra(ArmEmitterContext context)
|
public static void Vsra(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
|
@ -571,6 +571,10 @@ namespace ARMeilleure.Instructions
|
|||||||
Umaal,
|
Umaal,
|
||||||
Umlal,
|
Umlal,
|
||||||
Umull,
|
Umull,
|
||||||
|
Uqadd16,
|
||||||
|
Uqadd8,
|
||||||
|
Uqsub16,
|
||||||
|
Uqsub8,
|
||||||
Usat,
|
Usat,
|
||||||
Usat16,
|
Usat16,
|
||||||
Usub8,
|
Usub8,
|
||||||
@ -645,6 +649,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vqdmulh,
|
Vqdmulh,
|
||||||
Vqmovn,
|
Vqmovn,
|
||||||
Vqmovun,
|
Vqmovun,
|
||||||
|
Vqrdmulh,
|
||||||
Vqrshrn,
|
Vqrshrn,
|
||||||
Vqrshrun,
|
Vqrshrun,
|
||||||
Vqshrn,
|
Vqshrn,
|
||||||
@ -666,6 +671,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vshll,
|
Vshll,
|
||||||
Vshr,
|
Vshr,
|
||||||
Vshrn,
|
Vshrn,
|
||||||
|
Vsli,
|
||||||
Vst1,
|
Vst1,
|
||||||
Vst2,
|
Vst2,
|
||||||
Vst3,
|
Vst3,
|
||||||
@ -682,6 +688,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vsub,
|
Vsub,
|
||||||
Vsubl,
|
Vsubl,
|
||||||
Vsubw,
|
Vsubw,
|
||||||
|
Vswp,
|
||||||
Vtbl,
|
Vtbl,
|
||||||
Vtrn,
|
Vtrn,
|
||||||
Vtst,
|
Vtst,
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Memory
|
|
||||||
{
|
|
||||||
public partial class ByteMemoryPool
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
|
|
||||||
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
|
|
||||||
/// with a length of the requested size.
|
|
||||||
/// </summary>
|
|
||||||
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
|
|
||||||
{
|
|
||||||
private byte[] _array;
|
|
||||||
private readonly int _length;
|
|
||||||
|
|
||||||
public ByteMemoryPoolBuffer(int length)
|
|
||||||
{
|
|
||||||
_array = ArrayPool<byte>.Shared.Rent(length);
|
|
||||||
_length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
|
|
||||||
/// </summary>
|
|
||||||
public Memory<byte> Memory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
byte[] array = _array;
|
|
||||||
|
|
||||||
ObjectDisposedException.ThrowIf(array is null, this);
|
|
||||||
|
|
||||||
return new Memory<byte>(array, 0, _length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
var array = Interlocked.Exchange(ref _array, null);
|
|
||||||
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Memory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a pool of re-usable byte array instances.
|
|
||||||
/// </summary>
|
|
||||||
public static partial class ByteMemoryPool
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the maximum buffer size supported by this pool.
|
|
||||||
/// </summary>
|
|
||||||
public static int MaxBufferSize => Array.MaxLength;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer may contain data from a prior use.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> Rent(long length)
|
|
||||||
=> RentImpl(checked((int)length));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer may contain data from a prior use.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> Rent(ulong length)
|
|
||||||
=> RentImpl(checked((int)length));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer may contain data from a prior use.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> Rent(int length)
|
|
||||||
=> RentImpl(length);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> RentCleared(long length)
|
|
||||||
=> RentCleared(checked((int)length));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> RentCleared(ulong length)
|
|
||||||
=> RentCleared(checked((int)length));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
|
||||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length">The buffer's required length in bytes</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static IMemoryOwner<byte> RentCleared(int length)
|
|
||||||
{
|
|
||||||
var buffer = RentImpl(length);
|
|
||||||
|
|
||||||
buffer.Memory.Span.Clear();
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer">The byte buffer to copy</param>
|
|
||||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
|
|
||||||
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> buffer)
|
|
||||||
{
|
|
||||||
var copy = RentImpl(buffer.Length);
|
|
||||||
|
|
||||||
buffer.CopyTo(copy.Memory.Span);
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ByteMemoryPoolBuffer RentImpl(int length)
|
|
||||||
{
|
|
||||||
if ((uint)length > Array.MaxLength)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(length), length, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ByteMemoryPoolBuffer(length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -42,14 +42,14 @@ namespace Ryujinx.Common
|
|||||||
return StreamUtils.StreamToBytes(stream);
|
return StreamUtils.StreamToBytes(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename)
|
public static MemoryOwner<byte> ReadFileToRentedMemory(string filename)
|
||||||
{
|
{
|
||||||
var (assembly, path) = ResolveManifestPath(filename);
|
var (assembly, path) = ResolveManifestPath(filename);
|
||||||
|
|
||||||
return ReadFileToRentedMemory(assembly, path);
|
return ReadFileToRentedMemory(assembly, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
public static MemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
||||||
{
|
{
|
||||||
using var stream = GetStream(assembly, filename);
|
using var stream = GetStream(assembly, filename);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Microsoft.IO;
|
using Microsoft.IO;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using System.Buffers;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -16,7 +15,7 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return output.ToArray();
|
return output.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMemoryOwner<byte> StreamToRentedMemory(Stream input)
|
public static MemoryOwner<byte> StreamToRentedMemory(Stream input)
|
||||||
{
|
{
|
||||||
if (input is MemoryStream inputMemoryStream)
|
if (input is MemoryStream inputMemoryStream)
|
||||||
{
|
{
|
||||||
@ -26,9 +25,9 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
long bytesExpected = input.Length;
|
long bytesExpected = input.Length;
|
||||||
|
|
||||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected);
|
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)bytesExpected));
|
||||||
|
|
||||||
var destSpan = ownedMemory.Memory.Span;
|
var destSpan = ownedMemory.Span;
|
||||||
|
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
@ -66,14 +65,14 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
private static MemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
||||||
{
|
{
|
||||||
input.Position = 0;
|
input.Position = 0;
|
||||||
|
|
||||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length);
|
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)input.Length));
|
||||||
|
|
||||||
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
|
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
|
||||||
_ = input.Read(ownedMemory.Memory.Span);
|
_ = input.Read(ownedMemory.Span);
|
||||||
|
|
||||||
return ownedMemory;
|
return ownedMemory;
|
||||||
}
|
}
|
||||||
|
@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||||
|
|
||||||
Read(va, memoryOwner.Memory.Span);
|
Read(va, memoryOwner.Span);
|
||||||
|
|
||||||
return new WritableRegion(this, va, memoryOwner);
|
return new WritableRegion(this, va, memoryOwner);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||||
{
|
{
|
||||||
|
@ -114,7 +114,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
|
InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
|
||||||
{
|
{
|
||||||
context.Arm64Assembler.Add(d, n, m);
|
context.Arm64Assembler.Add(d, n, m);
|
||||||
EmitSaturateUnsignedRange(context, d, 16);
|
EmitSaturateUqadd(context, d, 16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
|
InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
|
||||||
{
|
{
|
||||||
context.Arm64Assembler.Add(d, n, m);
|
context.Arm64Assembler.Add(d, n, m);
|
||||||
EmitSaturateUnsignedRange(context, d, 8);
|
EmitSaturateUqadd(context, d, 8);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
context.Arm64Assembler.Add(d, n, m);
|
context.Arm64Assembler.Add(d, n, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmitSaturateUnsignedRange(context, d, 16);
|
EmitSaturateUq(context, d, 16, e == 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,25 +157,25 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
context.Arm64Assembler.Sub(d, n, m);
|
context.Arm64Assembler.Sub(d, n, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmitSaturateUnsignedRange(context, d, 16);
|
EmitSaturateUq(context, d, 16, e != 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm)
|
public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm)
|
||||||
{
|
{
|
||||||
InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) =>
|
InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
|
||||||
{
|
{
|
||||||
context.Arm64Assembler.Sub(d, n, m);
|
context.Arm64Assembler.Sub(d, n, m);
|
||||||
EmitSaturateUnsignedRange(context, d, 16);
|
EmitSaturateUqsub(context, d, 16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm)
|
public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm)
|
||||||
{
|
{
|
||||||
InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) =>
|
InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
|
||||||
{
|
{
|
||||||
context.Arm64Assembler.Sub(d, n, m);
|
context.Arm64Assembler.Sub(d, n, m);
|
||||||
EmitSaturateUnsignedRange(context, d, 8);
|
EmitSaturateUqsub(context, d, 8);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +358,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitSaturateUnsignedRange(CodeGenContext context, Operand value, uint saturateTo)
|
private static void EmitSaturateUqadd(CodeGenContext context, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
EmitSaturateUq(context, value, saturateTo, isSub: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUqsub(CodeGenContext context, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
EmitSaturateUq(context, value, saturateTo, isSub: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUq(CodeGenContext context, Operand value, uint saturateTo, bool isSub)
|
||||||
{
|
{
|
||||||
Debug.Assert(saturateTo <= 32);
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
|
||||||
@ -379,7 +389,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const(32 - (int)saturateTo));
|
context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const((int)saturateTo));
|
||||||
|
|
||||||
int branchIndex = context.CodeWriter.InstructionPointer;
|
int branchIndex = context.CodeWriter.InstructionPointer;
|
||||||
|
|
||||||
@ -387,7 +397,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
context.Arm64Assembler.Cbz(tempRegister.Operand, 0);
|
context.Arm64Assembler.Cbz(tempRegister.Operand, 0);
|
||||||
|
|
||||||
// Saturate.
|
// Saturate.
|
||||||
context.Arm64Assembler.Mov(value, uint.MaxValue >> (32 - (int)saturateTo));
|
context.Arm64Assembler.Mov(value, isSub ? 0u : uint.MaxValue >> (32 - (int)saturateTo));
|
||||||
|
|
||||||
int delta = context.CodeWriter.InstructionPointer - branchIndex;
|
int delta = context.CodeWriter.InstructionPointer - branchIndex;
|
||||||
context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5));
|
context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5));
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
@ -113,25 +112,6 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetLevelsClamped()
|
|
||||||
{
|
|
||||||
int maxSize = Width;
|
|
||||||
|
|
||||||
if (Target != Target.Texture1D &&
|
|
||||||
Target != Target.Texture1DArray)
|
|
||||||
{
|
|
||||||
maxSize = Math.Max(maxSize, Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Target == Target.Texture3D)
|
|
||||||
{
|
|
||||||
maxSize = Math.Max(maxSize, Depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
|
|
||||||
return Math.Min(Levels, maxLevels);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetLevelSize(int size, int level)
|
private static int GetLevelSize(int size, int level)
|
||||||
{
|
{
|
||||||
return Math.Max(1, size >> level);
|
return Math.Max(1, size >> level);
|
||||||
|
@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
|
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
|
||||||
public bool SamplerPoolModified()
|
public bool SamplerPoolModified()
|
||||||
{
|
{
|
||||||
return SamplerPool.WasModified(ref _samplerPoolSequence);
|
return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,6 +516,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if any of our cached samplers changed on the pool.
|
// Check if any of our cached samplers changed on the pool.
|
||||||
|
if (SamplerPool != null)
|
||||||
|
{
|
||||||
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
|
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
|
||||||
{
|
{
|
||||||
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
|
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
|
||||||
@ -524,6 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -899,13 +902,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sampler sampler = samplerPool?.Get(samplerId);
|
|
||||||
|
|
||||||
entry.TextureIds[textureId] = (texture, descriptor);
|
entry.TextureIds[textureId] = (texture, descriptor);
|
||||||
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
|
|
||||||
|
|
||||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
ISampler hostSampler = null;
|
||||||
|
|
||||||
|
if (!isImage && bindingInfo.Target != Target.TextureBuffer)
|
||||||
|
{
|
||||||
|
Sampler sampler = samplerPool?.Get(samplerId);
|
||||||
|
|
||||||
|
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
|
||||||
|
|
||||||
|
hostSampler = sampler?.GetHostSampler(texture);
|
||||||
|
}
|
||||||
|
|
||||||
Format format = bindingInfo.Format;
|
Format format = bindingInfo.Format;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using Ryujinx.Memory.Range;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
@ -490,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
levels = (maxLod - minLod) + 1;
|
levels = (maxLod - minLod) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
levels = ClampLevels(target, width, height, depthOrLayers, levels);
|
||||||
|
|
||||||
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
|
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
|
||||||
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
|
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
|
||||||
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
|
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
|
||||||
@ -540,6 +543,34 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
swizzleA);
|
swizzleA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Number of texture dimensions (1D, 2D, 3D, Cube, etc)</param>
|
||||||
|
/// <param name="width">Width of the texture</param>
|
||||||
|
/// <param name="height">Height of the texture, ignored for 1D textures</param>
|
||||||
|
/// <param name="depthOrLayers">Depth of the texture for 3D textures, otherwise ignored</param>
|
||||||
|
/// <param name="levels">Original amount of mipmap levels</param>
|
||||||
|
/// <returns>Clamped mipmap levels</returns>
|
||||||
|
private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels)
|
||||||
|
{
|
||||||
|
int maxSize = width;
|
||||||
|
|
||||||
|
if (target != Target.Texture1D &&
|
||||||
|
target != Target.Texture1DArray)
|
||||||
|
{
|
||||||
|
maxSize = Math.Max(maxSize, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == Target.Texture3D)
|
||||||
|
{
|
||||||
|
maxSize = Math.Max(maxSize, depthOrLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
|
||||||
|
return Math.Min(levels, maxLevels);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
|
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
|
||||||
/// The depth-stencil mode is determined based on how the driver sets those parameters.
|
/// The depth-stencil mode is determined based on how the driver sets those parameters.
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
|
internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.Levels;
|
||||||
|
|
||||||
switch (Info.Target)
|
switch (Info.Target)
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
pixelInternalFormat = format.PixelInternalFormat;
|
pixelInternalFormat = format.PixelInternalFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.Levels;
|
||||||
|
|
||||||
GL.TextureView(
|
GL.TextureView(
|
||||||
Handle,
|
Handle,
|
||||||
@ -267,7 +267,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
public unsafe PinnedSpan<byte> GetData()
|
public unsafe PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
int size = 0;
|
int size = 0;
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.Levels;
|
||||||
|
|
||||||
for (int level = 0; level < levels; level++)
|
for (int level = 0; level < levels; level++)
|
||||||
{
|
{
|
||||||
@ -426,7 +426,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
faces = 6;
|
faces = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.Levels;
|
||||||
|
|
||||||
for (int level = 0; level < levels; level++)
|
for (int level = 0; level < levels; level++)
|
||||||
{
|
{
|
||||||
@ -716,7 +716,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
int width = Info.Width;
|
int width = Info.Width;
|
||||||
int height = Info.Height;
|
int height = Info.Height;
|
||||||
int depth = Info.Depth;
|
int depth = Info.Depth;
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.Levels;
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_cachedCommandBufferIndex = -1;
|
_cachedCommandBufferIndex = -1;
|
||||||
_storages = null;
|
_storages = null;
|
||||||
SetDirty(_gd);
|
SetDirty(_gd, isImage: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||||
|
@ -14,15 +14,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private int _bindCount;
|
private int _bindCount;
|
||||||
|
|
||||||
protected void SetDirty(VulkanRenderer gd)
|
protected void SetDirty(VulkanRenderer gd, bool isImage)
|
||||||
{
|
{
|
||||||
ReleaseDescriptorSet();
|
ReleaseDescriptorSet();
|
||||||
|
|
||||||
if (_bindCount != 0)
|
if (_bindCount != 0)
|
||||||
|
{
|
||||||
|
if (isImage)
|
||||||
|
{
|
||||||
|
gd.PipelineInternal.ForceImageDirty();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
gd.PipelineInternal.ForceTextureDirty();
|
gd.PipelineInternal.ForceTextureDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetCachedDescriptorSets(CommandBufferScoped cbs, ShaderCollection program, int setIndex, out DescriptorSet[] sets)
|
public bool TryGetCachedDescriptorSets(CommandBufferScoped cbs, ShaderCollection program, int setIndex, out DescriptorSet[] sets)
|
||||||
{
|
{
|
||||||
|
@ -104,7 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_cachedCommandBufferIndex = -1;
|
_cachedCommandBufferIndex = -1;
|
||||||
_storages = null;
|
_storages = null;
|
||||||
SetDirty(_gd);
|
SetDirty(_gd, isImage: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||||
|
@ -256,6 +256,12 @@ namespace Ryujinx
|
|||||||
MainWindow mainWindow = new();
|
MainWindow mainWindow = new();
|
||||||
mainWindow.Show();
|
mainWindow.Show();
|
||||||
|
|
||||||
|
// Load the game table if no application was requested by the command line
|
||||||
|
if (CommandLineState.LaunchPathArg == null)
|
||||||
|
{
|
||||||
|
mainWindow.UpdateGameTable();
|
||||||
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
|
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
|
||||||
|
@ -187,7 +187,10 @@ namespace Ryujinx.UI
|
|||||||
: IntegrityCheckLevel.None;
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
// Instantiate GUI objects.
|
// Instantiate GUI objects.
|
||||||
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
|
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
|
||||||
|
{
|
||||||
|
DesiredLanguage = ConfigurationState.Instance.System.Language,
|
||||||
|
};
|
||||||
_uiHandler = new GtkHostUIHandler(this);
|
_uiHandler = new GtkHostUIHandler(this);
|
||||||
_deviceExitStatus = new AutoResetEvent(false);
|
_deviceExitStatus = new AutoResetEvent(false);
|
||||||
|
|
||||||
@ -325,7 +328,6 @@ namespace Ryujinx.UI
|
|||||||
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
|
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
|
||||||
|
|
||||||
UpdateColumns();
|
UpdateColumns();
|
||||||
UpdateGameTable();
|
|
||||||
|
|
||||||
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
|
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
|
||||||
{
|
{
|
||||||
@ -738,7 +740,8 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
Thread applicationLibraryThread = new(() =>
|
||||||
{
|
{
|
||||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
|
||||||
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
|
||||||
|
|
||||||
_updatingGameTable = false;
|
_updatingGameTable = false;
|
||||||
})
|
})
|
||||||
|
@ -40,5 +40,12 @@ namespace Ryujinx.HLE.HOS.Services.Nim
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(5)] // 17.0.0+
|
||||||
|
// CreateServerInterface2(pid, handle<unknown>, u64) -> object<nn::ec::IshopServiceAccessServer>
|
||||||
|
public ResultCode CreateServerInterface2(ServiceCtx context)
|
||||||
|
{
|
||||||
|
return CreateServerInterface(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,22 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
MultiRange Range { get; }
|
MultiRange Range { get; }
|
||||||
|
|
||||||
ulong BaseAddress => Range.GetSubRange(0).Address;
|
ulong BaseAddress
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
for (int index = 0; index < Range.Count; index++)
|
||||||
|
{
|
||||||
|
MemoryRange subRange = Range.GetSubRange(index);
|
||||||
|
|
||||||
|
if (!MemoryRange.IsInvalid(ref subRange))
|
||||||
|
{
|
||||||
|
return subRange.Address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MemoryRange.InvalidAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,11 @@ namespace Ryujinx.Memory.Range
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly record struct MemoryRange
|
public readonly record struct MemoryRange
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Special address value used to indicate than an address is invalid.
|
||||||
|
/// </summary>
|
||||||
|
internal const ulong InvalidAddress = ulong.MaxValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An empty memory range, with a null address and zero size.
|
/// An empty memory range, with a null address and zero size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,13 +63,24 @@ namespace Ryujinx.Memory.Range
|
|||||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a given sub-range of memory is invalid.
|
||||||
|
/// Those are used to represent unmapped memory regions (holes in the region mapping).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subRange">Memory range to check</param>
|
||||||
|
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
|
||||||
|
internal static bool IsInvalid(ref MemoryRange subRange)
|
||||||
|
{
|
||||||
|
return subRange.Address == InvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a string summary of the memory range.
|
/// Returns a string summary of the memory range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A string summary of the memory range</returns>
|
/// <returns>A string summary of the memory range</returns>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Address == ulong.MaxValue)
|
if (Address == InvalidAddress)
|
||||||
{
|
{
|
||||||
return $"[Unmapped 0x{Size:X}]";
|
return $"[Unmapped 0x{Size:X}]";
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
var subrange = range.GetSubRange(i);
|
var subrange = range.GetSubRange(i);
|
||||||
|
|
||||||
if (IsInvalid(ref subrange))
|
if (MemoryRange.IsInvalid(ref subrange))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
var subrange = range.GetSubRange(i);
|
var subrange = range.GetSubRange(i);
|
||||||
|
|
||||||
if (IsInvalid(ref subrange))
|
if (MemoryRange.IsInvalid(ref subrange))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
var subrange = range.GetSubRange(i);
|
var subrange = range.GetSubRange(i);
|
||||||
|
|
||||||
if (IsInvalid(ref subrange))
|
if (MemoryRange.IsInvalid(ref subrange))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -142,17 +142,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
return overlapCount;
|
return overlapCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a given sub-range of memory is invalid.
|
|
||||||
/// Those are used to represent unmapped memory regions (holes in the region mapping).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="subRange">Memory range to checl</param>
|
|
||||||
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
|
|
||||||
private static bool IsInvalid(ref MemoryRange subRange)
|
|
||||||
{
|
|
||||||
return subRange.Address == ulong.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all items on the list starting at the specified memory address.
|
/// Gets all items on the list starting at the specified memory address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -130,9 +130,9 @@ namespace Ryujinx.Memory
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||||
|
|
||||||
Read(va, memoryOwner.Memory.Span);
|
Read(va, memoryOwner.Span);
|
||||||
|
|
||||||
return new WritableRegion(this, va, memoryOwner);
|
return new WritableRegion(this, va, memoryOwner);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,24 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uint[] UQAddSub16()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
0xe6600f10u, // UQADD16 R0, R0, R0
|
||||||
|
0xe6600f70u, // UQSUB16 R0, R0, R0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint[] UQAddSub8()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
0xe6600f90u, // UQADD8 R0, R0, R0
|
||||||
|
0xe6600ff0u, // UQSUB8 R0, R0, R0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static uint[] SsatUsat()
|
private static uint[] SsatUsat()
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
@ -182,6 +200,42 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test, Pairwise]
|
||||||
|
public void U_Q_AddSub_16([ValueSource(nameof(UQAddSub16))] uint opcode,
|
||||||
|
[Values(0u, 0xdu)] uint rd,
|
||||||
|
[Values(1u)] uint rm,
|
||||||
|
[Values(2u)] uint rn,
|
||||||
|
[Random(RndCnt)] uint w0,
|
||||||
|
[Random(RndCnt)] uint w1,
|
||||||
|
[Random(RndCnt)] uint w2)
|
||||||
|
{
|
||||||
|
opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16);
|
||||||
|
|
||||||
|
uint sp = TestContext.CurrentContext.Random.NextUInt();
|
||||||
|
|
||||||
|
SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp);
|
||||||
|
|
||||||
|
CompareAgainstUnicorn();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test, Pairwise]
|
||||||
|
public void U_Q_AddSub_8([ValueSource(nameof(UQAddSub8))] uint opcode,
|
||||||
|
[Values(0u, 0xdu)] uint rd,
|
||||||
|
[Values(1u)] uint rm,
|
||||||
|
[Values(2u)] uint rn,
|
||||||
|
[Random(RndCnt)] uint w0,
|
||||||
|
[Random(RndCnt)] uint w1,
|
||||||
|
[Random(RndCnt)] uint w2)
|
||||||
|
{
|
||||||
|
opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16);
|
||||||
|
|
||||||
|
uint sp = TestContext.CurrentContext.Random.NextUInt();
|
||||||
|
|
||||||
|
SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp);
|
||||||
|
|
||||||
|
CompareAgainstUnicorn();
|
||||||
|
}
|
||||||
|
|
||||||
[Test, Pairwise]
|
[Test, Pairwise]
|
||||||
public void Uadd8_Sel([Values(0u)] uint rd,
|
public void Uadd8_Sel([Values(0u)] uint rd,
|
||||||
[Values(1u)] uint rm,
|
[Values(1u)] uint rm,
|
||||||
|
@ -327,6 +327,32 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
|
|
||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test, Pairwise, Description("VSWP D0, D0")]
|
||||||
|
public void Vswp([Values(0u, 1u)] uint rd,
|
||||||
|
[Values(0u, 1u)] uint rm,
|
||||||
|
[Values] bool q)
|
||||||
|
{
|
||||||
|
uint opcode = 0xf3b20000u; // VSWP D0, D0
|
||||||
|
|
||||||
|
if (q)
|
||||||
|
{
|
||||||
|
opcode |= 1u << 6;
|
||||||
|
|
||||||
|
rd &= ~1u;
|
||||||
|
rm &= ~1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||||
|
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||||
|
|
||||||
|
V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong());
|
||||||
|
V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong());
|
||||||
|
|
||||||
|
SingleOpcode(opcode, v0: v0, v1: v1);
|
||||||
|
|
||||||
|
CompareAgainstUnicorn();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -909,6 +909,39 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test, Pairwise, Description("VQRDMULH.<S16, S32> <Qd>, <Qn>, <Qm>")]
|
||||||
|
public void Vqrdmulh_I([Range(0u, 5u)] uint rd,
|
||||||
|
[Range(0u, 5u)] uint rn,
|
||||||
|
[Range(0u, 5u)] uint rm,
|
||||||
|
[ValueSource(nameof(_8B4H2S1D_))] ulong z,
|
||||||
|
[ValueSource(nameof(_8B4H2S1D_))] ulong a,
|
||||||
|
[ValueSource(nameof(_8B4H2S1D_))] ulong b,
|
||||||
|
[Values(1u, 2u)] uint size) // <S16, S32>
|
||||||
|
{
|
||||||
|
rd >>= 1;
|
||||||
|
rd <<= 1;
|
||||||
|
rn >>= 1;
|
||||||
|
rn <<= 1;
|
||||||
|
rm >>= 1;
|
||||||
|
rm <<= 1;
|
||||||
|
|
||||||
|
uint opcode = 0xf3100b40u & ~(3u << 20); // VQRDMULH.S16 Q0, Q0, Q0
|
||||||
|
|
||||||
|
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||||
|
opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3);
|
||||||
|
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||||
|
|
||||||
|
opcode |= (size & 0x3) << 20;
|
||||||
|
|
||||||
|
V128 v0 = MakeVectorE0E1(z, ~z);
|
||||||
|
V128 v1 = MakeVectorE0E1(a, ~a);
|
||||||
|
V128 v2 = MakeVectorE0E1(b, ~b);
|
||||||
|
|
||||||
|
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
|
||||||
|
|
||||||
|
CompareAgainstUnicorn();
|
||||||
|
}
|
||||||
|
|
||||||
[Test, Pairwise]
|
[Test, Pairwise]
|
||||||
public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd,
|
public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd,
|
||||||
[Values(0u, 2u, 4u, 8u)] uint rm,
|
[Values(0u, 2u, 4u, 8u)] uint rm,
|
||||||
|
@ -202,7 +202,7 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test, Pairwise, Description("VSHL.<size> {<Vd>}, <Vm>, #<imm>")]
|
[Test, Pairwise, Description("VSHL.<size> {<Vd>}, <Vm>, #<imm>")]
|
||||||
public void Vshl_Imm([Values(0u)] uint rd,
|
public void Vshl_Imm([Values(0u, 1u)] uint rd,
|
||||||
[Values(2u, 0u)] uint rm,
|
[Values(2u, 0u)] uint rm,
|
||||||
[Values(0u, 1u, 2u, 3u)] uint size,
|
[Values(0u, 1u, 2u, 3u)] uint size,
|
||||||
[Random(RndCntShiftImm)] uint shiftImm,
|
[Random(RndCntShiftImm)] uint shiftImm,
|
||||||
@ -262,6 +262,40 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
CompareAgainstUnicorn();
|
CompareAgainstUnicorn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test, Pairwise, Description("VSLI.<size> {<Vd>}, <Vm>, #<imm>")]
|
||||||
|
public void Vsli([Values(0u, 1u)] uint rd,
|
||||||
|
[Values(2u, 0u)] uint rm,
|
||||||
|
[Values(0u, 1u, 2u, 3u)] uint size,
|
||||||
|
[Random(RndCntShiftImm)] uint shiftImm,
|
||||||
|
[Random(RndCnt)] ulong z,
|
||||||
|
[Random(RndCnt)] ulong a,
|
||||||
|
[Random(RndCnt)] ulong b,
|
||||||
|
[Values] bool q)
|
||||||
|
{
|
||||||
|
uint opcode = 0xf3800510u; // VORR.I32 D0, #0x800000 (immediate value changes it into SLI)
|
||||||
|
if (q)
|
||||||
|
{
|
||||||
|
opcode |= 1 << 6;
|
||||||
|
rm <<= 1;
|
||||||
|
rd <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint imm = 1u << ((int)size + 3);
|
||||||
|
imm |= shiftImm & (imm - 1);
|
||||||
|
|
||||||
|
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
|
||||||
|
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
|
||||||
|
opcode |= ((imm & 0x3f) << 16) | ((imm & 0x40) << 1);
|
||||||
|
|
||||||
|
V128 v0 = MakeVectorE0E1(z, z);
|
||||||
|
V128 v1 = MakeVectorE0E1(a, z);
|
||||||
|
V128 v2 = MakeVectorE0E1(b, z);
|
||||||
|
|
||||||
|
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
|
||||||
|
|
||||||
|
CompareAgainstUnicorn();
|
||||||
|
}
|
||||||
|
|
||||||
[Test, Pairwise]
|
[Test, Pairwise]
|
||||||
public void Vqshrn_Vqrshrn_Vrshrn_Imm([ValueSource(nameof(_Vqshrn_Vqrshrn_Vrshrn_Imm_))] uint opcode,
|
public void Vqshrn_Vqrshrn_Vrshrn_Imm([ValueSource(nameof(_Vqshrn_Vqrshrn_Vrshrn_Imm_))] uint opcode,
|
||||||
[Values(0u, 1u)] uint rd,
|
[Values(0u, 1u)] uint rd,
|
||||||
|
@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
{
|
{
|
||||||
public class ApplicationLibrary
|
public class ApplicationLibrary
|
||||||
{
|
{
|
||||||
|
public Language DesiredLanguage { get; set; }
|
||||||
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||||
|
|
||||||
@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common
|
|||||||
|
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly IntegrityCheckLevel _checkLevel;
|
private readonly IntegrityCheckLevel _checkLevel;
|
||||||
private Language _desiredTitleLanguage;
|
|
||||||
private CancellationTokenSource _cancellationToken;
|
private CancellationTokenSource _cancellationToken;
|
||||||
|
|
||||||
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
{
|
{
|
||||||
using UniqueRef<IFile> icon = new();
|
using UniqueRef<IFile> icon = new();
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using MemoryStream stream = new();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
@ -431,6 +431,9 @@ namespace Ryujinx.UI.App.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var data in applications)
|
foreach (var data in applications)
|
||||||
|
{
|
||||||
|
// Only load metadata for applications with an ID
|
||||||
|
if (data.Id != 0)
|
||||||
{
|
{
|
||||||
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
|
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
|
||||||
{
|
{
|
||||||
@ -461,6 +464,8 @@ namespace Ryujinx.UI.App.Common
|
|||||||
data.Favorite = appMetadata.Favorite;
|
data.Favorite = appMetadata.Favorite;
|
||||||
data.TimePlayed = appMetadata.TimePlayed;
|
data.TimePlayed = appMetadata.TimePlayed;
|
||||||
data.LastPlayed = appMetadata.LastPlayed;
|
data.LastPlayed = appMetadata.LastPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
||||||
data.FileSize = fileSize;
|
data.FileSize = fileSize;
|
||||||
data.Path = applicationPath;
|
data.Path = applicationPath;
|
||||||
@ -482,13 +487,11 @@ namespace Ryujinx.UI.App.Common
|
|||||||
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
|
public void LoadApplications(List<string> appDirs)
|
||||||
{
|
{
|
||||||
int numApplicationsFound = 0;
|
int numApplicationsFound = 0;
|
||||||
int numApplicationsLoaded = 0;
|
int numApplicationsLoaded = 0;
|
||||||
|
|
||||||
_desiredTitleLanguage = desiredTitleLanguage;
|
|
||||||
|
|
||||||
_cancellationToken = new CancellationTokenSource();
|
_cancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
// Builds the applications list with paths to found applications
|
// Builds the applications list with paths to found applications
|
||||||
@ -847,7 +850,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
|
|
||||||
private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
|
private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
|
||||||
{
|
{
|
||||||
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
_ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||||
|
|
||||||
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
||||||
{
|
{
|
||||||
|
@ -21,7 +21,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
|
|
||||||
var builder = new CompiledBindingPathBuilder();
|
var builder = new CompiledBindingPathBuilder();
|
||||||
|
|
||||||
builder.SetRawSource(LocaleManager.Instance)
|
builder
|
||||||
.Property(new ClrPropertyInfo("Item",
|
.Property(new ClrPropertyInfo("Item",
|
||||||
obj => (LocaleManager.Instance[keyToUse]),
|
obj => (LocaleManager.Instance[keyToUse]),
|
||||||
null,
|
null,
|
||||||
@ -32,7 +32,10 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
|
|
||||||
var path = builder.Build();
|
var path = builder.Build();
|
||||||
|
|
||||||
var binding = new CompiledBindingExtension(path);
|
var binding = new CompiledBindingExtension(path)
|
||||||
|
{
|
||||||
|
Source = LocaleManager.Instance
|
||||||
|
};
|
||||||
|
|
||||||
return binding.ProvideValue(serviceProvider);
|
return binding.ProvideValue(serviceProvider);
|
||||||
}
|
}
|
||||||
|
@ -139,9 +139,11 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
|
|
||||||
foreach (var item in locale)
|
foreach (var item in locale)
|
||||||
{
|
{
|
||||||
this[item.Key] = item.Value;
|
_localeStrings[item.Key] = item.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged("Item");
|
||||||
|
|
||||||
LocaleChanged?.Invoke();
|
LocaleChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs
Normal file
43
src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Ryujinx.UI.App.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements a custom comparer which is used for sorting titles by favorite on a UI.
|
||||||
|
/// Returns a sorted list of favorites in alphabetical order, followed by all non-favorites sorted alphabetical.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct AppListFavoriteComparable : IComparable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The application data being compared.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ApplicationData app;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new <see cref="AppListFavoriteComparable"/> with the specified application data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The app data being compared.</param>
|
||||||
|
public AppListFavoriteComparable(ApplicationData app)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(app, nameof(app));
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly int CompareTo(object o)
|
||||||
|
{
|
||||||
|
if (o is AppListFavoriteComparable other)
|
||||||
|
{
|
||||||
|
if (app.Favorite == other.app.Favorite)
|
||||||
|
{
|
||||||
|
return string.Compare(app.Name, other.app.Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Favorite ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidCastException($"Cannot cast {o.GetType()} to {nameof(AppListFavoriteComparable)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -965,8 +965,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
|
||||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||||
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
ApplicationSort.Favorite => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => new AppListFavoriteComparable(app))
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
: SortExpressionComparer<ApplicationData>.Descending(app => new AppListFavoriteComparable(app)),
|
||||||
_ => null,
|
_ => null,
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
Profiles = new ObservableCollection<BaseModel>();
|
Profiles = new ObservableCollection<BaseModel>();
|
||||||
LostProfiles = new ObservableCollection<UserProfile>();
|
LostProfiles = new ObservableCollection<UserProfile>();
|
||||||
IsEmpty = LostProfiles.IsNullOrEmpty();
|
IsEmpty = !LostProfiles.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<BaseModel> Profiles { get; set; }
|
public ObservableCollection<BaseModel> Profiles { get; set; }
|
||||||
|
@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||||
|
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
|
private bool _applicationsLoadedOnce;
|
||||||
|
|
||||||
private UserChannelPersistence _userChannelPersistence;
|
private UserChannelPersistence _userChannelPersistence;
|
||||||
private static bool _deferLoad;
|
private static bool _deferLoad;
|
||||||
@ -224,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
? IntegrityCheckLevel.ErrorOnInvalid
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
: IntegrityCheckLevel.None;
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
|
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel)
|
||||||
|
{
|
||||||
|
DesiredLanguage = ConfigurationState.Instance.System.Language,
|
||||||
|
};
|
||||||
|
|
||||||
// Save data created before we supported extra data in directory save data will not work properly if
|
// Save data created before we supported extra data in directory save data will not work properly if
|
||||||
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
||||||
@ -472,7 +476,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
ViewModel.RefreshFirmwareStatus();
|
ViewModel.RefreshFirmwareStatus();
|
||||||
|
|
||||||
|
// Load applications if no application was requested by the command line
|
||||||
|
if (!_deferLoad)
|
||||||
|
{
|
||||||
LoadApplications();
|
LoadApplications();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
CheckLaunchState();
|
CheckLaunchState();
|
||||||
@ -485,6 +493,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
if (MainContent.Content != content)
|
if (MainContent.Content != content)
|
||||||
{
|
{
|
||||||
|
// Load applications while switching to the GameLibrary if we haven't done that yet
|
||||||
|
if (!_applicationsLoadedOnce && content == GameLibrary)
|
||||||
|
{
|
||||||
|
LoadApplications();
|
||||||
|
}
|
||||||
|
|
||||||
MainContent.Content = content;
|
MainContent.Content = content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -581,6 +595,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public void LoadApplications()
|
public void LoadApplications()
|
||||||
{
|
{
|
||||||
|
_applicationsLoadedOnce = true;
|
||||||
ViewModel.Applications.Clear();
|
ViewModel.Applications.Clear();
|
||||||
|
|
||||||
StatusBarView.LoadProgressBar.IsVisible = true;
|
StatusBarView.LoadProgressBar.IsVisible = true;
|
||||||
@ -622,7 +637,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
Thread applicationLibraryThread = new(() =>
|
||||||
{
|
{
|
||||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
|
||||||
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
|
||||||
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user