Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
4ce4299ca2 | ||
|
17620d18db | ||
|
9f1cf6458c |
@@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# Namespace preferences
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
resharper_csharp_namespace_body = block_scoped
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
|
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
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1034,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
||||
|
||||
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
|
||||
{
|
||||
WriteEvexInst(dest, src1, src2, type, flags, opCode);
|
||||
|
||||
opCode &= 0xff;
|
||||
}
|
||||
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
{
|
||||
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
|
||||
|
||||
@@ -1153,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEvexInst(
|
||||
Operand dest,
|
||||
Operand src1,
|
||||
Operand src2,
|
||||
OperandType type,
|
||||
InstructionFlags flags,
|
||||
int opCode,
|
||||
bool broadcast = false,
|
||||
int registerWidth = 128,
|
||||
int maskRegisterIdx = 0,
|
||||
bool zeroElements = false)
|
||||
{
|
||||
int op1Idx = dest.GetRegister().Index;
|
||||
int op2Idx = src1.GetRegister().Index;
|
||||
int op3Idx = src2.GetRegister().Index;
|
||||
|
||||
WriteByte(0x62);
|
||||
|
||||
// P0
|
||||
// Extend operand 1 register
|
||||
bool r = (op1Idx & 8) == 0;
|
||||
// Extend operand 3 register
|
||||
bool x = (op3Idx & 16) == 0;
|
||||
// Extend operand 3 register
|
||||
bool b = (op3Idx & 8) == 0;
|
||||
// Extend operand 1 register
|
||||
bool rp = (op1Idx & 16) == 0;
|
||||
// Escape code index
|
||||
byte mm = 0b00;
|
||||
|
||||
switch ((ushort)(opCode >> 8))
|
||||
{
|
||||
case 0xf00: mm = 0b01; break;
|
||||
case 0xf38: mm = 0b10; break;
|
||||
case 0xf3a: mm = 0b11; break;
|
||||
|
||||
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
|
||||
}
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(r ? 0x80 : 0) |
|
||||
(x ? 0x40 : 0) |
|
||||
(b ? 0x20 : 0) |
|
||||
(rp ? 0x10 : 0) |
|
||||
mm));
|
||||
|
||||
// P1
|
||||
// Specify 64-bit lane mode
|
||||
bool w = Is64Bits(type);
|
||||
// Operand 2 register index
|
||||
byte vvvv = (byte)(~op2Idx & 0b1111);
|
||||
// Opcode prefix
|
||||
byte pp = (flags & InstructionFlags.PrefixMask) switch
|
||||
{
|
||||
InstructionFlags.Prefix66 => 0b01,
|
||||
InstructionFlags.PrefixF3 => 0b10,
|
||||
InstructionFlags.PrefixF2 => 0b11,
|
||||
_ => 0
|
||||
};
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(w ? 0x80 : 0) |
|
||||
(vvvv << 3) |
|
||||
0b100 |
|
||||
pp));
|
||||
|
||||
// P2
|
||||
// Mask register determines what elements to zero, rather than what elements to merge
|
||||
bool z = zeroElements;
|
||||
// Specifies register-width
|
||||
byte ll = 0b00;
|
||||
switch (registerWidth)
|
||||
{
|
||||
case 128: ll = 0b00; break;
|
||||
case 256: ll = 0b01; break;
|
||||
case 512: ll = 0b10; break;
|
||||
|
||||
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
|
||||
}
|
||||
// Embedded broadcast in the case of a memory operand
|
||||
bool bcast = broadcast;
|
||||
// Extend operand 2 register
|
||||
bool vp = (op2Idx & 16) == 0;
|
||||
// Mask register index
|
||||
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
|
||||
byte aaa = (byte)(maskRegisterIdx & 0b111);
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(z ? 0x80 : 0) |
|
||||
(ll << 5) |
|
||||
(bcast ? 0x10 : 0) |
|
||||
(vp ? 8 : 0) |
|
||||
aaa));
|
||||
}
|
||||
|
||||
private void WriteCompactInst(Operand operand, int opCode)
|
||||
{
|
||||
int regIndex = operand.GetRegister().Index;
|
||||
|
@@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Reg8Dest = 1 << 2,
|
||||
RexW = 1 << 3,
|
||||
Vex = 1 << 4,
|
||||
Evex = 1 << 5,
|
||||
|
||||
PrefixBit = 16,
|
||||
PrefixMask = 7 << PrefixBit,
|
||||
@@ -278,6 +279,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
||||
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||
|
@@ -1,10 +1,14 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class HardwareCapabilities
|
||||
{
|
||||
private delegate uint GetXcr0();
|
||||
|
||||
static HardwareCapabilities()
|
||||
{
|
||||
if (!X86Base.IsSupported)
|
||||
@@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
|
||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
||||
}
|
||||
|
||||
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
|
||||
}
|
||||
|
||||
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
|
||||
0xf, 0x01, 0xd0, // xgetbv
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
|
||||
|
||||
memGetXcr0.Write(0, asmGetXcr0);
|
||||
|
||||
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
|
||||
|
||||
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
|
||||
|
||||
return fGetXcr0();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -44,6 +76,8 @@ 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
|
||||
}
|
||||
@@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public enum FeatureFlags7Ebx
|
||||
{
|
||||
Avx2 = 1 << 5,
|
||||
Sha = 1 << 29
|
||||
Avx512f = 1 << 16,
|
||||
Avx512dq = 1 << 17,
|
||||
Sha = 1 << 29,
|
||||
Avx512bw = 1 << 30,
|
||||
Avx512vl = 1 << 31
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Gfni = 1 << 8,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Xcr0FlagsEax
|
||||
{
|
||||
Sse = 1 << 1,
|
||||
YmmHi128 = 1 << 2,
|
||||
Opmask = 1 << 5,
|
||||
ZmmHi256 = 1 << 6,
|
||||
Hi16Zmm = 1 << 7
|
||||
}
|
||||
|
||||
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
||||
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
|
||||
|
||||
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
||||
@@ -76,8 +125,13 @@ 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);
|
||||
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.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;
|
||||
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
|
||||
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
||||
@@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool ForceLegacySse { get; set; }
|
||||
|
||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
||||
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
|
||||
}
|
||||
}
|
@@ -180,6 +180,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
||||
}
|
||||
|
@@ -219,6 +219,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Vfnmsub231sd,
|
||||
Vfnmsub231ss,
|
||||
Vpblendvb,
|
||||
Vpternlogd,
|
||||
Xor,
|
||||
Xorpd,
|
||||
Xorps,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
namespace ARMeilleure.Decoders;
|
||||
|
||||
interface IOpCode32Exception
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
int Id { get; }
|
||||
interface IOpCode32Exception
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
}
|
@@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Not_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
@@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
@@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mask = context.VectorOne();
|
||||
|
@@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Vmvn_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
|
@@ -173,6 +173,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Vfnmadd231ss,
|
||||
X86Vfnmsub231sd,
|
||||
X86Vfnmsub231ss,
|
||||
X86Vpternlogd,
|
||||
X86Xorpd,
|
||||
X86Xorps,
|
||||
|
||||
|
@@ -23,6 +23,10 @@ namespace ARMeilleure
|
||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512FIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512VlIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512BwIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512DqIfAvailable { get; set; } = true;
|
||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||
@@ -47,11 +51,18 @@ namespace ARMeilleure
|
||||
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
|
||||
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
|
||||
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
|
||||
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
|
||||
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||
|
||||
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
|
||||
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
|
||||
}
|
||||
}
|
||||
|
@@ -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 = 4484; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4485; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@@ -969,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
@@ -977,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
|
||||
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FeatureInfo(0, 0, 0, 0);
|
||||
return new FeatureInfo(0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return osPlatform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||
private struct OuterHeader
|
||||
{
|
||||
public ulong Magic;
|
||||
@@ -1034,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||
private struct InnerHeader
|
||||
|
@@ -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>
|
@@ -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,10 +320,14 @@ 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;
|
||||
var nacp = activeProcess.ApplicationControlProperties;
|
||||
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@@ -423,9 +427,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 +543,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 +563,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 +590,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 +603,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 +617,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 +632,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 +656,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 +989,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -19,6 +19,7 @@ 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;
|
||||
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
|
||||
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;
|
||||
|
@@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||
|
||||
foreach (var item in strings)
|
||||
{
|
||||
|
@@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using Ryujinx.Ui.Common.Models.Github;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -31,6 +32,7 @@ namespace Ryujinx.Modules
|
||||
internal static class Updater
|
||||
{
|
||||
private const string GitHubApiURL = "https://api.github.com";
|
||||
private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
@@ -99,22 +101,16 @@ namespace Ryujinx.Modules
|
||||
|
||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||
JToken assets = jsonRoot["assets"];
|
||||
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||
_buildVer = fetched.Name;
|
||||
|
||||
_buildVer = (string)jsonRoot["name"];
|
||||
|
||||
foreach (JToken asset in assets)
|
||||
foreach (var asset in fetched.Assets)
|
||||
{
|
||||
string assetName = (string)asset["name"];
|
||||
string assetState = (string)asset["state"];
|
||||
string downloadURL = (string)asset["browser_download_url"];
|
||||
|
||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||
{
|
||||
_buildUrl = downloadURL;
|
||||
_buildUrl = asset.BrowserDownloadUrl;
|
||||
|
||||
if (assetState != "uploaded")
|
||||
if (asset.State != "uploaded")
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
|
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class Amiibo
|
||||
{
|
||||
public struct AmiiboJson
|
||||
{
|
||||
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
|
||||
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
public struct AmiiboApi
|
||||
{
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
[JsonPropertyName("head")] public string Head { get; set; }
|
||||
[JsonPropertyName("tail")] public string Tail { get; set; }
|
||||
[JsonPropertyName("image")] public string Image { get; set; }
|
||||
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
|
||||
[JsonPropertyName("character")] public string Character { get; set; }
|
||||
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
|
||||
[JsonPropertyName("type")] public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
|
||||
|
||||
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string GetId()
|
||||
{
|
||||
return Head + Tail;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is AmiiboApi amiibo)
|
||||
{
|
||||
return amiibo.Head + amiibo.Tail == Head + Tail;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class AmiiboApiGamesSwitch
|
||||
{
|
||||
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
||||
|
||||
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
|
||||
|
||||
[JsonPropertyName("gameName")] public string GameName { get; set; }
|
||||
}
|
||||
|
||||
public class AmiiboApiUsage
|
||||
{
|
||||
[JsonPropertyName("Usage")] public string Usage { get; set; }
|
||||
|
||||
[JsonPropertyName("write")] public bool Write { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -4,11 +4,11 @@ using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -17,6 +17,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
@@ -31,8 +32,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private readonly StyleableWindow _owner;
|
||||
|
||||
private Bitmap _amiiboImage;
|
||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
|
||||
private int _amiiboSelectedIndex;
|
||||
@@ -42,6 +43,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private bool _useRandomUuid;
|
||||
private string _usage;
|
||||
|
||||
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
||||
{
|
||||
_owner = owner;
|
||||
@@ -52,9 +55,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||
|
||||
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||
_amiiboList = new List<Amiibo.AmiiboApi>();
|
||||
_amiiboList = new List<AmiiboApi>();
|
||||
_amiiboSeries = new ObservableCollection<string>();
|
||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
||||
_amiibos = new AvaloniaList<AmiiboApi>();
|
||||
|
||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
||||
|
||||
@@ -94,7 +97,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
|
||||
public AvaloniaList<AmiiboApi> AmiiboList
|
||||
{
|
||||
get => _amiibos;
|
||||
set
|
||||
@@ -187,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (File.Exists(_amiiboJsonPath))
|
||||
{
|
||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
||||
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||
|
||||
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
|
||||
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
||||
{
|
||||
amiiboJsonString = await DownloadAmiiboJson();
|
||||
}
|
||||
@@ -206,7 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
|
||||
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||
|
||||
ParseAmiiboData();
|
||||
@@ -223,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
if (!ShowAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
@@ -255,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void SelectLastScannedAmiibo()
|
||||
{
|
||||
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||
AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||
|
||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||
@@ -270,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
||||
List<AmiiboApi> amiiboSortedList = _amiiboList
|
||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||
|
||||
@@ -280,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
@@ -314,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
|
||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||
|
||||
@@ -326,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
bool writable = false;
|
||||
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (item.GameId.Contains(TitleId))
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageString += Environment.NewLine +
|
||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||
|
@@ -51,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private bool _isLoaded;
|
||||
private readonly UserControl _owner;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
public IGamepad SelectedGamepad { get; private set; }
|
||||
|
||||
@@ -706,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
using (Stream stream = File.OpenRead(path))
|
||||
{
|
||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
||||
}
|
||||
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||
}
|
||||
catch (JsonException) { }
|
||||
catch (InvalidOperationException)
|
||||
@@ -775,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, true);
|
||||
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
|
@@ -21,7 +21,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
@@ -41,6 +40,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private ulong _titleId;
|
||||
private string _titleName;
|
||||
|
||||
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||
{
|
||||
get => _downloadableContents;
|
||||
@@ -100,7 +101,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -330,10 +331,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
_downloadableContentContainerList.Add(container);
|
||||
}
|
||||
|
||||
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
||||
}
|
||||
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
@@ -1705,8 +1705,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,234 +17,236 @@ 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;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels;
|
||||
|
||||
public class TitleUpdateViewModel : BaseModel
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||
public readonly string _titleUpdateJsonPath;
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
private object _selectedUpdate;
|
||||
|
||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||
public class TitleUpdateViewModel : BaseModel
|
||||
{
|
||||
get => _titleUpdates;
|
||||
set
|
||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||
public readonly string _titleUpdateJsonPath;
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
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
|
||||
{
|
||||
_titleUpdates = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<object> Views
|
||||
{
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object SelectedUpdate
|
||||
{
|
||||
get => _selectedUpdate;
|
||||
set
|
||||
{
|
||||
_selectedUpdate = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
get => _titleUpdates;
|
||||
set
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||
|
||||
SelectedUpdate = selected;
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
Views.Clear();
|
||||
Views.Add(new BaseModel());
|
||||
Views.AddRange(list);
|
||||
|
||||
if (SelectedUpdate == null)
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||
{
|
||||
if (Views.Count > 1)
|
||||
{
|
||||
SelectedUpdate = Views[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
_titleUpdates = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
public AvaloniaList<object> Views
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object SelectedUpdate
|
||||
{
|
||||
get => _selectedUpdate;
|
||||
set
|
||||
{
|
||||
_selectedUpdate = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
Save();
|
||||
}
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
private void LoadUpdates()
|
||||
{
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||
|
||||
SelectedUpdate = selected;
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
Views.Clear();
|
||||
Views.Add(new BaseModel());
|
||||
Views.AddRange(list);
|
||||
|
||||
if (SelectedUpdate == null)
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||
{
|
||||
if (Views.Count > 1)
|
||||
{
|
||||
SelectedUpdate = Views[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveUpdate(TitleUpdateModel update)
|
||||
{
|
||||
TitleUpdates.Remove(update);
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new()
|
||||
public void RemoveUpdate(TitleUpdateModel update)
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
TitleUpdates.Remove(update);
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
OpenFileDialog dialog = new()
|
||||
{
|
||||
foreach (string file in files)
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
AddUpdate(file);
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
if (update == SelectedUpdate)
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
if (update == SelectedUpdate)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
}
|
||||
}
|
@@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
{
|
||||
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
||||
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||
|
||||
if (!strings.TryGetValue("Language", out string languageName))
|
||||
{
|
||||
@@ -126,7 +126,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 +148,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,7 +1,7 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
@@ -35,14 +35,14 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
}
|
||||
|
||||
public bool IsScanned { get; set; }
|
||||
public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
|
||||
public AmiiboApi ScannedAmiibo { get; set; }
|
||||
public AmiiboWindowViewModel ViewModel { get; set; }
|
||||
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.AmiiboSelectedIndex > -1)
|
||||
{
|
||||
Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||
ScannedAmiibo = amiibo;
|
||||
IsScanned = true;
|
||||
Close();
|
||||
|
@@ -6,11 +6,8 @@ using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Button = Avalonia.Controls.Button;
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||
public enum AspectRatio
|
||||
{
|
||||
Fixed4x3,
|
||||
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||
public enum BackendThreading
|
||||
{
|
||||
Auto,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
|
||||
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Vulkan,
|
||||
|
@@ -1,5 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
|
||||
public enum GraphicsDebugLevel
|
||||
{
|
||||
None,
|
||||
|
@@ -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,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -6,6 +7,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
|
||||
{
|
||||
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
|
||||
{
|
||||
// Temporary reader to get the backend type
|
||||
@@ -52,8 +55,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
|
||||
return motionBackendType switch
|
||||
{
|
||||
MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options),
|
||||
MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options),
|
||||
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
|
||||
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
|
||||
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
||||
};
|
||||
}
|
||||
@@ -63,10 +66,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
switch (value.MotionBackend)
|
||||
{
|
||||
case MotionInputBackendType.GamepadDriver:
|
||||
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options);
|
||||
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
|
||||
break;
|
||||
case MotionInputBackendType.CemuHook:
|
||||
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options);
|
||||
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
||||
|
@@ -1,5 +1,8 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(JsonMotionConfigControllerConverter))]
|
||||
public class MotionConfigController
|
||||
{
|
||||
public MotionInputBackendType MotionBackend { get; set; }
|
||||
|
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(MotionConfigController))]
|
||||
[JsonSerializable(typeof(CemuHookMotionConfigController))]
|
||||
[JsonSerializable(typeof(StandardMotionConfigController))]
|
||||
public partial class MotionConfigJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
|
||||
public enum MotionInputBackendType : byte
|
||||
{
|
||||
Invalid,
|
||||
|
@@ -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,
|
||||
|
@@ -1,9 +1,12 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[Flags]
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[Flags]
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||
public enum ControllerType : int
|
||||
{
|
||||
None,
|
||||
|
@@ -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<InputBackendType>))]
|
||||
public enum InputBackendType
|
||||
{
|
||||
Invalid,
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonInputConfigConverter))]
|
||||
public class InputConfig : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
|
@@ -0,0 +1,14 @@
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(InputConfig))]
|
||||
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
|
||||
[JsonSerializable(typeof(StandardControllerInputConfig))]
|
||||
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,13 +1,16 @@
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
class JsonInputConfigConverter : JsonConverter<InputConfig>
|
||||
public class JsonInputConfigConverter : JsonConverter<InputConfig>
|
||||
{
|
||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
|
||||
{
|
||||
// Temporary reader to get the backend type
|
||||
@@ -54,8 +57,8 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
|
||||
return backendType switch
|
||||
{
|
||||
InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
|
||||
InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
|
||||
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
|
||||
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
|
||||
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
||||
};
|
||||
}
|
||||
@@ -65,10 +68,10 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
switch (value.Backend)
|
||||
{
|
||||
case InputBackendType.WindowKeyboard:
|
||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
|
||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
|
||||
break;
|
||||
case InputBackendType.GamepadSDL2:
|
||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
|
||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
||||
|
@@ -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,
|
||||
|
@@ -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; }
|
||||
|
@@ -1,6 +1,10 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
|
||||
public enum PlayerIndex : int
|
||||
{
|
||||
Player1 = 0,
|
||||
|
@@ -1,5 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||
public enum MemoryManagerMode : byte
|
||||
{
|
||||
SoftwarePageTable,
|
||||
|
@@ -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,
|
||||
|
@@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(TitleUpdateMetadata))]
|
||||
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,22 +1,20 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DefaultLogFormatter : ILogFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public string Format(LogEventArgs args)
|
||||
{
|
||||
StringBuilder sb = _stringBuilderPool.Allocate();
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
sb.Clear();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
|
||||
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
|
||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||
|
||||
if (args.ThreadName != null)
|
||||
@@ -27,53 +25,17 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
sb.Append(args.Message);
|
||||
|
||||
if (args.Data != null)
|
||||
if (args.Data is not null)
|
||||
{
|
||||
PropertyInfo[] props = args.Data.GetType().GetProperties();
|
||||
|
||||
sb.Append(" {");
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Array array = (Array)prop.GetValue(args.Data);
|
||||
foreach (var item in array)
|
||||
{
|
||||
sb.Append(item.ToString());
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(prop.GetValue(args.Data));
|
||||
}
|
||||
|
||||
sb.Append(" ; ");
|
||||
}
|
||||
|
||||
// We remove the final ';' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
sb.Append(' ');
|
||||
DynamicObjectFormatter.Format(sb, args.Data);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stringBuilderPool.Release(sb);
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DynamicObjectFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public static string? Format(object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
Format(sb, dynamicObject);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Format(StringBuilder sb, object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
|
||||
|
||||
sb.Append('{');
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Array? array = (Array?) prop.GetValue(dynamicObject);
|
||||
|
||||
if (array is not null)
|
||||
{
|
||||
foreach (var item in array)
|
||||
{
|
||||
sb.Append(item);
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(prop.GetValue(dynamicObject));
|
||||
}
|
||||
|
||||
sb.Append(" ; ");
|
||||
}
|
||||
|
||||
// We remove the final ';' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||
public enum LogClass
|
||||
{
|
||||
Application,
|
||||
|
@@ -11,15 +11,7 @@ namespace Ryujinx.Common.Logging
|
||||
public readonly string Message;
|
||||
public readonly object Data;
|
||||
|
||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data)
|
||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
|
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class LogEventArgsJson
|
||||
{
|
||||
public LogLevel Level { get; }
|
||||
public TimeSpan Time { get; }
|
||||
public string ThreadName { get; }
|
||||
|
||||
public string Message { get; }
|
||||
public string Data { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
|
||||
{
|
||||
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonSerializable(typeof(LogEventArgsJson))]
|
||||
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
@@ -25,12 +25,8 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
string text = JsonSerializer.Serialize(e);
|
||||
|
||||
using (BinaryWriter writer = new BinaryWriter(_stream))
|
||||
{
|
||||
writer.Write(text);
|
||||
}
|
||||
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
|
||||
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@@ -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,21 +34,21 @@ 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);
|
||||
}
|
||||
@@ -55,13 +58,15 @@ namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
[JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>), TypeInfoPropertyName = "StringDictionary")]
|
||||
public partial class CommonJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,15 +1,62 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public class JsonHelper
|
||||
{
|
||||
public static JsonNamingPolicy SnakeCase { get; }
|
||||
private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy();
|
||||
private const int DefaultFileWriteBufferSize = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new serializer options with default settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is REQUIRED for you to save returned options statically or as a part of static serializer context
|
||||
/// in order to avoid performance issues. You can safely modify returned options for your case before storing.
|
||||
/// </remarks>
|
||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true)
|
||||
{
|
||||
JsonSerializerOptions options = new()
|
||||
{
|
||||
DictionaryKeyPolicy = SnakeCasePolicy,
|
||||
PropertyNamingPolicy = SnakeCasePolicy,
|
||||
WriteIndented = indented,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return JsonSerializer.Serialize(value, typeInfo);
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return JsonSerializer.Deserialize(value, typeInfo);
|
||||
}
|
||||
|
||||
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough);
|
||||
JsonSerializer.Serialize(file, value, typeInfo);
|
||||
}
|
||||
|
||||
public static T DeserializeFromFile<T>(string filePath, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
using FileStream file = File.OpenRead(filePath);
|
||||
return JsonSerializer.Deserialize(file, typeInfo);
|
||||
}
|
||||
|
||||
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
JsonSerializer.Serialize(stream, value, typeInfo);
|
||||
}
|
||||
|
||||
private class SnakeCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
@@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities
|
||||
return name;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
StringBuilder builder = new();
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
@@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("_");
|
||||
builder.Append('_');
|
||||
builder.Append(char.ToLowerInvariant(c));
|
||||
}
|
||||
}
|
||||
@@ -47,64 +94,5 @@ namespace Ryujinx.Common.Utilities
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
static JsonHelper()
|
||||
{
|
||||
SnakeCase = new SnakeCaseNamingPolicy();
|
||||
}
|
||||
|
||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false)
|
||||
{
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
DictionaryKeyPolicy = SnakeCase,
|
||||
PropertyNamingPolicy = SnakeCase,
|
||||
WriteIndented = prettyPrint,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new JsonInputConfigConverter());
|
||||
options.Converters.Add(new JsonMotionConfigControllerConverter());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(Stream stream)
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions());
|
||||
}
|
||||
}
|
||||
|
||||
public static T DeserializeFromFile<T>(string path)
|
||||
{
|
||||
return Deserialize<T>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(json, GetDefaultSerializerOptions());
|
||||
}
|
||||
|
||||
public static void Serialize<TValue>(Stream stream, TValue obj, bool prettyPrint = false)
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(SerializeToUtf8Bytes(obj, prettyPrint));
|
||||
}
|
||||
}
|
||||
|
||||
public static string Serialize<TValue>(TValue obj, bool prettyPrint = false)
|
||||
{
|
||||
return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint));
|
||||
}
|
||||
|
||||
public static byte[] SerializeToUtf8Bytes<T>(T obj, bool prettyPrint = false)
|
||||
{
|
||||
return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint));
|
||||
}
|
||||
}
|
||||
}
|
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
|
||||
/// Get rid of this converter if dotnet supports similar functionality out of the box.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
|
||||
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
|
||||
{
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var enumValue = reader.GetString();
|
||||
if (string.IsNullOrEmpty(enumValue))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return Enum.Parse<TEnum>(enumValue);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,12 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||
|
||||
BufferHandle CreateBuffer(int size);
|
||||
BufferHandle CreateBuffer(int size, BufferHandle storageHint);
|
||||
|
||||
BufferHandle CreateBuffer(int size)
|
||||
{
|
||||
return CreateBuffer(size, BufferHandle.Null);
|
||||
}
|
||||
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
@@ -26,7 +31,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void DeleteBuffer(BufferHandle buffer);
|
||||
|
||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
ulong GetCurrentSync();
|
||||
|
@@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
||||
|
||||
ReadOnlySpan<byte> GetData();
|
||||
ReadOnlySpan<byte> GetData(int layer, int level);
|
||||
PinnedSpan<byte> GetData();
|
||||
PinnedSpan<byte> GetData(int layer, int level);
|
||||
|
||||
void SetData(SpanOrArray<byte> data);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||
|
@@ -21,9 +21,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
|
||||
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
||||
PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,16 +5,25 @@
|
||||
public CommandType CommandType => CommandType.CreateBuffer;
|
||||
private BufferHandle _threadedHandle;
|
||||
private int _size;
|
||||
private BufferHandle _storageHint;
|
||||
|
||||
public void Set(BufferHandle threadedHandle, int size)
|
||||
public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
|
||||
{
|
||||
_threadedHandle = threadedHandle;
|
||||
_size = size;
|
||||
_storageHint = storageHint;
|
||||
}
|
||||
|
||||
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size));
|
||||
BufferHandle hint = BufferHandle.Null;
|
||||
|
||||
if (command._storageHint != BufferHandle.Null)
|
||||
{
|
||||
hint = threaded.Buffers.MapBuffer(command._storageHint);
|
||||
}
|
||||
|
||||
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,9 +18,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
|
||||
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
||||
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
|
||||
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
||||
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Model
|
||||
{
|
||||
unsafe struct PinnedSpan<T> where T : unmanaged
|
||||
{
|
||||
private void* _ptr;
|
||||
private int _size;
|
||||
|
||||
public PinnedSpan(ReadOnlySpan<T> span)
|
||||
{
|
||||
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
|
||||
_size = span.Length;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> Get()
|
||||
{
|
||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
return newTex;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
if (_renderer.IsGpuThread())
|
||||
{
|
||||
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
|
||||
_renderer.InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
if (_renderer.IsGpuThread())
|
||||
{
|
||||
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
|
||||
_renderer.InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -265,10 +265,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
{
|
||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||
New<CreateBufferCommand>().Set(handle, size);
|
||||
New<CreateBufferCommand>().Set(handle, size, storageHint);
|
||||
QueueCommand();
|
||||
|
||||
return handle;
|
||||
@@ -329,7 +329,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
QueueCommand();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
if (IsGpuThread())
|
||||
{
|
||||
@@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
|
||||
InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
|
||||
{
|
||||
private void* _ptr;
|
||||
private int _size;
|
||||
private Action _disposeAction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
|
||||
/// The data must be guaranteed to live until disposeAction is called.
|
||||
/// </summary>
|
||||
/// <param name="span">Existing span</param>
|
||||
/// <param name="disposeAction">Action to call on dispose</param>
|
||||
/// <remarks>
|
||||
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||
/// </remarks>
|
||||
public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
|
||||
{
|
||||
return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the region</param>
|
||||
/// <param name="size">The total items of T the region contains</param>
|
||||
/// <param name="disposeAction">Action to call on dispose</param>
|
||||
/// <remarks>
|
||||
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||
/// </remarks>
|
||||
public PinnedSpan(void* ptr, int size, Action disposeAction = null)
|
||||
{
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_disposeAction = disposeAction;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> Get()
|
||||
{
|
||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposeAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -1022,13 +1022,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||
/// This is not cheap, avoid doing that unless strictly needed.
|
||||
/// </remarks>
|
||||
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
||||
/// <param name="output">An output span to place the texture data into</param>
|
||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
/// <returns>The span containing the texture data</returns>
|
||||
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
||||
private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
PinnedSpan<byte> data;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
@@ -1054,9 +1053,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertFromHostCompatibleFormat(output, data);
|
||||
ConvertFromHostCompatibleFormat(output, data.Get());
|
||||
|
||||
return data;
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1071,10 +1070,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="level">The level of the texture to flush</param>
|
||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
/// <returns>The span containing the texture data</returns>
|
||||
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||
public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
PinnedSpan<byte> data;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
@@ -1100,9 +1098,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertFromHostCompatibleFormat(output, data, level, true);
|
||||
ConvertFromHostCompatibleFormat(output, data.Get(), level, true);
|
||||
|
||||
return data;
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -130,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return ref descriptor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
|
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
||||
Handle = context.Renderer.CreateBuffer((int)size);
|
||||
Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
|
||||
|
||||
_useGranular = size > GranularBufferThreshold;
|
||||
|
||||
@@ -415,10 +415,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
int offset = (int)(address - Address);
|
||||
|
||||
ReadOnlySpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
|
||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||
_physicalMemory.WriteUntracked(address, data);
|
||||
_physicalMemory.WriteUntracked(address, data.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -55,11 +55,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
(IntPtr)size);
|
||||
}
|
||||
|
||||
public static unsafe ReadOnlySpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
||||
public static unsafe PinnedSpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
// Data in the persistent buffer and host array is guaranteed to be available
|
||||
// until the next time the host thread requests data.
|
||||
|
||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||
{
|
||||
return renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -69,7 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
|
||||
|
||||
return new ReadOnlySpan<byte>(target.ToPointer(), size);
|
||||
return new PinnedSpan<byte>(target.ToPointer(), size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -39,12 +39,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
return GetData();
|
||||
}
|
||||
|
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData()
|
||||
public unsafe PinnedSpan<byte> GetData()
|
||||
{
|
||||
int size = 0;
|
||||
int levels = Info.GetLevelsClamped();
|
||||
@@ -196,16 +196,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
data = FormatConverter.ConvertD24S8ToS8D24(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(data);
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public unsafe PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
int size = Info.GetMipSize(level);
|
||||
|
||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||
{
|
||||
return _renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
int offset = WriteTo2D(target, layer, level);
|
||||
|
||||
return new ReadOnlySpan<byte>(target.ToPointer(), size).Slice(offset);
|
||||
return new PinnedSpan<byte>((byte*)target.ToPointer() + offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
ResourcePool = new ResourcePool();
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
{
|
||||
BufferCount++;
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
return Buffer.GetData(this, buffer, offset, size);
|
||||
}
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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>
|
||||
|
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal enum BufferAllocationType
|
||||
{
|
||||
Auto = 0,
|
||||
|
||||
HostMappedNoCache,
|
||||
HostMapped,
|
||||
DeviceLocal,
|
||||
DeviceLocalMapped
|
||||
}
|
||||
}
|
@@ -1,7 +1,10 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
@@ -11,6 +14,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
private const int SetCountThreshold = 100;
|
||||
private const int WriteCountThreshold = 50;
|
||||
private const int FlushCountThreshold = 5;
|
||||
|
||||
public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.IndirectCommandReadBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
@@ -21,10 +30,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private readonly MemoryAllocation _allocation;
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||
private readonly ulong _bufferHandle;
|
||||
private MemoryAllocation _allocation;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
private Auto<MemoryAllocation> _allocationAuto;
|
||||
private ulong _bufferHandle;
|
||||
|
||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||
|
||||
@@ -32,11 +41,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private IntPtr _map;
|
||||
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
private MultiFenceHolder _waitable;
|
||||
|
||||
private bool _lastAccessIsWrite;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size)
|
||||
private BufferAllocationType _baseType;
|
||||
private BufferAllocationType _currentType;
|
||||
private bool _swapQueued;
|
||||
|
||||
public BufferAllocationType DesiredType { get; private set; }
|
||||
|
||||
private int _setCount;
|
||||
private int _writeCount;
|
||||
private int _flushCount;
|
||||
private int _flushTemp;
|
||||
|
||||
private ReaderWriterLock _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
private List<Action> _swapActions;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
@@ -47,9 +73,153 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
_baseType = type;
|
||||
_currentType = currentType;
|
||||
DesiredType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLock();
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size)
|
||||
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||
{
|
||||
if (_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
// Only swap if the buffer is not used in any queued command buffer.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||
|
||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
|
||||
{
|
||||
var currentAllocation = _allocationAuto;
|
||||
var currentBuffer = _buffer;
|
||||
IntPtr currentMap = _map;
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_waitable = new MultiFenceHolder(Size);
|
||||
|
||||
_allocation = allocation;
|
||||
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
|
||||
{
|
||||
// Copy data directly. Readbacks don't have to wait if this is done.
|
||||
|
||||
unsafe
|
||||
{
|
||||
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cbs == null)
|
||||
{
|
||||
cbs = _gd.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
CommandBufferScoped cbsV = cbs.Value;
|
||||
|
||||
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
|
||||
|
||||
// Need to wait for the data to reach the new buffer before data can be flushed.
|
||||
|
||||
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
|
||||
_flushFence.Get();
|
||||
}
|
||||
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
|
||||
|
||||
_currentType = resultType;
|
||||
|
||||
if (_swapActions != null)
|
||||
{
|
||||
foreach (var action in _swapActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
_swapActions.Clear();
|
||||
}
|
||||
|
||||
currentBuffer.Dispose();
|
||||
currentAllocation.Dispose();
|
||||
|
||||
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConsiderBackingSwap()
|
||||
{
|
||||
if (_baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
|
||||
{
|
||||
if (_flushCount > 0 || _flushTemp-- > 0)
|
||||
{
|
||||
// Buffers that flush should ideally be mapped in host address space for easy copies.
|
||||
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
|
||||
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
|
||||
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
|
||||
|
||||
// It's harder for a buffer that is flushed to revert to another type of mapping.
|
||||
if (_flushCount > 0)
|
||||
{
|
||||
_flushTemp = 1000;
|
||||
}
|
||||
}
|
||||
else if (_writeCount >= WriteCountThreshold)
|
||||
{
|
||||
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
|
||||
DesiredType = BufferAllocationType.DeviceLocal;
|
||||
}
|
||||
else if (_setCount > SetCountThreshold)
|
||||
{
|
||||
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
|
||||
DesiredType = BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
_flushCount = 0;
|
||||
_writeCount = 0;
|
||||
_setCount = 0;
|
||||
}
|
||||
|
||||
if (!_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
_swapQueued = true;
|
||||
|
||||
_gd.PipelineInternal.AddBackingSwap(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
|
||||
{
|
||||
var bufferViewCreateInfo = new BufferViewCreateInfo()
|
||||
{
|
||||
@@ -62,9 +232,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||
|
||||
(_swapActions ??= new List<Action>()).Add(invalidateView);
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
||||
}
|
||||
|
||||
public void InheritMetrics(BufferHolder other)
|
||||
{
|
||||
_setCount = other._setCount;
|
||||
_writeCount = other._writeCount;
|
||||
_flushCount = other._flushCount;
|
||||
_flushTemp = other._flushTemp;
|
||||
}
|
||||
|
||||
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
|
||||
{
|
||||
// If the last access is write, we always need a barrier to be sure we will read or modify
|
||||
@@ -104,12 +284,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
else if (isSSBO)
|
||||
{
|
||||
// Always consider SSBO access for swapping to device local memory.
|
||||
|
||||
_writeCount++;
|
||||
|
||||
ConsiderBackingSwap();
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
@@ -118,6 +308,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
@@ -126,6 +318,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
ConsiderBackingSwap();
|
||||
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
@@ -147,11 +341,76 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _map;
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData(int offset, int size)
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Asusmes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
|
||||
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
var fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
var restoreCookie = _flushLock.ReleaseLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.RestoreLock(ref restoreCookie);
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
_flushLock.DowngradeFromWriterLock(ref cookie);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.AcquireReaderLock(Timeout.Infinite);
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
_flushCount++;
|
||||
|
||||
Span<byte> result;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
return GetDataStorage(offset, size);
|
||||
result = GetDataStorage(offset, size);
|
||||
|
||||
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||
_buffer.IncrementReferenceCount();
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,12 +420,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_gd.FlushAllCommands();
|
||||
|
||||
return resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||
result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||
}
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +454,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
_setCount++;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
||||
@@ -268,6 +534,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
|
||||
_writeCount--;
|
||||
|
||||
InsertBufferBarrier(
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
@@ -502,11 +770,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
||||
_buffer.Dispose();
|
||||
_allocationAuto.Dispose();
|
||||
_cachedConvertedBuffers.Dispose();
|
||||
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user