Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
009e6bcd1b | |||
eb2cc159fa | |||
bb89e36fd8 | |||
de3134adbe | |||
36d53819a4 | |||
ae4324032a | |||
f449895e6d | |||
410be95ab6 | |||
cff9046fc7 | |||
86fd0643c2 | |||
43a83a401e | |||
f0e27a23a5 | |||
e68650237d | |||
1faff14e73 | |||
784cf9d594 | |||
64263c5218 | |||
065c4e520d | |||
139a930407 | |||
719dc97bbd | |||
41bba5310a | |||
8071c8c8c0 | |||
b402b4e7f6 | |||
93df366b2c | |||
cd3a15aea5 | |||
070136b3f7 | |||
08ab47c6c0 | |||
85faa9d8fa | |||
dca5b14493 | |||
4d2c8e2a44 | |||
8fa248ceb4 | |||
30862b5ffd | |||
9f57747c57 | |||
fe29a2ff6e | |||
e9a173e00c | |||
a11784fcbf |
185
ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
Normal file
185
ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics.Arm;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
namespace ARMeilleure.CodeGen.Arm64
|
||||||
|
{
|
||||||
|
static partial class HardwareCapabilities
|
||||||
|
{
|
||||||
|
static HardwareCapabilities()
|
||||||
|
{
|
||||||
|
if (!ArmBase.Arm64.IsSupported)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP);
|
||||||
|
LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _sysctlNames.Length; i++)
|
||||||
|
{
|
||||||
|
if (CheckSysctlName(_sysctlNames[i]))
|
||||||
|
{
|
||||||
|
MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Linux
|
||||||
|
|
||||||
|
private const ulong AT_HWCAP = 16;
|
||||||
|
private const ulong AT_HWCAP2 = 26;
|
||||||
|
|
||||||
|
[LibraryImport("libc", SetLastError = true)]
|
||||||
|
private static partial ulong getauxval(ulong type);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum LinuxFeatureFlagsHwCap : ulong
|
||||||
|
{
|
||||||
|
Fp = 1 << 0,
|
||||||
|
Asimd = 1 << 1,
|
||||||
|
Evtstrm = 1 << 2,
|
||||||
|
Aes = 1 << 3,
|
||||||
|
Pmull = 1 << 4,
|
||||||
|
Sha1 = 1 << 5,
|
||||||
|
Sha2 = 1 << 6,
|
||||||
|
Crc32 = 1 << 7,
|
||||||
|
Atomics = 1 << 8,
|
||||||
|
FpHp = 1 << 9,
|
||||||
|
AsimdHp = 1 << 10,
|
||||||
|
CpuId = 1 << 11,
|
||||||
|
AsimdRdm = 1 << 12,
|
||||||
|
Jscvt = 1 << 13,
|
||||||
|
Fcma = 1 << 14,
|
||||||
|
Lrcpc = 1 << 15,
|
||||||
|
DcpOp = 1 << 16,
|
||||||
|
Sha3 = 1 << 17,
|
||||||
|
Sm3 = 1 << 18,
|
||||||
|
Sm4 = 1 << 19,
|
||||||
|
AsimdDp = 1 << 20,
|
||||||
|
Sha512 = 1 << 21,
|
||||||
|
Sve = 1 << 22,
|
||||||
|
AsimdFhm = 1 << 23,
|
||||||
|
Dit = 1 << 24,
|
||||||
|
Uscat = 1 << 25,
|
||||||
|
Ilrcpc = 1 << 26,
|
||||||
|
FlagM = 1 << 27,
|
||||||
|
Ssbs = 1 << 28,
|
||||||
|
Sb = 1 << 29,
|
||||||
|
Paca = 1 << 30,
|
||||||
|
Pacg = 1UL << 31
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum LinuxFeatureFlagsHwCap2 : ulong
|
||||||
|
{
|
||||||
|
Dcpodp = 1 << 0,
|
||||||
|
Sve2 = 1 << 1,
|
||||||
|
SveAes = 1 << 2,
|
||||||
|
SvePmull = 1 << 3,
|
||||||
|
SveBitperm = 1 << 4,
|
||||||
|
SveSha3 = 1 << 5,
|
||||||
|
SveSm4 = 1 << 6,
|
||||||
|
FlagM2 = 1 << 7,
|
||||||
|
Frint = 1 << 8,
|
||||||
|
SveI8mm = 1 << 9,
|
||||||
|
SveF32mm = 1 << 10,
|
||||||
|
SveF64mm = 1 << 11,
|
||||||
|
SveBf16 = 1 << 12,
|
||||||
|
I8mm = 1 << 13,
|
||||||
|
Bf16 = 1 << 14,
|
||||||
|
Dgh = 1 << 15,
|
||||||
|
Rng = 1 << 16,
|
||||||
|
Bti = 1 << 17,
|
||||||
|
Mte = 1 << 18,
|
||||||
|
Ecv = 1 << 19,
|
||||||
|
Afp = 1 << 20,
|
||||||
|
Rpres = 1 << 21,
|
||||||
|
Mte3 = 1 << 22,
|
||||||
|
Sme = 1 << 23,
|
||||||
|
Sme_i16i64 = 1 << 24,
|
||||||
|
Sme_f64f64 = 1 << 25,
|
||||||
|
Sme_i8i32 = 1 << 26,
|
||||||
|
Sme_f16f32 = 1 << 27,
|
||||||
|
Sme_b16f32 = 1 << 28,
|
||||||
|
Sme_f32f32 = 1 << 29,
|
||||||
|
Sme_fa64 = 1 << 30,
|
||||||
|
Wfxt = 1UL << 31,
|
||||||
|
Ebf16 = 1UL << 32,
|
||||||
|
Sve_Ebf16 = 1UL << 33,
|
||||||
|
Cssc = 1UL << 34,
|
||||||
|
Rprfm = 1UL << 35,
|
||||||
|
Sve2p1 = 1UL << 36
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0;
|
||||||
|
public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region macOS
|
||||||
|
|
||||||
|
[LibraryImport("libSystem.dylib", SetLastError = true)]
|
||||||
|
private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
private static bool CheckSysctlName(string name)
|
||||||
|
{
|
||||||
|
ulong size = sizeof(int);
|
||||||
|
if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int))
|
||||||
|
{
|
||||||
|
return val != 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] _sysctlNames = new string[]
|
||||||
|
{
|
||||||
|
"hw.optional.floatingpoint",
|
||||||
|
"hw.optional.AdvSIMD",
|
||||||
|
"hw.optional.arm.FEAT_FP16",
|
||||||
|
"hw.optional.arm.FEAT_AES",
|
||||||
|
"hw.optional.arm.FEAT_PMULL",
|
||||||
|
"hw.optional.arm.FEAT_LSE",
|
||||||
|
"hw.optional.armv8_crc32",
|
||||||
|
"hw.optional.arm.FEAT_SHA1",
|
||||||
|
"hw.optional.arm.FEAT_SHA256"
|
||||||
|
};
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MacOsFeatureFlags
|
||||||
|
{
|
||||||
|
Fp = 1 << 0,
|
||||||
|
AdvSimd = 1 << 1,
|
||||||
|
Fp16 = 1 << 2,
|
||||||
|
Aes = 1 << 3,
|
||||||
|
Pmull = 1 << 4,
|
||||||
|
Lse = 1 << 5,
|
||||||
|
Crc32 = 1 << 6,
|
||||||
|
Sha1 = 1 << 7,
|
||||||
|
Sha256 = 1 << 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd);
|
||||||
|
public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes);
|
||||||
|
public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull);
|
||||||
|
public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse);
|
||||||
|
public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32);
|
||||||
|
public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1);
|
||||||
|
public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256);
|
||||||
|
}
|
||||||
|
}
|
@ -1339,7 +1339,7 @@ namespace ARMeilleure.Decoders
|
|||||||
|
|
||||||
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||||
{
|
{
|
||||||
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
string reversedEncoding = $"{encoding.AsSpan(16)}{encoding.AsSpan(0, 16)}";
|
||||||
MakeOp reversedMakeOp =
|
MakeOp reversedMakeOp =
|
||||||
(inst, address, opCode)
|
(inst, address, opCode)
|
||||||
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
|
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
|
||||||
@ -1353,7 +1353,7 @@ namespace ARMeilleure.Decoders
|
|||||||
string thumbEncoding = encoding;
|
string thumbEncoding = encoding;
|
||||||
if (thumbEncoding.StartsWith("<<<<"))
|
if (thumbEncoding.StartsWith("<<<<"))
|
||||||
{
|
{
|
||||||
thumbEncoding = "1110" + thumbEncoding.Substring(4);
|
thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}";
|
||||||
}
|
}
|
||||||
SetT32(thumbEncoding, name, emitter, makeOpT32);
|
SetT32(thumbEncoding, name, emitter, makeOpT32);
|
||||||
}
|
}
|
||||||
@ -1365,19 +1365,19 @@ namespace ARMeilleure.Decoders
|
|||||||
string thumbEncoding = encoding;
|
string thumbEncoding = encoding;
|
||||||
if (thumbEncoding.StartsWith("11110100"))
|
if (thumbEncoding.StartsWith("11110100"))
|
||||||
{
|
{
|
||||||
thumbEncoding = "11111001" + encoding.Substring(8);
|
thumbEncoding = $"11111001{encoding.AsSpan(8)}";
|
||||||
}
|
}
|
||||||
else if (thumbEncoding.StartsWith("1111001x"))
|
else if (thumbEncoding.StartsWith("1111001x"))
|
||||||
{
|
{
|
||||||
thumbEncoding = "111x1111" + encoding.Substring(8);
|
thumbEncoding = $"111x1111{encoding.AsSpan(8)}";
|
||||||
}
|
}
|
||||||
else if (thumbEncoding.StartsWith("11110010"))
|
else if (thumbEncoding.StartsWith("11110010"))
|
||||||
{
|
{
|
||||||
thumbEncoding = "11101111" + encoding.Substring(8);
|
thumbEncoding = $"11101111{encoding.AsSpan(8)}";
|
||||||
}
|
}
|
||||||
else if (thumbEncoding.StartsWith("11110011"))
|
else if (thumbEncoding.StartsWith("11110011"))
|
||||||
{
|
{
|
||||||
thumbEncoding = "11111111" + encoding.Substring(8);
|
thumbEncoding = $"11111111{encoding.AsSpan(8)}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2556,7 +2556,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||||
|
|
||||||
if (Optimizations.UseAdvSimd && false) // Not supported by all Arm CPUs.
|
if (Optimizations.UseArm64Pmull)
|
||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV);
|
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV);
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,7 @@
|
|||||||
{
|
{
|
||||||
IJitMemoryBlock Allocate(ulong size);
|
IJitMemoryBlock Allocate(ulong size);
|
||||||
IJitMemoryBlock Reserve(ulong size);
|
IJitMemoryBlock Reserve(ulong size);
|
||||||
|
|
||||||
|
ulong GetPageSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using ARMeilleure.CodeGen.X86;
|
|
||||||
using System.Runtime.Intrinsics.Arm;
|
using System.Runtime.Intrinsics.Arm;
|
||||||
|
|
||||||
namespace ARMeilleure
|
namespace ARMeilleure
|
||||||
{
|
{
|
||||||
|
using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities;
|
||||||
|
using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities;
|
||||||
|
|
||||||
public static class Optimizations
|
public static class Optimizations
|
||||||
{
|
{
|
||||||
public static bool FastFP { get; set; } = true;
|
public static bool FastFP { get; set; } = true;
|
||||||
@ -10,7 +12,8 @@ namespace ARMeilleure
|
|||||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||||
|
|
||||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseArm64PmullIfAvailable { get; set; } = true;
|
||||||
|
|
||||||
public static bool UseSseIfAvailable { get; set; } = true;
|
public static bool UseSseIfAvailable { get; set; } = true;
|
||||||
public static bool UseSse2IfAvailable { get; set; } = true;
|
public static bool UseSse2IfAvailable { get; set; } = true;
|
||||||
@ -29,25 +32,26 @@ namespace ARMeilleure
|
|||||||
|
|
||||||
public static bool ForceLegacySse
|
public static bool ForceLegacySse
|
||||||
{
|
{
|
||||||
get => HardwareCapabilities.ForceLegacySse;
|
get => X86HardwareCapabilities.ForceLegacySse;
|
||||||
set => HardwareCapabilities.ForceLegacySse = value;
|
set => X86HardwareCapabilities.ForceLegacySse = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool UseAdvSimd => UseAdvSimdIfAvailable && AdvSimd.IsSupported;
|
internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd;
|
||||||
|
internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull;
|
||||||
|
|
||||||
internal static bool UseSse => UseSseIfAvailable && HardwareCapabilities.SupportsSse;
|
internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse;
|
||||||
internal static bool UseSse2 => UseSse2IfAvailable && HardwareCapabilities.SupportsSse2;
|
internal static bool UseSse2 => UseSse2IfAvailable && X86HardwareCapabilities.SupportsSse2;
|
||||||
internal static bool UseSse3 => UseSse3IfAvailable && HardwareCapabilities.SupportsSse3;
|
internal static bool UseSse3 => UseSse3IfAvailable && X86HardwareCapabilities.SupportsSse3;
|
||||||
internal static bool UseSsse3 => UseSsse3IfAvailable && HardwareCapabilities.SupportsSsse3;
|
internal static bool UseSsse3 => UseSsse3IfAvailable && X86HardwareCapabilities.SupportsSsse3;
|
||||||
internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41;
|
internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41;
|
||||||
internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42;
|
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||||
internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt;
|
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||||
internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||||
internal static bool UseF16c => UseF16cIfAvailable && HardwareCapabilities.SupportsF16c;
|
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||||
internal static bool UseFma => UseFmaIfAvailable && HardwareCapabilities.SupportsFma;
|
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||||
internal static bool UseAesni => UseAesniIfAvailable && HardwareCapabilities.SupportsAesni;
|
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && HardwareCapabilities.SupportsPclmulqdq;
|
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||||
internal static bool UseSha => UseShaIfAvailable && HardwareCapabilities.SupportsSha;
|
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||||
internal static bool UseGfni => UseGfniIfAvailable && HardwareCapabilities.SupportsGfni;
|
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||||
|
|
||||||
private static ulong _pageSize = GetPageSize();
|
private static ulong _pageSize;
|
||||||
private static ulong _pageMask = _pageSize - 1;
|
private static ulong _pageMask;
|
||||||
|
|
||||||
private static IntPtr _handlerConfig;
|
private static IntPtr _handlerConfig;
|
||||||
private static IntPtr _signalHandlerPtr;
|
private static IntPtr _signalHandlerPtr;
|
||||||
@ -81,19 +81,6 @@ namespace ARMeilleure.Signal
|
|||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
|
||||||
private static ulong GetPageSize()
|
|
||||||
{
|
|
||||||
// TODO: This needs to be based on the current memory manager configuration.
|
|
||||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
|
||||||
{
|
|
||||||
return 1UL << 14;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 1UL << 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static NativeSignalHandler()
|
static NativeSignalHandler()
|
||||||
{
|
{
|
||||||
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
||||||
@ -102,12 +89,12 @@ namespace ARMeilleure.Signal
|
|||||||
config = new SignalHandlerConfig();
|
config = new SignalHandlerConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeJitCache(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
JitCache.Initialize(allocator);
|
JitCache.Initialize(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeSignalHandler(Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
@ -115,16 +102,13 @@ namespace ARMeilleure.Signal
|
|||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
|
_pageSize = pageSize;
|
||||||
|
_pageMask = pageSize - 1;
|
||||||
|
|
||||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
// Unix siginfo struct locations.
|
|
||||||
// NOTE: These are incredibly likely to be different between kernel version and architectures.
|
|
||||||
|
|
||||||
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
|
|
||||||
config.StructWriteOffset = 8; // si_code
|
|
||||||
|
|
||||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
||||||
|
|
||||||
if (customSignalHandlerFactory != null)
|
if (customSignalHandlerFactory != null)
|
||||||
@ -261,18 +245,88 @@ namespace ARMeilleure.Signal
|
|||||||
return context.Copy(inRegionLocal);
|
return context.Copy(inRegionLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr)
|
||||||
|
{
|
||||||
|
ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr
|
||||||
|
return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
const ulong mcontextOffset = 48; // uc_mcontext
|
||||||
|
Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(mcontextOffset)));
|
||||||
|
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
|
{
|
||||||
|
const ulong esrOffset = 8; // __es.__esr
|
||||||
|
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(esrOffset)));
|
||||||
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
|
{
|
||||||
|
const ulong errOffset = 4; // __es.__err
|
||||||
|
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(errOffset)));
|
||||||
|
return context.BitwiseAnd(err, Const(2ul));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
|
{
|
||||||
|
Operand auxPtr = context.AllocateLocal(OperandType.I64);
|
||||||
|
|
||||||
|
Operand loopLabel = Label();
|
||||||
|
Operand successLabel = Label();
|
||||||
|
|
||||||
|
const ulong auxOffset = 464; // uc_mcontext.__reserved
|
||||||
|
const uint esrMagic = 0x45535201;
|
||||||
|
|
||||||
|
context.Copy(auxPtr, context.Add(ucontextPtr, Const(auxOffset)));
|
||||||
|
|
||||||
|
context.MarkLabel(loopLabel);
|
||||||
|
|
||||||
|
// _aarch64_ctx::magic
|
||||||
|
Operand magic = context.Load(OperandType.I32, auxPtr);
|
||||||
|
// _aarch64_ctx::size
|
||||||
|
Operand size = context.Load(OperandType.I32, context.Add(auxPtr, Const(4ul)));
|
||||||
|
|
||||||
|
context.BranchIf(successLabel, magic, Const(esrMagic), Comparison.Equal);
|
||||||
|
|
||||||
|
context.Copy(auxPtr, context.Add(auxPtr, context.ZeroExtend32(OperandType.I64, size)));
|
||||||
|
|
||||||
|
context.Branch(loopLabel);
|
||||||
|
|
||||||
|
context.MarkLabel(successLabel);
|
||||||
|
|
||||||
|
// esr_context::esr
|
||||||
|
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
|
||||||
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
|
{
|
||||||
|
const int errOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
||||||
|
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(errOffset)));
|
||||||
|
return context.BitwiseAnd(err, Const(2ul));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
||||||
{
|
{
|
||||||
EmitterContext context = new EmitterContext();
|
EmitterContext context = new EmitterContext();
|
||||||
|
|
||||||
// (int sig, SigInfo* sigInfo, void* ucontext)
|
// (int sig, SigInfo* sigInfo, void* ucontext)
|
||||||
Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1);
|
Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1);
|
||||||
|
Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2);
|
||||||
|
|
||||||
Operand structAddressOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructAddressOffset));
|
Operand faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr);
|
||||||
Operand structWriteOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructWriteOffset));
|
Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr);
|
||||||
|
|
||||||
Operand faultAddress = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset)));
|
|
||||||
Operand writeFlag = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset)));
|
|
||||||
|
|
||||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen;
|
using ARMeilleure.CodeGen;
|
||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.CodeGen.Unwinding;
|
using ARMeilleure.CodeGen.Unwinding;
|
||||||
using ARMeilleure.CodeGen.X86;
|
|
||||||
using ARMeilleure.Common;
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@ -22,12 +21,15 @@ using static ARMeilleure.Translation.PTC.PtcFormatter;
|
|||||||
|
|
||||||
namespace ARMeilleure.Translation.PTC
|
namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
|
using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities;
|
||||||
|
using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities;
|
||||||
|
|
||||||
class Ptc : IPtcLoadState
|
class Ptc : IPtcLoadState
|
||||||
{
|
{
|
||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 4114; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 4272; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -181,8 +183,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
private void PreLoad()
|
private void PreLoad()
|
||||||
{
|
{
|
||||||
string fileNameActual = string.Concat(CachePathActual, ".cache");
|
string fileNameActual = $"{CachePathActual}.cache";
|
||||||
string fileNameBackup = string.Concat(CachePathBackup, ".cache");
|
string fileNameBackup = $"{CachePathBackup}.cache";
|
||||||
|
|
||||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||||
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
||||||
@ -259,6 +261,13 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture)
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
IntPtr intPtr = IntPtr.Zero;
|
IntPtr intPtr = IntPtr.Zero;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -391,8 +400,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string fileNameActual = string.Concat(CachePathActual, ".cache");
|
string fileNameActual = $"{CachePathActual}.cache";
|
||||||
string fileNameBackup = string.Concat(CachePathBackup, ".cache");
|
string fileNameBackup = $"{CachePathBackup}.cache";
|
||||||
|
|
||||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||||
|
|
||||||
@ -435,6 +444,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
outerHeader.FeatureInfo = GetFeatureInfo();
|
outerHeader.FeatureInfo = GetFeatureInfo();
|
||||||
outerHeader.MemoryManagerMode = GetMemoryManagerMode();
|
outerHeader.MemoryManagerMode = GetMemoryManagerMode();
|
||||||
outerHeader.OSPlatform = GetOSPlatform();
|
outerHeader.OSPlatform = GetOSPlatform();
|
||||||
|
outerHeader.Architecture = (uint)RuntimeInformation.ProcessArchitecture;
|
||||||
|
|
||||||
outerHeader.UncompressedStreamSize =
|
outerHeader.UncompressedStreamSize =
|
||||||
(long)Unsafe.SizeOf<InnerHeader>() +
|
(long)Unsafe.SizeOf<InnerHeader>() +
|
||||||
@ -952,11 +962,26 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
private static FeatureInfo GetFeatureInfo()
|
private static FeatureInfo GetFeatureInfo()
|
||||||
{
|
{
|
||||||
return new FeatureInfo(
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
(uint)HardwareCapabilities.FeatureInfo1Ecx,
|
{
|
||||||
(uint)HardwareCapabilities.FeatureInfo1Edx,
|
return new FeatureInfo(
|
||||||
(uint)HardwareCapabilities.FeatureInfo7Ebx,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||||
(uint)HardwareCapabilities.FeatureInfo7Ecx);
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||||
|
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
|
{
|
||||||
|
return new FeatureInfo(
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new FeatureInfo(0, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte GetMemoryManagerMode()
|
private byte GetMemoryManagerMode()
|
||||||
@ -976,7 +1001,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return osPlatform;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 58*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
||||||
private struct OuterHeader
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public ulong Magic;
|
public ulong Magic;
|
||||||
@ -987,6 +1012,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
public FeatureInfo FeatureInfo;
|
public FeatureInfo FeatureInfo;
|
||||||
public byte MemoryManagerMode;
|
public byte MemoryManagerMode;
|
||||||
public uint OSPlatform;
|
public uint OSPlatform;
|
||||||
|
public uint Architecture;
|
||||||
|
|
||||||
public long UncompressedStreamSize;
|
public long UncompressedStreamSize;
|
||||||
|
|
||||||
@ -1007,8 +1033,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 16*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
||||||
private record struct FeatureInfo(uint FeatureInfo0, uint FeatureInfo1, uint FeatureInfo2, uint FeatureInfo3);
|
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||||
private struct InnerHeader
|
private struct InnerHeader
|
||||||
|
@ -125,8 +125,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
_lastHash = default;
|
_lastHash = default;
|
||||||
|
|
||||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
string fileNameActual = $"{_ptc.CachePathActual}.info";
|
||||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
string fileNameBackup = $"{_ptc.CachePathBackup}.info";
|
||||||
|
|
||||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||||
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
||||||
@ -246,8 +246,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
_waitEvent.Reset();
|
_waitEvent.Reset();
|
||||||
|
|
||||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
string fileNameActual = $"{_ptc.CachePathActual}.info";
|
||||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
string fileNameBackup = $"{_ptc.CachePathBackup}.info";
|
||||||
|
|
||||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
if (memory.Type.IsHostMapped())
|
if (memory.Type.IsHostMapped())
|
||||||
{
|
{
|
||||||
NativeSignalHandler.InitializeSignalHandler();
|
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +75,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
return SampleFormat.PcmFloat;
|
return SampleFormat.PcmFloat;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement PCM24 conversion.
|
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24))
|
||||||
|
{
|
||||||
|
return SampleFormat.PcmInt24;
|
||||||
|
}
|
||||||
|
|
||||||
// If nothing is truly supported, attempt PCM8 at the cost of loosing quality.
|
// If nothing is truly supported, attempt PCM8 at the cost of losing quality.
|
||||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
|
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
|
||||||
{
|
{
|
||||||
return SampleFormat.PcmInt8;
|
return SampleFormat.PcmInt8;
|
||||||
|
@ -58,10 +58,13 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
switch (realSampleFormat)
|
switch (realSampleFormat)
|
||||||
{
|
{
|
||||||
case SampleFormat.PcmInt8:
|
case SampleFormat.PcmInt8:
|
||||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
||||||
|
break;
|
||||||
|
case SampleFormat.PcmInt24:
|
||||||
|
PcmHelper.ConvertSampleToPcm24(convertedSamples, samples);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.PcmInt32:
|
case SampleFormat.PcmInt32:
|
||||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.PcmFloat:
|
case SampleFormat.PcmFloat:
|
||||||
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
|
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
|
||||||
|
@ -40,6 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info.BufferStates?.Length != (int)inputCount)
|
||||||
|
{
|
||||||
|
// Keep state if possible.
|
||||||
|
info.BufferStates = new UpsamplerBufferState[(int)inputCount];
|
||||||
|
}
|
||||||
|
|
||||||
UpsamplerInfo = info;
|
UpsamplerInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +56,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public void Process(CommandList context)
|
public void Process(CommandList context)
|
||||||
{
|
{
|
||||||
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
|
|
||||||
|
|
||||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||||
|
|
||||||
for (int i = 0; i < bufferCount; i++)
|
for (int i = 0; i < bufferCount; i++)
|
||||||
@ -59,9 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||||
|
|
||||||
float fraction = 0.0f;
|
UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]);
|
||||||
|
|
||||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,19 +37,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
public static void ConvertSampleToPcm8(Span<sbyte> output, ReadOnlySpan<short> input)
|
||||||
{
|
|
||||||
TInput conversionRate = TInput.CreateSaturating(TOutput.MaxValue / TOutput.CreateSaturating(TInput.MaxValue));
|
|
||||||
|
|
||||||
return TOutput.CreateSaturating(value * conversionRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void Convert<TInput, TOutput>(Span<TOutput> output, ReadOnlySpan<TInput> input) where TInput : INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < input.Length; i++)
|
for (int i = 0; i < input.Length; i++)
|
||||||
{
|
{
|
||||||
output[i] = ConvertSample<TInput, TOutput>(input[i]);
|
// Output most significant byte
|
||||||
|
output[i] = (sbyte)(input[i] >> 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ConvertSampleToPcm24(Span<byte> output, ReadOnlySpan<short> input)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
output[i * 3 + 2] = (byte)(input[i] >> 8);
|
||||||
|
output[i * 3 + 1] = (byte)(input[i] & 0xff);
|
||||||
|
output[i * 3 + 0] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ConvertSampleToPcm32(Span<int> output, ReadOnlySpan<short> input)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
output[i] = ((int)input[i]) << 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,52 +579,5 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
fraction -= (int)fraction;
|
fraction -= (int)fraction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
|
||||||
{
|
|
||||||
// Currently a simple cubic interpolation, assuming duplicated values at edges.
|
|
||||||
// TODO: Discover and use algorithm that the switch uses.
|
|
||||||
|
|
||||||
int inputBufferIndex = 0;
|
|
||||||
int maxIndex = inputBuffer.Length - 1;
|
|
||||||
int cubicEnd = inputBuffer.Length - 3;
|
|
||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
|
||||||
{
|
|
||||||
float s0, s1, s2, s3;
|
|
||||||
|
|
||||||
s1 = inputBuffer[inputBufferIndex];
|
|
||||||
|
|
||||||
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
|
|
||||||
{
|
|
||||||
// Clamp interplation values at the ends of the input buffer.
|
|
||||||
s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)];
|
|
||||||
s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)];
|
|
||||||
s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s0 = inputBuffer[inputBufferIndex - 1];
|
|
||||||
s2 = inputBuffer[inputBufferIndex + 1];
|
|
||||||
s3 = inputBuffer[inputBufferIndex + 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
float a = s3 - s2 - s0 + s1;
|
|
||||||
float b = s0 - s1 - a;
|
|
||||||
float c = s2 - s0;
|
|
||||||
float d = s1;
|
|
||||||
|
|
||||||
float f2 = fraction * fraction;
|
|
||||||
float f3 = f2 * fraction;
|
|
||||||
|
|
||||||
outputBuffer[i] = a * f3 + b * f2 + c * fraction + d;
|
|
||||||
|
|
||||||
fraction += ratio;
|
|
||||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
|
||||||
|
|
||||||
fraction -= (int)fraction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
|
{
|
||||||
|
public class UpsamplerHelper
|
||||||
|
{
|
||||||
|
private const int HistoryLength = UpsamplerBufferState.HistoryLength;
|
||||||
|
private const int FilterBankLength = 20;
|
||||||
|
// Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
private const int Bank0CenterIndex = 9;
|
||||||
|
private static readonly Array20<float> Bank1 = PrecomputeFilterBank(1.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank2 = PrecomputeFilterBank(2.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank3 = PrecomputeFilterBank(3.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank4 = PrecomputeFilterBank(4.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank5 = PrecomputeFilterBank(5.0f / 6.0f);
|
||||||
|
|
||||||
|
private static Array20<float> PrecomputeFilterBank(float offset)
|
||||||
|
{
|
||||||
|
float Sinc(float x)
|
||||||
|
{
|
||||||
|
if (x == 0)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
return (MathF.Sin(MathF.PI * x) / (MathF.PI * x));
|
||||||
|
}
|
||||||
|
|
||||||
|
float BlackmanWindow(float x)
|
||||||
|
{
|
||||||
|
const float a = 0.18f;
|
||||||
|
const float a0 = 0.5f - 0.5f * a;
|
||||||
|
const float a1 = -0.5f;
|
||||||
|
const float a2 = 0.5f * a;
|
||||||
|
return a0 + a1 * MathF.Cos(2 * MathF.PI * x) + a2 * MathF.Cos(4 * MathF.PI * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array20<float> result = new Array20<float>();
|
||||||
|
|
||||||
|
for (int i = 0; i < FilterBankLength; i++)
|
||||||
|
{
|
||||||
|
float x = (Bank0CenterIndex - i) + offset;
|
||||||
|
result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyphase upsampling algorithm
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Upsample(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state)
|
||||||
|
{
|
||||||
|
if (!state.Initialized)
|
||||||
|
{
|
||||||
|
state.Scale = inputSampleCount switch
|
||||||
|
{
|
||||||
|
40 => 6.0f,
|
||||||
|
80 => 3.0f,
|
||||||
|
160 => 1.5f,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
state.Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputSampleCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||||
|
{
|
||||||
|
float result = 0.0f;
|
||||||
|
|
||||||
|
Debug.Assert(state.History.Length == HistoryLength);
|
||||||
|
Debug.Assert(bank.Length == FilterBankLength);
|
||||||
|
for (int j = 0; j < FilterBankLength; j++)
|
||||||
|
{
|
||||||
|
result += bank[j] * state.History[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void NextInput(ref UpsamplerBufferState state, float input)
|
||||||
|
{
|
||||||
|
state.History.AsSpan().Slice(1).CopyTo(state.History.AsSpan());
|
||||||
|
state.History[HistoryLength - 1] = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
int inputBufferIndex = 0;
|
||||||
|
|
||||||
|
switch (state.Scale)
|
||||||
|
{
|
||||||
|
case 6.0f:
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank3);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 6;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3.0f:
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1.5f:
|
||||||
|
// Upsample by 3 then decimate by 2.
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||||
|
{
|
||||||
|
public struct UpsamplerBufferState
|
||||||
|
{
|
||||||
|
public const int HistoryLength = 20;
|
||||||
|
|
||||||
|
public float Scale;
|
||||||
|
public Array20<float> History;
|
||||||
|
public bool Initialized;
|
||||||
|
public int Phase;
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,11 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort[] InputBufferIndices;
|
public ushort[] InputBufferIndices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// State of each input buffer index kept across invocations of the upsampler.
|
||||||
|
/// </summary>
|
||||||
|
public UpsamplerBufferState[] BufferStates;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="UpsamplerState"/>.
|
/// Create a new <see cref="UpsamplerState"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -12,9 +12,9 @@ using Ryujinx.Audio.Integration;
|
|||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@ -46,6 +46,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
@ -58,12 +59,14 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
internal class AppHost
|
internal class AppHost
|
||||||
{
|
{
|
||||||
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
|
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||||
private const int TargetFps = 60;
|
private const int TargetFps = 60;
|
||||||
private const float VolumeDelta = 0.05f;
|
private const float VolumeDelta = 0.05f;
|
||||||
|
|
||||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||||
|
private readonly IntPtr InvisibleCursorWin;
|
||||||
|
private readonly IntPtr DefaultCursorWin;
|
||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
@ -76,12 +79,12 @@ namespace Ryujinx.Ava
|
|||||||
private readonly MainWindowViewModel _viewModel;
|
private readonly MainWindowViewModel _viewModel;
|
||||||
private readonly IKeyboard _keyboardInterface;
|
private readonly IKeyboard _keyboardInterface;
|
||||||
private readonly TopLevel _topLevel;
|
private readonly TopLevel _topLevel;
|
||||||
|
public RendererHost _rendererHost;
|
||||||
|
|
||||||
private readonly GraphicsDebugLevel _glLogLevel;
|
private readonly GraphicsDebugLevel _glLogLevel;
|
||||||
private float _newVolume;
|
private float _newVolume;
|
||||||
private KeyboardHotkeyState _prevHotkeyState;
|
private KeyboardHotkeyState _prevHotkeyState;
|
||||||
|
|
||||||
private bool _hideCursorOnIdle;
|
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
private bool _isCursorInRenderer;
|
private bool _isCursorInRenderer;
|
||||||
|
|
||||||
@ -102,7 +105,6 @@ namespace Ryujinx.Ava
|
|||||||
public event EventHandler AppExit;
|
public event EventHandler AppExit;
|
||||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
|
|
||||||
public RendererHost Renderer { get; }
|
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
public VirtualFileSystem VirtualFileSystem { get; }
|
||||||
public ContentManager ContentManager { get; }
|
public ContentManager ContentManager { get; }
|
||||||
public NpadManager NpadManager { get; }
|
public NpadManager NpadManager { get; }
|
||||||
@ -114,7 +116,6 @@ namespace Ryujinx.Ava
|
|||||||
public string ApplicationPath { get; private set; }
|
public string ApplicationPath { get; private set; }
|
||||||
public bool ScreenshotRequested { get; set; }
|
public bool ScreenshotRequested { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public AppHost(
|
public AppHost(
|
||||||
RendererHost renderer,
|
RendererHost renderer,
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
@ -131,7 +132,6 @@ namespace Ryujinx.Ava
|
|||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_userChannelPersistence = userChannelPersistence;
|
_userChannelPersistence = userChannelPersistence;
|
||||||
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||||
_topLevel = topLevel;
|
_topLevel = topLevel;
|
||||||
@ -142,11 +142,12 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||||
Renderer = renderer;
|
|
||||||
ApplicationPath = applicationPath;
|
ApplicationPath = applicationPath;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
|
_rendererHost = renderer;
|
||||||
|
|
||||||
_chrono = new Stopwatch();
|
_chrono = new Stopwatch();
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
|
||||||
@ -159,9 +160,14 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
||||||
|
|
||||||
_topLevel.PointerLeave += TopLevel_PointerLeave;
|
|
||||||
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
InvisibleCursorWin = CreateEmptyCursor();
|
||||||
|
DefaultCursorWin = CreateArrowCursor();
|
||||||
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||||
@ -172,20 +178,47 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Control visual)
|
if (sender is MainWindow window)
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
var point = e.GetCurrentPoint(visual).Position;
|
if (_rendererHost.EmbeddedWindow.TransformedBounds != null)
|
||||||
|
{
|
||||||
|
var point = e.GetCurrentPoint(window).Position;
|
||||||
|
var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
|
||||||
|
|
||||||
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
|
_isCursorInRenderer = point.X >= bounds.X &&
|
||||||
|
point.X <= bounds.Width + bounds.X &&
|
||||||
|
point.Y >= bounds.Y &&
|
||||||
|
point.Y <= bounds.Height + bounds.Y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
|
private void ShowCursor()
|
||||||
{
|
{
|
||||||
_isCursorInRenderer = false;
|
Dispatcher.UIThread.Post(() =>
|
||||||
_viewModel.Cursor = Cursor.Default;
|
{
|
||||||
|
_viewModel.Cursor = Cursor.Default;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
SetCursor(DefaultCursorWin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideCursor()
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
_viewModel.Cursor = InvisibleCursor;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
SetCursor(InvisibleCursorWin);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetRendererWindowSize(Size size)
|
private void SetRendererWindowSize(Size size)
|
||||||
@ -284,7 +317,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_viewModel.SetUIProgressHandlers(Device);
|
_viewModel.SetUIProgressHandlers(Device);
|
||||||
|
|
||||||
Renderer.SizeChanged += Window_SizeChanged;
|
_rendererHost.SizeChanged += Window_SizeChanged;
|
||||||
|
|
||||||
_isActive = true;
|
_isActive = true;
|
||||||
|
|
||||||
@ -380,7 +413,6 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||||
|
|
||||||
_topLevel.PointerLeave -= TopLevel_PointerLeave;
|
|
||||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||||
|
|
||||||
_gpuCancellationTokenSource.Cancel();
|
_gpuCancellationTokenSource.Cancel();
|
||||||
@ -397,28 +429,19 @@ namespace Ryujinx.Ava
|
|||||||
_windowsMultimediaTimerResolution = null;
|
_windowsMultimediaTimerResolution = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer?.MakeCurrent();
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent();
|
||||||
|
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(delegate
|
if (state.NewValue)
|
||||||
{
|
{
|
||||||
_hideCursorOnIdle = state.NewValue;
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
}
|
||||||
if (_hideCursorOnIdle)
|
|
||||||
{
|
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = Cursor.Default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadGuestApplication()
|
public async Task<bool> LoadGuestApplication()
|
||||||
@ -611,11 +634,12 @@ namespace Ryujinx.Ava
|
|||||||
// Initialize Renderer.
|
// Initialize Renderer.
|
||||||
IRenderer renderer;
|
IRenderer renderer;
|
||||||
|
|
||||||
if (Renderer.IsVulkan)
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||||
{
|
{
|
||||||
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
renderer = new VulkanRenderer(
|
||||||
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
|
||||||
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
VulkanHelper.GetRequiredInstanceExtensions,
|
||||||
|
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -763,14 +787,12 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext()));
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer);
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
Width = (int)Renderer.Bounds.Width;
|
Width = (int)_rendererHost.Bounds.Width;
|
||||||
Height = (int)Renderer.Bounds.Height;
|
Height = (int)_rendererHost.Bounds.Height;
|
||||||
|
|
||||||
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
||||||
|
|
||||||
@ -803,7 +825,7 @@ namespace Ryujinx.Ava
|
|||||||
_viewModel.SwitchToRenderer(false);
|
_viewModel.SwitchToRenderer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.PresentFrame(() => Renderer?.SwapBuffers());
|
Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
if (_ticks >= _ticksPerFrame)
|
||||||
@ -813,7 +835,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatus()
|
public void UpdateStatus()
|
||||||
@ -829,7 +851,7 @@ namespace Ryujinx.Ava
|
|||||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||||
Device.EnableDeviceVsync,
|
Device.EnableDeviceVsync,
|
||||||
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
|
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
|
||||||
Renderer.IsVulkan ? "Vulkan" : "OpenGL",
|
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL",
|
||||||
dockedMode,
|
dockedMode,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||||
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||||
@ -860,29 +882,6 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleScreenState()
|
|
||||||
{
|
|
||||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_hideCursorOnIdle)
|
|
||||||
{
|
|
||||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool UpdateFrame()
|
private bool UpdateFrame()
|
||||||
{
|
{
|
||||||
if (!_isActive)
|
if (!_isActive)
|
||||||
@ -890,23 +889,44 @@ namespace Ryujinx.Ava
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
if (_viewModel.IsActive)
|
||||||
{
|
{
|
||||||
|
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||||
|
{
|
||||||
|
if (_isCursorInRenderer)
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.HideCursorOnIdle)
|
||||||
|
{
|
||||||
|
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
HandleScreenState();
|
|
||||||
|
|
||||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||||
{
|
{
|
||||||
Device.Application.DiskCacheLoadState?.Cancel();
|
Device.Application.DiskCacheLoadState?.Cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
|
||||||
{
|
|
||||||
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
||||||
|
|
||||||
if (currentHotkeyState != _prevHotkeyState)
|
if (currentHotkeyState != _prevHotkeyState)
|
||||||
|
@ -119,7 +119,7 @@
|
|||||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||||
"SettingsTabSystemHacks": "Hacks",
|
"SettingsTabSystemHacks": "Hacks",
|
||||||
"SettingsTabSystemHacksNote": " (may cause instability)",
|
"SettingsTabSystemHacksNote": "May cause instability",
|
||||||
"SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)",
|
"SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)",
|
||||||
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
|
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
|
||||||
"SettingsTabGraphics": "Graphics",
|
"SettingsTabGraphics": "Graphics",
|
||||||
@ -157,7 +157,8 @@
|
|||||||
"SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs",
|
"SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs",
|
||||||
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
|
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
|
||||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
|
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
|
||||||
"SettingsTabLoggingDeveloperOptions": "Developer Options (WARNING: Will reduce performance)",
|
"SettingsTabLoggingDeveloperOptions": "Developer Options",
|
||||||
|
"SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
|
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
|
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
|
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
|
||||||
@ -523,7 +524,7 @@
|
|||||||
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
|
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
|
||||||
"OpenSetupGuideMessage": "Open the Setup Guide",
|
"OpenSetupGuideMessage": "Open the Setup Guide",
|
||||||
"NoUpdate": "No Update",
|
"NoUpdate": "No Update",
|
||||||
"TitleUpdateVersionLabel": "Version {0} - {1}",
|
"TitleUpdateVersionLabel": "Version {0}",
|
||||||
"RyujinxInfo": "Ryujinx - Info",
|
"RyujinxInfo": "Ryujinx - Info",
|
||||||
"RyujinxConfirm": "Ryujinx - Confirmation",
|
"RyujinxConfirm": "Ryujinx - Confirmation",
|
||||||
"FileDialogAllTypes": "All types",
|
"FileDialogAllTypes": "All types",
|
||||||
@ -584,7 +585,7 @@
|
|||||||
"UserProfilesSetProfileImage": "Set Profile Image",
|
"UserProfilesSetProfileImage": "Set Profile Image",
|
||||||
"UserProfileEmptyNameError": "Name is required",
|
"UserProfileEmptyNameError": "Name is required",
|
||||||
"UserProfileNoImageError": "Profile image must be set",
|
"UserProfileNoImageError": "Profile image must be set",
|
||||||
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
|
"GameUpdateWindowHeading": "Manage Updates for {0} ({1})",
|
||||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||||
"UserProfilesName": "Name:",
|
"UserProfilesName": "Name:",
|
||||||
@ -618,4 +619,4 @@
|
|||||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||||
"UserEditorTitle" : "Edit User",
|
"UserEditorTitle" : "Edit User",
|
||||||
"UserEditorTitleCreate" : "Create User"
|
"UserEditorTitleCreate" : "Create User"
|
||||||
}
|
}
|
@ -60,5 +60,6 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
</Styles>
|
</Styles>
|
@ -52,5 +52,6 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
</Styles>
|
</Styles>
|
@ -56,8 +56,8 @@
|
|||||||
<Style Selector="Border.settings">
|
<Style Selector="Border.settings">
|
||||||
<Setter Property="Background" Value="{DynamicResource ThemeDarkColor}" />
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkColor}" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="CornerRadius" Value="3" />
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Image.small">
|
<Style Selector="Image.small">
|
||||||
<Setter Property="Width" Value="50" />
|
<Setter Property="Width" Value="50" />
|
||||||
@ -234,6 +234,9 @@
|
|||||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
<Style Selector="TextBox.NumberBoxTextBoxStyle">
|
<Style Selector="TextBox.NumberBoxTextBoxStyle">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
@ -303,6 +306,9 @@
|
|||||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||||
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
|
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
|
||||||
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
|
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
|
||||||
|
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||||
|
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
||||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
int button = (int)args.InitialPressMouseButton - 1;
|
||||||
|
|
||||||
|
if (PressedButtons.Count() >= button)
|
||||||
|
{
|
||||||
|
PressedButtons[button] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
|
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
||||||
|
|
||||||
|
if (PressedButtons.Count() >= button)
|
||||||
|
{
|
||||||
|
PressedButtons[button] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
||||||
@ -86,12 +97,18 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
public void SetMousePressed(MouseButton button)
|
public void SetMousePressed(MouseButton button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = true;
|
if (PressedButtons.Count() >= (int)button)
|
||||||
|
{
|
||||||
|
PressedButtons[(int)button] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMouseReleased(MouseButton button)
|
public void SetMouseReleased(MouseButton button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = false;
|
if (PressedButtons.Count() >= (int)button)
|
||||||
|
{
|
||||||
|
PressedButtons[(int)button] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPosition(double x, double y)
|
public void SetPosition(double x, double y)
|
||||||
@ -101,7 +118,12 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
public bool IsButtonPressed(MouseButton button)
|
public bool IsButtonPressed(MouseButton button)
|
||||||
{
|
{
|
||||||
return PressedButtons[(int)button];
|
if (PressedButtons.Count() >= (int)button)
|
||||||
|
{
|
||||||
|
return PressedButtons[(int)button];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Size GetClientSize()
|
public Size GetClientSize()
|
||||||
|
@ -7,9 +7,7 @@ using ICSharpCode.SharpZipLib.Zip;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Ryujinx.Ava;
|
using Ryujinx.Ava;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
@ -32,31 +30,29 @@ namespace Ryujinx.Modules
|
|||||||
internal static class Updater
|
internal static class Updater
|
||||||
{
|
{
|
||||||
private const string GitHubApiURL = "https://api.github.com";
|
private const string GitHubApiURL = "https://api.github.com";
|
||||||
internal static bool Running;
|
|
||||||
|
|
||||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
|
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
|
||||||
private static readonly int ConnectionCount = 4;
|
private static readonly int ConnectionCount = 4;
|
||||||
|
|
||||||
private static string _buildVer;
|
private static string _buildVer;
|
||||||
private static string _platformExt;
|
private static string _platformExt;
|
||||||
private static string _buildUrl;
|
private static string _buildUrl;
|
||||||
private static long _buildSize;
|
private static long _buildSize;
|
||||||
|
private static bool _updateSuccessful;
|
||||||
|
private static bool _running;
|
||||||
|
|
||||||
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
||||||
|
|
||||||
public static bool UpdateSuccessful { get; private set; }
|
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
|
||||||
|
|
||||||
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
|
|
||||||
{
|
{
|
||||||
if (Running)
|
if (_running)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Running = true;
|
_running = true;
|
||||||
mainWindow.ViewModel.CanUpdate = false;
|
|
||||||
|
|
||||||
// Detect current platform
|
// Detect current platform
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
@ -82,77 +78,87 @@ namespace Ryujinx.Modules
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get latest version number from GitHub API
|
// Get latest version number from GitHub API
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (HttpClient jsonClient = ConstructHttpClient())
|
using HttpClient jsonClient = ConstructHttpClient();
|
||||||
|
|
||||||
|
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"];
|
||||||
|
|
||||||
|
_buildVer = (string)jsonRoot["name"];
|
||||||
|
|
||||||
|
foreach (JToken asset in assets)
|
||||||
{
|
{
|
||||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
string assetName = (string)asset["name"];
|
||||||
|
string assetState = (string)asset["state"];
|
||||||
|
string downloadURL = (string)asset["browser_download_url"];
|
||||||
|
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
|
||||||
JToken assets = jsonRoot["assets"];
|
|
||||||
|
|
||||||
_buildVer = (string)jsonRoot["name"];
|
|
||||||
|
|
||||||
foreach (JToken asset in assets)
|
|
||||||
{
|
{
|
||||||
string assetName = (string)asset["name"];
|
_buildUrl = downloadURL;
|
||||||
string assetState = (string)asset["state"];
|
|
||||||
string downloadURL = (string)asset["browser_download_url"];
|
|
||||||
|
|
||||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
if (assetState != "uploaded")
|
||||||
{
|
{
|
||||||
_buildUrl = downloadURL;
|
if (showVersionUpToDate)
|
||||||
|
|
||||||
if (assetState != "uploaded")
|
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||||
{
|
});
|
||||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
_running = false;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If build not done, assume no new update are availaible.
|
return;
|
||||||
if (_buildUrl == null)
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If build not done, assume no new update are availaible.
|
||||||
|
if (_buildUrl == null)
|
||||||
|
{
|
||||||
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||||
{
|
});
|
||||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,11 +169,16 @@ namespace Ryujinx.Modules
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,8 +192,7 @@ namespace Ryujinx.Modules
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Running = false;
|
_running = false;
|
||||||
mainWindow.ViewModel.CanUpdate = true;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -210,7 +220,8 @@ namespace Ryujinx.Modules
|
|||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
// Show a message asking the user if they want to update
|
// Show a message asking the user if they want to update
|
||||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
|
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
|
||||||
$"{Program.Version} -> {newVersion}");
|
$"{Program.Version} -> {newVersion}");
|
||||||
|
|
||||||
@ -218,12 +229,16 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
UpdateRyujinx(mainWindow, _buildUrl);
|
UpdateRyujinx(mainWindow, _buildUrl);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpClient ConstructHttpClient()
|
private static HttpClient ConstructHttpClient()
|
||||||
{
|
{
|
||||||
HttpClient result = new HttpClient();
|
HttpClient result = new();
|
||||||
|
|
||||||
// Required by GitHub to interract with APIs.
|
// Required by GitHub to interract with APIs.
|
||||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||||
@ -233,7 +248,7 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||||
{
|
{
|
||||||
UpdateSuccessful = false;
|
_updateSuccessful = false;
|
||||||
|
|
||||||
// Empty update dir, although it shouldn't ever have anything inside it
|
// Empty update dir, although it shouldn't ever have anything inside it
|
||||||
if (Directory.Exists(UpdateDir))
|
if (Directory.Exists(UpdateDir))
|
||||||
@ -245,17 +260,16 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
||||||
|
|
||||||
var taskDialog = new TaskDialog()
|
TaskDialog taskDialog = new()
|
||||||
{
|
{
|
||||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||||
Buttons = { },
|
Buttons = { },
|
||||||
ShowProgressBar = true
|
ShowProgressBar = true,
|
||||||
|
XamlRoot = parent
|
||||||
};
|
};
|
||||||
|
|
||||||
taskDialog.XamlRoot = parent;
|
|
||||||
|
|
||||||
taskDialog.Opened += (s, e) =>
|
taskDialog.Opened += (s, e) =>
|
||||||
{
|
{
|
||||||
if (_buildSize >= 0)
|
if (_buildSize >= 0)
|
||||||
@ -270,7 +284,7 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
await taskDialog.ShowAsync(true);
|
await taskDialog.ShowAsync(true);
|
||||||
|
|
||||||
if (UpdateSuccessful)
|
if (_updateSuccessful)
|
||||||
{
|
{
|
||||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||||
@ -279,7 +293,7 @@ namespace Ryujinx.Modules
|
|||||||
if (shouldRestart)
|
if (shouldRestart)
|
||||||
{
|
{
|
||||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||||
|
|
||||||
if (!Path.Exists(ryuExe))
|
if (!Path.Exists(ryuExe))
|
||||||
{
|
{
|
||||||
@ -298,15 +312,15 @@ namespace Ryujinx.Modules
|
|||||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
// Multi-Threaded Updater
|
// Multi-Threaded Updater
|
||||||
long chunkSize = _buildSize / ConnectionCount;
|
long chunkSize = _buildSize / ConnectionCount;
|
||||||
long remainderChunk = _buildSize % ConnectionCount;
|
long remainderChunk = _buildSize % ConnectionCount;
|
||||||
|
|
||||||
int completedRequests = 0;
|
int completedRequests = 0;
|
||||||
int totalProgressPercentage = 0;
|
int totalProgressPercentage = 0;
|
||||||
int[] progressPercentage = new int[ConnectionCount];
|
int[] progressPercentage = new int[ConnectionCount];
|
||||||
|
|
||||||
List<byte[]> list = new List<byte[]>(ConnectionCount);
|
List<byte[]> list = new(ConnectionCount);
|
||||||
List<WebClient> webClients = new List<WebClient>(ConnectionCount);
|
List<WebClient> webClients = new(ConnectionCount);
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < ConnectionCount; i++)
|
||||||
{
|
{
|
||||||
@ -317,133 +331,129 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
#pragma warning disable SYSLIB0014
|
#pragma warning disable SYSLIB0014
|
||||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||||
using (WebClient client = new WebClient())
|
using WebClient client = new();
|
||||||
#pragma warning restore SYSLIB0014
|
#pragma warning restore SYSLIB0014
|
||||||
|
|
||||||
|
webClients.Add(client);
|
||||||
|
|
||||||
|
if (i == ConnectionCount - 1)
|
||||||
{
|
{
|
||||||
webClients.Add(client);
|
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
||||||
|
}
|
||||||
|
|
||||||
if (i == ConnectionCount - 1)
|
client.DownloadProgressChanged += (_, args) =>
|
||||||
|
{
|
||||||
|
int index = (int)args.UserState;
|
||||||
|
|
||||||
|
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
||||||
|
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||||
|
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||||
|
|
||||||
|
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||||
|
};
|
||||||
|
|
||||||
|
client.DownloadDataCompleted += (_, args) =>
|
||||||
|
{
|
||||||
|
int index = (int)args.UserState;
|
||||||
|
|
||||||
|
if (args.Cancelled)
|
||||||
{
|
{
|
||||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
webClients[index].Dispose();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
|
||||||
}
|
|
||||||
|
|
||||||
client.DownloadProgressChanged += (_, args) =>
|
taskDialog.Hide();
|
||||||
{
|
|
||||||
int index = (int)args.UserState;
|
|
||||||
|
|
||||||
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
|
||||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
|
||||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
|
||||||
};
|
|
||||||
|
|
||||||
client.DownloadDataCompleted += (_, args) =>
|
|
||||||
{
|
|
||||||
int index = (int)args.UserState;
|
|
||||||
|
|
||||||
if (args.Cancelled)
|
|
||||||
{
|
|
||||||
webClients[index].Dispose();
|
|
||||||
|
|
||||||
taskDialog.Hide();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
list[index] = args.Result;
|
|
||||||
Interlocked.Increment(ref completedRequests);
|
|
||||||
|
|
||||||
if (Equals(completedRequests, ConnectionCount))
|
|
||||||
{
|
|
||||||
byte[] mergedFileBytes = new byte[_buildSize];
|
|
||||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
|
||||||
{
|
|
||||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
|
||||||
destinationOffset += list[connectionIndex].Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InstallUpdate(taskDialog, updateFile);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
|
||||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
|
||||||
|
|
||||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
|
||||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
|
||||||
|
|
||||||
for (int j = 0; j < webClients.Count; j++)
|
|
||||||
{
|
|
||||||
webClients[j].CancelAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list[index] = args.Result;
|
||||||
|
Interlocked.Increment(ref completedRequests);
|
||||||
|
|
||||||
|
if (Equals(completedRequests, ConnectionCount))
|
||||||
|
{
|
||||||
|
byte[] mergedFileBytes = new byte[_buildSize];
|
||||||
|
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||||
|
{
|
||||||
|
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||||
|
destinationOffset += list[connectionIndex].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InstallUpdate(taskDialog, updateFile);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||||
|
|
||||||
|
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||||
|
|
||||||
|
for (int j = 0; j < webClients.Count; j++)
|
||||||
|
{
|
||||||
|
webClients[j].CancelAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
using (HttpClient client = new HttpClient())
|
using HttpClient client = new();
|
||||||
|
// We do not want to timeout while downloading
|
||||||
|
client.Timeout = TimeSpan.FromDays(1);
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||||
|
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
||||||
{
|
{
|
||||||
// We do not want to timeout while downloading
|
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
|
||||||
client.Timeout = TimeSpan.FromDays(1);
|
|
||||||
|
|
||||||
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||||
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
long byteWritten = 0;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[32 * 1024];
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
|
int readSize = remoteFileStream.Read(buffer);
|
||||||
|
|
||||||
|
if (readSize == 0)
|
||||||
{
|
{
|
||||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
break;
|
||||||
long byteWritten = 0;
|
|
||||||
|
|
||||||
byte[] buffer = new byte[32 * 1024];
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int readSize = remoteFileStream.Read(buffer);
|
|
||||||
|
|
||||||
if (readSize == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
byteWritten += readSize;
|
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
|
||||||
|
|
||||||
updateFileStream.Write(buffer, 0, readSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
InstallUpdate(taskDialog, updateFile);
|
byteWritten += readSize;
|
||||||
|
|
||||||
|
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||||
|
|
||||||
|
updateFileStream.Write(buffer, 0, readSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallUpdate(taskDialog, updateFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@ -454,8 +464,11 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile));
|
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
||||||
worker.Name = "Updater.SingleThreadWorker";
|
{
|
||||||
|
Name = "Updater.SingleThreadWorker"
|
||||||
|
};
|
||||||
|
|
||||||
worker.Start();
|
worker.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,72 +496,70 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
using (Stream inStream = File.OpenRead(updateFile))
|
using Stream inStream = File.OpenRead(updateFile);
|
||||||
using (Stream gzipStream = new GZipInputStream(inStream))
|
using GZipInputStream gzipStream = new(inStream);
|
||||||
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
|
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
TarEntry tarEntry;
|
||||||
|
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||||
{
|
{
|
||||||
TarEntry tarEntry;
|
if (tarEntry.IsDirectory) continue;
|
||||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
|
||||||
|
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||||
|
|
||||||
|
using (FileStream outStream = File.OpenWrite(outPath))
|
||||||
{
|
{
|
||||||
if (tarEntry.IsDirectory) continue;
|
tarStream.CopyEntryContents(outStream);
|
||||||
|
|
||||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
using (FileStream outStream = File.OpenWrite(outPath))
|
|
||||||
{
|
|
||||||
tarStream.CopyEntryContents(outStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
|
||||||
|
|
||||||
TarEntry entry = tarEntry;
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||||
}
|
|
||||||
|
TarEntry entry = tarEntry;
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (Stream inStream = File.OpenRead(updateFile))
|
using Stream inStream = File.OpenRead(updateFile);
|
||||||
using (ZipFile zipFile = new ZipFile(inStream))
|
using ZipFile zipFile = new(inStream);
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
double count = 0;
|
||||||
|
foreach (ZipEntry zipEntry in zipFile)
|
||||||
{
|
{
|
||||||
double count = 0;
|
count++;
|
||||||
foreach (ZipEntry zipEntry in zipFile)
|
if (zipEntry.IsDirectory) continue;
|
||||||
|
|
||||||
|
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||||
|
|
||||||
|
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||||
|
using (FileStream outStream = File.OpenWrite(outPath))
|
||||||
{
|
{
|
||||||
count++;
|
zipStream.CopyTo(outStream);
|
||||||
if (zipEntry.IsDirectory) continue;
|
|
||||||
|
|
||||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
|
||||||
using (FileStream outStream = File.OpenWrite(outPath))
|
|
||||||
{
|
|
||||||
zipStream.CopyTo(outStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete downloaded zip
|
// Delete downloaded zip
|
||||||
@ -594,21 +605,24 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
||||||
|
|
||||||
UpdateSuccessful = true;
|
_updateSuccessful = true;
|
||||||
|
|
||||||
taskDialog.Hide();
|
taskDialog.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
public static bool CanUpdate(bool showWarnings)
|
||||||
public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
|
|
||||||
{
|
{
|
||||||
#if !DISABLE_UPDATER
|
#if !DISABLE_UPDATER
|
||||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
||||||
{
|
{
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
|
Dispatcher.UIThread.Post(async () =>
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
|
{
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -618,8 +632,12 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
Dispatcher.UIThread.Post(async () =>
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
|
{
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -629,8 +647,12 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
Dispatcher.UIThread.Post(async () =>
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
{
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -642,18 +664,27 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
if (ReleaseInformation.IsFlatHubBuild())
|
if (ReleaseInformation.IsFlatHubBuild())
|
||||||
{
|
{
|
||||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
|
|
||||||
// NOTE: This method should always reflect the latest build layout.s
|
// NOTE: This method should always reflect the latest build layout.s
|
||||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||||
@ -677,7 +708,7 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||||
{
|
{
|
||||||
var total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||||
foreach (string directory in Directory.GetDirectories(root))
|
foreach (string directory in Directory.GetDirectories(root))
|
||||||
{
|
{
|
||||||
string dirName = Path.GetFileName(directory);
|
string dirName = Path.GetFileName(directory);
|
||||||
@ -694,6 +725,7 @@ namespace Ryujinx.Modules
|
|||||||
foreach (string file in Directory.GetFiles(root))
|
foreach (string file in Directory.GetFiles(root))
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Ava
|
|||||||
public static double DesktopScaleFactor { get; set; } = 1.0;
|
public static double DesktopScaleFactor { get; set; } = 1.0;
|
||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true)]
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
|
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
|
||||||
@ -276,4 +276,4 @@ namespace Ryujinx.Ava
|
|||||||
Logger.Shutdown();
|
Logger.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_hiddenTextBox.Clear();
|
_hiddenTextBox.Clear();
|
||||||
_parent.ViewModel.RendererControl.Focus();
|
_parent.ViewModel.RendererHostControl.Focus();
|
||||||
|
|
||||||
_parent = null;
|
_parent = null;
|
||||||
});
|
});
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d"
|
Focusable="True"
|
||||||
Focusable="True">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||||
@ -130,7 +130,8 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="10" />
|
<ColumnDefinition Width="10" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="150" />
|
||||||
|
<ColumnDefinition Width="100" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Image
|
<Image
|
||||||
Grid.RowSpan="3"
|
Grid.RowSpan="3"
|
||||||
@ -141,30 +142,54 @@
|
|||||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||||
<StackPanel
|
<Border
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
|
Margin="0,0,5,0"
|
||||||
|
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||||
|
BorderThickness="0,0,1,0">
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="5">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="{Binding TitleName}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Developer}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Version}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="10,0,0,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="5" >
|
Spacing="5">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding TitleId}"
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding Developer}"
|
Text="{Binding FileExtension}"
|
||||||
TextAlignment="Left"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Text="{Binding Version}"
|
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="3"
|
Grid.Column="4"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
|
||||||
{
|
|
||||||
public partial class RendererHost : UserControl, IDisposable
|
|
||||||
{
|
|
||||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
|
||||||
private EmbeddedWindow _currentWindow;
|
|
||||||
|
|
||||||
public bool IsVulkan { get; private set; }
|
|
||||||
|
|
||||||
public RendererHost(GraphicsDebugLevel graphicsDebugLevel)
|
|
||||||
{
|
|
||||||
_graphicsDebugLevel = graphicsDebugLevel;
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RendererHost()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateOpenGL()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
|
|
||||||
_currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel);
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
IsVulkan = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Initialize()
|
|
||||||
{
|
|
||||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
|
||||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
|
||||||
Content = _currentWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateVulkan()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
|
|
||||||
_currentWindow = new VulkanEmbeddedWindow();
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
IsVulkan = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenGLContextBase GetContext()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
return openGlEmbeddedWindow.Context;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnDetachedFromVisualTree(e);
|
|
||||||
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CurrentWindow_SizeChanged(object sender, Size e)
|
|
||||||
{
|
|
||||||
SizeChanged?.Invoke(sender, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
|
||||||
{
|
|
||||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MakeCurrent()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.MakeCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.MakeCurrent(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SwapBuffers()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.SwapBuffers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs> RendererInitialized;
|
|
||||||
public event Action<object, Size> SizeChanged;
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_currentWindow != null)
|
|
||||||
{
|
|
||||||
_currentWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
|
||||||
_currentWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api)
|
|
||||||
{
|
|
||||||
return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow)
|
|
||||||
? vulkanEmbeddedWindow.CreateSurface(instance)
|
|
||||||
: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using SPB.Graphics;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
|
|
||||||
{
|
|
||||||
public AvaloniaGlxContext(IntPtr handle)
|
|
||||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
|
||||||
{
|
|
||||||
ContextHandle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using SPB.Graphics;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
|
|
||||||
{
|
|
||||||
public AvaloniaWglContext(IntPtr handle)
|
|
||||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
|
||||||
{
|
|
||||||
ContextHandle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,232 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using SPB.Graphics;
|
|
||||||
using SPB.Platform;
|
|
||||||
using SPB.Platform.GLX;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
public class EmbeddedWindow : NativeControlHost
|
|
||||||
{
|
|
||||||
private WindowProc _wndProcDelegate;
|
|
||||||
private string _className;
|
|
||||||
|
|
||||||
protected GLXWindow X11Window { get; set; }
|
|
||||||
protected IntPtr WindowHandle { get; set; }
|
|
||||||
protected IntPtr X11Display { get; set; }
|
|
||||||
protected IntPtr NsView { get; set; }
|
|
||||||
protected IntPtr MetalLayer { get; set; }
|
|
||||||
|
|
||||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
|
||||||
|
|
||||||
public event EventHandler<IntPtr> WindowCreated;
|
|
||||||
public event EventHandler<Size> SizeChanged;
|
|
||||||
|
|
||||||
protected virtual void OnWindowDestroyed() { }
|
|
||||||
protected virtual void OnWindowDestroying()
|
|
||||||
{
|
|
||||||
WindowHandle = IntPtr.Zero;
|
|
||||||
X11Display = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EmbeddedWindow()
|
|
||||||
{
|
|
||||||
var stateObserverable = this.GetObservable(BoundsProperty);
|
|
||||||
|
|
||||||
stateObserverable.Subscribe(StateChanged);
|
|
||||||
|
|
||||||
this.Initialized += NativeEmbeddedWindow_Initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnWindowCreated() { }
|
|
||||||
|
|
||||||
private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
OnWindowCreated();
|
|
||||||
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
WindowCreated?.Invoke(this, WindowHandle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StateChanged(Rect rect)
|
|
||||||
{
|
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
|
||||||
_updateBoundsCallback?.Invoke(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
return CreateLinux(parent);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
return CreateWin32(parent);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
return CreateMacOs(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.CreateNativeControlCore(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
|
||||||
{
|
|
||||||
OnWindowDestroying();
|
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
DestroyLinux();
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
DestroyWin32(control);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
DestroyMacOS();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.DestroyNativeControlCore(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnWindowDestroyed();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
|
||||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
|
||||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "X11");
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
IPlatformHandle CreateWin32(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
_className = "NativeWindow-" + Guid.NewGuid();
|
|
||||||
_wndProcDelegate = WndProc;
|
|
||||||
var wndClassEx = new WNDCLASSEX
|
|
||||||
{
|
|
||||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
|
||||||
hInstance = GetModuleHandle(null),
|
|
||||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
|
||||||
style = ClassStyles.CS_OWNDC,
|
|
||||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
|
||||||
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
|
||||||
};
|
|
||||||
|
|
||||||
var atom = RegisterClassEx(ref wndClassEx);
|
|
||||||
|
|
||||||
var handle = CreateWindowEx(
|
|
||||||
0,
|
|
||||||
_className,
|
|
||||||
"NativeWindow",
|
|
||||||
WindowStyles.WS_CHILD,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
640,
|
|
||||||
480,
|
|
||||||
parent.Handle,
|
|
||||||
IntPtr.Zero,
|
|
||||||
IntPtr.Zero,
|
|
||||||
IntPtr.Zero);
|
|
||||||
|
|
||||||
WindowHandle = handle;
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "HWND");
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
|
||||||
{
|
|
||||||
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
|
||||||
var root = VisualRoot as Window;
|
|
||||||
bool isLeft = false;
|
|
||||||
switch (msg)
|
|
||||||
{
|
|
||||||
case WindowsMessages.LBUTTONDOWN:
|
|
||||||
case WindowsMessages.RBUTTONDOWN:
|
|
||||||
isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
|
||||||
this.RaiseEvent(new PointerPressedEventArgs(
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed),
|
|
||||||
KeyModifiers.None));
|
|
||||||
break;
|
|
||||||
case WindowsMessages.LBUTTONUP:
|
|
||||||
case WindowsMessages.RBUTTONUP:
|
|
||||||
isLeft = msg == WindowsMessages.LBUTTONUP;
|
|
||||||
this.RaiseEvent(new PointerReleasedEventArgs(
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased),
|
|
||||||
KeyModifiers.None,
|
|
||||||
isLeft ? MouseButton.Left : MouseButton.Right));
|
|
||||||
break;
|
|
||||||
case WindowsMessages.MOUSEMOVE:
|
|
||||||
this.RaiseEvent(new PointerEventArgs(
|
|
||||||
PointerMovedEvent,
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
|
||||||
KeyModifiers.None));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
IPlatformHandle CreateMacOs(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
|
||||||
|
|
||||||
NsView = nsView;
|
|
||||||
|
|
||||||
return new PlatformHandle(nsView, "NSView");
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyLinux()
|
|
||||||
{
|
|
||||||
X11Window?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
void DestroyWin32(IPlatformHandle handle)
|
|
||||||
{
|
|
||||||
DestroyWindow(handle.Handle);
|
|
||||||
UnregisterClass(_className, GetModuleHandle(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
void DestroyMacOS()
|
|
||||||
{
|
|
||||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using Avalonia.OpenGL;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
internal static class IGlContextExtension
|
|
||||||
{
|
|
||||||
public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context)
|
|
||||||
{
|
|
||||||
var handle = (IntPtr)context.GetType().GetProperty("Handle").GetValue(context);
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
return new AvaloniaWglContext(handle);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
return new AvaloniaGlxContext(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
using Avalonia.Platform;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using SPB.Graphics.Vulkan;
|
|
||||||
using SPB.Platform.GLX;
|
|
||||||
using SPB.Platform.Metal;
|
|
||||||
using SPB.Platform.Win32;
|
|
||||||
using SPB.Platform.X11;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
public class VulkanEmbeddedWindow : EmbeddedWindow
|
|
||||||
{
|
|
||||||
private NativeWindowBase _window;
|
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
|
|
||||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
|
||||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
|
||||||
|
|
||||||
X11Window.Hide();
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "X11");
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceKHR CreateSurface(Instance instance)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
_window = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new PlatformNotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -70,6 +70,22 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IntPtr CreateEmptyCursor()
|
||||||
|
{
|
||||||
|
return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IntPtr CreateArrowCursor()
|
||||||
|
{
|
||||||
|
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll")]
|
||||||
|
public static partial IntPtr SetCursor(IntPtr handle);
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll")]
|
||||||
|
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane);
|
||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
||||||
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
using LibHac;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Shim;
|
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using Ryujinx.Ava.Common;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -16,7 +12,6 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
{
|
{
|
||||||
public class SaveModel : BaseModel
|
public class SaveModel : BaseModel
|
||||||
{
|
{
|
||||||
private readonly HorizonClient _horizonClient;
|
|
||||||
private long _size;
|
private long _size;
|
||||||
|
|
||||||
public ulong SaveId { get; }
|
public ulong SaveId { get; }
|
||||||
@ -41,11 +36,29 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public bool SizeAvailable { get; set; }
|
public bool SizeAvailable { get; set; }
|
||||||
|
|
||||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
public string SizeString => GetSizeString();
|
||||||
|
|
||||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
private string GetSizeString()
|
||||||
|
{
|
||||||
|
const int scale = 1024;
|
||||||
|
string[] orders = { "GiB", "MiB", "KiB" };
|
||||||
|
long max = (long)Math.Pow(scale, orders.Length);
|
||||||
|
|
||||||
|
foreach (string order in orders)
|
||||||
|
{
|
||||||
|
if (Size > max)
|
||||||
|
{
|
||||||
|
return $"{decimal.Divide(Size, max):##.##} {order}";
|
||||||
|
}
|
||||||
|
|
||||||
|
max /= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0 KiB";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveModel(SaveDataInfo info, VirtualFileSystem virtualFileSystem)
|
||||||
{
|
{
|
||||||
_horizonClient = horizonClient;
|
|
||||||
SaveId = info.SaveDataId;
|
SaveId = info.SaveDataId;
|
||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
UserId = info.UserId;
|
UserId = info.UserId;
|
||||||
|
@ -3,23 +3,17 @@ using Ryujinx.Ava.Common.Locale;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
internal class TitleUpdateModel
|
public class TitleUpdateModel
|
||||||
{
|
{
|
||||||
public bool IsEnabled { get; set; }
|
|
||||||
public bool IsNoUpdate { get; }
|
|
||||||
public ApplicationControlProperty Control { get; }
|
public ApplicationControlProperty Control { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public string Label => IsNoUpdate
|
public string Label => string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString());
|
||||||
? LocaleManager.Instance[LocaleKeys.NoUpdate]
|
|
||||||
: string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString(),
|
|
||||||
Path);
|
|
||||||
|
|
||||||
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false)
|
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||||
{
|
{
|
||||||
Control = control;
|
Control = control;
|
||||||
Path = path;
|
Path = path;
|
||||||
IsNoUpdate = isNoUpdate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
259
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
259
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using SPB.Graphics;
|
||||||
|
using SPB.Platform;
|
||||||
|
using SPB.Platform.GLX;
|
||||||
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public class EmbeddedWindow : NativeControlHost
|
||||||
|
{
|
||||||
|
private WindowProc _wndProcDelegate;
|
||||||
|
private string _className;
|
||||||
|
|
||||||
|
protected GLXWindow X11Window { get; set; }
|
||||||
|
|
||||||
|
protected IntPtr WindowHandle { get; set; }
|
||||||
|
protected IntPtr X11Display { get; set; }
|
||||||
|
protected IntPtr NsView { get; set; }
|
||||||
|
protected IntPtr MetalLayer { get; set; }
|
||||||
|
|
||||||
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
|
||||||
|
public EmbeddedWindow()
|
||||||
|
{
|
||||||
|
this.GetObservable(BoundsProperty).Subscribe(StateChanged);
|
||||||
|
|
||||||
|
Initialized += OnNativeEmbeddedWindowCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnWindowCreated() { }
|
||||||
|
|
||||||
|
protected virtual void OnWindowDestroyed() { }
|
||||||
|
|
||||||
|
protected virtual void OnWindowDestroying()
|
||||||
|
{
|
||||||
|
WindowHandle = IntPtr.Zero;
|
||||||
|
X11Display = IntPtr.Zero;
|
||||||
|
NsView = IntPtr.Zero;
|
||||||
|
MetalLayer = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OnWindowCreated();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
WindowCreated?.Invoke(this, WindowHandle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StateChanged(Rect rect)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
_updateBoundsCallback?.Invoke(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
return CreateLinux(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return CreateWin32(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return CreateMacOS();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNativeControlCore(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
OnWindowDestroying();
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
DestroyLinux();
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
DestroyWin32(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
DestroyMacOS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.DestroyNativeControlCore(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWindowDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
private IPlatformHandle CreateLinux(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||||
|
{
|
||||||
|
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle));
|
||||||
|
X11Window.Hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||||
|
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "X11");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
IPlatformHandle CreateWin32(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
_className = "NativeWindow-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
_wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||||
|
{
|
||||||
|
if (VisualRoot != null)
|
||||||
|
{
|
||||||
|
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
||||||
|
Pointer pointer = new(0, PointerType.Mouse, true);
|
||||||
|
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WindowsMessages.LBUTTONDOWN:
|
||||||
|
case WindowsMessages.RBUTTONDOWN:
|
||||||
|
{
|
||||||
|
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||||
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
||||||
|
|
||||||
|
var evnt = new PointerPressedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WindowsMessages.LBUTTONUP:
|
||||||
|
case WindowsMessages.RBUTTONUP:
|
||||||
|
{
|
||||||
|
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||||
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
||||||
|
|
||||||
|
var evnt = new PointerReleasedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None,
|
||||||
|
isLeft ? MouseButton.Left : MouseButton.Right);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WindowsMessages.MOUSEMOVE:
|
||||||
|
{
|
||||||
|
var evnt = new PointerEventArgs(
|
||||||
|
PointerMovedEvent,
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
|
};
|
||||||
|
|
||||||
|
WNDCLASSEX wndClassEx = new()
|
||||||
|
{
|
||||||
|
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||||
|
hInstance = GetModuleHandle(null),
|
||||||
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||||
|
style = ClassStyles.CS_OWNDC,
|
||||||
|
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||||
|
hCursor = CreateArrowCursor()
|
||||||
|
};
|
||||||
|
|
||||||
|
RegisterClassEx(ref wndClassEx);
|
||||||
|
|
||||||
|
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WS_CHILD, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "HWND");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
IPlatformHandle CreateMacOS()
|
||||||
|
{
|
||||||
|
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||||
|
|
||||||
|
NsView = nsView;
|
||||||
|
|
||||||
|
return new PlatformHandle(nsView, "NSView");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("Linux")]
|
||||||
|
void DestroyLinux()
|
||||||
|
{
|
||||||
|
X11Window?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
void DestroyWin32(IPlatformHandle handle)
|
||||||
|
{
|
||||||
|
DestroyWindow(handle.Handle);
|
||||||
|
UnregisterClass(_className, GetModuleHandle(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
void DestroyMacOS()
|
||||||
|
{
|
||||||
|
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Graphics.OpenGL;
|
using SPB.Graphics.OpenGL;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
@ -7,26 +10,20 @@ using SPB.Platform.WGL;
|
|||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
public class OpenGLEmbeddedWindow : EmbeddedWindow
|
public class EmbeddedWindowOpenGL : EmbeddedWindow
|
||||||
{
|
{
|
||||||
private readonly int _major;
|
|
||||||
private readonly int _minor;
|
|
||||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
|
||||||
private SwappableNativeWindowBase _window;
|
private SwappableNativeWindowBase _window;
|
||||||
|
|
||||||
public OpenGLContextBase Context { get; set; }
|
public OpenGLContextBase Context { get; set; }
|
||||||
|
|
||||||
public OpenGLEmbeddedWindow(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
public EmbeddedWindowOpenGL() { }
|
||||||
{
|
|
||||||
_major = major;
|
|
||||||
_minor = minor;
|
|
||||||
_graphicsDebugLevel = graphicsDebugLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnWindowDestroying()
|
protected override void OnWindowDestroying()
|
||||||
{
|
{
|
||||||
Context.Dispose();
|
Context.Dispose();
|
||||||
|
|
||||||
base.OnWindowDestroying();
|
base.OnWindowDestroying();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,19 +45,20 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var flags = OpenGLContextFlags.Compat;
|
var flags = OpenGLContextFlags.Compat;
|
||||||
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
|
if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None)
|
||||||
{
|
{
|
||||||
flags |= OpenGLContextFlags.Debug;
|
flags |= OpenGLContextFlags.Debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, _major, _minor, flags);
|
var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
|
||||||
|
|
||||||
|
Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags);
|
||||||
|
|
||||||
Context.Initialize(_window);
|
Context.Initialize(_window);
|
||||||
Context.MakeCurrent(_window);
|
Context.MakeCurrent(_window);
|
||||||
|
|
||||||
var bindingsContext = new OpenToolkitBindingsContext(Context.GetProcAddress);
|
GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress));
|
||||||
|
|
||||||
GL.LoadBindings(bindingsContext);
|
|
||||||
Context.MakeCurrent(null);
|
Context.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +74,14 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public void SwapBuffers()
|
public void SwapBuffers()
|
||||||
{
|
{
|
||||||
_window.SwapBuffers();
|
_window?.SwapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeBackgroundContext(IRenderer renderer)
|
||||||
|
{
|
||||||
|
(renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context));
|
||||||
|
|
||||||
|
MakeCurrent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SPB.Graphics.Vulkan;
|
||||||
|
using SPB.Platform.Metal;
|
||||||
|
using SPB.Platform.Win32;
|
||||||
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public class EmbeddedWindowVulkan : EmbeddedWindow
|
||||||
|
{
|
||||||
|
public SurfaceKHR CreateSurface(Instance instance)
|
||||||
|
{
|
||||||
|
NativeWindowBase nativeWindowBase;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SurfaceKHR CreateSurface(Instance instance, Vk api)
|
||||||
|
{
|
||||||
|
return CreateSurface(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
using OpenTK;
|
using OpenTK;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
internal class OpenToolkitBindingsContext : IBindingsContext
|
internal class OpenTKBindingsContext : IBindingsContext
|
||||||
{
|
{
|
||||||
private readonly Func<string, IntPtr> _getProcAddress;
|
private readonly Func<string, IntPtr> _getProcAddress;
|
||||||
|
|
||||||
public OpenToolkitBindingsContext(Func<string, IntPtr> getProcAddress)
|
public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress)
|
||||||
{
|
{
|
||||||
_getProcAddress = getProcAddress;
|
_getProcAddress = getProcAddress;
|
||||||
}
|
}
|
@ -6,6 +6,6 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.RendererHost"
|
x:Class="Ryujinx.Ava.UI.Renderer.RendererHost"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
</UserControl>
|
</UserControl>
|
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public partial class RendererHost : UserControl, IDisposable
|
||||||
|
{
|
||||||
|
public readonly EmbeddedWindow EmbeddedWindow;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs> WindowCreated;
|
||||||
|
public event Action<object, Size> SizeChanged;
|
||||||
|
|
||||||
|
public RendererHost()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||||
|
{
|
||||||
|
EmbeddedWindow = new EmbeddedWindowOpenGL();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmbeddedWindow = new EmbeddedWindowVulkan();
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||||
|
EmbeddedWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||||
|
|
||||||
|
Content = EmbeddedWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (EmbeddedWindow != null)
|
||||||
|
{
|
||||||
|
EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||||
|
EmbeddedWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||||
|
{
|
||||||
|
WindowCreated?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,17 @@ using SPB.Graphics.OpenGL;
|
|||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
class SPBOpenGLContext : IOpenGLContext
|
class SPBOpenGLContext : IOpenGLContext
|
||||||
{
|
{
|
||||||
private OpenGLContextBase _context;
|
private readonly OpenGLContextBase _context;
|
||||||
private NativeWindowBase _window;
|
private readonly NativeWindowBase _window;
|
||||||
|
|
||||||
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_window = window;
|
_window = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -32,12 +32,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||||
{
|
{
|
||||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||||
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||||
|
|
||||||
context.Initialize(window);
|
context.Initialize(window);
|
||||||
context.MakeCurrent(window);
|
context.MakeCurrent(window);
|
||||||
|
|
||||||
GL.LoadBindings(new OpenToolkitBindingsContext(context.GetProcAddress));
|
GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress));
|
||||||
|
|
||||||
context.MakeCurrent(null);
|
context.MakeCurrent(null);
|
||||||
|
|
@ -435,7 +435,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (str.Length > MaxSize)
|
if (str.Length > MaxSize)
|
||||||
{
|
{
|
||||||
return str.Substring(0, MaxSize - Ellipsis.Length) + Ellipsis;
|
return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
@ -13,6 +13,7 @@ using Ryujinx.Ava.Input;
|
|||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
@ -87,7 +88,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private float _volume;
|
private float _volume;
|
||||||
private string _backendText;
|
private string _backendText;
|
||||||
|
|
||||||
private bool _canUpdate;
|
private bool _canUpdate = true;
|
||||||
private Cursor _cursor;
|
private Cursor _cursor;
|
||||||
private string _title;
|
private string _title;
|
||||||
private string _currentEmulatedGamePath;
|
private string _currentEmulatedGamePath;
|
||||||
@ -176,11 +177,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool CanUpdate
|
public bool CanUpdate
|
||||||
{
|
{
|
||||||
get => _canUpdate;
|
get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_canUpdate = value;
|
_canUpdate = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -870,7 +870,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public Action<bool> SwitchToGameControl { get; private set; }
|
public Action<bool> SwitchToGameControl { get; private set; }
|
||||||
public Action<Control> SetMainContent { get; private set; }
|
public Action<Control> SetMainContent { get; private set; }
|
||||||
public TopLevel TopLevel { get; private set; }
|
public TopLevel TopLevel { get; private set; }
|
||||||
public RendererHost RendererControl { get; private set; }
|
public RendererHost RendererHostControl { get; private set; }
|
||||||
public bool IsClosing { get; set; }
|
public bool IsClosing { get; set; }
|
||||||
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
|
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
|
||||||
public IHostUiHandler UiHandler { get; internal set; }
|
public IHostUiHandler UiHandler { get; internal set; }
|
||||||
@ -1144,7 +1144,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
RendererControl.RendererInitialized += GlRenderer_Created;
|
RendererHostControl.WindowCreated += RendererHost_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
AppHost.AppExit += AppHost_AppExit;
|
AppHost.AppExit += AppHost_AppExit;
|
||||||
@ -1203,7 +1203,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GlRenderer_Created(object sender, EventArgs e)
|
private void RendererHost_Created(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ShowLoading(false);
|
ShowLoading(false);
|
||||||
|
|
||||||
@ -1601,13 +1601,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void OpenTitleUpdateManager()
|
public async void OpenTitleUpdateManager()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
await TitleUpdateWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||||
{
|
|
||||||
await new TitleUpdateWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1735,18 +1731,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
RendererHostControl = new RendererHost();
|
||||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
|
||||||
{
|
|
||||||
RendererControl.CreateOpenGL();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RendererControl.CreateVulkan();
|
|
||||||
}
|
|
||||||
|
|
||||||
AppHost = new AppHost(
|
AppHost = new AppHost(
|
||||||
RendererControl,
|
RendererHostControl,
|
||||||
InputManager,
|
InputManager,
|
||||||
path,
|
path,
|
||||||
VirtualFileSystem,
|
VirtualFileSystem,
|
||||||
@ -1787,9 +1775,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
SwitchToGameControl(startFullscreen);
|
SwitchToGameControl(startFullscreen);
|
||||||
|
|
||||||
SetMainContent(RendererControl);
|
SetMainContent(RendererHostControl);
|
||||||
|
|
||||||
RendererControl.Focus();
|
RendererHostControl.Focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1857,8 +1845,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
HandleRelaunch();
|
HandleRelaunch();
|
||||||
});
|
});
|
||||||
|
|
||||||
RendererControl.RendererInitialized -= GlRenderer_Created;
|
RendererHostControl.WindowCreated -= RendererHost_Created;
|
||||||
RendererControl = null;
|
RendererHostControl = null;
|
||||||
|
|
||||||
SelectedIcon = null;
|
SelectedIcon = null;
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsSoundIoEnabled { get; set; }
|
public bool IsSoundIoEnabled { get; set; }
|
||||||
public bool IsSDL2Enabled { get; set; }
|
public bool IsSDL2Enabled { get; set; }
|
||||||
public bool EnableCustomTheme { get; set; }
|
public bool EnableCustomTheme { get; set; }
|
||||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 0;
|
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||||
|
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
@ -311,25 +311,66 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
GameDirectories.Clear();
|
// User Interface
|
||||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
|
||||||
|
|
||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||||
ShowConfirmExit = config.ShowConfirmExit;
|
ShowConfirmExit = config.ShowConfirmExit;
|
||||||
HideCursorOnIdle = config.HideCursorOnIdle;
|
HideCursorOnIdle = config.HideCursorOnIdle;
|
||||||
|
|
||||||
|
GameDirectories.Clear();
|
||||||
|
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||||
|
|
||||||
|
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||||
|
CustomThemePath = config.Ui.CustomThemePath;
|
||||||
|
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||||
|
|
||||||
|
// Input
|
||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
|
|
||||||
|
// Keyboard Hotkeys
|
||||||
|
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
||||||
|
|
||||||
|
// System
|
||||||
|
Region = (int)config.System.Region.Value;
|
||||||
|
Language = (int)config.System.Language.Value;
|
||||||
|
TimeZone = config.System.TimeZone;
|
||||||
|
|
||||||
|
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||||
|
|
||||||
|
DateOffset = dateTimeOffset.Date;
|
||||||
|
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||||
EnableVsync = config.Graphics.EnableVsync;
|
EnableVsync = config.Graphics.EnableVsync;
|
||||||
EnablePptc = config.System.EnablePtc;
|
|
||||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
|
||||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
|
||||||
ExpandDramSize = config.System.ExpandRam;
|
ExpandDramSize = config.System.ExpandRam;
|
||||||
|
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
EnablePptc = config.System.EnablePtc;
|
||||||
|
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||||
|
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
||||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||||
|
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||||
|
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||||
|
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
|
||||||
|
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||||
|
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||||
|
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||||
|
Volume = config.System.AudioVolume * 100;
|
||||||
|
|
||||||
|
// Network
|
||||||
|
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||||
|
|
||||||
|
// Logging
|
||||||
EnableFileLog = config.Logger.EnableFileLog;
|
EnableFileLog = config.Logger.EnableFileLog;
|
||||||
EnableStub = config.Logger.EnableStub;
|
EnableStub = config.Logger.EnableStub;
|
||||||
EnableInfo = config.Logger.EnableInfo;
|
EnableInfo = config.Logger.EnableInfo;
|
||||||
@ -339,94 +380,69 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableGuest = config.Logger.EnableGuest;
|
EnableGuest = config.Logger.EnableGuest;
|
||||||
EnableDebug = config.Logger.EnableDebug;
|
EnableDebug = config.Logger.EnableDebug;
|
||||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
|
||||||
Volume = config.System.AudioVolume * 100;
|
|
||||||
|
|
||||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
|
||||||
|
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
|
||||||
|
|
||||||
TimeZone = config.System.TimeZone;
|
|
||||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
|
||||||
CustomThemePath = config.Ui.CustomThemePath;
|
|
||||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
|
||||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
|
||||||
|
|
||||||
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
|
||||||
|
|
||||||
Language = (int)config.System.Language.Value;
|
|
||||||
Region = (int)config.System.Region.Value;
|
|
||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
|
||||||
|
|
||||||
float anisotropy = config.Graphics.MaxAnisotropy;
|
|
||||||
|
|
||||||
MaxAnisotropy = anisotropy == -1 ? 0 : (int)(MathF.Log2(anisotropy));
|
|
||||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
|
||||||
|
|
||||||
int resolution = config.Graphics.ResScale;
|
|
||||||
|
|
||||||
ResolutionScale = resolution == -1 ? 0 : resolution;
|
|
||||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
|
||||||
|
|
||||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
|
||||||
|
|
||||||
DateOffset = dateTimeOffset.Date;
|
|
||||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
|
||||||
|
|
||||||
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
|
// User Interface
|
||||||
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
|
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||||
|
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||||
|
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
||||||
|
|
||||||
if (_directoryChanged)
|
if (_directoryChanged)
|
||||||
{
|
{
|
||||||
List<string> gameDirs = new List<string>(GameDirectories);
|
List<string> gameDirs = new(GameDirectories);
|
||||||
config.Ui.GameDirs.Value = gameDirs;
|
config.Ui.GameDirs.Value = gameDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||||
|
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||||
|
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||||
|
|
||||||
|
// Input
|
||||||
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
|
|
||||||
|
// Keyboard Hotkeys
|
||||||
|
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
||||||
|
|
||||||
|
// System
|
||||||
|
config.System.Region.Value = (Region)Region;
|
||||||
|
config.System.Language.Value = (Language)Language;
|
||||||
|
|
||||||
if (_validTzRegions.Contains(TimeZone))
|
if (_validTzRegions.Contains(TimeZone))
|
||||||
{
|
{
|
||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Logger.EnableError.Value = EnableError;
|
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||||
config.Logger.EnableTrace.Value = EnableTrace;
|
|
||||||
config.Logger.EnableWarn.Value = EnableWarn;
|
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||||
config.Logger.EnableInfo.Value = EnableInfo;
|
|
||||||
config.Logger.EnableStub.Value = EnableStub;
|
|
||||||
config.Logger.EnableDebug.Value = EnableDebug;
|
|
||||||
config.Logger.EnableGuest.Value = EnableGuest;
|
|
||||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
|
||||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
|
||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
|
||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
|
||||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
|
||||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
|
||||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
|
||||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||||
|
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||||
|
config.System.ExpandRam.Value = ExpandDramSize;
|
||||||
|
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
config.System.EnablePtc.Value = EnablePptc;
|
||||||
|
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||||
|
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||||
config.System.EnablePtc.Value = EnablePptc;
|
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
|
||||||
config.System.ExpandRam.Value = ExpandDramSize;
|
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
|
||||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
|
||||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
|
||||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
|
||||||
config.System.Language.Value = (Language)Language;
|
|
||||||
config.System.Region.Value = (Region)Region;
|
|
||||||
|
|
||||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||||
{
|
{
|
||||||
@ -434,22 +450,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||||
|
|
||||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
|
||||||
|
|
||||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
|
||||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
|
||||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
|
||||||
|
|
||||||
float anisotropy = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
|
||||||
|
|
||||||
config.Graphics.MaxAnisotropy.Value = anisotropy;
|
|
||||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
|
||||||
config.Graphics.ResScale.Value = ResolutionScale == 0 ? -1 : ResolutionScale;
|
|
||||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
|
||||||
config.System.AudioVolume.Value = Volume / 100;
|
|
||||||
|
|
||||||
|
// Audio
|
||||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||||
if (audioBackend != config.System.AudioBackend.Value)
|
if (audioBackend != config.System.AudioBackend.Value)
|
||||||
{
|
{
|
||||||
@ -458,7 +461,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
config.System.AudioVolume.Value = Volume / 100;
|
||||||
|
|
||||||
|
// Network
|
||||||
|
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||||
|
config.Logger.EnableStub.Value = EnableStub;
|
||||||
|
config.Logger.EnableInfo.Value = EnableInfo;
|
||||||
|
config.Logger.EnableWarn.Value = EnableWarn;
|
||||||
|
config.Logger.EnableError.Value = EnableError;
|
||||||
|
config.Logger.EnableTrace.Value = EnableTrace;
|
||||||
|
config.Logger.EnableGuest.Value = EnableGuest;
|
||||||
|
config.Logger.EnableDebug.Value = EnableDebug;
|
||||||
|
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||||
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
|
226
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
226
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels;
|
||||||
|
|
||||||
|
public class TitleUpdateViewModel : BaseModel
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
get => _titleUpdates;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_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
|
||||||
|
{
|
||||||
|
Selected = "",
|
||||||
|
Paths = new List<string>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadUpdates()
|
||||||
|
{
|
||||||
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
|
{
|
||||||
|
AddUpdate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||||
|
|
||||||
|
SelectedUpdate = selected;
|
||||||
|
|
||||||
|
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) = ApplicationLoader.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(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveUpdate(TitleUpdateModel update)
|
||||||
|
{
|
||||||
|
TitleUpdates.Remove(update);
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
|
{
|
||||||
|
OpenFileDialog dialog = new()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
AddUpdate(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private int _sortIndex;
|
private int _sortIndex;
|
||||||
private int _orderIndex;
|
private int _orderIndex;
|
||||||
private string _search;
|
private string _search;
|
||||||
private ObservableCollection<SaveModel> _saves;
|
private ObservableCollection<SaveModel> _saves = new();
|
||||||
private ObservableCollection<SaveModel> _views;
|
private ObservableCollection<SaveModel> _views = new();
|
||||||
private AccountManager _accountManager;
|
private AccountManager _accountManager;
|
||||||
|
|
||||||
public string SaveManagerHeading =>
|
public string SaveManagerHeading =>
|
||||||
@ -77,8 +77,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public UserSaveManagerViewModel(AccountManager accountManager)
|
public UserSaveManagerViewModel(AccountManager accountManager)
|
||||||
{
|
{
|
||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_saves = new ObservableCollection<SaveModel>();
|
|
||||||
_views = new ObservableCollection<SaveModel>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Sort()
|
public void Sort()
|
||||||
|
@ -165,7 +165,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
|
|
||||||
public async void CheckForUpdates(object sender, RoutedEventArgs e)
|
public async void CheckForUpdates(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Updater.CanUpdate(true, Window))
|
if (Updater.CanUpdate(true))
|
||||||
{
|
{
|
||||||
await Updater.BeginParse(Window, true);
|
await Updater.BeginParse(Window, true);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@
|
|||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
VerticalContentAlignment="Center"
|
|
||||||
DockPanel.Dock="Right"
|
DockPanel.Dock="Right"
|
||||||
KeyUp="SearchBox_OnKeyUp"
|
KeyUp="SearchBox_OnKeyUp"
|
||||||
Text="{Binding SearchText}"
|
Text="{Binding SearchText}"
|
||||||
|
@ -82,9 +82,6 @@
|
|||||||
Width="350"
|
Width="350"
|
||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
@ -97,6 +94,9 @@
|
|||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
||||||
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<ui:NumberBox
|
<ui:NumberBox
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsLoggingView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsLoggingView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -47,31 +47,34 @@
|
|||||||
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
|
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding EnableTrace}"
|
|
||||||
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox IsChecked="{Binding EnableGuest}"
|
<CheckBox IsChecked="{Binding EnableGuest}"
|
||||||
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
|
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
|
<StackPanel Orientation="Vertical" Spacing="2">
|
||||||
|
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
|
||||||
|
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<CheckBox IsChecked="{Binding EnableDebug}"
|
<CheckBox IsChecked="{Binding EnableTrace}"
|
||||||
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
|
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
|
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
|
||||||
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
|
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<CheckBox IsChecked="{Binding EnableDebug}"
|
||||||
|
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||||
|
</CheckBox>
|
||||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
|
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsSystemView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsSystemView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<viewModels:SettingsViewModel />
|
<viewModels:SettingsViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Name="SystemPage"
|
Name="SystemPage"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
@ -172,9 +172,9 @@
|
|||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Vertical" Spacing="2">
|
||||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
|
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
|
||||||
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
|
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabSystemHacksNote}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
|
@ -55,6 +55,11 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
Content="{locale:Locale Size}" />
|
Content="{locale:Locale Size}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
|
<ComboBox.Styles>
|
||||||
|
<Style Selector="ContentControl#ContentPresenter">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
</Style>
|
||||||
|
</ComboBox.Styles>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
@ -69,6 +74,11 @@
|
|||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
Content="{locale:Locale OrderDescending}" />
|
Content="{locale:Locale OrderDescending}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
|
<ComboBox.Styles>
|
||||||
|
<Style Selector="ContentControl#ContentPresenter">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
</Style>
|
||||||
|
</ComboBox.Styles>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid
|
<Grid
|
||||||
@ -97,6 +107,7 @@
|
|||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<ListBox
|
<ListBox
|
||||||
Name="SaveList"
|
Name="SaveList"
|
||||||
|
VirtualizationMode="None"
|
||||||
Items="{Binding Views}"
|
Items="{Binding Views}"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
@ -106,6 +117,9 @@
|
|||||||
<Setter Property="Margin" Value="5" />
|
<Setter Property="Margin" Value="5" />
|
||||||
<Setter Property="CornerRadius" Value="4" />
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||||
|
<Setter Property="IsVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
</ListBox.Styles>
|
</ListBox.Styles>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="models:SaveModel">
|
<DataTemplate x:DataType="models:SaveModel">
|
||||||
@ -122,6 +136,8 @@
|
|||||||
Height="42"
|
Height="42"
|
||||||
Width="42"
|
Width="42"
|
||||||
Padding="10"
|
Padding="10"
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
BorderThickness="1"
|
||||||
IsVisible="{Binding !InGameList}">
|
IsVisible="{Binding !InGameList}">
|
||||||
<ui:SymbolIcon
|
<ui:SymbolIcon
|
||||||
Symbol="Help"
|
Symbol="Help"
|
||||||
|
@ -94,7 +94,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
var save = saveDataInfo[i];
|
var save = saveDataInfo[i];
|
||||||
if (save.ProgramId.Value != 0)
|
if (save.ProgramId.Value != 0)
|
||||||
{
|
{
|
||||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
var saveModel = new SaveModel(save, _virtualFileSystem);
|
||||||
saves.Add(saveModel);
|
saves.Add(saveModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,10 +137,9 @@ namespace Ryujinx.Ava.UI.Views.User
|
|||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId);
|
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId);
|
||||||
|
ViewModel.Saves.Remove(saveModel);
|
||||||
|
ViewModel.Sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewModel.Saves.Remove(saveModel);
|
|
||||||
ViewModel.Views.Remove(saveModel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ViewModel.LoadApplication(_launchPath, _startFullscreen);
|
ViewModel.LoadApplication(_launchPath, _startFullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
|
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||||
{
|
{
|
||||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
Updater.BeginParse(this, false).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
|
@ -44,20 +44,22 @@
|
|||||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<ui:NavigationView Grid.Row="1"
|
<ui:NavigationView
|
||||||
IsSettingsVisible="False"
|
Grid.Row="1"
|
||||||
Name="NavPanel"
|
IsSettingsVisible="False"
|
||||||
IsBackEnabled="False"
|
Name="NavPanel"
|
||||||
PaneDisplayMode="Left"
|
IsBackEnabled="False"
|
||||||
Margin="2,10,10,0"
|
PaneDisplayMode="Left"
|
||||||
VerticalAlignment="Stretch"
|
Margin="2,10,10,0"
|
||||||
HorizontalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
OpenPaneLength="200">
|
HorizontalAlignment="Stretch"
|
||||||
|
OpenPaneLength="200">
|
||||||
<ui:NavigationView.MenuItems>
|
<ui:NavigationView.MenuItems>
|
||||||
<ui:NavigationViewItem IsSelected="True"
|
<ui:NavigationViewItem
|
||||||
Content="{locale:Locale SettingsTabGeneral}"
|
IsSelected="True"
|
||||||
Tag="UiPage"
|
Content="{locale:Locale SettingsTabGeneral}"
|
||||||
Icon="New" />
|
Tag="UiPage"
|
||||||
|
Icon="New" />
|
||||||
<ui:NavigationViewItem
|
<ui:NavigationViewItem
|
||||||
Content="{locale:Locale SettingsTabInput}"
|
Content="{locale:Locale SettingsTabInput}"
|
||||||
Tag="InputPage"
|
Tag="InputPage"
|
||||||
@ -74,8 +76,9 @@
|
|||||||
Content="{locale:Locale SettingsTabCpu}"
|
Content="{locale:Locale SettingsTabCpu}"
|
||||||
Tag="CpuPage">
|
Tag="CpuPage">
|
||||||
<ui:NavigationViewItem.Icon>
|
<ui:NavigationViewItem.Icon>
|
||||||
<ui:FontIcon FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
|
<ui:FontIcon
|
||||||
Glyph="{helpers:GlyphValueConverter Chip}" />
|
FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
|
||||||
|
Glyph="{helpers:GlyphValueConverter Chip}" />
|
||||||
</ui:NavigationViewItem.Icon>
|
</ui:NavigationViewItem.Icon>
|
||||||
</ui:NavigationViewItem>
|
</ui:NavigationViewItem>
|
||||||
<ui:NavigationViewItem
|
<ui:NavigationViewItem
|
||||||
@ -95,6 +98,11 @@
|
|||||||
Tag="LoggingPage"
|
Tag="LoggingPage"
|
||||||
Icon="Document" />
|
Icon="Document" />
|
||||||
</ui:NavigationView.MenuItems>
|
</ui:NavigationView.MenuItems>
|
||||||
|
<ui:NavigationView.Styles>
|
||||||
|
<Style Selector="Grid#PlaceholderGrid">
|
||||||
|
<Setter Property="Height" Value="40" />
|
||||||
|
</Style>
|
||||||
|
</ui:NavigationView.Styles>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
<ReversibleStackPanel
|
<ReversibleStackPanel
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
@ -103,17 +111,17 @@
|
|||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
ReverseOrder="{Binding IsMacOS}">
|
ReverseOrder="{Binding IsMacOS}">
|
||||||
<Button
|
<Button
|
||||||
HotKey="Enter"
|
HotKey="Enter"
|
||||||
Classes="accent"
|
Classes="accent"
|
||||||
Content="{locale:Locale SettingsButtonOk}"
|
Content="{locale:Locale SettingsButtonOk}"
|
||||||
Command="{ReflectionBinding OkButton}" />
|
Command="{ReflectionBinding OkButton}" />
|
||||||
<Button
|
<Button
|
||||||
HotKey="Escape"
|
HotKey="Escape"
|
||||||
Content="{locale:Locale SettingsButtonCancel}"
|
Content="{locale:Locale SettingsButtonCancel}"
|
||||||
Command="{ReflectionBinding CancelButton}" />
|
Command="{ReflectionBinding CancelButton}" />
|
||||||
<Button
|
<Button
|
||||||
Content="{locale:Locale SettingsButtonApply}"
|
Content="{locale:Locale SettingsButtonApply}"
|
||||||
Command="{ReflectionBinding ApplyButton}" />
|
Command="{ReflectionBinding ApplyButton}" />
|
||||||
</ReversibleStackPanel>
|
</ReversibleStackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,115 +1,135 @@
|
|||||||
<window:StyleableWindow
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
Width="600"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
Height="400"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
MinWidth="600"
|
Width="500"
|
||||||
MinHeight="400"
|
Height="300"
|
||||||
MaxWidth="600"
|
|
||||||
MaxHeight="400"
|
|
||||||
SizeToContent="Height"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:TitleUpdateViewModel"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Grid Margin="15">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
|
||||||
Name="Heading"
|
|
||||||
Grid.Row="1"
|
|
||||||
MaxWidth="500"
|
|
||||||
Margin="20,15,20,20"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
LineHeight="18"
|
|
||||||
TextAlignment="Center"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="2"
|
Grid.Row="0"
|
||||||
Margin="5"
|
Margin="0 0 0 24"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
BorderBrush="Gray"
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
BorderThickness="1">
|
BorderThickness="1"
|
||||||
<ScrollViewer
|
CornerRadius="5"
|
||||||
VerticalAlignment="Stretch"
|
Padding="2.5">
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<ListBox
|
||||||
VerticalScrollBarVisibility="Auto">
|
VirtualizationMode="None"
|
||||||
<ItemsControl
|
Background="Transparent"
|
||||||
Margin="10"
|
SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Stretch"
|
Items="{Binding Views}">
|
||||||
VerticalAlignment="Stretch"
|
<ListBox.DataTemplates>
|
||||||
Items="{Binding _titleUpdates}">
|
<DataTemplate
|
||||||
<ItemsControl.ItemTemplate>
|
DataType="models:TitleUpdateModel">
|
||||||
<DataTemplate>
|
<Panel Margin="10">
|
||||||
<RadioButton
|
<TextBlock
|
||||||
Padding="8,0"
|
HorizontalAlignment="Left"
|
||||||
VerticalContentAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
GroupName="Update"
|
TextWrapping="Wrap"
|
||||||
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
Text="{Binding Label}" />
|
||||||
<Label
|
<StackPanel
|
||||||
Margin="0"
|
Spacing="10"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Content="{Binding Label}"
|
HorizontalAlignment="Right"
|
||||||
FontSize="12" />
|
Padding="10"
|
||||||
</RadioButton>
|
MinWidth="0"
|
||||||
</DataTemplate>
|
MinHeight="0"
|
||||||
</ItemsControl.ItemTemplate>
|
Click="OpenLocation">
|
||||||
</ItemsControl>
|
<ui:SymbolIcon
|
||||||
</ScrollViewer>
|
Symbol="OpenFolder"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Padding="10"
|
||||||
|
MinWidth="0"
|
||||||
|
MinHeight="0"
|
||||||
|
Click="RemoveUpdate">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Cancel"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate
|
||||||
|
DataType="viewModels:BaseModel">
|
||||||
|
<Panel
|
||||||
|
Height="33"
|
||||||
|
Margin="10">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{locale:Locale NoUpdate}" />
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel
|
<Panel
|
||||||
Grid.Row="3"
|
Grid.Row="1"
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
<Button
|
<Button
|
||||||
Name="AddButton"
|
Name="AddButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Command="{ReflectionBinding Add}">
|
||||||
Command="{Binding Add}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
Name="RemoveButton"
|
|
||||||
MinWidth="90"
|
|
||||||
Margin="5"
|
|
||||||
Command="{Binding RemoveSelected}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
Name="RemoveAllButton"
|
Name="RemoveAllButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Click="RemoveAll">
|
||||||
Command="{Binding RemoveAll}">
|
|
||||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
<Button
|
<Button
|
||||||
Name="SaveButton"
|
Name="SaveButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Click="Save">
|
||||||
Command="{Binding Save}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Name="CancelButton"
|
Name="CancelButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Click="Close">
|
||||||
Command="{Binding Close}">
|
|
||||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableWindow>
|
</UserControl>
|
@ -1,271 +1,116 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Interactivity;
|
||||||
using LibHac.Common;
|
using Avalonia.Styling;
|
||||||
using LibHac.Fs;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Ns;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Path = System.IO.Path;
|
using System.Threading.Tasks;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class TitleUpdateWindow : StyleableWindow
|
public partial class TitleUpdateWindow : UserControl
|
||||||
{
|
{
|
||||||
private readonly string _titleUpdateJsonPath;
|
public TitleUpdateViewModel ViewModel;
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
|
|
||||||
|
|
||||||
private ulong _titleId { get; }
|
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
public TitleUpdateWindow()
|
public TitleUpdateWindow()
|
||||||
{
|
{
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId, titleName);
|
||||||
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
|
|
||||||
|
|
||||||
_titleId = titleId;
|
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
|
||||||
{
|
|
||||||
Selected = "",
|
|
||||||
Paths = new List<string>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = this;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
|
|
||||||
LoadUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintHeading()
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
Heading.Text = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], _titleUpdates.Count - 1, _titleName, _titleId.ToString("X16"));
|
ContentDialog contentDialog = new()
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadUpdates()
|
|
||||||
{
|
|
||||||
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
|
||||||
{
|
{
|
||||||
AddUpdate(path);
|
PrimaryButtonText = "",
|
||||||
}
|
SecondaryButtonText = "",
|
||||||
|
CloseButtonText = "",
|
||||||
if (_titleUpdateWindowData.Selected == "")
|
Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
|
||||||
{
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], titleName, titleId.ToString("X16"))
|
||||||
_titleUpdates[0].IsEnabled = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
|
||||||
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in enabled)
|
|
||||||
{
|
|
||||||
update.IsEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected != null)
|
|
||||||
{
|
|
||||||
selected.IsEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
|
||||||
{
|
|
||||||
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
|
|
||||||
{
|
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.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));
|
|
||||||
|
|
||||||
foreach (var update in _titleUpdates)
|
|
||||||
{
|
|
||||||
update.IsEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_titleUpdates.Last().IsEnabled = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
|
||||||
{
|
|
||||||
if (removeSelectedOnly)
|
|
||||||
{
|
|
||||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSelected()
|
|
||||||
{
|
|
||||||
RemoveUpdates(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAll()
|
|
||||||
{
|
|
||||||
RemoveUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new()
|
|
||||||
{
|
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
|
||||||
AllowMultiple = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
{
|
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||||
Name = "NSP",
|
|
||||||
Extensions = { "nsp" }
|
|
||||||
});
|
|
||||||
|
|
||||||
string[] files = await dialog.ShowAsync(this);
|
contentDialog.Styles.Add(bottomBorder);
|
||||||
|
|
||||||
if (files != null)
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
{
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
AddUpdate(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortUpdates()
|
private void Close(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var list = _titleUpdates.ToList();
|
((ContentDialog)Parent).Hide();
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
_titleUpdates.Clear();
|
|
||||||
_titleUpdates.AddRange(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
ViewModel._titleUpdateWindowData.Paths.Clear();
|
||||||
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
ViewModel._titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in _titleUpdates)
|
foreach (TitleUpdateModel update in ViewModel.TitleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
ViewModel._titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
|
|
||||||
if (update.IsEnabled)
|
if (update == ViewModel.SelectedUpdate)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Selected = update.Path;
|
ViewModel._titleUpdateWindowData.Selected = update.Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
using (FileStream titleUpdateJsonStream = File.Create(ViewModel._titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
{
|
{
|
||||||
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(ViewModel._titleUpdateWindowData, true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner is MainWindow window)
|
if (VisualRoot is MainWindow window)
|
||||||
{
|
{
|
||||||
window.ViewModel.LoadApplications();
|
window.ViewModel.LoadApplications();
|
||||||
}
|
}
|
||||||
|
|
||||||
Close();
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is TitleUpdateModel model)
|
||||||
|
{
|
||||||
|
OpenHelper.LocateFile(model.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveUpdate(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAll(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.TitleUpdates.Clear();
|
||||||
|
|
||||||
|
ViewModel.SortUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
@ -6,7 +7,7 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
public static UInt128 FromHex(string hex)
|
public static UInt128 FromHex(string hex)
|
||||||
{
|
{
|
||||||
return new UInt128((ulong)Convert.ToInt64(hex.Substring(0, 16), 16), (ulong)Convert.ToInt64(hex.Substring(16), 16));
|
return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UInt128 CreateRandom()
|
public static UInt128 CreateRandom()
|
||||||
|
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Collections;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
class AddressSpace : IDisposable
|
||||||
|
{
|
||||||
|
private const ulong PageSize = 0x1000;
|
||||||
|
|
||||||
|
private const int DefaultBlockAlignment = 1 << 20;
|
||||||
|
|
||||||
|
private enum MappingType : byte
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Private,
|
||||||
|
Shared
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>
|
||||||
|
{
|
||||||
|
public ulong Address { get; private set; }
|
||||||
|
public ulong Size { get; private set; }
|
||||||
|
public ulong EndAddress => Address + Size;
|
||||||
|
public MappingType Type { get; private set; }
|
||||||
|
|
||||||
|
public Mapping(ulong address, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mapping Split(ulong splitAddress)
|
||||||
|
{
|
||||||
|
ulong leftSize = splitAddress - Address;
|
||||||
|
ulong rightSize = EndAddress - splitAddress;
|
||||||
|
|
||||||
|
Mapping left = new Mapping(Address, leftSize, Type);
|
||||||
|
|
||||||
|
Address = splitAddress;
|
||||||
|
Size = rightSize;
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(MappingType newType)
|
||||||
|
{
|
||||||
|
Type = newType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Extend(ulong sizeDelta)
|
||||||
|
{
|
||||||
|
Size += sizeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Mapping other)
|
||||||
|
{
|
||||||
|
if (Address < other.Address)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (Address <= other.EndAddress - 1UL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>
|
||||||
|
{
|
||||||
|
public ulong Address { get; private set; }
|
||||||
|
public ulong Size { get; private set; }
|
||||||
|
public ulong EndAddress => Address + Size;
|
||||||
|
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
|
||||||
|
|
||||||
|
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
PrivateAllocation = privateAllocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMapping Split(ulong splitAddress)
|
||||||
|
{
|
||||||
|
ulong leftSize = splitAddress - Address;
|
||||||
|
ulong rightSize = EndAddress - splitAddress;
|
||||||
|
|
||||||
|
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
|
||||||
|
|
||||||
|
PrivateMapping left = new PrivateMapping(Address, leftSize, leftAllocation);
|
||||||
|
|
||||||
|
Address = splitAddress;
|
||||||
|
Size = rightSize;
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation)
|
||||||
|
{
|
||||||
|
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||||
|
mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||||
|
PrivateAllocation = newAllocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock)
|
||||||
|
{
|
||||||
|
if (PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||||
|
mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||||
|
PrivateAllocation.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateAllocation = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Extend(ulong sizeDelta)
|
||||||
|
{
|
||||||
|
Size += sizeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(PrivateMapping other)
|
||||||
|
{
|
||||||
|
if (Address < other.Address)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (Address <= other.EndAddress - 1UL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MemoryBlock _backingMemory;
|
||||||
|
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
|
||||||
|
private readonly IntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||||
|
private readonly IntrusiveRedBlackTree<PrivateMapping> _privateTree;
|
||||||
|
|
||||||
|
private readonly object _treeLock;
|
||||||
|
|
||||||
|
private readonly bool _supports4KBPages;
|
||||||
|
|
||||||
|
public MemoryBlock Base { get; }
|
||||||
|
public MemoryBlock Mirror { get; }
|
||||||
|
|
||||||
|
public AddressSpace(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages)
|
||||||
|
{
|
||||||
|
if (!supports4KBPages)
|
||||||
|
{
|
||||||
|
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap);
|
||||||
|
_mappingTree = new IntrusiveRedBlackTree<Mapping>();
|
||||||
|
_privateTree = new IntrusiveRedBlackTree<PrivateMapping>();
|
||||||
|
_treeLock = new object();
|
||||||
|
|
||||||
|
_mappingTree.Add(new Mapping(0UL, asSize, MappingType.None));
|
||||||
|
_privateTree.Add(new PrivateMapping(0UL, asSize, default));
|
||||||
|
}
|
||||||
|
|
||||||
|
_backingMemory = backingMemory;
|
||||||
|
_supports4KBPages = supports4KBPages;
|
||||||
|
|
||||||
|
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||||
|
|
||||||
|
Base = new MemoryBlock(asSize, asFlags);
|
||||||
|
Mirror = new MemoryBlock(asSize, asFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
|
{
|
||||||
|
if (_supports4KBPages)
|
||||||
|
{
|
||||||
|
Base.MapView(_backingMemory, pa, va, size);
|
||||||
|
Mirror.MapView(_backingMemory, pa, va, size);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_treeLock)
|
||||||
|
{
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
bool isAligned = ((va | pa | size) & (alignment - 1)) == 0;
|
||||||
|
|
||||||
|
if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned)
|
||||||
|
{
|
||||||
|
Update(va, pa, size, MappingType.Private);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The update method assumes that shared mappings are already aligned.
|
||||||
|
|
||||||
|
if (!flags.HasFlag(MemoryMapFlags.Private))
|
||||||
|
{
|
||||||
|
if ((va & (alignment - 1)) != (pa & (alignment - 1)))
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
va = BitUtils.AlignDown(va, alignment);
|
||||||
|
pa = BitUtils.AlignDown(pa, alignment);
|
||||||
|
size = BitUtils.AlignUp(endAddress, alignment) - va;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(va, pa, size, MappingType.Shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (_supports4KBPages)
|
||||||
|
{
|
||||||
|
Base.UnmapView(_backingMemory, va, size);
|
||||||
|
Mirror.UnmapView(_backingMemory, va, size);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_treeLock)
|
||||||
|
{
|
||||||
|
Update(va, 0UL, size, MappingType.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update(ulong va, ulong pa, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None));
|
||||||
|
|
||||||
|
Update(map, va, pa, size, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (map.Address < va)
|
||||||
|
{
|
||||||
|
_mappingTree.Add(map.Split(va));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddress)
|
||||||
|
{
|
||||||
|
Mapping newMap = map.Split(endAddress);
|
||||||
|
_mappingTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MappingType.None:
|
||||||
|
if (map.Type == MappingType.Shared)
|
||||||
|
{
|
||||||
|
ulong startOffset = map.Address - va;
|
||||||
|
ulong mapVa = va + startOffset;
|
||||||
|
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||||
|
ulong mapEndAddress = mapVa + mapSize;
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
mapVa = BitUtils.AlignDown(mapVa, alignment);
|
||||||
|
mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment);
|
||||||
|
|
||||||
|
mapSize = mapEndAddress - mapVa;
|
||||||
|
|
||||||
|
Base.UnmapView(_backingMemory, mapVa, mapSize);
|
||||||
|
Mirror.UnmapView(_backingMemory, mapVa, mapSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnmapPrivate(va, size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MappingType.Private:
|
||||||
|
if (map.Type == MappingType.Shared)
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MapPrivate(va, size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MappingType.Shared:
|
||||||
|
if (map.Type != MappingType.None)
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong startOffset = map.Address - va;
|
||||||
|
ulong mapPa = pa + startOffset;
|
||||||
|
ulong mapVa = va + startOffset;
|
||||||
|
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||||
|
|
||||||
|
Base.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||||
|
Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.UpdateState(type);
|
||||||
|
map = TryCoalesce(map);
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddress)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mapping TryCoalesce(Mapping map)
|
||||||
|
{
|
||||||
|
Mapping previousMap = map.Predecessor;
|
||||||
|
Mapping nextMap = map.Successor;
|
||||||
|
|
||||||
|
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||||
|
{
|
||||||
|
previousMap.Extend(map.Size);
|
||||||
|
_mappingTree.Remove(map);
|
||||||
|
map = previousMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||||
|
{
|
||||||
|
map.Extend(nextMap.Size);
|
||||||
|
_mappingTree.Remove(nextMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanCoalesce(Mapping left, Mapping right)
|
||||||
|
{
|
||||||
|
return left.Type == right.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapPrivate(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
|
||||||
|
ulong vaAligned = BitUtils.AlignDown(va, alignment);
|
||||||
|
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
|
||||||
|
|
||||||
|
ulong sizeAligned = endAddressAligned - vaAligned;
|
||||||
|
|
||||||
|
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (!map.PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
if (map.Address < vaAligned)
|
||||||
|
{
|
||||||
|
_privateTree.Add(map.Split(vaAligned));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddressAligned)
|
||||||
|
{
|
||||||
|
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||||
|
_privateTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddressAligned)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnmapPrivate(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
// Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use.
|
||||||
|
ulong vaAligned = BitUtils.AlignUp(va, alignment);
|
||||||
|
ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment);
|
||||||
|
|
||||||
|
if (endAddressAligned <= vaAligned)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong alignedSize = endAddressAligned - vaAligned;
|
||||||
|
|
||||||
|
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (map.PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
if (map.Address < vaAligned)
|
||||||
|
{
|
||||||
|
_privateTree.Add(map.Split(vaAligned));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddressAligned)
|
||||||
|
{
|
||||||
|
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||||
|
_privateTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.Unmap(Base, Mirror);
|
||||||
|
map = TryCoalesce(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddressAligned)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrivateMapping TryCoalesce(PrivateMapping map)
|
||||||
|
{
|
||||||
|
PrivateMapping previousMap = map.Predecessor;
|
||||||
|
PrivateMapping nextMap = map.Successor;
|
||||||
|
|
||||||
|
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||||
|
{
|
||||||
|
previousMap.Extend(map.Size);
|
||||||
|
_privateTree.Remove(map);
|
||||||
|
map = previousMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||||
|
{
|
||||||
|
map.Extend(nextMap.Size);
|
||||||
|
_privateTree.Remove(nextMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
|
||||||
|
{
|
||||||
|
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_privateMemoryAllocator?.Dispose();
|
||||||
|
Base.Dispose();
|
||||||
|
Mirror.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,5 +7,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
||||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
||||||
|
|
||||||
|
public ulong GetPageSize() => MemoryBlock.GetPageSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
private readonly MemoryBlock _backingMemory;
|
private readonly MemoryBlock _backingMemory;
|
||||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Supports4KBPages => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address space width in bits.
|
/// Address space width in bits.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -76,7 +79,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
AssertValidAddressAndSize(va, size);
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
@ -91,9 +94,16 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
pa += PageSize;
|
pa += PageSize;
|
||||||
remainingSize -= PageSize;
|
remainingSize -= PageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tracking.Map(oVa, size);
|
Tracking.Map(oVa, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Unmap(ulong va, ulong size)
|
public void Unmap(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -378,6 +388,32 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<HostMemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||||
|
if (guestRegions == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regions = new HostMemoryRange[guestRegions.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
var guestRegion = guestRegions[i];
|
||||||
|
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||||
|
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -386,6 +422,11 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return Enumerable.Empty<MemoryRange>();
|
return Enumerable.Empty<MemoryRange>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return GetPhysicalRegionsImpl(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||||
|
{
|
||||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -5,6 +5,7 @@ using Ryujinx.Memory.Range;
|
|||||||
using Ryujinx.Memory.Tracking;
|
using Ryujinx.Memory.Tracking;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -37,20 +38,21 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||||
private readonly bool _unsafeMode;
|
private readonly bool _unsafeMode;
|
||||||
|
|
||||||
private readonly MemoryBlock _addressSpace;
|
private readonly AddressSpace _addressSpace;
|
||||||
private readonly MemoryBlock _addressSpaceMirror;
|
|
||||||
private readonly ulong _addressSpaceSize;
|
private readonly ulong _addressSpaceSize;
|
||||||
|
|
||||||
private readonly MemoryBlock _backingMemory;
|
|
||||||
private readonly PageTable<ulong> _pageTable;
|
private readonly PageTable<ulong> _pageTable;
|
||||||
|
|
||||||
private readonly MemoryEhMeilleure _memoryEh;
|
private readonly MemoryEhMeilleure _memoryEh;
|
||||||
|
|
||||||
private readonly ulong[] _pageBitmap;
|
private readonly ulong[] _pageBitmap;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
|
||||||
|
|
||||||
public int AddressSpaceBits { get; }
|
public int AddressSpaceBits { get; }
|
||||||
|
|
||||||
public IntPtr PageTablePointer => _addressSpace.Pointer;
|
public IntPtr PageTablePointer => _addressSpace.Base.Pointer;
|
||||||
|
|
||||||
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
||||||
|
|
||||||
@ -67,7 +69,6 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||||
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||||
{
|
{
|
||||||
_backingMemory = backingMemory;
|
|
||||||
_pageTable = new PageTable<ulong>();
|
_pageTable = new PageTable<ulong>();
|
||||||
_invalidAccessHandler = invalidAccessHandler;
|
_invalidAccessHandler = invalidAccessHandler;
|
||||||
_unsafeMode = unsafeMode;
|
_unsafeMode = unsafeMode;
|
||||||
@ -86,13 +87,10 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
|
|
||||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||||
|
|
||||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
_addressSpace = new AddressSpace(backingMemory, asSize, Supports4KBPages);
|
||||||
|
|
||||||
_addressSpace = new MemoryBlock(asSize, asFlags);
|
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
|
||||||
_addressSpaceMirror = new MemoryBlock(asSize, asFlags);
|
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
|
||||||
|
|
||||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
|
||||||
_memoryEh = new MemoryEhMeilleure(_addressSpace, _addressSpaceMirror, Tracking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -145,18 +143,23 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
AssertValidAddressAndSize(va, size);
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
_addressSpace.Map(va, pa, size, flags);
|
||||||
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
|
|
||||||
AddMapping(va, size);
|
AddMapping(va, size);
|
||||||
PtMap(va, pa, size);
|
PtMap(va, pa, size);
|
||||||
|
|
||||||
Tracking.Map(va, size);
|
Tracking.Map(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Unmap(ulong va, ulong size)
|
public void Unmap(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -167,8 +170,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
|
|
||||||
RemoveMapping(va, size);
|
RemoveMapping(va, size);
|
||||||
PtUnmap(va, size);
|
PtUnmap(va, size);
|
||||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
_addressSpace.Unmap(va, size);
|
||||||
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PtMap(ulong va, ulong pa, ulong size)
|
private void PtMap(ulong va, ulong pa, ulong size)
|
||||||
@ -201,7 +203,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
|
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
|
||||||
|
|
||||||
return _addressSpaceMirror.Read<T>(va);
|
return _addressSpace.Mirror.Read<T>(va);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -241,7 +243,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)data.Length);
|
AssertMapped(va, (ulong)data.Length);
|
||||||
|
|
||||||
_addressSpaceMirror.Read(va, data);
|
_addressSpace.Mirror.Read(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -260,7 +262,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, value);
|
_addressSpace.Mirror.Write(va, value);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -278,7 +280,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)data.Length, write: true);
|
SignalMemoryTracking(va, (ulong)data.Length, write: true);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, data);
|
_addressSpace.Mirror.Write(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -296,7 +298,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)data.Length);
|
AssertMapped(va, (ulong)data.Length);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, data);
|
_addressSpace.Mirror.Write(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -314,7 +316,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||||
|
|
||||||
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
|
Span<byte> target = _addressSpace.Mirror.GetSpan(va, data.Length);
|
||||||
bool changed = !data.SequenceEqual(target);
|
bool changed = !data.SequenceEqual(target);
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
@ -347,7 +349,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
AssertMapped(va, (ulong)size);
|
AssertMapped(va, (ulong)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _addressSpaceMirror.GetSpan(va, size);
|
return _addressSpace.Mirror.GetSpan(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -362,7 +364,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
AssertMapped(va, (ulong)size);
|
AssertMapped(va, (ulong)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _addressSpaceMirror.GetWritableRegion(va, size);
|
return _addressSpace.Mirror.GetWritableRegion(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -370,7 +372,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||||
|
|
||||||
return ref _addressSpaceMirror.GetRef<T>(va);
|
return ref _addressSpace.Mirror.GetRef<T>(va);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -454,6 +456,14 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpace.Mirror.GetPointer(va, size), size), 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -692,7 +702,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
_ => MemoryPermission.None
|
_ => MemoryPermission.None
|
||||||
};
|
};
|
||||||
|
|
||||||
_addressSpace.Reprotect(va, size, protection, false);
|
_addressSpace.Base.Reprotect(va, size, protection, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -799,7 +809,6 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
protected override void Destroy()
|
protected override void Destroy()
|
||||||
{
|
{
|
||||||
_addressSpace.Dispose();
|
_addressSpace.Dispose();
|
||||||
_addressSpaceMirror.Dispose();
|
|
||||||
_memoryEh.Dispose();
|
_memoryEh.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
struct PrivateMemoryAllocation : IDisposable
|
||||||
|
{
|
||||||
|
private readonly PrivateMemoryAllocator _owner;
|
||||||
|
private readonly PrivateMemoryAllocator.Block _block;
|
||||||
|
|
||||||
|
public bool IsValid => _owner != null;
|
||||||
|
public MemoryBlock Memory => _block?.Memory;
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public PrivateMemoryAllocation(
|
||||||
|
PrivateMemoryAllocator owner,
|
||||||
|
PrivateMemoryAllocator.Block block,
|
||||||
|
ulong offset,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_block = block;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (PrivateMemoryAllocation, PrivateMemoryAllocation) Split(ulong splitOffset)
|
||||||
|
{
|
||||||
|
PrivateMemoryAllocation left = new PrivateMemoryAllocation(_owner, _block, Offset, splitOffset);
|
||||||
|
PrivateMemoryAllocation right = new PrivateMemoryAllocation(_owner, _block, Offset + splitOffset, Size - splitOffset);
|
||||||
|
|
||||||
|
return (left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_owner.Free(_block, Offset, Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
class PrivateMemoryAllocator : PrivateMemoryAllocatorImpl<PrivateMemoryAllocator.Block>
|
||||||
|
{
|
||||||
|
public const ulong InvalidOffset = ulong.MaxValue;
|
||||||
|
|
||||||
|
public class Block : IComparable<Block>
|
||||||
|
{
|
||||||
|
public MemoryBlock Memory { get; private set; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
private struct Range : IComparable<Range>
|
||||||
|
{
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public Range(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Range other)
|
||||||
|
{
|
||||||
|
return Offset.CompareTo(other.Offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Range> _freeRanges;
|
||||||
|
|
||||||
|
public Block(MemoryBlock memory, ulong size)
|
||||||
|
{
|
||||||
|
Memory = memory;
|
||||||
|
Size = size;
|
||||||
|
_freeRanges = new List<Range>
|
||||||
|
{
|
||||||
|
new Range(0, size)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Allocate(ulong size, ulong alignment)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _freeRanges.Count; i++)
|
||||||
|
{
|
||||||
|
var range = _freeRanges[i];
|
||||||
|
|
||||||
|
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
||||||
|
ulong sizeDelta = alignedOffset - range.Offset;
|
||||||
|
ulong usableSize = range.Size - sizeDelta;
|
||||||
|
|
||||||
|
if (sizeDelta < range.Size && usableSize >= size)
|
||||||
|
{
|
||||||
|
_freeRanges.RemoveAt(i);
|
||||||
|
|
||||||
|
if (sizeDelta != 0)
|
||||||
|
{
|
||||||
|
InsertFreeRange(range.Offset, sizeDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endOffset = range.Offset + range.Size;
|
||||||
|
ulong remainingSize = endOffset - (alignedOffset + size);
|
||||||
|
if (remainingSize != 0)
|
||||||
|
{
|
||||||
|
InsertFreeRange(endOffset - remainingSize, remainingSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return alignedOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
InsertFreeRangeComingled(offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertFreeRange(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
var range = new Range(offset, size);
|
||||||
|
int index = _freeRanges.BinarySearch(range);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
_freeRanges.Insert(index, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
ulong endOffset = offset + size;
|
||||||
|
var range = new Range(offset, size);
|
||||||
|
int index = _freeRanges.BinarySearch(range);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset)
|
||||||
|
{
|
||||||
|
endOffset = _freeRanges[index].Offset + _freeRanges[index].Size;
|
||||||
|
_freeRanges.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset)
|
||||||
|
{
|
||||||
|
offset = _freeRanges[index - 1].Offset;
|
||||||
|
_freeRanges.RemoveAt(--index);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = new Range(offset, endOffset - offset);
|
||||||
|
|
||||||
|
_freeRanges.Insert(index, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTotallyFree()
|
||||||
|
{
|
||||||
|
if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size)
|
||||||
|
{
|
||||||
|
Debug.Assert(_freeRanges[0].Offset == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Block other)
|
||||||
|
{
|
||||||
|
return Size.CompareTo(other.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destroy()
|
||||||
|
{
|
||||||
|
Memory.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMemoryAllocation Allocate(ulong size, ulong alignment)
|
||||||
|
{
|
||||||
|
var allocation = Allocate(size, alignment, CreateBlock);
|
||||||
|
|
||||||
|
return new PrivateMemoryAllocation(this, allocation.Block, allocation.Offset, allocation.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block CreateBlock(MemoryBlock memory, ulong size)
|
||||||
|
{
|
||||||
|
return new Block(memory, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivateMemoryAllocatorImpl<T> : IDisposable where T : PrivateMemoryAllocator.Block
|
||||||
|
{
|
||||||
|
private const ulong InvalidOffset = ulong.MaxValue;
|
||||||
|
|
||||||
|
public struct Allocation
|
||||||
|
{
|
||||||
|
public T Block { get; }
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public Allocation(T block, ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
Block = block;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<T> _blocks;
|
||||||
|
|
||||||
|
private readonly int _blockAlignment;
|
||||||
|
private readonly MemoryAllocationFlags _allocationFlags;
|
||||||
|
|
||||||
|
public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags)
|
||||||
|
{
|
||||||
|
_blocks = new List<T>();
|
||||||
|
_blockAlignment = blockAlignment;
|
||||||
|
_allocationFlags = allocationFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Allocation Allocate(ulong size, ulong alignment, Func<MemoryBlock, ulong, T> createBlock)
|
||||||
|
{
|
||||||
|
// Ensure we have a sane alignment value.
|
||||||
|
if ((ulong)(int)alignment != alignment || (int)alignment <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
var block = _blocks[i];
|
||||||
|
|
||||||
|
if (block.Size >= size)
|
||||||
|
{
|
||||||
|
ulong offset = block.Allocate(size, alignment);
|
||||||
|
if (offset != InvalidOffset)
|
||||||
|
{
|
||||||
|
return new Allocation(block, offset, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
|
||||||
|
|
||||||
|
var memory = new MemoryBlock(blockAlignedSize, _allocationFlags);
|
||||||
|
var newBlock = createBlock(memory, blockAlignedSize);
|
||||||
|
|
||||||
|
InsertBlock(newBlock);
|
||||||
|
|
||||||
|
ulong newBlockOffset = newBlock.Allocate(size, alignment);
|
||||||
|
Debug.Assert(newBlockOffset != InvalidOffset);
|
||||||
|
|
||||||
|
return new Allocation(newBlock, newBlockOffset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(PrivateMemoryAllocator.Block block, ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
block.Free(offset, size);
|
||||||
|
|
||||||
|
if (block.IsTotallyFree())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
if (_blocks[i] == block)
|
||||||
|
{
|
||||||
|
_blocks.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertBlock(T block)
|
||||||
|
{
|
||||||
|
int index = _blocks.BinarySearch(block);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
_blocks.Insert(index, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
_blocks[i].Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
_blocks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
public readonly bool HasFrontFacingBug;
|
public readonly bool HasFrontFacingBug;
|
||||||
public readonly bool HasVectorIndexingBug;
|
public readonly bool HasVectorIndexingBug;
|
||||||
|
public readonly bool NeedsFragmentOutputSpecialization;
|
||||||
|
public readonly bool ReduceShaderPrecision;
|
||||||
|
|
||||||
public readonly bool SupportsAstcCompression;
|
public readonly bool SupportsAstcCompression;
|
||||||
public readonly bool SupportsBc123Compression;
|
public readonly bool SupportsBc123Compression;
|
||||||
@ -49,6 +51,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
string vendorName,
|
string vendorName,
|
||||||
bool hasFrontFacingBug,
|
bool hasFrontFacingBug,
|
||||||
bool hasVectorIndexingBug,
|
bool hasVectorIndexingBug,
|
||||||
|
bool needsFragmentOutputSpecialization,
|
||||||
|
bool reduceShaderPrecision,
|
||||||
bool supportsAstcCompression,
|
bool supportsAstcCompression,
|
||||||
bool supportsBc123Compression,
|
bool supportsBc123Compression,
|
||||||
bool supportsBc45Compression,
|
bool supportsBc45Compression,
|
||||||
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
VendorName = vendorName;
|
VendorName = vendorName;
|
||||||
HasFrontFacingBug = hasFrontFacingBug;
|
HasFrontFacingBug = hasFrontFacingBug;
|
||||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||||
|
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
|
||||||
|
ReduceShaderPrecision = reduceShaderPrecision;
|
||||||
SupportsAstcCompression = supportsAstcCompression;
|
SupportsAstcCompression = supportsAstcCompression;
|
||||||
SupportsBc123Compression = supportsBc123Compression;
|
SupportsBc123Compression = supportsBc123Compression;
|
||||||
SupportsBc45Compression = supportsBc45Compression;
|
SupportsBc45Compression = supportsBc45Compression;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Shader;
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SpecializationStateUpdater
|
internal class SpecializationStateUpdater
|
||||||
{
|
{
|
||||||
|
private readonly GpuContext _context;
|
||||||
private GpuChannelGraphicsState _graphics;
|
private GpuChannelGraphicsState _graphics;
|
||||||
private GpuChannelPoolState _pool;
|
private GpuChannelPoolState _pool;
|
||||||
|
|
||||||
@ -18,6 +20,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
private bool _changed;
|
private bool _changed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the specialization state updater class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
public SpecializationStateUpdater(GpuContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that the specialization state has changed.
|
/// Signal that the specialization state has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -232,6 +243,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the type of the outputs produced by the fragment shader based on the current render target state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rtControl">The render target control register</param>
|
||||||
|
/// <param name="state">The color attachment state</param>
|
||||||
|
public void SetFragmentOutputTypes(RtControl rtControl, ref Array8<RtColorState> state)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
int count = rtControl.UnpackCount();
|
||||||
|
|
||||||
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||||
|
{
|
||||||
|
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||||
|
|
||||||
|
var colorState = state[rtIndex];
|
||||||
|
|
||||||
|
if (index < count && StateUpdater.IsRtEnabled(colorState))
|
||||||
|
{
|
||||||
|
Format format = colorState.Format.Convert().Format;
|
||||||
|
|
||||||
|
AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
|
||||||
|
|
||||||
|
if (type != _graphics.FragmentOutputTypes[index])
|
||||||
|
{
|
||||||
|
_graphics.FragmentOutputTypes[index] = type;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization)
|
||||||
|
{
|
||||||
|
Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given register group is dirty without clearing it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupIndex">Index of the group to check</param>
|
||||||
|
/// <returns>True if dirty, false otherwise</returns>
|
||||||
|
public bool IsDirty(int groupIndex)
|
||||||
|
{
|
||||||
|
return (_dirtyMask & (1UL << groupIndex)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
public const int ScissorStateIndex = 16;
|
public const int ScissorStateIndex = 16;
|
||||||
public const int VertexBufferStateIndex = 0;
|
public const int VertexBufferStateIndex = 0;
|
||||||
public const int PrimitiveRestartStateIndex = 12;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
|
public const int RenderTargetStateIndex = 27;
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
@ -264,6 +265,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
_prevTfEnable = false;
|
_prevTfEnable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_updateTracker.IsDirty(RenderTargetStateIndex))
|
||||||
|
{
|
||||||
|
UpdateRenderTargetSpecialization();
|
||||||
|
}
|
||||||
|
|
||||||
_updateTracker.Update(ulong.MaxValue);
|
_updateTracker.Update(ulong.MaxValue);
|
||||||
|
|
||||||
// If any state that the shader depends on changed,
|
// If any state that the shader depends on changed,
|
||||||
@ -526,12 +532,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates specialization state based on render target state.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateRenderTargetSpecialization()
|
||||||
|
{
|
||||||
|
_currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a render target color buffer is used.
|
/// Checks if a render target color buffer is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="colorState">Color buffer information</param>
|
/// <param name="colorState">Color buffer information</param>
|
||||||
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
||||||
private static bool IsRtEnabled(RtColorState colorState)
|
internal static bool IsRtEnabled(RtColorState colorState)
|
||||||
{
|
{
|
||||||
// Colors are disabled by writing 0 to the format.
|
// Colors are disabled by writing 0 to the format.
|
||||||
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
||||||
@ -893,7 +907,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||||
|
|
||||||
format = Format.R32G32B32A32Float;
|
format = vertexAttrib.UnpackType() switch
|
||||||
|
{
|
||||||
|
VertexAttribType.Sint => Format.R32G32B32A32Sint,
|
||||||
|
VertexAttribType.Uint => Format.R32G32B32A32Uint,
|
||||||
|
_ => Format.R32G32B32A32Float
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
vertexAttribs[index] = new VertexAttribDescriptor(
|
vertexAttribs[index] = new VertexAttribDescriptor(
|
||||||
|
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||||
|
|
||||||
var spec = new SpecializationStateUpdater();
|
var spec = new SpecializationStateUpdater(context);
|
||||||
var drawState = new DrawState();
|
var drawState = new DrawState();
|
||||||
|
|
||||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using System;
|
using System;
|
||||||
@ -31,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal MemoryManager MemoryManager => _memoryManager;
|
internal MemoryManager MemoryManager => _memoryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Host hardware capabilities from the GPU context.
|
||||||
|
/// </summary>
|
||||||
|
internal ref Capabilities Capabilities => ref _context.Capabilities;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of a GPU channel.
|
/// Creates a new instance of a GPU channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -67,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
memoryManager.Physical.BufferCache.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -78,7 +84,9 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
||||||
{
|
{
|
||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
|
||||||
|
var memoryManager = Volatile.Read(ref _memoryManager);
|
||||||
|
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -4,6 +4,28 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry on the short duration texture cache.
|
||||||
|
/// </summary>
|
||||||
|
class ShortTextureCacheEntry
|
||||||
|
{
|
||||||
|
public readonly TextureDescriptor Descriptor;
|
||||||
|
public readonly int InvalidatedSequence;
|
||||||
|
public readonly Texture Texture;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new entry on the short duration texture cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">Last descriptor that referenced the texture</param>
|
||||||
|
/// <param name="texture">The texture</param>
|
||||||
|
public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture)
|
||||||
|
{
|
||||||
|
Descriptor = descriptor;
|
||||||
|
InvalidatedSequence = texture.InvalidatedSequence;
|
||||||
|
Texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A texture cache that automatically removes older textures that are not used for some time.
|
/// A texture cache that automatically removes older textures that are not used for some time.
|
||||||
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
||||||
@ -16,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
private readonly LinkedList<Texture> _textures;
|
private readonly LinkedList<Texture> _textures;
|
||||||
private readonly ConcurrentQueue<Texture> _deferredRemovals;
|
private readonly ConcurrentQueue<Texture> _deferredRemovals;
|
||||||
|
|
||||||
|
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
|
||||||
|
private HashSet<ShortTextureCacheEntry> _shortCache;
|
||||||
|
|
||||||
|
private Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the automatic deletion cache.
|
/// Creates a new instance of the automatic deletion cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -23,6 +50,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
_textures = new LinkedList<Texture>();
|
_textures = new LinkedList<Texture>();
|
||||||
_deferredRemovals = new ConcurrentQueue<Texture>();
|
_deferredRemovals = new ConcurrentQueue<Texture>();
|
||||||
|
|
||||||
|
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
|
||||||
|
_shortCache = new HashSet<ShortTextureCacheEntry>();
|
||||||
|
|
||||||
|
_shortCacheLookup = new Dictionary<TextureDescriptor, ShortTextureCacheEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -130,6 +162,85 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_deferredRemovals.Enqueue(texture);
|
_deferredRemovals.Enqueue(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to find a texture on the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The texture descriptor</param>
|
||||||
|
/// <returns>The texture if found, null otherwise</returns>
|
||||||
|
public Texture FindShortCache(in TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry))
|
||||||
|
{
|
||||||
|
if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence)
|
||||||
|
{
|
||||||
|
return entry.Texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_shortCacheLookup.Remove(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a texture from the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to remove from the short cache</param>
|
||||||
|
public void RemoveShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
bool removed = _shortCache.Remove(texture.ShortCacheEntry);
|
||||||
|
removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry);
|
||||||
|
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
|
||||||
|
texture.ShortCacheEntry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache.
|
||||||
|
/// It starts in the builder set, and it is moved into the deletion set on next process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
/// <param name="descriptor">Last used texture descriptor</param>
|
||||||
|
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
var entry = new ShortTextureCacheEntry(descriptor, texture);
|
||||||
|
|
||||||
|
_shortCacheBuilder.Add(entry);
|
||||||
|
_shortCacheLookup.Add(entry.Descriptor, entry);
|
||||||
|
|
||||||
|
texture.ShortCacheEntry = entry;
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete textures from the short duration cache.
|
||||||
|
/// Moves the builder set to be deleted on next process.
|
||||||
|
/// </summary>
|
||||||
|
public void ProcessShortCache()
|
||||||
|
{
|
||||||
|
HashSet<ShortTextureCacheEntry> toRemove = _shortCache;
|
||||||
|
|
||||||
|
foreach (var entry in toRemove)
|
||||||
|
{
|
||||||
|
entry.Texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
_shortCacheLookup.Remove(entry.Descriptor);
|
||||||
|
entry.Texture.ShortCacheEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toRemove.Clear();
|
||||||
|
_shortCache = _shortCacheBuilder;
|
||||||
|
_shortCacheBuilder = toRemove;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<Texture> GetEnumerator()
|
public IEnumerator<Texture> GetEnumerator()
|
||||||
{
|
{
|
||||||
return _textures.GetEnumerator();
|
return _textures.GetEnumerator();
|
||||||
|
@ -91,7 +91,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <returns>A reference to the descriptor</returns>
|
/// <returns>A reference to the descriptor</returns>
|
||||||
public ref readonly T2 GetDescriptorRef(int id)
|
public ref readonly T2 GetDescriptorRef(int id)
|
||||||
{
|
{
|
||||||
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
|
return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the descriptor for a given address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address of the descriptor</param>
|
||||||
|
/// <returns>A reference to the descriptor</returns>
|
||||||
|
public ref readonly T2 GetDescriptorRefAddress(ulong address)
|
||||||
|
{
|
||||||
|
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(address, DescriptorSize))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -138,6 +138,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
public LinkedListNode<Texture> CacheNode { get; set; }
|
public LinkedListNode<Texture> CacheNode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Entry for this texture in the short duration cache, if present.
|
||||||
|
/// </summary>
|
||||||
|
public ShortTextureCacheEntry ShortCacheEntry { get; set; }
|
||||||
|
|
||||||
/// Physical memory ranges where the texture data is located.
|
/// Physical memory ranges where the texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiRange Range { get; private set; }
|
public MultiRange Range { get; private set; }
|
||||||
@ -1555,6 +1559,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
||||||
}
|
}
|
||||||
_referenceCount++;
|
_referenceCount++;
|
||||||
|
|
||||||
|
if (ShortCacheEntry != null)
|
||||||
|
{
|
||||||
|
_physicalMemory.TextureCache.RemoveShortCache(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the texture has one reference left, and will delete on reference decrement.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if there is one reference remaining, false otherwise</returns>
|
||||||
|
public bool HasOneReference()
|
||||||
|
{
|
||||||
|
return _referenceCount == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1624,6 +1642,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_poolOwners.Clear();
|
_poolOwners.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShortCacheEntry != null && _context.IsGpuThread())
|
||||||
|
{
|
||||||
|
// If this is called from another thread (unmapped), the short cache will
|
||||||
|
// have to remove this texture on a future tick.
|
||||||
|
|
||||||
|
_physicalMemory.TextureCache.RemoveShortCache(this);
|
||||||
|
}
|
||||||
|
|
||||||
InvalidatedSequence++;
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,6 +894,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to find a texture on the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The texture descriptor</param>
|
||||||
|
/// <returns>The texture if found, null otherwise</returns>
|
||||||
|
public Texture FindShortCache(in TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return _cache.FindShortCache(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null.
|
/// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1178,6 +1188,33 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_cache.RemoveDeferred(texture);
|
_cache.RemoveDeferred(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
/// <param name="descriptor">Last used texture descriptor</param>
|
||||||
|
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
_cache.AddShortCache(texture, ref descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a texture from the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to remove from the short cache</param>
|
||||||
|
public void RemoveShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
_cache.RemoveShortCache(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ticks periodic elements of the texture cache.
|
||||||
|
/// </summary>
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
_cache.ProcessShortCache();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes all textures and samplers in the cache.
|
/// Disposes all textures and samplers in the cache.
|
||||||
/// It's an error to use the texture cache after disposal.
|
/// It's an error to use the texture cache after disposal.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Intrinsics;
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
|
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct TextureDescriptor : ITextureDescriptor
|
struct TextureDescriptor : ITextureDescriptor, IEquatable<TextureDescriptor>
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
public uint Word0;
|
public uint Word0;
|
||||||
@ -249,5 +250,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
|
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if two descriptors are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The descriptor to compare against</param>
|
||||||
|
/// <returns>True if they are equal, false otherwise</returns>
|
||||||
|
public bool Equals(TextureDescriptor other)
|
||||||
|
{
|
||||||
|
return Equals(ref other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a hash code for this descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The hash code for this descriptor.</returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1420,6 +1420,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="size">The size of the flushing memory access</param>
|
/// <param name="size">The size of the flushing memory access</param>
|
||||||
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
// If the page size is larger than 4KB, we will have a lot of false positives for flushing.
|
||||||
|
// Let's avoid flushing textures that are unlikely to be read from CPU to improve performance
|
||||||
|
// on those platforms.
|
||||||
|
if (!_physicalMemory.Supports4KBPages && !Storage.Info.IsLinear && !_context.IsGpuThread())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||||
// In this case it will skip registering the action, but here we are already handling it,
|
// In this case it will skip registering the action, but here we are already handling it,
|
||||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||||
@ -1431,9 +1439,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isGpuThread = _context.IsGpuThread();
|
||||||
|
|
||||||
|
if (isGpuThread)
|
||||||
|
{
|
||||||
|
// No need to wait if we're on the GPU thread, we can just clear the modified flag immediately.
|
||||||
|
handle.Modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
_context.Renderer.BackgroundContextAction(() =>
|
_context.Renderer.BackgroundContextAction(() =>
|
||||||
{
|
{
|
||||||
handle.Sync(_context);
|
if (!isGpuThread)
|
||||||
|
{
|
||||||
|
handle.Sync(_context);
|
||||||
|
}
|
||||||
|
|
||||||
Storage.SignalModifiedDirty();
|
Storage.SignalModifiedDirty();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// A tracking handle for a texture group, which represents a range of views in a storage texture.
|
/// A tracking handle for a texture group, which represents a range of views in a storage texture.
|
||||||
/// Retains a list of overlapping texture views, a modified flag, and tracking for each
|
/// Retains a list of overlapping texture views, a modified flag, and tracking for each
|
||||||
/// CPU VA range that the views cover.
|
/// CPU VA range that the views cover.
|
||||||
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
||||||
/// in sync with this one before use.
|
/// in sync with this one before use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class TextureGroupHandle : IDisposable
|
class TextureGroupHandle : IDisposable
|
||||||
@ -232,32 +232,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="context">The GPU context used to wait for sync</param>
|
/// <param name="context">The GPU context used to wait for sync</param>
|
||||||
public void Sync(GpuContext context)
|
public void Sync(GpuContext context)
|
||||||
{
|
{
|
||||||
bool needsSync = !context.IsGpuThread();
|
ulong registeredSync = _registeredSync;
|
||||||
|
long diff = (long)(context.SyncNumber - registeredSync);
|
||||||
|
|
||||||
if (needsSync)
|
if (diff > 0)
|
||||||
{
|
{
|
||||||
ulong registeredSync = _registeredSync;
|
context.Renderer.WaitSync(registeredSync);
|
||||||
long diff = (long)(context.SyncNumber - registeredSync);
|
|
||||||
|
|
||||||
if (diff > 0)
|
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||||
{
|
{
|
||||||
context.Renderer.WaitSync(registeredSync);
|
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||||
|
return;
|
||||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
|
||||||
{
|
|
||||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Modified = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Modified = false;
|
Modified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -52,16 +52,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
|
||||||
|
|
||||||
ProcessDereferenceQueue();
|
|
||||||
|
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
|
||||||
|
|
||||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
return ref descriptor;
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
|
|
||||||
|
ProcessDereferenceQueue();
|
||||||
|
|
||||||
|
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
|
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.IncrementReferenceCount(this, id);
|
texture.IncrementReferenceCount(this, id);
|
||||||
@ -208,15 +213,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (texture != null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
TextureDescriptor descriptor = PhysicalMemory.Read<TextureDescriptor>(address);
|
ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id];
|
||||||
|
ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address);
|
||||||
|
|
||||||
// If the descriptors are the same, the texture is the same,
|
// If the descriptors are the same, the texture is the same,
|
||||||
// we don't need to remove as it was not modified. Just continue.
|
// we don't need to remove as it was not modified. Just continue.
|
||||||
if (descriptor.Equals(ref DescriptorCache[id]))
|
if (descriptor.Equals(ref cachedDescriptor))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (texture.HasOneReference())
|
||||||
|
{
|
||||||
|
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
texture.DecrementReferenceCount(this, id);
|
texture.DecrementReferenceCount(this, id);
|
||||||
|
|
||||||
Items[id] = null;
|
Items[id] = null;
|
||||||
|
@ -470,19 +470,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address < Address)
|
ulong maxAddress = Math.Max(address, Address);
|
||||||
|
ulong minEndAddress = Math.Min(address + size, Address + Size);
|
||||||
|
|
||||||
|
if (maxAddress >= minEndAddress)
|
||||||
{
|
{
|
||||||
address = Address;
|
// Access doesn't overlap.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong maxSize = Address + Size - address;
|
ForceDirty(maxAddress, minEndAddress - maxAddress);
|
||||||
|
|
||||||
if (size > maxSize)
|
|
||||||
{
|
|
||||||
size = maxSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
ForceDirty(address, size);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
private IVirtualMemoryManagerTracked _cpuMemory;
|
private IVirtualMemoryManagerTracked _cpuMemory;
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whenever the memory manager supports 4KB pages.
|
||||||
|
/// </summary>
|
||||||
|
public bool Supports4KBPages => _cpuMemory.Supports4KBPages;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory shader cache.
|
/// In-memory shader cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -107,6 +107,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
||||||
|
|
||||||
|
@ -113,6 +113,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return _state.GraphicsState.AttributeTypes[location];
|
return _state.GraphicsState.AttributeTypes[location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.FragmentOutputTypes[location];
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
||||||
|
|
||||||
|
@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
||||||
|
|
||||||
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
||||||
|
|
||||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||||
|
@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasUnalignedStorageBuffer;
|
public bool HasUnalignedStorageBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of the fragment shader outputs.
|
||||||
|
/// </summary>
|
||||||
|
public Array8<AttributeType> FragmentOutputTypes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU graphics state.
|
/// Creates a new GPU graphics state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,6 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
||||||
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
/// <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="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
||||||
|
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
||||||
public GpuChannelGraphicsState(
|
public GpuChannelGraphicsState(
|
||||||
bool earlyZForce,
|
bool earlyZForce,
|
||||||
PrimitiveTopology topology,
|
PrimitiveTopology topology,
|
||||||
@ -120,7 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
float alphaTestReference,
|
float alphaTestReference,
|
||||||
ref Array32<AttributeType> attributeTypes,
|
ref Array32<AttributeType> attributeTypes,
|
||||||
bool hasConstantBufferDrawParameters,
|
bool hasConstantBufferDrawParameters,
|
||||||
bool hasUnalignedStorageBuffer)
|
bool hasUnalignedStorageBuffer,
|
||||||
|
ref Array8<AttributeType> fragmentOutputTypes)
|
||||||
{
|
{
|
||||||
EarlyZForce = earlyZForce;
|
EarlyZForce = earlyZForce;
|
||||||
Topology = topology;
|
Topology = topology;
|
||||||
@ -137,6 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
AttributeTypes = attributeTypes;
|
AttributeTypes = attributeTypes;
|
||||||
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
||||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||||
|
FragmentOutputTypes = fragmentOutputTypes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -530,6 +530,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,6 +204,8 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
||||||
|
|
||||||
|
pt.Cache.Tick();
|
||||||
|
|
||||||
texture.SynchronizeMemory();
|
texture.SynchronizeMemory();
|
||||||
|
|
||||||
ImageCrop crop = pt.Crop;
|
ImageCrop crop = pt.Crop;
|
||||||
|
@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
vendorName: GpuVendor,
|
vendorName: GpuVendor,
|
||||||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||||
|
needsFragmentOutputSpecialization: false,
|
||||||
|
reduceShaderPrecision: false,
|
||||||
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
||||||
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
||||||
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
||||||
|
@ -346,12 +346,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
string name = context.OperandManager.DeclareLocal(decl);
|
string name = context.OperandManager.DeclareLocal(decl);
|
||||||
|
|
||||||
context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
|
context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetVarTypeName(AggregateType type, bool precise = true)
|
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
|
||||||
{
|
{
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
precise = false;
|
||||||
|
}
|
||||||
|
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
AggregateType.Void => "void",
|
AggregateType.Void => "void",
|
||||||
@ -666,7 +671,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.AppendLine($"layout (location = {attr}) out vec4 {name};");
|
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
|
||||||
|
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
|
||||||
|
{
|
||||||
|
AttributeType.Sint => "ivec4",
|
||||||
|
AttributeType.Uint => "uvec4",
|
||||||
|
_ => "vec4"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
|
||||||
|
{
|
||||||
|
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.AppendLine($"layout (location = {attr}) out {type} {name};");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
for (int i = 1; i < info.Functions.Count; i++)
|
for (int i = 1; i < info.Functions.Count; i++)
|
||||||
{
|
{
|
||||||
context.AppendLine($"{GetFunctionSignature(info.Functions[i])};");
|
context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
context.CurrentFunction = function;
|
context.CurrentFunction = function;
|
||||||
|
|
||||||
context.AppendLine(GetFunctionSignature(function, funcName));
|
context.AppendLine(GetFunctionSignature(context, function, funcName));
|
||||||
context.EnterScope();
|
context.EnterScope();
|
||||||
|
|
||||||
Declarations.DeclareLocals(context, function);
|
Declarations.DeclareLocals(context, function);
|
||||||
@ -54,23 +54,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
context.LeaveScope();
|
context.LeaveScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetFunctionSignature(StructuredFunction function, string funcName = null)
|
private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null)
|
||||||
{
|
{
|
||||||
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
|
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
|
||||||
|
|
||||||
for (int i = 0; i < function.InArguments.Length; i++)
|
for (int i = 0; i < function.InArguments.Length; i++)
|
||||||
{
|
{
|
||||||
args[i] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < function.OutArguments.Length; i++)
|
for (int i = 0; i < function.OutArguments.Length; i++)
|
||||||
{
|
{
|
||||||
int j = i + function.InArguments.Length;
|
int j = i + function.InArguments.Length;
|
||||||
|
|
||||||
args[j] = $"out {Declarations.GetVarTypeName(function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{Declarations.GetVarTypeName(function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
||||||
|
@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
||||||
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
||||||
@ -32,7 +33,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||||
{
|
{
|
||||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({imageConst})";
|
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageConst;
|
return imageConst;
|
||||||
@ -44,11 +45,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||||
|
|
||||||
string texCall;
|
var texCallBuilder = new StringBuilder();
|
||||||
|
|
||||||
if (texOp.Inst == Instruction.ImageAtomic)
|
if (texOp.Inst == Instruction.ImageAtomic)
|
||||||
{
|
{
|
||||||
texCall = (texOp.Flags & TextureFlags.AtomicMask) switch {
|
texCallBuilder.Append((texOp.Flags & TextureFlags.AtomicMask) switch {
|
||||||
TextureFlags.Add => "imageAtomicAdd",
|
TextureFlags.Add => "imageAtomicAdd",
|
||||||
TextureFlags.Minimum => "imageAtomicMin",
|
TextureFlags.Minimum => "imageAtomicMin",
|
||||||
TextureFlags.Maximum => "imageAtomicMax",
|
TextureFlags.Maximum => "imageAtomicMax",
|
||||||
@ -60,11 +61,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
TextureFlags.Swap => "imageAtomicExchange",
|
TextureFlags.Swap => "imageAtomicExchange",
|
||||||
TextureFlags.CAS => "imageAtomicCompSwap",
|
TextureFlags.CAS => "imageAtomicCompSwap",
|
||||||
_ => "imageAtomicAdd",
|
_ => "imageAtomicAdd",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";
|
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore");
|
||||||
}
|
}
|
||||||
|
|
||||||
int srcIndex = isBindless ? 1 : 0;
|
int srcIndex = isBindless ? 1 : 0;
|
||||||
@ -83,7 +84,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);
|
string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);
|
||||||
|
|
||||||
texCall += "(" + imageName;
|
texCallBuilder.Append('(');
|
||||||
|
texCallBuilder.Append(imageName);
|
||||||
|
|
||||||
int coordsCount = texOp.Type.GetDimensions();
|
int coordsCount = texOp.Type.GetDimensions();
|
||||||
|
|
||||||
@ -91,7 +93,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
void Append(string str)
|
void Append(string str)
|
||||||
{
|
{
|
||||||
texCall += ", " + str;
|
texCallBuilder.Append(", ");
|
||||||
|
texCallBuilder.Append(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
string ApplyScaling(string vector)
|
string ApplyScaling(string vector)
|
||||||
@ -107,11 +110,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
if (pCount == 3 && isArray)
|
if (pCount == 3 && isArray)
|
||||||
{
|
{
|
||||||
// The array index is not scaled, just x and y.
|
// The array index is not scaled, just x and y.
|
||||||
vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + scaleIndex + "), (" + vector + ").z)";
|
vector = $"ivec3(Helper_TexelFetchScale(({vector}).xy, {scaleIndex}), ({vector}).z)";
|
||||||
}
|
}
|
||||||
else if (pCount == 2 && !isArray)
|
else if (pCount == 2 && !isArray)
|
||||||
{
|
{
|
||||||
vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")";
|
vector = $"Helper_TexelFetchScale({vector}, {scaleIndex})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +130,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
elems[index] = Src(AggregateType.S32);
|
elems[index] = Src(AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
Append(ApplyScaling("ivec" + pCount + "(" + string.Join(", ", elems) + ")"));
|
Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -164,7 +167,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
|
Append($"{prefix}vec4({string.Join(", ", cElems)})");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texOp.Inst == Instruction.ImageAtomic)
|
if (texOp.Inst == Instruction.ImageAtomic)
|
||||||
@ -185,19 +188,26 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
Append(value);
|
Append(value);
|
||||||
|
|
||||||
texCall += ")";
|
texCallBuilder.Append(')');
|
||||||
|
|
||||||
if (type != AggregateType.S32)
|
if (type != AggregateType.S32)
|
||||||
{
|
{
|
||||||
texCall = "int(" + texCall + ")";
|
texCallBuilder
|
||||||
|
.Insert(0, "int(")
|
||||||
|
.Append(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMaskMultiDest(texOp.Index) : "");
|
texCallBuilder.Append(')');
|
||||||
|
|
||||||
|
if (texOp.Inst == Instruction.ImageLoad)
|
||||||
|
{
|
||||||
|
texCallBuilder.Append(GetMaskMultiDest(texOp.Index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return texCall;
|
return texCallBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string LoadAttribute(CodeGenContext context, AstOperation operation)
|
public static string LoadAttribute(CodeGenContext context, AstOperation operation)
|
||||||
@ -513,7 +523,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||||
{
|
{
|
||||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({scalarValue})";
|
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -827,7 +837,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
private static string GetMask(int index)
|
private static string GetMask(int index)
|
||||||
{
|
{
|
||||||
return '.' + "rgba".Substring(index, 1);
|
return $".{"rgba".AsSpan(index, 1)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetMaskMultiDest(int mask)
|
private static string GetMaskMultiDest(int mask)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
|
using System;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
|
||||||
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
||||||
@ -49,7 +50,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
private static string GetMask(int index)
|
private static string GetMask(int index)
|
||||||
{
|
{
|
||||||
return '.' + "xy".Substring(index, 1);
|
return $".{"xy".AsSpan(index, 1)}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -577,6 +577,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
context.Decorate(spvVar, Decoration.Patch);
|
context.Decorate(spvVar, Decoration.Patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
context.Decorate(spvVar, Decoration.Invariant);
|
||||||
|
}
|
||||||
|
|
||||||
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
|
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
|
||||||
|
|
||||||
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
|
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
|
||||||
|
@ -2194,13 +2194,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP64, result);
|
return new OperationResult(AggregateType.FP64, result);
|
||||||
}
|
}
|
||||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP32, result);
|
return new OperationResult(AggregateType.FP32, result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2255,13 +2265,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP64, result);
|
return new OperationResult(AggregateType.FP64, result);
|
||||||
}
|
}
|
||||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP32, result);
|
return new OperationResult(AggregateType.FP32, result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.Shader.Instructions;
|
using Ryujinx.Graphics.Shader.Instructions;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Decoders
|
namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
{
|
{
|
||||||
@ -329,18 +330,18 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
|||||||
|
|
||||||
private static void Add(string encoding, InstName name, InstEmitter emitter, InstProps props = InstProps.None)
|
private static void Add(string encoding, InstName name, InstEmitter emitter, InstProps props = InstProps.None)
|
||||||
{
|
{
|
||||||
encoding = encoding.Substring(0, EncodingBits);
|
ReadOnlySpan<char> encodingPart = encoding.AsSpan(0, EncodingBits);
|
||||||
|
|
||||||
int bit = encoding.Length - 1;
|
int bit = encodingPart.Length - 1;
|
||||||
int value = 0;
|
int value = 0;
|
||||||
int xMask = 0;
|
int xMask = 0;
|
||||||
int xBits = 0;
|
int xBits = 0;
|
||||||
|
|
||||||
int[] xPos = new int[encoding.Length];
|
int[] xPos = new int[encodingPart.Length];
|
||||||
|
|
||||||
for (int index = 0; index < encoding.Length; index++, bit--)
|
for (int index = 0; index < encodingPart.Length; index++, bit--)
|
||||||
{
|
{
|
||||||
char chr = encoding[index];
|
char chr = encodingPart[index];
|
||||||
|
|
||||||
if (chr == '1')
|
if (chr == '1')
|
||||||
{
|
{
|
||||||
|
@ -114,6 +114,16 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries output type for fragment shaders.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="location">Location of the framgent output</param>
|
||||||
|
/// <returns>Output location</returns>
|
||||||
|
AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return AttributeType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries Local Size X for compute shaders.
|
/// Queries Local Size X for compute shaders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -186,6 +196,15 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries host about whether to reduce precision to improve performance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if precision is limited to vertex position, false otherwise</returns>
|
||||||
|
bool QueryHostReducedPrecision()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -128,7 +128,15 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
}
|
}
|
||||||
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||||
{
|
{
|
||||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false);
|
int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||||
|
var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
|
||||||
|
{
|
||||||
|
AttributeType.Sint => AggregateType.S32,
|
||||||
|
AttributeType.Uint => AggregateType.U32,
|
||||||
|
_ => AggregateType.FP32
|
||||||
|
};
|
||||||
|
|
||||||
|
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
|
||||||
}
|
}
|
||||||
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
||||||
{
|
{
|
||||||
|
@ -140,6 +140,25 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _attachments[index];
|
return _attachments[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentType GetAttachmentComponentType(int index)
|
||||||
|
{
|
||||||
|
if (_colors != null && (uint)index < _colors.Length)
|
||||||
|
{
|
||||||
|
var format = _colors[index].Info.Format;
|
||||||
|
|
||||||
|
if (format.IsSint())
|
||||||
|
{
|
||||||
|
return ComponentType.SignedInteger;
|
||||||
|
}
|
||||||
|
else if (format.IsUint())
|
||||||
|
{
|
||||||
|
return ComponentType.UnsignedInteger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComponentType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsValidColorAttachment(int bindIndex)
|
public bool IsValidColorAttachment(int bindIndex)
|
||||||
{
|
{
|
||||||
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
|
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
[Flags]
|
||||||
|
enum PortabilitySubsetFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
VertexBufferAlignment4B = 1,
|
||||||
|
NoTriangleFans = 1 << 1,
|
||||||
|
NoPointMode = 1 << 2,
|
||||||
|
No3DImageView = 1 << 3,
|
||||||
|
NoLodBias = 1 << 4
|
||||||
|
}
|
||||||
|
|
||||||
readonly struct HardwareCapabilities
|
readonly struct HardwareCapabilities
|
||||||
{
|
{
|
||||||
public readonly bool SupportsIndexTypeUint8;
|
public readonly bool SupportsIndexTypeUint8;
|
||||||
@ -15,6 +28,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly bool SupportsExtendedDynamicState;
|
public readonly bool SupportsExtendedDynamicState;
|
||||||
public readonly bool SupportsMultiView;
|
public readonly bool SupportsMultiView;
|
||||||
public readonly bool SupportsNullDescriptors;
|
public readonly bool SupportsNullDescriptors;
|
||||||
|
public readonly bool SupportsPreciseOcclusionQueries;
|
||||||
public readonly bool SupportsPushDescriptors;
|
public readonly bool SupportsPushDescriptors;
|
||||||
public readonly bool SupportsTransformFeedback;
|
public readonly bool SupportsTransformFeedback;
|
||||||
public readonly bool SupportsTransformFeedbackQueries;
|
public readonly bool SupportsTransformFeedbackQueries;
|
||||||
@ -23,6 +37,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly uint MaxSubgroupSize;
|
public readonly uint MaxSubgroupSize;
|
||||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||||
public readonly SampleCountFlags SupportedSampleCounts;
|
public readonly SampleCountFlags SupportedSampleCounts;
|
||||||
|
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||||
|
|
||||||
public HardwareCapabilities(
|
public HardwareCapabilities(
|
||||||
bool supportsIndexTypeUint8,
|
bool supportsIndexTypeUint8,
|
||||||
@ -39,11 +54,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
bool supportsPushDescriptors,
|
bool supportsPushDescriptors,
|
||||||
bool supportsTransformFeedback,
|
bool supportsTransformFeedback,
|
||||||
bool supportsTransformFeedbackQueries,
|
bool supportsTransformFeedbackQueries,
|
||||||
|
bool supportsPreciseOcclusionQueries,
|
||||||
bool supportsGeometryShader,
|
bool supportsGeometryShader,
|
||||||
uint minSubgroupSize,
|
uint minSubgroupSize,
|
||||||
uint maxSubgroupSize,
|
uint maxSubgroupSize,
|
||||||
ShaderStageFlags requiredSubgroupSizeStages,
|
ShaderStageFlags requiredSubgroupSizeStages,
|
||||||
SampleCountFlags supportedSampleCounts)
|
SampleCountFlags supportedSampleCounts,
|
||||||
|
PortabilitySubsetFlags portabilitySubset)
|
||||||
{
|
{
|
||||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||||
@ -59,11 +76,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SupportsPushDescriptors = supportsPushDescriptors;
|
SupportsPushDescriptors = supportsPushDescriptors;
|
||||||
SupportsTransformFeedback = supportsTransformFeedback;
|
SupportsTransformFeedback = supportsTransformFeedback;
|
||||||
SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries;
|
SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries;
|
||||||
|
SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries;
|
||||||
SupportsGeometryShader = supportsGeometryShader;
|
SupportsGeometryShader = supportsGeometryShader;
|
||||||
MinSubgroupSize = minSubgroupSize;
|
MinSubgroupSize = minSubgroupSize;
|
||||||
MaxSubgroupSize = maxSubgroupSize;
|
MaxSubgroupSize = maxSubgroupSize;
|
||||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||||
SupportedSampleCounts = supportedSampleCounts;
|
SupportedSampleCounts = supportedSampleCounts;
|
||||||
|
PortabilitySubset = portabilitySubset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user