Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6dbcdfea47 | ||
|
c5258cf082 | ||
|
5c89e22bb9 | ||
|
11ecff2ff0 | ||
|
4c3f09644a | ||
|
e187a8870a | ||
|
a64fee29dc | ||
|
9ef94c8292 | ||
|
915d6d044c | ||
|
a4780ab33b | ||
|
a947a45d81 | ||
|
9db73f74cf | ||
|
a1efd87c45 | ||
|
49be977588 | ||
|
c95be55091 | ||
|
63dedbda86 | ||
|
c532118d94 | ||
|
52d6f2e656 | ||
|
c9bc4eaf58 | ||
|
3249f8ff41 | ||
|
1b41b285ac | ||
|
f5a6f45b27 | ||
|
210557951b | ||
|
4c2d9ff3ff | ||
|
8198b99935 | ||
|
460f96967d | ||
|
7ca779a26d | ||
|
b5032b3c91 | ||
|
f0a3dff136 | ||
|
f659dcb9d8 | ||
|
a34fb0e939 | ||
|
21ce8a9b80 | ||
|
9ecbee8032 | ||
|
80519af67d | ||
|
26e30faff3 | ||
|
0992310b76 | ||
|
009c1101d2 | ||
|
ba95ee54ab |
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -112,6 +112,17 @@ jobs:
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
|
@@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
|
||||
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
|
||||
Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi));
|
||||
Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister));
|
||||
Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister));
|
||||
Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister));
|
||||
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
|
||||
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));
|
||||
|
@@ -268,11 +268,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
|
@@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
|
||||
case IntrinsicType.Mxcsr:
|
||||
{
|
||||
Operand offset = operation.GetSource(0);
|
||||
Operand bits = operation.GetSource(1);
|
||||
|
||||
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant);
|
||||
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32);
|
||||
Debug.Assert(offset.Kind == OperandKind.Constant);
|
||||
Debug.Assert(offset.Type == OperandType.I32);
|
||||
|
||||
int offs = offset.AsInt32() + context.CallArgsRegionSize;
|
||||
|
||||
@@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
|
||||
|
||||
context.Assembler.Stmxcsr(memOp);
|
||||
|
||||
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
|
||||
if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||
{
|
||||
context.Assembler.Or(memOp, bits, OperandType.I32);
|
||||
Operand bits = operation.GetSource(1);
|
||||
Debug.Assert(bits.Type == OperandType.I32);
|
||||
|
||||
context.Assembler.Mov(memOp, bits, OperandType.I32);
|
||||
context.Assembler.Ldmxcsr(memOp);
|
||||
}
|
||||
else /* if (intrinOp.Intrinsic == Intrinsic.X86Mxcsrub) */
|
||||
else if (operation.Intrinsic == Intrinsic.X86Stmxcsr)
|
||||
{
|
||||
Operand notBits = Const(~bits.AsInt32());
|
||||
Operand dest = operation.Destination;
|
||||
Debug.Assert(dest.Type == OperandType.I32);
|
||||
|
||||
context.Assembler.And(memOp, notBits, OperandType.I32);
|
||||
context.Assembler.Stmxcsr(memOp);
|
||||
context.Assembler.Mov(dest, memOp, OperandType.I32);
|
||||
}
|
||||
|
||||
context.Assembler.Ldmxcsr(memOp);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -34,6 +34,12 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
private static uint GetXcr0Eax()
|
||||
{
|
||||
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
|
||||
{
|
||||
// XSAVE feature required for xgetbv
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
|
||||
{
|
||||
0x31, 0xc9, // xor ecx, ecx
|
||||
@@ -70,6 +76,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Sse42 = 1 << 20,
|
||||
Popcnt = 1 << 23,
|
||||
Aes = 1 << 25,
|
||||
Xsave = 1 << 26,
|
||||
Osxsave = 1 << 27,
|
||||
Avx = 1 << 28,
|
||||
F16c = 1 << 29
|
||||
@@ -118,9 +125,9 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
|
||||
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
||||
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Osxsave)
|
||||
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
|
||||
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
|
||||
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
|
||||
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
|
||||
|
@@ -60,6 +60,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
|
||||
Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary));
|
||||
@@ -75,8 +76,6 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mxcsrmb, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Mask bits.
|
||||
Add(Intrinsic.X86Mxcsrub, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Unmask bits.
|
||||
Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary));
|
||||
@@ -160,6 +159,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
|
||||
Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
|
||||
@@ -170,11 +170,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||
|
15
ARMeilleure/CodeGen/X86/Mxcsr.cs
Normal file
15
ARMeilleure/CodeGen/X86/Mxcsr.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
[Flags]
|
||||
enum Mxcsr
|
||||
{
|
||||
Ftz = 1 << 15, // Flush To Zero.
|
||||
Rhi = 1 << 14, // Round Mode high bit.
|
||||
Rlo = 1 << 13, // Round Mode low bit.
|
||||
Um = 1 << 11, // Underflow Mask.
|
||||
Dm = 1 << 8, // Denormal Mask.
|
||||
Daz = 1 << 6 // Denormals Are Zero.
|
||||
}
|
||||
}
|
@@ -120,12 +120,18 @@ namespace ARMeilleure.CodeGen.X86
|
||||
break;
|
||||
|
||||
case Instruction.Extended:
|
||||
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub)
|
||||
if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||
{
|
||||
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
||||
|
||||
node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) });
|
||||
}
|
||||
else if (node.Intrinsic == Intrinsic.X86Stmxcsr)
|
||||
{
|
||||
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
||||
|
||||
node.SetSources(new Operand[] { Const(stackOffset) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -208,11 +208,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Vblendvps,
|
||||
Vcvtph2ps,
|
||||
Vcvtps2ph,
|
||||
Vfmadd231pd,
|
||||
Vfmadd231ps,
|
||||
Vfmadd231sd,
|
||||
Vfmadd231ss,
|
||||
Vfmsub231sd,
|
||||
Vfmsub231ss,
|
||||
Vfnmadd231pd,
|
||||
Vfnmadd231ps,
|
||||
Vfnmadd231sd,
|
||||
Vfnmadd231ss,
|
||||
|
@@ -108,6 +108,13 @@ namespace ARMeilleure.Decoders
|
||||
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
|
||||
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
||||
SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
||||
SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create);
|
||||
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
|
||||
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);
|
||||
|
@@ -615,14 +615,11 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
|
||||
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
|
||||
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
|
||||
|
||||
return context.AddIntrinsic(addInst, op1, op2);
|
||||
}, scalar: false, op1, op2);
|
||||
return context.AddIntrinsic(addInst, op1, op2);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@@ -696,17 +693,33 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@@ -730,10 +743,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: true, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: true);
|
||||
}
|
||||
else
|
||||
@@ -755,10 +765,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false);
|
||||
}
|
||||
else
|
||||
@@ -886,10 +893,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@@ -914,10 +918,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@@ -940,10 +941,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: true, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: true);
|
||||
}
|
||||
else
|
||||
@@ -965,10 +963,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false);
|
||||
}
|
||||
else
|
||||
@@ -1096,10 +1091,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@@ -1124,10 +1116,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@@ -1146,6 +1135,37 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe);
|
||||
}
|
||||
else if (Optimizations.UseFma)
|
||||
{
|
||||
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 1;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||
@@ -1171,11 +1191,19 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@@ -1186,9 +1214,15 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@@ -1224,8 +1258,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@@ -1240,8 +1281,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@@ -1261,6 +1309,37 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe);
|
||||
}
|
||||
else if (Optimizations.UseFma)
|
||||
{
|
||||
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 1;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||
@@ -1286,11 +1365,19 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@@ -1301,9 +1388,15 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@@ -1339,8 +1432,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@@ -1355,8 +1455,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@@ -1385,17 +1492,33 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@@ -1669,25 +1792,39 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@@ -1716,25 +1853,39 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@@ -1830,13 +1981,22 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, 2f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||
@@ -1845,9 +2005,16 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
Operand mask = X86GetScalar(context, 2d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||
@@ -1877,14 +2044,23 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, 2f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@@ -1897,10 +2073,17 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, 2d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
@@ -2113,20 +2296,32 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Frintx_S(ArmEmitterContext context)
|
||||
{
|
||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frintx_V(ArmEmitterContext context)
|
||||
{
|
||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frintz_S(ArmEmitterContext context)
|
||||
@@ -2237,16 +2432,25 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand maskHalf = X86GetScalar(context, 0.5f);
|
||||
Operand maskThree = X86GetScalar(context, 3f);
|
||||
Operand maskOneHalf = X86GetScalar(context, 1.5f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||
@@ -2257,10 +2461,17 @@ namespace ARMeilleure.Instructions
|
||||
Operand maskThree = X86GetScalar(context, 3d);
|
||||
Operand maskOneHalf = X86GetScalar(context, 1.5d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||
@@ -2290,15 +2501,24 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand maskHalf = X86GetAllElements(context, 0.5f);
|
||||
Operand maskThree = X86GetAllElements(context, 3f);
|
||||
Operand maskOneHalf = X86GetAllElements(context, 1.5f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
||||
|
||||
@@ -2315,9 +2535,16 @@ namespace ARMeilleure.Instructions
|
||||
Operand maskThree = X86GetAllElements(context, 3d);
|
||||
Operand maskOneHalf = X86GetAllElements(context, 1.5d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
||||
|
||||
@@ -4728,53 +4955,6 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static Operand EmitSseOrAvxHandleFzModeOpF(
|
||||
ArmEmitterContext context,
|
||||
Func2I emit,
|
||||
bool scalar,
|
||||
Operand n = default,
|
||||
Operand m = default)
|
||||
{
|
||||
Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n;
|
||||
Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m;
|
||||
|
||||
EmitSseOrAvxEnterFtzAndDazModesOpF(context, out Operand isTrue);
|
||||
|
||||
Operand res = emit(nCopy, mCopy);
|
||||
|
||||
EmitSseOrAvxExitFtzAndDazModesOpF(context, isTrue);
|
||||
|
||||
if (n != default || m != default)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper96(res);
|
||||
}
|
||||
else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
}
|
||||
|
||||
context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax)
|
||||
{
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
@@ -4834,10 +5014,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, nCopy, mCopy);
|
||||
|
||||
if (n != default || m != default)
|
||||
@@ -4872,10 +5049,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, nCopy, mCopy);
|
||||
|
||||
if (n != default || m != default)
|
||||
|
@@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
||||
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
@@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
||||
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
@@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
|
||||
// VRINTX (floating-point).
|
||||
public static void Vrintx_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
@@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
|
||||
};
|
||||
#endregion
|
||||
|
||||
public static void EnterArmFpMode(EmitterContext context, Func<FPState, Operand> getFpFlag)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
Operand fzTrue = getFpFlag(FPState.FzFlag);
|
||||
Operand r0True = getFpFlag(FPState.RMode0Flag);
|
||||
Operand r1True = getFpFlag(FPState.RMode1Flag);
|
||||
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
|
||||
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0)));
|
||||
|
||||
// X86 round modes in order: nearest, negative, positive, zero
|
||||
// ARM round modes in order: nearest, positive, negative, zero
|
||||
// Read the bits backwards to correct this.
|
||||
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0)));
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
Operand fzTrue = getFpFlag(FPState.FzFlag);
|
||||
Operand r0True = getFpFlag(FPState.RMode0Flag);
|
||||
Operand r1True = getFpFlag(FPState.RMode1Flag);
|
||||
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
|
||||
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0)));
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0)));
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
// TODO: Restore FPSR
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitArmFpMode(EmitterContext context, Action<FPState, Operand> setFpFlag)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
// Unset round mode (to nearest) and ftz.
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
|
||||
// Status flags would be stored here if they were used.
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
// Unset round mode (to nearest) and fz.
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
// TODO: Store FPSR
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetImmShl(OpCodeSimdShImm op)
|
||||
{
|
||||
return op.Imm - (8 << op.Size);
|
||||
@@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat32).GetMethod(name)
|
||||
: typeof(SoftFloat64).GetMethod(name);
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(info, callArgs);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1358,39 +1430,6 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Mxcsr
|
||||
{
|
||||
Ftz = 1 << 15, // Flush To Zero.
|
||||
Um = 1 << 11, // Underflow Mask.
|
||||
Dm = 1 << 8, // Denormal Mask.
|
||||
Daz = 1 << 6 // Denormals Are Zero.
|
||||
}
|
||||
|
||||
public static void EmitSseOrAvxEnterFtzAndDazModesOpF(ArmEmitterContext context, out Operand isTrue)
|
||||
{
|
||||
isTrue = GetFpFlag(FPState.FzFlag);
|
||||
|
||||
Operand lblTrue = Label();
|
||||
context.BranchIfFalse(lblTrue, isTrue);
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrmb, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm | Mxcsr.Daz)));
|
||||
|
||||
context.MarkLabel(lblTrue);
|
||||
}
|
||||
|
||||
public static void EmitSseOrAvxExitFtzAndDazModesOpF(ArmEmitterContext context, Operand isTrue = default)
|
||||
{
|
||||
isTrue = isTrue == default ? GetFpFlag(FPState.FzFlag) : isTrue;
|
||||
|
||||
Operand lblTrue = Label();
|
||||
context.BranchIfFalse(lblTrue, isTrue);
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrub, Const((int)(Mxcsr.Ftz | Mxcsr.Daz)));
|
||||
|
||||
context.MarkLabel(lblTrue);
|
||||
}
|
||||
|
||||
public enum CmpCondition
|
||||
{
|
||||
// Legacy Sse.
|
||||
|
@@ -1197,9 +1197,11 @@ namespace ARMeilleure.Instructions
|
||||
Array.Resize(ref callArgs, callArgs.Length + 1);
|
||||
callArgs[callArgs.Length - 1] = Const(1);
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(info, callArgs);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
|
||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitGetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
|
||||
case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return;
|
||||
case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return;
|
||||
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
|
||||
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
|
||||
@@ -49,19 +49,15 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitSetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return;
|
||||
|
||||
default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntOrZR(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Nop(ArmEmitterContext context)
|
||||
@@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
|
||||
SetIntOrZR(context, op.Rt, fpsr);
|
||||
}
|
||||
|
||||
private static void EmitGetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
|
||||
|
||||
SetIntOrZR(context, op.Rt, result);
|
||||
}
|
||||
|
||||
private static void EmitGetTpidrroEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
|
||||
|
||||
SetIntOrZR(context, op.Rt, result);
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
@@ -192,6 +210,8 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static void EmitSetFpsr(ArmEmitterContext context)
|
||||
@@ -210,6 +230,19 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static void EmitSetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand value = GetIntOrZR(context, op.Rt);
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,6 @@ namespace ARMeilleure.Instructions
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
case 13: // Process and Thread Info.
|
||||
@@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
|
||||
EmitSetTpidrEl0(context); return;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 7:
|
||||
switch (op.CRm) // Cache and Memory barrier.
|
||||
{
|
||||
@@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntA32(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Mrc(ArmEmitterContext context)
|
||||
@@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
Operand result;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
@@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
|
||||
result = EmitGetTpidrEl0(context); break;
|
||||
|
||||
case 3:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
|
||||
result = EmitGetTpidrroEl0(context); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
@@ -110,13 +104,13 @@ namespace ARMeilleure.Instructions
|
||||
if (op.Rt == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
// Special behavior: copy NZCV flags into APSR.
|
||||
EmitSetNzcv(context, context.Call(info));
|
||||
EmitSetNzcv(context, result);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIntA32(context, op.Rt, context.Call(info));
|
||||
SetIntA32(context, op.Rt, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +315,37 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static Operand EmitGetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
|
||||
}
|
||||
|
||||
private static Operand EmitGetTpidrroEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
|
||||
}
|
||||
|
||||
private static void EmitSetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand value = GetIntA32(context, op.Rt);
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -72,26 +72,6 @@ namespace ARMeilleure.Instructions
|
||||
return (ulong)GetContext().DczidEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidrEl032()
|
||||
{
|
||||
return (uint)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrroEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidr32()
|
||||
{
|
||||
return (uint)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntfrqEl0()
|
||||
{
|
||||
return GetContext().CntfrqEl0;
|
||||
@@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return GetContext().CntvctEl0;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl0(ulong value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl032(uint value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Read"
|
||||
|
@@ -53,6 +53,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Haddpd,
|
||||
X86Haddps,
|
||||
X86Insertps,
|
||||
X86Ldmxcsr,
|
||||
X86Maxpd,
|
||||
X86Maxps,
|
||||
X86Maxsd,
|
||||
@@ -68,8 +69,6 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Mulps,
|
||||
X86Mulsd,
|
||||
X86Mulss,
|
||||
X86Mxcsrmb,
|
||||
X86Mxcsrub,
|
||||
X86Paddb,
|
||||
X86Paddd,
|
||||
X86Paddq,
|
||||
@@ -153,6 +152,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Sqrtps,
|
||||
X86Sqrtsd,
|
||||
X86Sqrtss,
|
||||
X86Stmxcsr,
|
||||
X86Subpd,
|
||||
X86Subps,
|
||||
X86Subsd,
|
||||
@@ -163,11 +163,13 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Unpcklps,
|
||||
X86Vcvtph2ps,
|
||||
X86Vcvtps2ph,
|
||||
X86Vfmadd231pd,
|
||||
X86Vfmadd231ps,
|
||||
X86Vfmadd231sd,
|
||||
X86Vfmadd231ss,
|
||||
X86Vfmsub231sd,
|
||||
X86Vfmsub231ss,
|
||||
X86Vfnmadd231pd,
|
||||
X86Vfnmadd231ps,
|
||||
X86Vfnmadd231sd,
|
||||
X86Vfnmadd231ss,
|
||||
@@ -394,6 +396,8 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
Arm64MlsVe,
|
||||
Arm64MlsV,
|
||||
Arm64MoviV,
|
||||
Arm64MrsFpcr,
|
||||
Arm64MsrFpcr,
|
||||
Arm64MrsFpsr,
|
||||
Arm64MsrFpsr,
|
||||
Arm64MulVe,
|
||||
|
@@ -27,8 +27,17 @@ namespace ARMeilleure.State
|
||||
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
||||
public ulong CntvctEl0 => CntpctEl0;
|
||||
|
||||
public long TpidrEl0 { get; set; }
|
||||
public long TpidrroEl0 { get; set; }
|
||||
public long TpidrEl0
|
||||
{
|
||||
get => _nativeContext.GetTpidrEl0();
|
||||
set => _nativeContext.SetTpidrEl0(value);
|
||||
}
|
||||
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get => _nativeContext.GetTpidrroEl0();
|
||||
set => _nativeContext.SetTpidrroEl0(value);
|
||||
}
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
|
@@ -13,6 +13,8 @@ namespace ARMeilleure.State
|
||||
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
||||
public fixed uint Flags[RegisterConsts.FlagsCount];
|
||||
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
||||
public long TpidrEl0;
|
||||
public long TpidrroEl0;
|
||||
public int Counter;
|
||||
public ulong DispatchAddress;
|
||||
public ulong ExclusiveAddress;
|
||||
@@ -168,6 +170,12 @@ namespace ARMeilleure.State
|
||||
}
|
||||
}
|
||||
|
||||
public long GetTpidrEl0() => GetStorage().TpidrEl0;
|
||||
public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value;
|
||||
|
||||
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
||||
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
||||
|
||||
public int GetCounter() => GetStorage().Counter;
|
||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||
|
||||
@@ -214,6 +222,16 @@ namespace ARMeilleure.State
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetTpidrEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0);
|
||||
}
|
||||
|
||||
public static int GetTpidrroEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||
}
|
||||
|
||||
public static int GetCounterOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||
|
@@ -188,6 +188,21 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterArmFpMode()
|
||||
{
|
||||
InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag);
|
||||
}
|
||||
|
||||
public void UpdateArmFpMode()
|
||||
{
|
||||
EnterArmFpMode();
|
||||
}
|
||||
|
||||
public void ExitArmFpMode()
|
||||
{
|
||||
InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value));
|
||||
}
|
||||
|
||||
public Operand TryGetComparisonResult(Condition condition)
|
||||
{
|
||||
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)
|
||||
|
@@ -105,17 +105,11 @@ namespace ARMeilleure.Translation
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));
|
||||
|
@@ -3,4 +3,5 @@
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
|
||||
delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress);
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 4485; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4661; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@@ -25,5 +25,10 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
return _func(context.NativeContextPtr);
|
||||
}
|
||||
|
||||
public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context)
|
||||
{
|
||||
return dispatcher(context.NativeContextPtr, (ulong)FuncPointer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -183,7 +183,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
Statistics.StartTimer();
|
||||
|
||||
ulong nextAddr = func.Execute(context);
|
||||
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
|
||||
|
||||
Statistics.StopTimer(address);
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||
|
||||
address = func.Execute(context);
|
||||
address = func.Execute(Stubs.ContextWrapper, context);
|
||||
|
||||
EnqueueForDeletion(address, func);
|
||||
|
||||
|
@@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
|
||||
private readonly Translator _translator;
|
||||
private readonly Lazy<IntPtr> _dispatchStub;
|
||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatch stub.
|
||||
@@ -64,6 +65,20 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the context wrapper function.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
|
||||
public WrapperFunction ContextWrapper
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _contextWrapper.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
@@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
|
||||
_translator = translator;
|
||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits code that syncs FP state before executing guest code, or returns it to normal.
|
||||
/// </summary>
|
||||
/// <param name="context">Emitter context for the method</param>
|
||||
/// <param name="nativeContext">Pointer to the native context</param>
|
||||
/// <param name="enter">True if entering guest code, false otherwise</param>
|
||||
private void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
|
||||
{
|
||||
if (enter)
|
||||
{
|
||||
InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
|
||||
{
|
||||
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
|
||||
return context.Load(OperandType.I32, flagAddress);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
|
||||
{
|
||||
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
|
||||
context.Store(flagAddress, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="DispatchLoop"/> function.
|
||||
/// </summary>
|
||||
@@ -221,6 +263,8 @@ namespace ARMeilleure.Translation
|
||||
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
||||
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, true);
|
||||
|
||||
context.MarkLabel(beginLbl);
|
||||
context.Store(dispatchAddress, guestAddress);
|
||||
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
||||
@@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
|
||||
context.Branch(beginLbl);
|
||||
|
||||
context.MarkLabel(endLbl);
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, false);
|
||||
|
||||
context.Return();
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
@@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="ContextWrapper"/> function.
|
||||
/// </summary>
|
||||
/// <returns><see cref="ContextWrapper"/> function</returns>
|
||||
private WrapperFunction GenerateContextWrapper()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, true);
|
||||
Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
|
||||
EmitSyncFpContext(context, nativeContext, false);
|
||||
|
||||
context.Return(returnValue);
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
var retType = OperandType.I64;
|
||||
var argTypes = new[] { OperandType.I64, OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
148
ARMeilleure/Translation/TranslatorTestMethods.cs
Normal file
148
ARMeilleure/Translation/TranslatorTestMethods.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
public static class TranslatorTestMethods
|
||||
{
|
||||
public delegate int FpFlagsPInvokeTest(IntPtr managedMethod);
|
||||
|
||||
private static bool SetPlatformFtz(EmitterContext context, bool ftz)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
if (ftz)
|
||||
{
|
||||
mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm)));
|
||||
}
|
||||
else
|
||||
{
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz));
|
||||
}
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
if (ftz)
|
||||
{
|
||||
fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz));
|
||||
}
|
||||
else
|
||||
{
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz));
|
||||
}
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand FpBitsToInt(EmitterContext context, Operand fp)
|
||||
{
|
||||
Operand vec = context.VectorInsert(context.VectorZero(), fp, 0);
|
||||
return context.VectorExtract(OperandType.I32, vec, 0);
|
||||
}
|
||||
|
||||
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0));
|
||||
|
||||
// Verify that default dotnet fp state does not flush to zero.
|
||||
// This is required for SoftFloat to function.
|
||||
|
||||
// Denormal + zero != 0
|
||||
|
||||
Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45
|
||||
Operand zeroF = ConstF(0f);
|
||||
Operand zero = Const(0);
|
||||
|
||||
Operand result = context.Add(zeroF, denormal);
|
||||
|
||||
// Must not be zero.
|
||||
|
||||
Operand correct1Label = Label();
|
||||
|
||||
context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero));
|
||||
|
||||
context.Return(Const(1));
|
||||
|
||||
context.MarkLabel(correct1Label);
|
||||
|
||||
// Set flush to zero flag. If unsupported by the backend, just return true.
|
||||
|
||||
if (!SetPlatformFtz(context, true))
|
||||
{
|
||||
context.Return(Const(0));
|
||||
}
|
||||
|
||||
// Denormal + zero == 0
|
||||
|
||||
Operand resultFz = context.Add(zeroF, denormal);
|
||||
|
||||
// Must equal zero.
|
||||
|
||||
Operand correct2Label = Label();
|
||||
|
||||
context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero));
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(2));
|
||||
|
||||
context.MarkLabel(correct2Label);
|
||||
|
||||
// Call a managed method. This method should not change Fz state.
|
||||
|
||||
context.Call(methodAddress, OperandType.None);
|
||||
|
||||
// Denormal + zero == 0
|
||||
|
||||
Operand resultFz2 = context.Add(zeroF, denormal);
|
||||
|
||||
// Must equal zero.
|
||||
|
||||
Operand correct3Label = Label();
|
||||
|
||||
context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero));
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(3));
|
||||
|
||||
context.MarkLabel(correct3Label);
|
||||
|
||||
// Success.
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(0));
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,17 +3,17 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||
<PackageVersion Include="DynamicData" Version="7.13.1" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
@@ -34,7 +34,7 @@
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
@@ -44,10 +44,10 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.28.1" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
@@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private ulong _playedSampleCount;
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private uint _outputStream;
|
||||
private bool _hasSetupError;
|
||||
private SDL_AudioCallback _callbackDelegate;
|
||||
private int _bytesPerFrame;
|
||||
private uint _sampleCount;
|
||||
@@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
{
|
||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
bool needAudioSetup = _outputStream == 0 ||
|
||||
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
|
||||
if (needAudioSetup)
|
||||
@@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
if (newOutputStream == 0)
|
||||
{
|
||||
// No stream in place, this is unexpected.
|
||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
||||
}
|
||||
else
|
||||
_hasSetupError = newOutputStream == 0;
|
||||
|
||||
if (!_hasSetupError)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
@@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
EnsureAudioStreamSetup(buffer);
|
||||
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
|
@@ -320,14 +320,16 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.IsGameRunning = true;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
|
||||
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
||||
var activeProcess = Device.Processes.ActiveApplication;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
|
||||
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||
});
|
||||
|
||||
_viewModel.SetUIProgressHandlers(Device);
|
||||
@@ -423,9 +425,9 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void Dispose()
|
||||
{
|
||||
if (Device.Application != null)
|
||||
if (Device.Processes != null)
|
||||
{
|
||||
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
|
||||
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||
@@ -539,7 +541,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Directory.Exists(ApplicationPath))
|
||||
{
|
||||
@@ -554,13 +561,23 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath, romFsFiles[0]);
|
||||
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath);
|
||||
if (!Device.LoadCart(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (File.Exists(ApplicationPath))
|
||||
@@ -571,7 +588,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||
|
||||
Device.LoadXci(ApplicationPath);
|
||||
if (!Device.LoadXci(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -579,7 +601,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -588,7 +615,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||
|
||||
Device.LoadNsp(ApplicationPath);
|
||||
if (!Device.LoadNsp(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -598,13 +630,18 @@ namespace Ryujinx.Ava
|
||||
|
||||
try
|
||||
{
|
||||
Device.LoadProgram(ApplicationPath);
|
||||
if (!Device.LoadProgram(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -617,14 +654,14 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
|
||||
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
});
|
||||
@@ -950,7 +987,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1088,4 +1125,4 @@ namespace Ryujinx.Ava
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -429,6 +429,7 @@
|
||||
"DlcManagerEnableAllButton": "Enable All",
|
||||
"DlcManagerDisableAllButton": "Disable All",
|
||||
"MenuBarOptionsChangeLanguage": "Change Language",
|
||||
"MenuBarShowFileTypes": "Show File Types",
|
||||
"CommonSort": "Sort",
|
||||
"CommonShowNames": "Show Names",
|
||||
"CommonFavorite": "Favorite",
|
||||
|
@@ -13,12 +13,14 @@ using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
@@ -151,25 +153,17 @@ namespace Ryujinx.Ava.Common
|
||||
string destination = await folderDialog.ShowAsync(_owner);
|
||||
var cancellationToken = new CancellationTokenSource();
|
||||
|
||||
UpdateWaitWindow waitingDialog = new(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
cancellationToken);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(destination))
|
||||
{
|
||||
Thread extractorThread = new(() =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
|
||||
Dispatcher.UIThread.Post(waitingDialog.Show);
|
||||
|
||||
if (result == UserResult.Cancel)
|
||||
{
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
@@ -221,13 +215,15 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
@@ -262,11 +258,15 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||
});
|
||||
}
|
||||
else if (resultCode.Value.IsSuccess())
|
||||
{
|
||||
Dispatcher.UIThread.Post(waitingDialog.Close);
|
||||
|
||||
NotificationHelper.Show(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
||||
@@ -283,6 +283,8 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||
});
|
||||
}
|
||||
|
@@ -53,6 +53,8 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
bool opened = false;
|
||||
|
||||
_parent.Activate();
|
||||
|
||||
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
||||
title,
|
||||
message,
|
||||
|
@@ -48,6 +48,7 @@
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="True"
|
||||
KeyUp="Message_KeyUp"
|
||||
Text="{Binding Message}"
|
||||
TextInput="Message_TextInput"
|
||||
@@ -61,4 +62,4 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
private ContentDialog _host;
|
||||
|
||||
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder)
|
||||
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
|
||||
{
|
||||
MainText = mainText;
|
||||
SecondaryText = secondaryText;
|
||||
Message = message ?? "";
|
||||
DataContext = this;
|
||||
_placeholder = placeholder;
|
||||
InitializeComponent();
|
||||
@@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||
{
|
||||
// FIXME: This does not work. Might be a bug in Avalonia with DialogHost
|
||||
// Currently focus will be redirected to the overlay window instead.
|
||||
Input.Focus();
|
||||
}
|
||||
|
||||
public string Message { get; set; } = "";
|
||||
public string MainText { get; set; } = "";
|
||||
public string SecondaryText { get; set; } = "";
|
||||
@@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
UserResult result = UserResult.Cancel;
|
||||
|
||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText)
|
||||
{
|
||||
Message = args.InitialText ?? ""
|
||||
};
|
||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
|
||||
|
||||
string input = string.Empty;
|
||||
|
||||
var overlay = new ContentDialogOverlayWindow()
|
||||
{
|
||||
Height = window.Bounds.Height,
|
||||
Width = window.Bounds.Width,
|
||||
Position = window.PointToScreen(new Point())
|
||||
};
|
||||
|
||||
window.PositionChanged += OverlayOnPositionChanged;
|
||||
|
||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||
{
|
||||
overlay.Position = window.PointToScreen(new Point());
|
||||
}
|
||||
|
||||
contentDialog = overlay.ContentDialog;
|
||||
|
||||
bool opened = false;
|
||||
|
||||
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
||||
|
||||
content._host = contentDialog;
|
||||
@@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
};
|
||||
contentDialog.Closed += handler;
|
||||
|
||||
overlay.Opened += OverlayOnActivated;
|
||||
|
||||
async void OverlayOnActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
|
||||
overlay.Position = window.PointToScreen(new Point());
|
||||
|
||||
await contentDialog.ShowAsync(overlay);
|
||||
contentDialog.Closed -= handler;
|
||||
overlay.Close();
|
||||
};
|
||||
|
||||
await overlay.ShowDialog(window);
|
||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||
|
||||
return (result, input);
|
||||
}
|
||||
|
@@ -1,32 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.InputDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Width="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLength="{Binding MaxLength}"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding SubMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -1,57 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class InputDialog : UserControl
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Input { get; set; }
|
||||
public string SubMessage { get; set; }
|
||||
|
||||
public uint MaxLength { get; }
|
||||
|
||||
public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
Message = message;
|
||||
Input = input;
|
||||
SubMessage = subMessage;
|
||||
MaxLength = maxLength;
|
||||
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public InputDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
|
||||
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
UserResult result = UserResult.Cancel;
|
||||
|
||||
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
||||
Content = content,
|
||||
PrimaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = UserResult.Ok;
|
||||
input = content.Input;
|
||||
})
|
||||
};
|
||||
await contentDialog.ShowAsync();
|
||||
|
||||
return (result, input);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,26 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class UpdateWaitWindow : StyleableWindow
|
||||
{
|
||||
public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText)
|
||||
{
|
||||
SystemDecorations = SystemDecorations.Full;
|
||||
ShowInTaskbar = true;
|
||||
|
||||
Closing += (_, _) => cancellationToken.Cancel();
|
||||
}
|
||||
|
||||
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
|
||||
{
|
||||
PrimaryText.Text = primaryText;
|
||||
SecondaryText.Text = secondaryText;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
SystemDecorations = SystemDecorations.BorderOnly;
|
||||
ShowInTaskbar = false;
|
||||
}
|
||||
|
||||
public UpdateWaitWindow()
|
||||
|
@@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string closeButton,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
{
|
||||
UserResult result = UserResult.None;
|
||||
@@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
int iconSymbol,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
{
|
||||
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
|
||||
|
||||
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction);
|
||||
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
|
||||
}
|
||||
|
||||
public async static Task<UserResult> ShowDeferredContentDialog(
|
||||
@@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
iconSymbol,
|
||||
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
|
||||
deferResetEvent,
|
||||
doWhileDeferred,
|
||||
DeferClose);
|
||||
|
||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
@@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
primaryButtonResult);
|
||||
}
|
||||
|
||||
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
|
||||
{
|
||||
return new(mainText, secondaryText);
|
||||
}
|
||||
|
||||
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
|
||||
{
|
||||
await ShowTextDialog(
|
||||
@@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
|
||||
}
|
||||
|
||||
internal static async Task<string> CreateInputDialog(
|
||||
string title,
|
||||
string mainText,
|
||||
string subText,
|
||||
uint maxLength = int.MaxValue,
|
||||
string input = "")
|
||||
{
|
||||
var result = await InputDialog.ShowInputDialog(
|
||||
title,
|
||||
mainText,
|
||||
input,
|
||||
subText,
|
||||
maxLength);
|
||||
|
||||
if (result.Result == UserResult.Ok)
|
||||
{
|
||||
return result.Input;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
|
||||
{
|
||||
ContentDialogResult result;
|
||||
|
@@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray) + "\n\n");
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -972,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
|
||||
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
|
||||
UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void SetUIProgressHandlers(Switch emulationContext)
|
||||
{
|
||||
if (emulationContext.Application.DiskCacheLoadState != null)
|
||||
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||
{
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
}
|
||||
|
||||
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
||||
@@ -1336,6 +1336,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleFileType(string fileType)
|
||||
{
|
||||
_ = fileType switch
|
||||
{
|
||||
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
|
||||
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
|
||||
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
|
||||
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
|
||||
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
|
||||
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
|
||||
_ => throw new ArgumentOutOfRangeException(fileType),
|
||||
};
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
LoadApplications();
|
||||
}
|
||||
|
||||
public async void ManageProfiles()
|
||||
{
|
||||
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
|
||||
@@ -1705,8 +1722,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
||||
TitleName = AppHost.Device.Application.TitleName;
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||
}
|
||||
|
||||
SwitchToRenderer(startFullscreen);
|
||||
|
@@ -17,11 +17,11 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
private object _selectedUpdate;
|
||||
|
||||
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||
@@ -163,7 +163,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
|
@@ -78,6 +78,7 @@
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
||||
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Click="OpenSettings"
|
||||
|
@@ -11,6 +11,8 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ToggleFileTypesMenuItem.Items = GenerateToggleFileTypeItems();
|
||||
ChangeLanguageMenuItem.Items = GenerateLanguageMenuItems();
|
||||
}
|
||||
|
||||
private CheckBox[] GenerateToggleFileTypeItems()
|
||||
{
|
||||
List<CheckBox> checkBoxes = new();
|
||||
|
||||
foreach (var item in Enum.GetValues(typeof (FileTypes)))
|
||||
{
|
||||
string fileName = Enum.GetName(typeof (FileTypes), item);
|
||||
checkBoxes.Add(new CheckBox()
|
||||
{
|
||||
Content = $".{fileName}",
|
||||
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
|
||||
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName))
|
||||
});
|
||||
}
|
||||
|
||||
return checkBoxes.ToArray();
|
||||
}
|
||||
|
||||
private MenuItem[] GenerateLanguageMenuItems()
|
||||
{
|
||||
List<MenuItem> menuItems = new();
|
||||
|
||||
string localePath = "Ryujinx.Ava/Assets/Locales";
|
||||
@@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
|
||||
ChangeLanguageMenuItem.Items = menuItems.ToArray();
|
||||
return menuItems.ToArray();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@@ -126,7 +152,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||
{
|
||||
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
|
||||
string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
|
||||
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
||||
|
||||
await window.ShowDialog(Window);
|
||||
@@ -148,13 +174,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationLoader application = ViewModel.AppHost.Device.Application;
|
||||
if (application != null)
|
||||
{
|
||||
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
|
||||
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
|
||||
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<window:StyleableWindow
|
||||
<window:StyleableWindow
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -10,20 +10,16 @@
|
||||
d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow"
|
||||
Title="ContentDialogOverlayWindow"
|
||||
Focusable="True">
|
||||
Focusable="False">
|
||||
<window:StyleableWindow.Styles>
|
||||
<Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
</window:StyleableWindow.Styles>
|
||||
<ContentControl
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog Name="ContentDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="False" />
|
||||
</ContentControl>
|
||||
<ui:ContentDialog Name="ContentDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="False"
|
||||
Focusable="True"/>
|
||||
</window:StyleableWindow>
|
||||
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
@@ -9,4 +13,4 @@
|
||||
SmaaHigh,
|
||||
SmaaUltra
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
|
||||
public enum GamepadInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
@@ -51,4 +55,4 @@
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
|
||||
public enum StickInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
@@ -8,4 +12,4 @@
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
|
||||
public enum Key
|
||||
{
|
||||
Unknown,
|
||||
@@ -136,4 +140,4 @@
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// NOTE: Please don't change this to struct.
|
||||
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
|
||||
public class KeyboardHotkeys
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
@@ -12,4 +14,4 @@
|
||||
public Key VolumeUp { get; set; }
|
||||
public Key VolumeDown { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
|
@@ -1,18 +1,21 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
public partial class StdErrAdapter : IDisposable
|
||||
{
|
||||
private bool _disposable = false;
|
||||
private UnixStream _pipeReader;
|
||||
private UnixStream _pipeWriter;
|
||||
private Thread _worker;
|
||||
private Stream _pipeReader;
|
||||
private Stream _pipeWriter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _worker;
|
||||
|
||||
public StdErrAdapter()
|
||||
{
|
||||
@@ -31,37 +34,39 @@ namespace Ryujinx.Common.SystemInterop
|
||||
(int readFd, int writeFd) = MakePipe();
|
||||
dup2(writeFd, stdErrFileno);
|
||||
|
||||
_pipeReader = new UnixStream(readFd);
|
||||
_pipeWriter = new UnixStream(writeFd);
|
||||
_pipeReader = CreateFileDescriptorStream(readFd);
|
||||
_pipeWriter = CreateFileDescriptorStream(writeFd);
|
||||
|
||||
_worker = new Thread(EventWorker);
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
_disposable = true;
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private void EventWorker()
|
||||
private async Task EventWorkerAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
TextReader reader = new StreamReader(_pipeReader);
|
||||
using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
|
||||
{
|
||||
Logger.Error?.PrintRawMsg(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposable)
|
||||
{
|
||||
_disposable = false;
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_worker.Wait(0);
|
||||
_pipeReader?.Close();
|
||||
_pipeWriter?.Close();
|
||||
}
|
||||
|
||||
_disposable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +79,11 @@ namespace Ryujinx.Common.SystemInterop
|
||||
private static partial int dup2(int fd, int fd2);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static unsafe partial int pipe(int* pipefd);
|
||||
private static partial int pipe(Span<int> pipefd);
|
||||
|
||||
private static unsafe (int, int) MakePipe()
|
||||
private static (int, int) MakePipe()
|
||||
{
|
||||
int *pipefd = stackalloc int[2];
|
||||
Span<int> pipefd = stackalloc int[2];
|
||||
|
||||
if (pipe(pipefd) == 0)
|
||||
{
|
||||
@@ -89,5 +94,16 @@ namespace Ryujinx.Common.SystemInterop
|
||||
throw new();
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static Stream CreateFileDescriptorStream(int fd)
|
||||
{
|
||||
return new FileStream(
|
||||
new SafeFileHandle((IntPtr)fd, ownsHandle: true),
|
||||
FileAccess.ReadWrite
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,155 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
public partial class UnixStream : Stream, IDisposable
|
||||
{
|
||||
private const int InvalidFd = -1;
|
||||
|
||||
private int _fd;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long read(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long write(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int close(int fd);
|
||||
|
||||
public UnixStream(int fd)
|
||||
{
|
||||
if (InvalidFd == fd)
|
||||
{
|
||||
throw new ArgumentException("Invalid file descriptor");
|
||||
}
|
||||
|
||||
_fd = fd;
|
||||
|
||||
CanRead = read(fd, IntPtr.Zero, 0) != -1;
|
||||
CanWrite = write(fd, IntPtr.Zero, 0) != -1;
|
||||
}
|
||||
|
||||
~UnixStream()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public override bool CanRead { get; }
|
||||
public override bool CanWrite { get; }
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long r = 0;
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
do
|
||||
{
|
||||
r = read(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
|
||||
return (int)r;
|
||||
}
|
||||
|
||||
public override unsafe void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
long r = 0;
|
||||
do {
|
||||
r = write(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
if (_fd == InvalidFd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Flush();
|
||||
|
||||
int r;
|
||||
do {
|
||||
r = close(_fd);
|
||||
} while (ShouldRetry(r));
|
||||
|
||||
_fd = InvalidFd;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool ShouldRetry(long r)
|
||||
{
|
||||
if (r == -1)
|
||||
{
|
||||
const int eintr = 4;
|
||||
|
||||
int errno = Marshal.GetLastPInvokeError();
|
||||
|
||||
if (errno == eintr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new SystemException($"Operation failed with error 0x{errno:X}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -38,4 +38,25 @@ namespace Ryujinx.Graphics.GAL
|
||||
Src1AlphaGl = 0xc902,
|
||||
OneMinusSrc1AlphaGl = 0xc903
|
||||
}
|
||||
|
||||
public static class BlendFactorExtensions
|
||||
{
|
||||
public static bool IsDualSource(this BlendFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case BlendFactor.Src1Color:
|
||||
case BlendFactor.Src1ColorGl:
|
||||
case BlendFactor.Src1Alpha:
|
||||
case BlendFactor.Src1AlphaGl:
|
||||
case BlendFactor.OneMinusSrc1Color:
|
||||
case BlendFactor.OneMinusSrc1ColorGl:
|
||||
case BlendFactor.OneMinusSrc1Alpha:
|
||||
case BlendFactor.OneMinusSrc1AlphaGl:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
|
||||
if (inlineIndexCount != 0)
|
||||
{
|
||||
@@ -670,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
if (indexedInline)
|
||||
{
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
|
@@ -11,9 +11,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
struct IbStreamer
|
||||
{
|
||||
private const int BufferCapacity = 256; // Must be a power of 2.
|
||||
|
||||
private BufferHandle _inlineIndexBuffer;
|
||||
private int _inlineIndexBufferSize;
|
||||
private int _inlineIndexCount;
|
||||
private uint[] _buffer;
|
||||
private int _bufferOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any index buffer data has been pushed.
|
||||
@@ -38,9 +42,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Gets the number of elements on the current inline index buffer,
|
||||
/// while also reseting it to zero for the next draw.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <returns>Inline index bufffer count</returns>
|
||||
public int GetAndResetInlineIndexCount()
|
||||
public int GetAndResetInlineIndexCount(IRenderer renderer)
|
||||
{
|
||||
UpdateRemaining(renderer);
|
||||
int temp = _inlineIndexCount;
|
||||
_inlineIndexCount = 0;
|
||||
return temp;
|
||||
@@ -58,16 +64,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
byte i2 = (byte)(argument >> 16);
|
||||
byte i3 = (byte)(argument >> 24);
|
||||
|
||||
Span<uint> data = stackalloc uint[4];
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
data[0] = i0;
|
||||
data[1] = i1;
|
||||
data[2] = i2;
|
||||
data[3] = i3;
|
||||
|
||||
int offset = _inlineIndexCount * 4;
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
PushData(renderer, offset + 2, i2);
|
||||
PushData(renderer, offset + 3, i3);
|
||||
|
||||
_inlineIndexCount += 4;
|
||||
}
|
||||
@@ -82,14 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
ushort i0 = (ushort)argument;
|
||||
ushort i1 = (ushort)(argument >> 16);
|
||||
|
||||
Span<uint> data = stackalloc uint[2];
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
data[0] = i0;
|
||||
data[1] = i1;
|
||||
|
||||
int offset = _inlineIndexCount * 4;
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
|
||||
_inlineIndexCount += 2;
|
||||
}
|
||||
@@ -103,13 +101,61 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
uint i0 = (uint)argument;
|
||||
|
||||
Span<uint> data = stackalloc uint[1];
|
||||
int offset = _inlineIndexCount++;
|
||||
|
||||
data[0] = i0;
|
||||
PushData(renderer, offset, i0);
|
||||
}
|
||||
|
||||
int offset = _inlineIndexCount++ * 4;
|
||||
/// <summary>
|
||||
/// Pushes a 32-bit value to the index buffer.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
|
||||
/// <param name="value">Index value to be written</param>
|
||||
private void PushData(IRenderer renderer, int offset, uint value)
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
_buffer = new uint[BufferCapacity];
|
||||
}
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
// We upload data in chunks.
|
||||
// If we are at the start of a chunk, then the buffer might be full,
|
||||
// in that case we need to submit any existing data before overwriting the buffer.
|
||||
int subOffset = offset & (BufferCapacity - 1);
|
||||
|
||||
if (subOffset == 0 && offset != 0)
|
||||
{
|
||||
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
|
||||
}
|
||||
|
||||
_buffer[subOffset] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
private void UpdateRemaining(IRenderer renderer)
|
||||
{
|
||||
int offset = _inlineIndexCount;
|
||||
if (offset == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = offset & (BufferCapacity - 1);
|
||||
if (count == 0)
|
||||
{
|
||||
count = BufferCapacity;
|
||||
}
|
||||
|
||||
int baseOffset = (offset - count) * sizeof(uint);
|
||||
int length = count * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,12 +163,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data will be written</param>
|
||||
/// <param name="length">Number of bytes that will be written</param>
|
||||
/// <returns>Buffer handle</returns>
|
||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset)
|
||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
|
||||
{
|
||||
// Calculate a reasonable size for the buffer that can fit all the data,
|
||||
// and that also won't require frequent resizes if we need to push more data.
|
||||
int size = BitUtils.AlignUp(offset + 0x10, 0x200);
|
||||
int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
|
||||
|
||||
if (_inlineIndexBuffer == BufferHandle.Null)
|
||||
{
|
||||
|
@@ -328,5 +328,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the dual-source blend enabled state.
|
||||
/// </summary>
|
||||
/// <param name="enabled">True if blending is enabled and using dual-source blend</param>
|
||||
public void SetDualSourceBlendEnabled(bool enabled)
|
||||
{
|
||||
if (enabled != _graphics.DualSourceBlendEnable)
|
||||
{
|
||||
_graphics.DualSourceBlendEnable = enabled;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1183,6 +1183,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
bool blendIndependent = _state.State.BlendIndependent;
|
||||
ColorF blendConstant = _state.State.BlendConstant;
|
||||
|
||||
bool dualSourceBlendEnabled = false;
|
||||
|
||||
if (blendIndependent)
|
||||
{
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
@@ -1200,6 +1202,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
FilterBlendFactor(blend.AlphaSrcFactor, index),
|
||||
FilterBlendFactor(blend.AlphaDstFactor, index));
|
||||
|
||||
if (enable &&
|
||||
(blend.ColorSrcFactor.IsDualSource() ||
|
||||
blend.ColorDstFactor.IsDualSource() ||
|
||||
blend.AlphaSrcFactor.IsDualSource() ||
|
||||
blend.AlphaDstFactor.IsDualSource()))
|
||||
{
|
||||
dualSourceBlendEnabled = true;
|
||||
}
|
||||
|
||||
_pipeline.BlendDescriptors[index] = descriptor;
|
||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||
}
|
||||
@@ -1219,12 +1230,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
FilterBlendFactor(blend.AlphaSrcFactor, 0),
|
||||
FilterBlendFactor(blend.AlphaDstFactor, 0));
|
||||
|
||||
if (enable &&
|
||||
(blend.ColorSrcFactor.IsDualSource() ||
|
||||
blend.ColorDstFactor.IsDualSource() ||
|
||||
blend.AlphaSrcFactor.IsDualSource() ||
|
||||
blend.AlphaDstFactor.IsDualSource()))
|
||||
{
|
||||
dualSourceBlendEnabled = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
_pipeline.BlendDescriptors[index] = descriptor;
|
||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
_currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -130,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return ref descriptor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
@@ -233,7 +237,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture with potential mapping change</param>
|
||||
|
@@ -141,6 +141,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryDualSourceBlendEnable()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public InputTopology QueryPrimitiveTopology()
|
||||
{
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 4368;
|
||||
private const uint CodeGenVersion = 4404;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -157,6 +157,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryDualSourceBlendEnable()
|
||||
{
|
||||
return _state.GraphicsState.DualSourceBlendEnable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public InputTopology QueryPrimitiveTopology()
|
||||
{
|
||||
|
@@ -92,6 +92,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
public Array8<AttributeType> FragmentOutputTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether dual source blend is enabled.
|
||||
/// </summary>
|
||||
public bool DualSourceBlendEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
@@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
||||
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
||||
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
||||
/// <param name="dualSourceBlendEnable">Type of the vertex attributes consumed by the shader</param>
|
||||
public GpuChannelGraphicsState(
|
||||
bool earlyZForce,
|
||||
PrimitiveTopology topology,
|
||||
@@ -127,7 +133,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
ref Array32<AttributeType> attributeTypes,
|
||||
bool hasConstantBufferDrawParameters,
|
||||
bool hasUnalignedStorageBuffer,
|
||||
ref Array8<AttributeType> fragmentOutputTypes)
|
||||
ref Array8<AttributeType> fragmentOutputTypes,
|
||||
bool dualSourceBlendEnable)
|
||||
{
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
@@ -145,6 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||
FragmentOutputTypes = fragmentOutputTypes;
|
||||
DualSourceBlendEnable = dualSourceBlendEnable;
|
||||
}
|
||||
}
|
||||
}
|
@@ -535,6 +535,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||
}
|
||||
|
||||
|
@@ -833,31 +833,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
(BlendingFactorSrc)blend.AlphaSrcFactor.Convert(),
|
||||
(BlendingFactorDest)blend.AlphaDstFactor.Convert());
|
||||
|
||||
static bool IsDualSource(BlendFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case BlendFactor.Src1Color:
|
||||
case BlendFactor.Src1ColorGl:
|
||||
case BlendFactor.Src1Alpha:
|
||||
case BlendFactor.Src1AlphaGl:
|
||||
case BlendFactor.OneMinusSrc1Color:
|
||||
case BlendFactor.OneMinusSrc1ColorGl:
|
||||
case BlendFactor.OneMinusSrc1Alpha:
|
||||
case BlendFactor.OneMinusSrc1AlphaGl:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureFramebuffer();
|
||||
|
||||
_framebuffer.SetDualSourceBlend(
|
||||
IsDualSource(blend.ColorSrcFactor) ||
|
||||
IsDualSource(blend.ColorDstFactor) ||
|
||||
IsDualSource(blend.AlphaSrcFactor) ||
|
||||
IsDualSource(blend.AlphaDstFactor));
|
||||
blend.ColorSrcFactor.IsDualSource() ||
|
||||
blend.ColorDstFactor.IsDualSource() ||
|
||||
blend.AlphaSrcFactor.IsDualSource() ||
|
||||
blend.AlphaDstFactor.IsDualSource());
|
||||
|
||||
if (_blendConstant != blend.BlendConstant)
|
||||
{
|
||||
|
@@ -226,6 +226,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
// Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
|
||||
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
|
||||
GL.Viewport(0, 0, _width, _height);
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer);
|
||||
|
||||
swapBuffersCallback();
|
||||
|
||||
|
@@ -612,6 +612,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
else
|
||||
{
|
||||
int usedAttributes = context.Config.UsedOutputAttributes;
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
|
||||
{
|
||||
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
|
||||
int mask = 3 << firstOutput;
|
||||
|
||||
if ((usedAttributes & mask) == mask)
|
||||
{
|
||||
usedAttributes &= ~mask;
|
||||
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
|
||||
}
|
||||
}
|
||||
|
||||
while (usedAttributes != 0)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(usedAttributes);
|
||||
@@ -690,6 +703,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
|
||||
{
|
||||
string name = $"{DefaultNames.OAttributePrefix}{attr}";
|
||||
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
|
||||
|
||||
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
|
||||
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
|
||||
}
|
||||
|
||||
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
|
||||
{
|
||||
foreach (int attr in attrs.Order())
|
||||
|
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using static Spv.Specification;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
@@ -622,7 +623,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
|
||||
{
|
||||
int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
|
||||
int index = location - firstLocation;
|
||||
int mask = 3 << firstLocation;
|
||||
|
||||
if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
|
||||
context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOutAttr)
|
||||
|
@@ -205,6 +205,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries dual source blend state.
|
||||
/// </summary>
|
||||
/// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
|
||||
bool QueryDualSourceBlendEnable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
@@ -7,12 +8,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal class AutoFlushCounter
|
||||
{
|
||||
// How often to flush on framebuffer change.
|
||||
private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000;
|
||||
private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000; // (1ms)
|
||||
|
||||
// How often to flush on draw when fast flush mode is enabled.
|
||||
private readonly static long DrawFlushTimer = Stopwatch.Frequency / 666; // (1.5ms)
|
||||
|
||||
// Average wait time that triggers fast flush mode to be entered.
|
||||
private readonly static long FastFlushEnterThreshold = Stopwatch.Frequency / 666; // (1.5ms)
|
||||
|
||||
// Average wait time that triggers fast flush mode to be exited.
|
||||
private readonly static long FastFlushExitThreshold = Stopwatch.Frequency / 10000; // (0.1ms)
|
||||
|
||||
// Number of frames to average waiting times over.
|
||||
private const int SyncWaitAverageCount = 20;
|
||||
|
||||
private const int MinDrawCountForFlush = 10;
|
||||
private const int MinConsecutiveQueryForFlush = 10;
|
||||
private const int InitialQueryCountForFlush = 32;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private long _lastFlush;
|
||||
private ulong _lastDrawCount;
|
||||
private bool _hasPendingQuery;
|
||||
@@ -23,6 +38,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _queryCountHistoryIndex;
|
||||
private int _remainingQueries;
|
||||
|
||||
private long[] _syncWaitHistory = new long[SyncWaitAverageCount];
|
||||
private int _syncWaitHistoryIndex;
|
||||
|
||||
private bool _fastFlushMode;
|
||||
|
||||
public AutoFlushCounter(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public void RegisterFlush(ulong drawCount)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
@@ -69,6 +94,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _hasPendingQuery;
|
||||
}
|
||||
|
||||
public bool ShouldFlushDraw(ulong drawCount)
|
||||
{
|
||||
if (_fastFlushMode)
|
||||
{
|
||||
long draws = (long)(drawCount - _lastDrawCount);
|
||||
|
||||
if (draws < MinDrawCountForFlush)
|
||||
{
|
||||
if (draws == 0)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
long flushTimeout = DrawFlushTimer;
|
||||
|
||||
long now = Stopwatch.GetTimestamp();
|
||||
|
||||
return now > _lastFlush + flushTimeout;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ShouldFlushAttachmentChange(ulong drawCount)
|
||||
{
|
||||
_queryCount = 0;
|
||||
@@ -102,11 +153,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Present()
|
||||
{
|
||||
// Query flush prediction.
|
||||
|
||||
_queryCountHistoryIndex = (_queryCountHistoryIndex + 1) % 3;
|
||||
|
||||
_remainingQueries = _queryCountHistory.Max() + 10;
|
||||
|
||||
_queryCountHistory[_queryCountHistoryIndex] = 0;
|
||||
|
||||
// Fast flush mode toggle.
|
||||
|
||||
_syncWaitHistory[_syncWaitHistoryIndex] = _gd.SyncManager.GetAndResetWaitTicks();
|
||||
|
||||
_syncWaitHistoryIndex = (_syncWaitHistoryIndex + 1) % SyncWaitAverageCount;
|
||||
|
||||
long averageWait = (long)_syncWaitHistory.Average();
|
||||
|
||||
if (_fastFlushMode ? averageWait < FastFlushExitThreshold : averageWait > FastFlushEnterThreshold)
|
||||
{
|
||||
_fastFlushMode = !_fastFlushMode;
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Switched fast flush mode: ({_fastFlushMode})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -236,7 +236,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
|
@@ -322,7 +322,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
@@ -218,5 +218,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
|
||||
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
int realIndex = Array.IndexOf(AttachmentIndices, index);
|
||||
|
||||
if (realIndex != -1)
|
||||
{
|
||||
_colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
||||
{
|
||||
_depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.EarlyFragmentTestsBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,21 +9,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024;
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly PhysicalDevice _physicalDevice;
|
||||
private readonly VulkanPhysicalDevice _physicalDevice;
|
||||
private readonly Device _device;
|
||||
private readonly List<MemoryAllocatorBlockList> _blockLists;
|
||||
private readonly int _blockAlignment;
|
||||
private readonly PhysicalDeviceMemoryProperties _physicalDeviceMemoryProperties;
|
||||
|
||||
public MemoryAllocator(Vk api, PhysicalDevice physicalDevice, Device device, uint maxMemoryAllocationCount)
|
||||
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
|
||||
{
|
||||
_api = api;
|
||||
_physicalDevice = physicalDevice;
|
||||
_device = device;
|
||||
_blockLists = new List<MemoryAllocatorBlockList>();
|
||||
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)maxMemoryAllocationCount);
|
||||
|
||||
_api.GetPhysicalDeviceMemoryProperties(_physicalDevice, out _physicalDeviceMemoryProperties);
|
||||
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)_physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
|
||||
}
|
||||
|
||||
public MemoryAllocation AllocateDeviceMemory(
|
||||
@@ -64,9 +61,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
uint memoryTypeBits,
|
||||
MemoryPropertyFlags flags)
|
||||
{
|
||||
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||
for (int i = 0; i < _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
|
||||
var type = _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypes[i];
|
||||
|
||||
if ((memoryTypeBits & (1 << i)) != 0)
|
||||
{
|
||||
@@ -80,15 +77,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static bool IsDeviceMemoryShared(Vk api, PhysicalDevice physicalDevice)
|
||||
public static bool IsDeviceMemoryShared(VulkanPhysicalDevice physicalDevice)
|
||||
{
|
||||
// The device is regarded as having shared memory if all heaps have the device local bit.
|
||||
|
||||
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
|
||||
|
||||
for (int i = 0; i < properties.MemoryHeapCount; i++)
|
||||
for (int i = 0; i < physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeapCount; i++)
|
||||
{
|
||||
if (!properties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||
if (!physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Gd = gd;
|
||||
Device = device;
|
||||
|
||||
AutoFlush = new AutoFlushCounter();
|
||||
AutoFlush = new AutoFlushCounter(gd);
|
||||
|
||||
var pipelineCacheCreateInfo = new PipelineCacheCreateInfo()
|
||||
{
|
||||
@@ -226,6 +226,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrier(Cbs, index);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@@ -256,6 +258,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var attachment = new ClearAttachment(flags, 0, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrierDS(Cbs);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@@ -1558,6 +1562,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private void RecreatePipelineIfNeeded(PipelineBindPoint pbp)
|
||||
{
|
||||
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
||||
{
|
||||
Gd.FlushAllCommands();
|
||||
}
|
||||
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
|
||||
// Commit changes to the support buffer before drawing.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
@@ -26,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Device _device;
|
||||
private List<SyncHandle> _handles;
|
||||
private ulong FlushId;
|
||||
private long WaitTicks;
|
||||
|
||||
public SyncManager(VulkanRenderer gd, Device device)
|
||||
{
|
||||
@@ -130,6 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
long beforeTicks = Stopwatch.GetTimestamp();
|
||||
|
||||
if (result.NeedsFlush(FlushId))
|
||||
{
|
||||
_gd.InterruptAction(() =>
|
||||
@@ -142,12 +146,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
|
||||
|
||||
if (!signaled)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitTicks += Stopwatch.GetTimestamp() - beforeTicks;
|
||||
result.Signalled = true;
|
||||
}
|
||||
}
|
||||
@@ -188,5 +194,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long GetAndResetWaitTicks()
|
||||
{
|
||||
long result = WaitTicks;
|
||||
WaitTicks = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private AccessFlags _lastModificationAccess;
|
||||
private PipelineStageFlags _lastModificationStage;
|
||||
private AccessFlags _lastReadAccess;
|
||||
private PipelineStageFlags _lastReadStage;
|
||||
|
||||
private int _viewsCount;
|
||||
private ulong _size;
|
||||
@@ -440,31 +442,39 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_lastModificationStage = stage;
|
||||
}
|
||||
|
||||
public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
if (_lastReadAccess != AccessFlags.NoneKhr)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
_lastReadStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_lastReadAccess = AccessFlags.NoneKhr;
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
_lastReadAccess |= dstAccessFlags;
|
||||
_lastReadStage |= dstStageFlags;
|
||||
|
||||
if (_lastModificationAccess != AccessFlags.NoneKhr)
|
||||
{
|
||||
ImageAspectFlags aspectFlags;
|
||||
|
||||
if (_info.Format.IsDepthOrStencil())
|
||||
{
|
||||
if (_info.Format == GAL.Format.S8Uint)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.StencilBit;
|
||||
}
|
||||
else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.DepthBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ColorBit;
|
||||
}
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
|
153
Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs
Normal file
153
Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class VulkanDebugMessenger : IDisposable
|
||||
{
|
||||
private static string[] _excludedMessages = new string[]
|
||||
{
|
||||
// NOTE: Done on purpose right now.
|
||||
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
||||
// TODO: Figure out if fixable
|
||||
"VUID-vkCmdDrawIndexed-None-04584",
|
||||
// TODO: Might be worth looking into making this happy to possibly optimize copies.
|
||||
"UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
|
||||
// TODO: Fix this, it's causing too much noise right now.
|
||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||
};
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly Instance _instance;
|
||||
private readonly GraphicsDebugLevel _logLevel;
|
||||
private readonly ExtDebugUtils _debugUtils;
|
||||
private readonly DebugUtilsMessengerEXT? _debugUtilsMessenger;
|
||||
private bool _disposed;
|
||||
|
||||
public VulkanDebugMessenger(Vk api, Instance instance, GraphicsDebugLevel logLevel)
|
||||
{
|
||||
_api = api;
|
||||
_instance = instance;
|
||||
_logLevel = logLevel;
|
||||
|
||||
_api.TryGetInstanceExtension(instance, out _debugUtils);
|
||||
|
||||
Result result = TryInitialize(out _debugUtilsMessenger);
|
||||
|
||||
if (result != Result.Success)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Vulkan debug messenger initialization failed with error {result}");
|
||||
}
|
||||
}
|
||||
|
||||
private Result TryInitialize(out DebugUtilsMessengerEXT? debugUtilsMessengerHandle)
|
||||
{
|
||||
debugUtilsMessengerHandle = null;
|
||||
|
||||
if (_debugUtils != null && _logLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
var messageType = _logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{_logLevel}\".")
|
||||
};
|
||||
|
||||
var messageSeverity = _logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{_logLevel}\".")
|
||||
};
|
||||
|
||||
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageType = messageType,
|
||||
MessageSeverity = messageSeverity
|
||||
};
|
||||
|
||||
unsafe
|
||||
{
|
||||
debugUtilsMessengerCreateInfo.PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(UserCallback);
|
||||
}
|
||||
|
||||
DebugUtilsMessengerEXT messengerHandle = default;
|
||||
|
||||
Result result = _debugUtils.CreateDebugUtilsMessenger(_instance, SpanHelpers.AsReadOnlySpan(ref debugUtilsMessengerCreateInfo), ReadOnlySpan<AllocationCallbacks>.Empty, SpanHelpers.AsSpan(ref messengerHandle));
|
||||
|
||||
if (result == Result.Success)
|
||||
{
|
||||
debugUtilsMessengerHandle = messengerHandle;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private unsafe static uint UserCallback(
|
||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData)
|
||||
{
|
||||
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||
|
||||
foreach (string excludedMessagePart in _excludedMessages)
|
||||
{
|
||||
if (msg.Contains(excludedMessagePart))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_debugUtilsMessenger.HasValue)
|
||||
{
|
||||
_debugUtils.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger.Value, Span<AllocationCallbacks>.Empty);
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||
private const int QueuesCount = 2;
|
||||
|
||||
public static string[] DesirableExtensions { get; } = new string[]
|
||||
private static readonly string[] _desirableExtensions = new string[]
|
||||
{
|
||||
ExtConditionalRendering.ExtensionName,
|
||||
ExtExtendedDynamicState.ExtensionName,
|
||||
@@ -42,52 +42,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
"VK_KHR_portability_subset", // By spec, we should enable this if present.
|
||||
};
|
||||
|
||||
public static string[] RequiredExtensions { get; } = new string[]
|
||||
private static readonly string[] _requiredExtensions = new string[]
|
||||
{
|
||||
KhrSwapchain.ExtensionName
|
||||
};
|
||||
|
||||
private static string[] _excludedMessages = new string[]
|
||||
{
|
||||
// NOTE: Done on purpose right now.
|
||||
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
||||
// TODO: Figure out if fixable
|
||||
"VUID-vkCmdDrawIndexed-None-04584",
|
||||
// TODO: Might be worth looking into making this happy to possibly optimize copies.
|
||||
"UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
|
||||
// TODO: Fix this, it's causing too much noise right now.
|
||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||
};
|
||||
|
||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
internal static VulkanInstance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions)
|
||||
{
|
||||
var enabledLayers = new List<string>();
|
||||
|
||||
var instanceExtensions = VulkanInstance.GetInstanceExtensions(api);
|
||||
var instanceLayers = VulkanInstance.GetInstanceLayers(api);
|
||||
|
||||
void AddAvailableLayer(string layerName)
|
||||
{
|
||||
uint layerPropertiesCount;
|
||||
|
||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
|
||||
|
||||
LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount];
|
||||
|
||||
fixed (LayerProperties* pLayerProperties = layerProperties)
|
||||
if (instanceLayers.Contains(layerName))
|
||||
{
|
||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < layerPropertiesCount; i++)
|
||||
{
|
||||
string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
|
||||
|
||||
if (currentLayerName == layerName)
|
||||
{
|
||||
enabledLayers.Add(layerName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
enabledLayers.Add(layerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
|
||||
}
|
||||
|
||||
if (logLevel != GraphicsDebugLevel.None)
|
||||
@@ -95,7 +71,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||
var enabledExtensions = requiredExtensions;
|
||||
|
||||
if (instanceExtensions.Contains("VK_EXT_debug_utils"))
|
||||
{
|
||||
enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||
}
|
||||
|
||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
@@ -131,7 +112,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
EnabledLayerCount = (uint)enabledLayers.Count
|
||||
};
|
||||
|
||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
||||
Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var instance);
|
||||
|
||||
Marshal.FreeHGlobal(appName);
|
||||
|
||||
@@ -145,59 +126,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||
}
|
||||
|
||||
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
|
||||
result.ThrowOnError();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private unsafe static uint DebugMessenger(
|
||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData)
|
||||
internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(Vk api, VulkanInstance instance, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||
|
||||
foreach (string excludedMessagePart in _excludedMessages)
|
||||
{
|
||||
if (msg.Contains(excludedMessagePart))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
uint physicalDeviceCount;
|
||||
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
||||
|
||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||
|
||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
||||
{
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
||||
}
|
||||
instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError();
|
||||
|
||||
// First we try to pick the the user preferred GPU.
|
||||
for (int i = 0; i < physicalDevices.Length; i++)
|
||||
@@ -243,76 +179,41 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
EnabledLayerCount = 0
|
||||
};
|
||||
|
||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
||||
|
||||
// We ensure that vkEnumerateInstanceVersion is present (added in 1.1).
|
||||
// If the instance doesn't support it, no device is going to be 1.1 compatible.
|
||||
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
|
||||
{
|
||||
api.DestroyInstance(instance, null);
|
||||
|
||||
return Array.Empty<DeviceInfo>();
|
||||
}
|
||||
|
||||
// We currently assume that the instance is compatible with Vulkan 1.2
|
||||
// TODO: Remove this once we relax our initialization codepaths.
|
||||
uint instanceApiVerison = 0;
|
||||
api.EnumerateInstanceVersion(ref instanceApiVerison).ThrowOnError();
|
||||
|
||||
if (instanceApiVerison < MinimalInstanceVulkanVersion)
|
||||
{
|
||||
api.DestroyInstance(instance, null);
|
||||
|
||||
return Array.Empty<DeviceInfo>();
|
||||
}
|
||||
Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var rawInstance);
|
||||
|
||||
Marshal.FreeHGlobal(appName);
|
||||
|
||||
uint physicalDeviceCount;
|
||||
result.ThrowOnError();
|
||||
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
||||
using VulkanInstance instance = rawInstance;
|
||||
|
||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||
|
||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
||||
// We currently assume that the instance is compatible with Vulkan 1.2
|
||||
// TODO: Remove this once we relax our initialization codepaths.
|
||||
if (instance.InstanceVersion < MinimalInstanceVulkanVersion)
|
||||
{
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
||||
return Array.Empty<DeviceInfo>();
|
||||
}
|
||||
|
||||
DeviceInfo[] devices = new DeviceInfo[physicalDevices.Length];
|
||||
instance.EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < physicalDevices.Length; i++)
|
||||
List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
|
||||
|
||||
foreach (VulkanPhysicalDevice physicalDevice in physicalDevices)
|
||||
{
|
||||
var physicalDevice = physicalDevices[i];
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
|
||||
if (properties.ApiVersion < MinimalVulkanVersion)
|
||||
if (physicalDevice.PhysicalDeviceProperties.ApiVersion < MinimalVulkanVersion)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
devices[i] = new DeviceInfo(
|
||||
StringFromIdPair(properties.VendorID, properties.DeviceID),
|
||||
VendorUtils.GetNameFromId(properties.VendorID),
|
||||
Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName),
|
||||
properties.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||
deviceInfos.Add(physicalDevice.ToDeviceInfo());
|
||||
}
|
||||
|
||||
api.DestroyInstance(instance, null);
|
||||
|
||||
return devices;
|
||||
return deviceInfos.ToArray();
|
||||
}
|
||||
|
||||
public static string StringFromIdPair(uint vendorId, uint deviceId)
|
||||
private static bool IsPreferredAndSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
return $"0x{vendorId:X}_0x{deviceId:X}";
|
||||
}
|
||||
|
||||
private static bool IsPreferredAndSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
|
||||
if (StringFromIdPair(properties.VendorID, properties.DeviceID) != preferredGpuId)
|
||||
if (physicalDevice.Id != preferredGpuId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -320,68 +221,47 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return IsSuitableDevice(api, physicalDevice, surface);
|
||||
}
|
||||
|
||||
private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface)
|
||||
private static bool IsSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface)
|
||||
{
|
||||
int extensionMatches = 0;
|
||||
uint propertiesCount;
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||
foreach (string requiredExtension in _requiredExtensions)
|
||||
{
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < propertiesCount; i++)
|
||||
if (physicalDevice.IsDeviceExtensionPresent(requiredExtension))
|
||||
{
|
||||
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
||||
|
||||
if (RequiredExtensions.Contains(extensionName))
|
||||
{
|
||||
extensionMatches++;
|
||||
}
|
||||
extensionMatches++;
|
||||
}
|
||||
}
|
||||
|
||||
return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||
return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||
}
|
||||
|
||||
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||
internal static uint FindSuitableQueueFamily(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||
{
|
||||
const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
|
||||
|
||||
var khrSurface = new KhrSurface(api.Context);
|
||||
|
||||
uint propertiesCount;
|
||||
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
|
||||
|
||||
QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount];
|
||||
|
||||
fixed (QueueFamilyProperties* pProperties = properties)
|
||||
for (uint index = 0; index < physicalDevice.QueueFamilyProperties.Length; index++)
|
||||
{
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
|
||||
}
|
||||
ref QueueFamilyProperties property = ref physicalDevice.QueueFamilyProperties[index];
|
||||
|
||||
for (uint index = 0; index < propertiesCount; index++)
|
||||
{
|
||||
var queueFlags = properties[index].QueueFlags;
|
||||
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice.PhysicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
|
||||
|
||||
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
|
||||
|
||||
if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
||||
if (property.QueueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
||||
{
|
||||
queueCount = properties[index].QueueCount;
|
||||
queueCount = property.QueueCount;
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
queueCount = 0;
|
||||
|
||||
return InvalidIndex;
|
||||
}
|
||||
|
||||
public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount)
|
||||
internal static Device CreateDevice(Vk api, VulkanPhysicalDevice physicalDevice, uint queueFamilyIndex, uint queueCount)
|
||||
{
|
||||
if (queueCount > QueuesCount)
|
||||
{
|
||||
@@ -403,8 +283,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PQueuePriorities = queuePriorities
|
||||
};
|
||||
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
bool useRobustBufferAccess = VendorUtils.FromId(properties.VendorID) == Vendor.Nvidia;
|
||||
bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia;
|
||||
|
||||
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
||||
{
|
||||
@@ -425,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = features2.PNext
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesCustomBorderColor;
|
||||
}
|
||||
@@ -436,7 +315,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = features2.PNext
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesPrimitiveTopologyListRestart;
|
||||
}
|
||||
@@ -447,7 +326,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = features2.PNext
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
|
||||
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesTransformFeedback;
|
||||
}
|
||||
@@ -457,14 +336,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||
{
|
||||
supportedFeaturesRobustness2.PNext = features2.PNext;
|
||||
|
||||
features2.PNext = &supportedFeaturesRobustness2;
|
||||
}
|
||||
|
||||
api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
|
||||
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
|
||||
|
||||
var supportedFeatures = features2.Features;
|
||||
|
||||
@@ -497,7 +376,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback;
|
||||
|
||||
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
|
||||
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
|
||||
{
|
||||
featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
|
||||
{
|
||||
@@ -511,7 +390,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||
{
|
||||
featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT()
|
||||
{
|
||||
@@ -526,7 +405,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||
{
|
||||
featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||
{
|
||||
@@ -542,7 +421,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
ExtendedDynamicState = supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)
|
||||
ExtendedDynamicState = physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresExtendedDynamicState;
|
||||
@@ -560,16 +439,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = pExtendedFeatures,
|
||||
DescriptorIndexing = supportedExtensions.Contains("VK_EXT_descriptor_indexing"),
|
||||
DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
||||
UniformBufferStandardLayout = supportedExtensions.Contains("VK_KHR_uniform_buffer_standard_layout")
|
||||
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
|
||||
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout")
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresVk12;
|
||||
|
||||
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_index_type_uint8"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"))
|
||||
{
|
||||
featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
|
||||
{
|
||||
@@ -583,7 +462,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"))
|
||||
{
|
||||
featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
|
||||
{
|
||||
@@ -597,7 +476,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_subgroup_size_control"))
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control"))
|
||||
{
|
||||
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
|
||||
{
|
||||
@@ -611,7 +490,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
|
||||
supportedFeaturesCustomBorderColor.CustomBorderColors &&
|
||||
supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat)
|
||||
{
|
||||
@@ -626,7 +505,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pExtendedFeatures = &featuresCustomBorderColor;
|
||||
}
|
||||
|
||||
var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
|
||||
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
||||
|
||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||
|
||||
@@ -646,7 +525,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PEnabledFeatures = &features
|
||||
};
|
||||
|
||||
api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
|
||||
api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||
{
|
||||
@@ -655,82 +534,5 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice)
|
||||
{
|
||||
uint propertiesCount;
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||
{
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
||||
}
|
||||
|
||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
||||
}
|
||||
|
||||
internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
||||
{
|
||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||
}
|
||||
|
||||
internal unsafe static void CreateDebugMessenger(
|
||||
Vk api,
|
||||
GraphicsDebugLevel logLevel,
|
||||
Instance instance,
|
||||
out ExtDebugUtils debugUtils,
|
||||
out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
{
|
||||
debugUtils = default;
|
||||
|
||||
if (logLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
if (!api.TryGetInstanceExtension(instance, out debugUtils))
|
||||
{
|
||||
debugUtilsMessenger = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var filterLogType = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
var filterLogSeverity = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageType = filterLogType,
|
||||
MessageSeverity = filterLogSeverity,
|
||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
|
||||
};
|
||||
|
||||
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
|
||||
}
|
||||
else
|
||||
{
|
||||
debugUtilsMessenger = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
127
Ryujinx.Graphics.Vulkan/VulkanInstance.cs
Normal file
127
Ryujinx.Graphics.Vulkan/VulkanInstance.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class VulkanInstance : IDisposable
|
||||
{
|
||||
private readonly Vk _api;
|
||||
public readonly Instance Instance;
|
||||
public readonly Version32 InstanceVersion;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private VulkanInstance(Vk api, Instance instance)
|
||||
{
|
||||
_api = api;
|
||||
Instance = instance;
|
||||
|
||||
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
|
||||
{
|
||||
InstanceVersion = Vk.Version10;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint rawInstanceVersion = 0;
|
||||
|
||||
if (api.EnumerateInstanceVersion(ref rawInstanceVersion) != Result.Success)
|
||||
{
|
||||
rawInstanceVersion = Vk.Version11.Value;
|
||||
}
|
||||
|
||||
InstanceVersion = (Version32)rawInstanceVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result Create(Vk api, ref InstanceCreateInfo createInfo, out VulkanInstance instance)
|
||||
{
|
||||
instance = null;
|
||||
|
||||
Instance rawInstance = default;
|
||||
|
||||
Result result = api.CreateInstance(SpanHelpers.AsReadOnlySpan(ref createInfo), ReadOnlySpan<AllocationCallbacks>.Empty, SpanHelpers.AsSpan(ref rawInstance));
|
||||
|
||||
if (result == Result.Success)
|
||||
{
|
||||
instance = new VulkanInstance(api, rawInstance);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Result EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices)
|
||||
{
|
||||
physicalDevices = null;
|
||||
|
||||
uint physicalDeviceCount = 0;
|
||||
|
||||
Result result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), Span<PhysicalDevice>.Empty);
|
||||
|
||||
if (result != Result.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
PhysicalDevice[] rawPhysicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||
|
||||
result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), rawPhysicalDevices);
|
||||
|
||||
if (result != Result.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
physicalDevices = rawPhysicalDevices.Select(x => new VulkanPhysicalDevice(_api, x)).ToArray();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static IReadOnlySet<string> GetInstanceExtensions(Vk api)
|
||||
{
|
||||
uint propertiesCount = 0;
|
||||
|
||||
api.EnumerateInstanceExtensionProperties(ReadOnlySpan<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span<ExtensionProperties>.Empty).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
api.EnumerateInstanceExtensionProperties(ReadOnlySpan<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlySet<string> GetInstanceLayers(Vk api)
|
||||
{
|
||||
uint propertiesCount = 0;
|
||||
|
||||
api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), Span<LayerProperties>.Empty).ThrowOnError();
|
||||
|
||||
LayerProperties[] layerProperties = new LayerProperties[propertiesCount];
|
||||
|
||||
api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), layerProperties).ThrowOnError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
return layerProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.LayerName)).ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_api.DestroyInstance(Instance, ReadOnlySpan<AllocationCallbacks>.Empty);
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs
Normal file
70
Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly struct VulkanPhysicalDevice
|
||||
{
|
||||
public readonly PhysicalDevice PhysicalDevice;
|
||||
public readonly PhysicalDeviceFeatures PhysicalDeviceFeatures;
|
||||
public readonly PhysicalDeviceProperties PhysicalDeviceProperties;
|
||||
public readonly PhysicalDeviceMemoryProperties PhysicalDeviceMemoryProperties;
|
||||
public readonly QueueFamilyProperties[] QueueFamilyProperties;
|
||||
public readonly string DeviceName;
|
||||
public readonly IReadOnlySet<string> DeviceExtensions;
|
||||
|
||||
public VulkanPhysicalDevice(Vk api, PhysicalDevice physicalDevice)
|
||||
{
|
||||
PhysicalDevice = physicalDevice;
|
||||
PhysicalDeviceFeatures = api.GetPhysicalDeviceFeature(PhysicalDevice);
|
||||
|
||||
api.GetPhysicalDeviceProperties(PhysicalDevice, out var physicalDeviceProperties);
|
||||
PhysicalDeviceProperties = physicalDeviceProperties;
|
||||
|
||||
api.GetPhysicalDeviceMemoryProperties(PhysicalDevice, out PhysicalDeviceMemoryProperties);
|
||||
|
||||
unsafe
|
||||
{
|
||||
DeviceName = Marshal.PtrToStringAnsi((IntPtr)physicalDeviceProperties.DeviceName);
|
||||
}
|
||||
|
||||
uint propertiesCount = 0;
|
||||
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), Span<QueueFamilyProperties>.Empty);
|
||||
|
||||
QueueFamilyProperties = new QueueFamilyProperties[propertiesCount];
|
||||
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), QueueFamilyProperties);
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span<ExtensionProperties>.Empty).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
DeviceExtensions = extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public string Id => $"0x{PhysicalDeviceProperties.VendorID:X}_0x{PhysicalDeviceProperties.DeviceID:X}";
|
||||
|
||||
public bool IsDeviceExtensionPresent(string extension) => DeviceExtensions.Contains(extension);
|
||||
|
||||
public DeviceInfo ToDeviceInfo()
|
||||
{
|
||||
return new DeviceInfo(
|
||||
Id,
|
||||
VendorUtils.GetNameFromId(PhysicalDeviceProperties.VendorID),
|
||||
DeviceName,
|
||||
PhysicalDeviceProperties.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,9 +17,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public sealed class VulkanRenderer : IRenderer
|
||||
{
|
||||
private Instance _instance;
|
||||
private VulkanInstance _instance;
|
||||
private SurfaceKHR _surface;
|
||||
private PhysicalDevice _physicalDevice;
|
||||
private VulkanPhysicalDevice _physicalDevice;
|
||||
private Device _device;
|
||||
private WindowBase _window;
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||
internal ExtDebugUtils DebugUtilsApi { get; private set; }
|
||||
|
||||
internal uint QueueFamilyIndex { get; private set; }
|
||||
internal Queue Queue { get; private set; }
|
||||
@@ -50,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
|
||||
internal BackgroundResources BackgroundResources { get; private set; }
|
||||
internal Action<Action> InterruptAction { get; private set; }
|
||||
internal SyncManager SyncManager { get; private set; }
|
||||
|
||||
internal BufferManager BufferManager { get; private set; }
|
||||
|
||||
@@ -57,11 +57,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal HashSet<ITexture> Textures { get; }
|
||||
internal HashSet<SamplerHolder> Samplers { get; }
|
||||
|
||||
private VulkanDebugMessenger _debugMessenger;
|
||||
private Counters _counters;
|
||||
private SyncManager _syncManager;
|
||||
|
||||
private PipelineFull _pipeline;
|
||||
private DebugUtilsMessengerEXT _debugUtilsMessenger;
|
||||
|
||||
internal HelperShader HelperShader { get; private set; }
|
||||
internal PipelineFull PipelineInternal => _pipeline;
|
||||
@@ -107,33 +106,31 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
||||
private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex)
|
||||
{
|
||||
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
|
||||
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice);
|
||||
|
||||
var supportedFeatures = Api.GetPhysicalDeviceFeature(_physicalDevice);
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
||||
{
|
||||
ConditionalRenderingApi = conditionalRenderingApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
||||
{
|
||||
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance, _device, out KhrPushDescriptor pushDescriptorApi))
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
|
||||
{
|
||||
PushDescriptorApi = pushDescriptorApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
||||
{
|
||||
TransformFeedbackApi = transformFeedbackApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
||||
{
|
||||
DrawIndirectCountApi = drawIndirectCountApi;
|
||||
}
|
||||
@@ -155,7 +152,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt
|
||||
};
|
||||
|
||||
bool supportsBlendOperationAdvanced = supportedExtensions.Contains("VK_EXT_blend_operation_advanced");
|
||||
bool supportsBlendOperationAdvanced = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_blend_operation_advanced");
|
||||
|
||||
if (supportsBlendOperationAdvanced)
|
||||
{
|
||||
@@ -168,12 +165,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt
|
||||
};
|
||||
|
||||
if (Capabilities.SupportsSubgroupSizeControl)
|
||||
bool supportsSubgroupSizeControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control");
|
||||
|
||||
if (supportsSubgroupSizeControl)
|
||||
{
|
||||
properties2.PNext = &propertiesSubgroupSizeControl;
|
||||
}
|
||||
|
||||
bool supportsTransformFeedback = supportedExtensions.Contains(ExtTransformFeedback.ExtensionName);
|
||||
bool supportsTransformFeedback = _physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName);
|
||||
|
||||
PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new PhysicalDeviceTransformFeedbackPropertiesEXT()
|
||||
{
|
||||
@@ -221,30 +220,30 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
||||
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||
{
|
||||
features2.PNext = &featuresPrimitiveTopologyListRestart;
|
||||
}
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
||||
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||
{
|
||||
featuresRobustness2.PNext = features2.PNext;
|
||||
features2.PNext = &featuresRobustness2;
|
||||
}
|
||||
|
||||
if (supportedExtensions.Contains("VK_KHR_shader_float16_int8"))
|
||||
if (_physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8"))
|
||||
{
|
||||
featuresShaderInt8.PNext = features2.PNext;
|
||||
features2.PNext = &featuresShaderInt8;
|
||||
}
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
|
||||
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
|
||||
{
|
||||
featuresCustomBorderColor.PNext = features2.PNext;
|
||||
features2.PNext = &featuresCustomBorderColor;
|
||||
}
|
||||
|
||||
bool usePortability = supportedExtensions.Contains("VK_KHR_portability_subset");
|
||||
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
||||
|
||||
if (usePortability)
|
||||
{
|
||||
@@ -255,8 +254,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &featuresPortabilitySubset;
|
||||
}
|
||||
|
||||
Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
||||
Api.GetPhysicalDeviceProperties2(_physicalDevice.PhysicalDevice, &properties2);
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
|
||||
|
||||
var portabilityFlags = PortabilitySubsetFlags.None;
|
||||
uint vertexBufferAlignment = 1;
|
||||
@@ -271,7 +270,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
|
||||
}
|
||||
|
||||
bool supportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
||||
bool supportsCustomBorderColor = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
|
||||
featuresCustomBorderColor.CustomBorderColors &&
|
||||
featuresCustomBorderColor.CustomBorderColorWithoutFormat;
|
||||
|
||||
@@ -283,30 +282,30 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
properties.Limits.FramebufferStencilSampleCounts;
|
||||
|
||||
Capabilities = new HardwareCapabilities(
|
||||
supportedExtensions.Contains("VK_EXT_index_type_uint8"),
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
|
||||
supportsCustomBorderColor,
|
||||
supportsBlendOperationAdvanced,
|
||||
propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap,
|
||||
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor,
|
||||
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor,
|
||||
supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
||||
supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"),
|
||||
supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"),
|
||||
supportedExtensions.Contains("VK_EXT_subgroup_size_control"),
|
||||
_physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
|
||||
supportsSubgroupSizeControl,
|
||||
featuresShaderInt8.ShaderInt8,
|
||||
supportedExtensions.Contains("VK_EXT_shader_stencil_export"),
|
||||
supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
|
||||
supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||
features2.Features.MultiViewport,
|
||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||
supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
|
||||
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
||||
supportsTransformFeedback,
|
||||
propertiesTransformFeedback.TransformFeedbackQueries,
|
||||
features2.Features.OcclusionQueryPrecise,
|
||||
supportedFeatures.PipelineStatisticsQuery,
|
||||
supportedFeatures.GeometryShader,
|
||||
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
|
||||
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
||||
propertiesSubgroupSizeControl.MinSubgroupSize,
|
||||
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
||||
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
||||
@@ -314,11 +313,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
portabilityFlags,
|
||||
vertexBufferAlignment);
|
||||
|
||||
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(Api, _physicalDevice);
|
||||
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice);
|
||||
|
||||
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device, properties.Limits.MaxMemoryAllocationCount);
|
||||
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
|
||||
|
||||
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||
|
||||
DescriptorSetManager = new DescriptorSetManager(_device);
|
||||
|
||||
@@ -328,7 +327,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
BufferManager = new BufferManager(this, _device);
|
||||
|
||||
_syncManager = new SyncManager(this, _device);
|
||||
SyncManager = new SyncManager(this, _device);
|
||||
_pipeline = new PipelineFull(this, _device);
|
||||
_pipeline.Initialize();
|
||||
|
||||
@@ -343,24 +342,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
Api = api;
|
||||
|
||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugUtils debugUtils, out _debugUtilsMessenger);
|
||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions());
|
||||
_debugMessenger = new VulkanDebugMessenger(api, _instance.Instance, logLevel);
|
||||
|
||||
DebugUtilsApi = debugUtils;
|
||||
|
||||
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
||||
if (api.TryGetInstanceExtension(_instance.Instance, out KhrSurface surfaceApi))
|
||||
{
|
||||
SurfaceApi = surfaceApi;
|
||||
}
|
||||
|
||||
_surface = _getSurface(_instance, api);
|
||||
_surface = _getSurface(_instance.Instance, api);
|
||||
_physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface, _preferredGpuId);
|
||||
|
||||
var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
|
||||
var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
|
||||
|
||||
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount);
|
||||
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, maxQueueCount);
|
||||
|
||||
if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi))
|
||||
if (api.TryGetDeviceExtension(_instance.Instance, _device, out KhrSwapchain swapchainApi))
|
||||
{
|
||||
SwapchainApi = swapchainApi;
|
||||
}
|
||||
@@ -369,9 +366,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Queue = queue;
|
||||
QueueLock = new object();
|
||||
|
||||
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
|
||||
LoadFeatures(maxQueueCount, queueFamilyIndex);
|
||||
|
||||
_window = new Window(this, _surface, _physicalDevice, _device);
|
||||
_window = new Window(this, _surface, _physicalDevice.PhysicalDevice, _device);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
@@ -439,7 +436,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
internal void RegisterFlush()
|
||||
{
|
||||
_syncManager.RegisterFlush();
|
||||
SyncManager.RegisterFlush();
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
@@ -536,10 +533,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = &featuresVk12
|
||||
};
|
||||
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
||||
Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
|
||||
|
||||
var limits = properties.Limits;
|
||||
var limits = _physicalDevice.PhysicalDeviceProperties.Limits;
|
||||
|
||||
return new Capabilities(
|
||||
api: TargetApi.Vulkan,
|
||||
@@ -623,7 +619,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private unsafe void PrintGpuInformation()
|
||||
{
|
||||
Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
||||
var properties = _physicalDevice.PhysicalDeviceProperties;
|
||||
|
||||
string vendorName = VendorUtils.GetNameFromId(properties.VendorID);
|
||||
|
||||
@@ -700,7 +696,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void PreFrame()
|
||||
{
|
||||
_syncManager.Cleanup();
|
||||
SyncManager.Cleanup();
|
||||
}
|
||||
|
||||
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
|
||||
@@ -740,7 +736,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void CreateSync(ulong id, bool strict)
|
||||
{
|
||||
_syncManager.Create(id, strict);
|
||||
SyncManager.Create(id, strict);
|
||||
}
|
||||
|
||||
public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info)
|
||||
@@ -750,12 +746,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void WaitSync(ulong id)
|
||||
{
|
||||
_syncManager.Wait(id);
|
||||
SyncManager.Wait(id);
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _syncManager.GetCurrent();
|
||||
return SyncManager.GetCurrent();
|
||||
}
|
||||
|
||||
public void SetInterruptAction(Action<Action> interruptAction)
|
||||
@@ -792,11 +788,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
MemoryAllocator.Dispose();
|
||||
|
||||
if (_debugUtilsMessenger.Handle != 0)
|
||||
{
|
||||
DebugUtilsApi.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger, null);
|
||||
}
|
||||
|
||||
foreach (var shader in Shaders)
|
||||
{
|
||||
shader.Dispose();
|
||||
@@ -812,12 +803,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
sampler.Dispose();
|
||||
}
|
||||
|
||||
SurfaceApi.DestroySurface(_instance, _surface, null);
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
|
||||
Api.DestroyDevice(_device, null);
|
||||
|
||||
_debugMessenger.Dispose();
|
||||
|
||||
// Last step destroy the instance
|
||||
Api.DestroyInstance(_instance, null);
|
||||
_instance.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -55,7 +55,7 @@ namespace Ryujinx.HLE.Exceptions
|
||||
if (callingType != null && callingMethod != null)
|
||||
{
|
||||
// If the type is past 0xF, we are using TIPC
|
||||
var ipcCommands = Request.Type > IpcMessageType.TipcCloseSession ? Service.TipcCommands : Service.HipcCommands;
|
||||
var ipcCommands = Request.Type > IpcMessageType.TipcCloseSession ? Service.TipcCommands : Service.CmifCommands;
|
||||
|
||||
// Find the handler for the method called
|
||||
var ipcHandler = ipcCommands.FirstOrDefault(x => x.Value == callingMethod);
|
||||
|
@@ -20,7 +20,6 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Path = System.IO.Path;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
|
||||
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
return $"{basePath}:/{fileName}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||
var fsServer = new FileSystemServer(fsServerClient);
|
||||
|
||||
RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
|
||||
RandomDataGenerator randomGenerator = Random.Shared.NextBytes;
|
||||
|
||||
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new Ticket(ticketFile.Get.AsStream());
|
||||
Ticket ticket = new(ticketFile.Get.AsStream());
|
||||
var titleKey = ticket.GetTitleKey(KeySet);
|
||||
|
||||
if (titleKey != null)
|
||||
|
@@ -1,912 +0,0 @@
|
||||
using LibHac;
|
||||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using static Ryujinx.HLE.HOS.ModLoader;
|
||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
public class ApplicationLoader
|
||||
{
|
||||
// Binaries from exefs are loaded into mem in this order. Do not change.
|
||||
internal static readonly string[] ExeFsPrefixes =
|
||||
{
|
||||
"rtld",
|
||||
"main",
|
||||
"subsdk0",
|
||||
"subsdk1",
|
||||
"subsdk2",
|
||||
"subsdk3",
|
||||
"subsdk4",
|
||||
"subsdk5",
|
||||
"subsdk6",
|
||||
"subsdk7",
|
||||
"subsdk8",
|
||||
"subsdk9",
|
||||
"sdk"
|
||||
};
|
||||
|
||||
private readonly Switch _device;
|
||||
private string _titleName;
|
||||
private string _displayVersion;
|
||||
private BlitStruct<ApplicationControlProperty> _controlData;
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = JsonHelper.GetDefaultSerializerOptions();
|
||||
private static readonly DownloadableContentJsonSerializerContext ContentSerializerContext = new(SerializerOptions);
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext TitleSerializerContext = new(SerializerOptions);
|
||||
|
||||
public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
|
||||
public string TitleName => _titleName;
|
||||
public string DisplayVersion => _displayVersion;
|
||||
|
||||
public ulong TitleId { get; private set; }
|
||||
public bool TitleIs64Bit { get; private set; }
|
||||
|
||||
public string TitleIdText => TitleId.ToString("x16");
|
||||
|
||||
public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
|
||||
|
||||
public ApplicationLoader(Switch device)
|
||||
{
|
||||
_device = device;
|
||||
_controlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||
}
|
||||
|
||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||
{
|
||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
new[] { TitleId },
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (TitleId != 0)
|
||||
{
|
||||
EnsureSaveData(new ApplicationId(TitleId));
|
||||
}
|
||||
|
||||
ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
|
||||
|
||||
if (romFsFile != null)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
||||
{
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
|
||||
{
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
|
||||
{
|
||||
updatePath = null;
|
||||
|
||||
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
||||
{
|
||||
// Clear the program index part.
|
||||
titleIdBase &= 0xFFFFFFFFFFFFFFF0;
|
||||
|
||||
// Load update informations if existing.
|
||||
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
||||
|
||||
if (File.Exists(titleUpdateMetadataPath))
|
||||
{
|
||||
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, TitleSerializerContext.TitleUpdateMetadata).Selected;
|
||||
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public void LoadXci(string xciFile)
|
||||
{
|
||||
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
||||
Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
if (!xci.HasPartition(XciPartitionType.Secure))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(securePartition).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public void LoadNsp(string nspFile)
|
||||
{
|
||||
FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(nsp).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca != null)
|
||||
{
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
||||
LoadExeFs(nsp, null, isHomebrew: true);
|
||||
}
|
||||
|
||||
public void LoadNca(string ncaFile)
|
||||
{
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
LoadNca(nca, null, null);
|
||||
}
|
||||
|
||||
public void LoadServiceNca(string ncaFile)
|
||||
{
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
|
||||
bool usePtc = _device.System.EnablePtc;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
||||
|
||||
string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16");
|
||||
bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
|
||||
string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0');
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
||||
{
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IStorage dataStorage = null;
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
(Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
if (updateControlNca != null)
|
||||
{
|
||||
controlNca = updateControlNca;
|
||||
}
|
||||
|
||||
// Load program 0 control NCA as we are going to need it for display version.
|
||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||
|
||||
// Load Aoc
|
||||
string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||
|
||||
if (File.Exists(titleAocMetadataPath))
|
||||
{
|
||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(titleAocMetadataPath, ContentSerializerContext.ListDownloadableContentContainer);
|
||||
|
||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
||||
{
|
||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
||||
{
|
||||
_device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (patchNca == null)
|
||||
{
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
string displayVersion = string.Empty;
|
||||
|
||||
if (controlNca != null)
|
||||
{
|
||||
ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlData.ByteSpan.Clear();
|
||||
}
|
||||
|
||||
// NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
|
||||
// BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
|
||||
if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
|
||||
{
|
||||
string dummyTitleName = "";
|
||||
BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
|
||||
|
||||
ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
|
||||
}
|
||||
|
||||
_displayVersion = displayVersion;
|
||||
|
||||
ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
|
||||
|
||||
if (dataStorage == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
||||
}
|
||||
else
|
||||
{
|
||||
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
|
||||
}
|
||||
|
||||
// Don't create save data for system programs.
|
||||
if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
|
||||
{
|
||||
// Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
|
||||
// We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
|
||||
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
// Sets TitleId, so be sure to call before using it
|
||||
private MetaLoader ReadNpdm(IFileSystem fs)
|
||||
{
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
if (ResultFs.PathNotFound.Includes(result))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
|
||||
|
||||
metaData = GetDefaultNpdm();
|
||||
}
|
||||
else
|
||||
{
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
}
|
||||
|
||||
metaData.GetNpdm(out var npdm).ThrowIfFailure();
|
||||
|
||||
TitleId = npdm.Aci.ProgramId.Value;
|
||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
||||
{
|
||||
using var controlFile = new UniqueRef<IFile>();
|
||||
|
||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
||||
Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
||||
|
||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||
{
|
||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
controlData.ByteSpan.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
|
||||
{
|
||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||
{
|
||||
metaData = null; // TODO: Check if we should retain old npdm.
|
||||
}
|
||||
|
||||
metaData ??= ReadNpdm(codeFs);
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// ExeFs file replacements.
|
||||
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
// Take the npdm from mods if present.
|
||||
if (modLoadResult.Npdm != null)
|
||||
{
|
||||
metaData = modLoadResult.Npdm;
|
||||
}
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
bool usePtc = _device.System.EnablePtc;
|
||||
|
||||
// Don't use PPTC if ExeFs files have been replaced.
|
||||
usePtc &= !modLoadResult.Modified;
|
||||
|
||||
if (_device.System.EnablePtc && !usePtc)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
|
||||
}
|
||||
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
|
||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
||||
|
||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||
|
||||
return result.ProcessId;
|
||||
}
|
||||
|
||||
public void LoadProgram(string filePath)
|
||||
{
|
||||
MetaLoader metaData = GetDefaultNpdm();
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
|
||||
|
||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||
|
||||
IExecutable executable;
|
||||
Stream romfsStream = null;
|
||||
|
||||
if (isNro)
|
||||
{
|
||||
FileStream input = new FileStream(filePath, FileMode.Open);
|
||||
NroExecutable obj = new NroExecutable(input.AsStorage());
|
||||
|
||||
executable = obj;
|
||||
|
||||
// Homebrew NRO can actually have some data after the actual NRO.
|
||||
if (input.Length > obj.FileSize)
|
||||
{
|
||||
input.Position = obj.FileSize;
|
||||
|
||||
BinaryReader reader = new BinaryReader(input);
|
||||
|
||||
uint asetMagic = reader.ReadUInt32();
|
||||
if (asetMagic == 0x54455341)
|
||||
{
|
||||
uint asetVersion = reader.ReadUInt32();
|
||||
if (asetVersion == 0)
|
||||
{
|
||||
ulong iconOffset = reader.ReadUInt64();
|
||||
ulong iconSize = reader.ReadUInt64();
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
ulong romfsOffset = reader.ReadUInt64();
|
||||
ulong romfsSize = reader.ReadUInt64();
|
||||
|
||||
if (romfsSize != 0)
|
||||
{
|
||||
romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset);
|
||||
}
|
||||
|
||||
if (nacpSize != 0)
|
||||
{
|
||||
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
|
||||
|
||||
reader.Read(ControlData.ByteSpan);
|
||||
|
||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
||||
|
||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
||||
{
|
||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
||||
}
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
programInfo.ProgramId = 0000000000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
_titleName = programInfo.Name;
|
||||
TitleId = programInfo.ProgramId;
|
||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
// Explicitly null titleid to disable the shader cache.
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
|
||||
|
||||
if (romfsStream != null)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
|
||||
}
|
||||
|
||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||
}
|
||||
|
||||
private MetaLoader GetDefaultNpdm()
|
||||
{
|
||||
Assembly asm = Assembly.GetCallingAssembly();
|
||||
|
||||
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
||||
{
|
||||
var npdmBuffer = new byte[npdmStream.Length];
|
||||
npdmStream.Read(npdmBuffer);
|
||||
|
||||
var metaLoader = new MetaLoader();
|
||||
metaLoader.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
return metaLoader;
|
||||
}
|
||||
}
|
||||
|
||||
private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
|
||||
{
|
||||
ulong mainProgramId = 0;
|
||||
Span<bool> hasIndex = stackalloc bool[0x10];
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong currentProgramId = nca.Header.TitleId;
|
||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
||||
|
||||
if (mainProgramId == 0 && currentMainProgramId != 0)
|
||||
{
|
||||
mainProgramId = currentMainProgramId;
|
||||
}
|
||||
|
||||
if (mainProgramId != currentMainProgramId)
|
||||
{
|
||||
// As far as I know there aren't any multi-application game cards containing multi-program applications,
|
||||
// so because multi-application game cards are the only way we should run into multiple applications
|
||||
// we'll just return that there's a single program.
|
||||
return (mainProgramId, 1);
|
||||
}
|
||||
|
||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
||||
}
|
||||
|
||||
int programCount = 0;
|
||||
|
||||
for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
|
||||
{
|
||||
programCount++;
|
||||
}
|
||||
|
||||
return (mainProgramId, programCount);
|
||||
}
|
||||
|
||||
private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
|
||||
{
|
||||
(ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
|
||||
|
||||
if (programCount <= 0)
|
||||
return Result.Success;
|
||||
|
||||
Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
|
||||
|
||||
for (int i = 0; i < programCount; i++)
|
||||
{
|
||||
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
|
||||
mapInfo[i].MainProgramId = new ApplicationId(applicationId);
|
||||
mapInfo[i].ProgramIndex = (byte)i;
|
||||
}
|
||||
|
||||
return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
|
||||
}
|
||||
|
||||
private Result EnsureSaveData(ApplicationId applicationId)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
|
||||
|
||||
Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
|
||||
|
||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
||||
|
||||
if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan))
|
||||
{
|
||||
// If the current application doesn't have a loaded control property, create a dummy one
|
||||
// and set the savedata sizes so a user savedata will be created.
|
||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
||||
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
control.SaveDataOwnerId = applicationId.Value;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadKip(string kipPath)
|
||||
public bool LoadKip(string kipPath)
|
||||
{
|
||||
using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
|
||||
|
||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||
return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||
}
|
||||
|
||||
public void ChangeDockedModeState(bool newState)
|
||||
|
@@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
|
||||
long recvListPos = reader.BaseStream.Position + rawDataSize;
|
||||
|
||||
// only HIPC have the padding requirements.
|
||||
// Only CMIF has the padding requirements.
|
||||
if (Type < IpcMessageType.TipcCloseSession)
|
||||
{
|
||||
long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr);
|
||||
|
@@ -2,12 +2,12 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
{
|
||||
enum IpcMessageType
|
||||
{
|
||||
HipcResponse = 0,
|
||||
HipcCloseSession = 2,
|
||||
HipcRequest = 4,
|
||||
HipcControl = 5,
|
||||
HipcRequestWithContext = 6,
|
||||
HipcControlWithContext = 7,
|
||||
CmifResponse = 0,
|
||||
CmifCloseSession = 2,
|
||||
CmifRequest = 4,
|
||||
CmifControl = 5,
|
||||
CmifRequestWithContext = 6,
|
||||
CmifControlWithContext = 7,
|
||||
TipcCloseSession = 0xF
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
internal class ProcessTamperInfo
|
||||
class ProcessTamperInfo
|
||||
{
|
||||
public KProcess Process { get; }
|
||||
public IEnumerable<string> BuildIds { get; }
|
||||
|
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Mods;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS
|
||||
return modLoadResult;
|
||||
}
|
||||
|
||||
if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
|
||||
if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("NSO Count is incorrect");
|
||||
}
|
||||
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
foreach (var mod in exeMods)
|
||||
{
|
||||
for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
|
||||
for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
|
||||
{
|
||||
var nsoName = ApplicationLoader.ExeFsPrefixes[i];
|
||||
var nsoName = ProcessConst.ExeFsPrefixes[i];
|
||||
|
||||
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
|
||||
if (nsoFile.Exists)
|
||||
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
||||
for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
||||
{
|
||||
if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
|
||||
{
|
||||
|
@@ -9,21 +9,21 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
_managerServer = new ManagerServer(userId);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// CheckAvailability()
|
||||
public ResultCode CheckAvailability(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.CheckAvailability(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetAccountId() -> nn::account::NetworkServiceAccountId
|
||||
public ResultCode GetAccountId(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.GetAccountId(context);
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// EnsureIdTokenCacheAsync() -> object<nn::account::detail::IAsyncContext>
|
||||
public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context)
|
||||
{
|
||||
@@ -37,28 +37,28 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// LoadIdTokenCache() -> (u32 id_token_cache_size, buffer<bytes, 6>)
|
||||
public ResultCode LoadIdTokenCache(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.LoadIdTokenCache(context);
|
||||
}
|
||||
|
||||
[CommandHipc(130)]
|
||||
[CommandCmif(130)]
|
||||
// GetNintendoAccountUserResourceCacheForApplication() -> (nn::account::NintendoAccountId, nn::account::nas::NasUserBaseForApplication, buffer<bytes, 6>)
|
||||
public ResultCode GetNintendoAccountUserResourceCacheForApplication(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.GetNintendoAccountUserResourceCacheForApplication(context);
|
||||
}
|
||||
|
||||
[CommandHipc(160)] // 5.0.0+
|
||||
[CommandCmif(160)] // 5.0.0+
|
||||
// StoreOpenContext()
|
||||
public ResultCode StoreOpenContext(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.StoreOpenContext(context);
|
||||
}
|
||||
|
||||
[CommandHipc(170)] // 6.0.0+
|
||||
[CommandCmif(170)] // 6.0.0+
|
||||
// LoadNetworkServiceLicenseKindAsync() -> object<nn::account::detail::IAsyncNetworkServiceLicenseKindContext>
|
||||
public ResultCode LoadNetworkServiceLicenseKindAsync(ServiceCtx context)
|
||||
{
|
||||
|
@@ -9,21 +9,21 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
_managerServer = new ManagerServer(userId);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// CheckAvailability()
|
||||
public ResultCode CheckAvailability(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.CheckAvailability(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetAccountId() -> nn::account::NetworkServiceAccountId
|
||||
public ResultCode GetAccountId(ServiceCtx context)
|
||||
{
|
||||
return _managerServer.GetAccountId(context);
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// EnsureIdTokenCacheAsync() -> object<nn::account::detail::IAsyncContext>
|
||||
public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context)
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// LoadIdTokenCache() -> (u32 id_token_cache_size, buffer<bytes, 6>)
|
||||
public ResultCode LoadIdTokenCache(ServiceCtx context)
|
||||
{
|
||||
|
@@ -9,28 +9,28 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
_profileServer = new ProfileServer(profile);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// Get() -> (nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x1a>)
|
||||
public ResultCode Get(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.Get(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetBase() -> nn::account::profile::ProfileBase
|
||||
public ResultCode GetBase(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.GetBase(context);
|
||||
}
|
||||
|
||||
[CommandHipc(10)]
|
||||
[CommandCmif(10)]
|
||||
// GetImageSize() -> u32
|
||||
public ResultCode GetImageSize(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.GetImageSize(context);
|
||||
}
|
||||
|
||||
[CommandHipc(11)]
|
||||
[CommandCmif(11)]
|
||||
// LoadImage() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode LoadImage(ServiceCtx context)
|
||||
{
|
||||
|
@@ -9,42 +9,42 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
||||
_profileServer = new ProfileServer(profile);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// Get() -> (nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x1a>)
|
||||
public ResultCode Get(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.Get(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetBase() -> nn::account::profile::ProfileBase
|
||||
public ResultCode GetBase(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.GetBase(context);
|
||||
}
|
||||
|
||||
[CommandHipc(10)]
|
||||
[CommandCmif(10)]
|
||||
// GetImageSize() -> u32
|
||||
public ResultCode GetImageSize(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.GetImageSize(context);
|
||||
}
|
||||
|
||||
[CommandHipc(11)]
|
||||
[CommandCmif(11)]
|
||||
// LoadImage() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode LoadImage(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.LoadImage(context);
|
||||
}
|
||||
|
||||
[CommandHipc(100)]
|
||||
[CommandCmif(100)]
|
||||
// Store(nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x19>)
|
||||
public ResultCode Store(ServiceCtx context)
|
||||
{
|
||||
return _profileServer.Store(context);
|
||||
}
|
||||
|
||||
[CommandHipc(101)]
|
||||
[CommandCmif(101)]
|
||||
// StoreWithImage(nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x19>, buffer<bytes, 5>)
|
||||
public ResultCode StoreWithImage(ServiceCtx context)
|
||||
{
|
||||
|
@@ -14,42 +14,42 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// GetUserCount() -> i32
|
||||
public ResultCode GetUserCount(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserCountImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetUserExistence(nn::account::Uid) -> bool
|
||||
public ResultCode GetUserExistence(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserExistenceImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// ListAllUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListAllUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListAllUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListOpenUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListOpenUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(4)]
|
||||
[CommandCmif(4)]
|
||||
// GetLastOpenedUser() -> nn::account::Uid
|
||||
public ResultCode GetLastOpenedUser(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetLastOpenedUser(context);
|
||||
}
|
||||
|
||||
[CommandHipc(5)]
|
||||
[CommandCmif(5)]
|
||||
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
|
||||
public ResultCode GetProfile(ServiceCtx context)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(50)]
|
||||
[CommandCmif(50)]
|
||||
// IsUserRegistrationRequestPermitted(pid) -> bool
|
||||
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
|
||||
{
|
||||
@@ -72,14 +72,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
|
||||
}
|
||||
|
||||
[CommandHipc(51)]
|
||||
[CommandCmif(51)]
|
||||
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
||||
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
|
||||
}
|
||||
|
||||
[CommandHipc(102)]
|
||||
[CommandCmif(102)]
|
||||
// GetBaasAccountManagerForSystemService(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
|
||||
public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context)
|
||||
{
|
||||
@@ -98,14 +98,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(140)] // 6.0.0+
|
||||
[CommandCmif(140)] // 6.0.0+
|
||||
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListQualifiedUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListQualifiedUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(205)]
|
||||
[CommandCmif(205)]
|
||||
// GetProfileEditor(nn::account::Uid) -> object<nn::account::profile::IProfileEditor>
|
||||
public ResultCode GetProfileEditor(ServiceCtx context)
|
||||
{
|
||||
|
@@ -14,42 +14,42 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// GetUserCount() -> i32
|
||||
public ResultCode GetUserCount(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserCountImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetUserExistence(nn::account::Uid) -> bool
|
||||
public ResultCode GetUserExistence(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserExistenceImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// ListAllUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListAllUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListAllUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListOpenUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListOpenUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(4)]
|
||||
[CommandCmif(4)]
|
||||
// GetLastOpenedUser() -> nn::account::Uid
|
||||
public ResultCode GetLastOpenedUser(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetLastOpenedUser(context);
|
||||
}
|
||||
|
||||
[CommandHipc(5)]
|
||||
[CommandCmif(5)]
|
||||
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
|
||||
public ResultCode GetProfile(ServiceCtx context)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(50)]
|
||||
[CommandCmif(50)]
|
||||
// IsUserRegistrationRequestPermitted(pid) -> bool
|
||||
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
|
||||
{
|
||||
@@ -71,16 +71,16 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
|
||||
}
|
||||
|
||||
[CommandHipc(51)]
|
||||
[CommandCmif(51)]
|
||||
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
||||
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
|
||||
}
|
||||
|
||||
[CommandHipc(100)]
|
||||
[CommandHipc(140)] // 6.0.0+
|
||||
[CommandHipc(160)] // 13.0.0+
|
||||
[CommandCmif(100)]
|
||||
[CommandCmif(140)] // 6.0.0+
|
||||
[CommandCmif(160)] // 13.0.0+
|
||||
// InitializeApplicationInfo(u64 pid_placeholder, pid)
|
||||
public ResultCode InitializeApplicationInfo(ServiceCtx context)
|
||||
{
|
||||
@@ -105,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(101)]
|
||||
[CommandCmif(101)]
|
||||
// GetBaasAccountManagerForApplication(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
|
||||
public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context)
|
||||
{
|
||||
@@ -124,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(103)] // 4.0.0+
|
||||
[CommandCmif(103)] // 4.0.0+
|
||||
// CheckNetworkServiceAvailabilityAsync() -> object<nn::account::detail::IAsyncContext>
|
||||
public ResultCode CheckNetworkServiceAvailabilityAsync(ServiceCtx context)
|
||||
{
|
||||
@@ -138,21 +138,21 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(110)]
|
||||
[CommandCmif(110)]
|
||||
// StoreSaveDataThumbnail(nn::account::Uid, buffer<bytes, 5>)
|
||||
public ResultCode StoreSaveDataThumbnail(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.StoreSaveDataThumbnail(context);
|
||||
}
|
||||
|
||||
[CommandHipc(111)]
|
||||
[CommandCmif(111)]
|
||||
// ClearSaveDataThumbnail(nn::account::Uid)
|
||||
public ResultCode ClearSaveDataThumbnail(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ClearSaveDataThumbnail(context);
|
||||
}
|
||||
|
||||
[CommandHipc(130)] // 5.0.0+
|
||||
[CommandCmif(130)] // 5.0.0+
|
||||
// LoadOpenContext(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
|
||||
public ResultCode LoadOpenContext(ServiceCtx context)
|
||||
{
|
||||
@@ -168,29 +168,29 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(60)] // 5.0.0-5.1.0
|
||||
[CommandHipc(131)] // 6.0.0+
|
||||
[CommandCmif(60)] // 5.0.0-5.1.0
|
||||
[CommandCmif(131)] // 6.0.0+
|
||||
// ListOpenContextStoredUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListOpenContextStoredUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListOpenContextStoredUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(141)] // 6.0.0+
|
||||
[CommandCmif(141)] // 6.0.0+
|
||||
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListQualifiedUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListQualifiedUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(150)] // 6.0.0+
|
||||
[CommandCmif(150)] // 6.0.0+
|
||||
// IsUserAccountSwitchLocked() -> bool
|
||||
public ResultCode IsUserAccountSwitchLocked(ServiceCtx context)
|
||||
{
|
||||
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
||||
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||
|
||||
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
||||
|
||||
|
@@ -13,42 +13,42 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// GetUserCount() -> i32
|
||||
public ResultCode GetUserCount(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserCountImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// GetUserExistence(nn::account::Uid) -> bool
|
||||
public ResultCode GetUserExistence(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetUserExistenceImpl(context);
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// ListAllUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListAllUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListAllUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListOpenUsers(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.ListOpenUsers(context);
|
||||
}
|
||||
|
||||
[CommandHipc(4)]
|
||||
[CommandCmif(4)]
|
||||
// GetLastOpenedUser() -> nn::account::Uid
|
||||
public ResultCode GetLastOpenedUser(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.GetLastOpenedUser(context);
|
||||
}
|
||||
|
||||
[CommandHipc(5)]
|
||||
[CommandCmif(5)]
|
||||
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
|
||||
public ResultCode GetProfile(ServiceCtx context)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(50)]
|
||||
[CommandCmif(50)]
|
||||
// IsUserRegistrationRequestPermitted(pid) -> bool
|
||||
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
|
||||
{
|
||||
@@ -71,14 +71,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
|
||||
}
|
||||
|
||||
[CommandHipc(51)]
|
||||
[CommandCmif(51)]
|
||||
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
||||
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
||||
{
|
||||
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
|
||||
}
|
||||
|
||||
[CommandHipc(102)]
|
||||
[CommandCmif(102)]
|
||||
// GetBaasAccountManagerForSystemService(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
|
||||
public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context)
|
||||
{
|
||||
@@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(140)] // 6.0.0+
|
||||
[CommandCmif(140)] // 6.0.0+
|
||||
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
|
||||
public ResultCode ListQualifiedUsers(ServiceCtx context)
|
||||
{
|
||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
AsyncExecution = asyncExecution;
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandCmif(0)]
|
||||
// GetSystemEvent() -> handle<copy>
|
||||
public ResultCode GetSystemEvent(ServiceCtx context)
|
||||
{
|
||||
@@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandCmif(1)]
|
||||
// Cancel()
|
||||
public ResultCode Cancel(ServiceCtx context)
|
||||
{
|
||||
@@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandCmif(2)]
|
||||
// HasDone() -> b8
|
||||
public ResultCode HasDone(ServiceCtx context)
|
||||
{
|
||||
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandCmif(3)]
|
||||
// GetResult()
|
||||
public ResultCode GetResult(ServiceCtx context)
|
||||
{
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
_serviceLicenseKind = serviceLicenseKind;
|
||||
}
|
||||
|
||||
[CommandHipc(100)]
|
||||
[CommandCmif(100)]
|
||||
// GetNetworkServiceLicenseKind() -> nn::account::NetworkServiceLicenseKind
|
||||
public ResultCode GetNetworkServiceLicenseKind(ServiceCtx context)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user