diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index 6b8152b9d..efdb422e7 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -13,5 +13,7 @@ namespace Ryujinx.Common.Configuration.Hid public Key VolumeDown { get; set; } public Key CustomVSyncIntervalIncrement { get; set; } public Key CustomVSyncIntervalDecrement { get; set; } + public Key TurboMode { get; set; } + public bool TurboModeWhileHeld { get; set; } } } diff --git a/src/Ryujinx.Cpu/ITickSource.cs b/src/Ryujinx.Cpu/ITickSource.cs index e65e99e26..4aff612f0 100644 --- a/src/Ryujinx.Cpu/ITickSource.cs +++ b/src/Ryujinx.Cpu/ITickSource.cs @@ -8,10 +8,17 @@ namespace Ryujinx.Cpu /// public interface ITickSource : ICounter { + public const long RealityTickScalar = 100; + /// /// Time elapsed since the counter was created. /// TimeSpan ElapsedTime { get; } + + /// + /// Clock tick scalar, in percent points (100 = 1.0). + /// + long TickScalar { get; set; } /// /// Time elapsed since the counter was created, in seconds. diff --git a/src/Ryujinx.Cpu/TickSource.cs b/src/Ryujinx.Cpu/TickSource.cs index eee83fc62..3bc01d6b9 100644 --- a/src/Ryujinx.Cpu/TickSource.cs +++ b/src/Ryujinx.Cpu/TickSource.cs @@ -14,12 +14,37 @@ namespace Ryujinx.Cpu /// public ulong Counter => (ulong)(ElapsedSeconds * Frequency); + + + public long TickScalar { get; set; } + + + private static long _acumElapsedTicks; + + + private static long _lastElapsedTicks; + + + private long ElapsedTicks + { + get + { + long elapsedTicks = _tickCounter.ElapsedTicks; + + _acumElapsedTicks += (elapsedTicks - _lastElapsedTicks) * TickScalar / 100; + + _lastElapsedTicks = elapsedTicks; + + return _acumElapsedTicks; + } + } /// - public TimeSpan ElapsedTime => _tickCounter.Elapsed; + + public TimeSpan ElapsedTime => Stopwatch.GetElapsedTime(0, ElapsedTicks); /// - public double ElapsedSeconds => _tickCounter.ElapsedTicks * _hostTickFreq; + public double ElapsedSeconds => ElapsedTicks * _hostTickFreq; public TickSource(ulong frequency) { diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index f50ec852e..4620821cb 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -1065,7 +1065,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// private void UpdateIndexBufferState() { - IndexBufferState indexBuffer = _state.State.IndexBufferState; + IndexBufferState? indexBufferNullable = _state?.State.IndexBufferState; + + if (!indexBufferNullable.HasValue) + { + return; + } + + IndexBufferState indexBuffer = indexBufferNullable.Value; if (_drawState.IndexCount == 0) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 4fc66c4c0..2ab413848 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -117,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private static string GetDiskCachePath() { return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null - ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader") + ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId.ToLower(), "cache", "shader") : null; } diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs index 1b95b6712..de58bb178 100644 --- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -1,8 +1,10 @@ +using Gommon; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -21,22 +23,43 @@ namespace Ryujinx.HLE.HOS.Services private int _selfId; private bool _isDomain; - public IpcService(ServerBase server = null) + public IpcService(ServerBase server = null, bool registerTipc = false) { - CmifCommands = GetType().Assembly.GetTypes() - .Where(type => type == GetType()) - .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + Stopwatch sw = Stopwatch.StartNew(); + + CmifCommands = GetType() + .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public) .SelectMany(methodInfo => methodInfo.GetCustomAttributes() .Select(command => (command.Id, methodInfo))) .ToDictionary(command => command.Id, command => command.methodInfo); + + sw.Stop(); + + Logger.Debug?.Print( + LogClass.Emulation, + $"{CmifCommands.Count} Cmif commands loaded in {sw.ElapsedTicks} ticks ({Stopwatch.Frequency} tps).", + GetType().AsPrettyString() + ); - TipcCommands = GetType().Assembly.GetTypes() - .Where(type => type == GetType()) - .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) - .SelectMany(methodInfo => methodInfo.GetCustomAttributes() - .Select(command => (command.Id, methodInfo))) - .ToDictionary(command => command.Id, command => command.methodInfo); + if (registerTipc) + { + sw.Start(); + TipcCommands = GetType() + .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes() + .Select(command => (command.Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + sw.Stop(); + + Logger.Debug?.Print( + LogClass.Emulation, + $"{TipcCommands.Count} Tipc commands loaded in {sw.ElapsedTicks} ticks ({Stopwatch.Frequency} tps).", + GetType().AsPrettyString() + ); + } + Server = server; _parent = this; @@ -127,10 +150,7 @@ namespace Ryujinx.HLE.HOS.Services } else { - string serviceName; - - - serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName; + string serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName; Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); } diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index 6d03d8d05..af511af29 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm private bool _isInitialized; - public IUserInterface(KernelContext context, SmRegistry registry) + public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true) { _commonServer = new ServerBase(context, "CommonServer"); _registry = registry; diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs index 935e9895e..294192363 100644 --- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -2,6 +2,7 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.PreciseSleep; +using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; @@ -89,7 +90,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger } else { - _ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval; + _ticksPerFrame = ((Stopwatch.Frequency / _device.TargetVSyncInterval) * 100) / _device.TickScalar; _targetVSyncInterval = _device.TargetVSyncInterval; } } diff --git a/src/Ryujinx.HLE/HleConfiguration.cs b/src/Ryujinx.HLE/HleConfiguration.cs index 97835033e..39745ff53 100644 --- a/src/Ryujinx.HLE/HleConfiguration.cs +++ b/src/Ryujinx.HLE/HleConfiguration.cs @@ -102,6 +102,11 @@ namespace Ryujinx.HLE /// Control if the Profiled Translation Cache (PTC) should be used. /// internal readonly bool EnablePtc; + + /// + /// Control the arbitrary scalar applied to emulated CPU tick timing. + /// + public long TickScalar { get; set; } /// /// Control if the guest application should be told that there is a Internet connection available. @@ -201,6 +206,7 @@ namespace Ryujinx.HLE VSyncMode vSyncMode, bool enableDockedMode, bool enablePtc, + long tickScalar, bool enableInternetAccess, IntegrityCheckLevel fsIntegrityCheckLevel, int fsGlobalAccessLogMode, @@ -226,6 +232,7 @@ namespace Ryujinx.HLE CustomVSyncInterval = customVSyncInterval; EnableDockedMode = enableDockedMode; EnablePtc = enablePtc; + TickScalar = tickScalar; EnableInternetAccess = enableInternetAccess; FsIntegrityCheckLevel = fsIntegrityCheckLevel; FsGlobalAccessLogMode = fsGlobalAccessLogMode; diff --git a/src/Ryujinx.HLE/PerformanceStatistics.cs b/src/Ryujinx.HLE/PerformanceStatistics.cs index e80faa7d2..9363ff2d3 100644 --- a/src/Ryujinx.HLE/PerformanceStatistics.cs +++ b/src/Ryujinx.HLE/PerformanceStatistics.cs @@ -6,6 +6,8 @@ namespace Ryujinx.HLE { public class PerformanceStatistics { + private readonly Switch _device; + private const int FrameTypeGame = 0; private const int PercentTypeFifo = 0; @@ -28,8 +30,10 @@ namespace Ryujinx.HLE private readonly System.Timers.Timer _resetTimer; - public PerformanceStatistics() + public PerformanceStatistics(Switch device) { + _device = device; + _frameRate = new double[1]; _accumulatedFrameTime = new double[1]; _previousFrameTime = new double[1]; @@ -162,14 +166,6 @@ namespace Ryujinx.HLE return 1000 / _frameRate[FrameTypeGame]; } - public string FormatGameFrameRate() - { - double frameRate = GetGameFrameRate(); - double frameTime = GetGameFrameTime(); - - return $"{frameRate:00.00} FPS ({frameTime:00.00}ms)"; - } - public string FormatFifoPercent() { double fifoPercent = GetFifoPercent(); diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index df5b48103..e52b3df15 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -4,6 +4,7 @@ using Ryujinx.Audio.Backends.CompatLayer; using Ryujinx.Audio.Integration; using Ryujinx.Common; using Ryujinx.Common.Configuration; +using Ryujinx.Cpu; using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; @@ -26,18 +27,26 @@ namespace Ryujinx.HLE public GpuContext Gpu { get; } public VirtualFileSystem FileSystem { get; } public HOS.Horizon System { get; } + + public bool TurboMode = false; + + public long TickScalar + { + get => System?.TickSource?.TickScalar ?? ITickSource.RealityTickScalar; + set => System.TickSource.TickScalar = value; + } + public ProcessLoader Processes { get; } public PerformanceStatistics Statistics { get; } public Hid Hid { get; } public TamperMachine TamperMachine { get; } public IHostUIHandler UIHandler { get; } - public int CpuCoresCount = 4; //Switch 1 has 4 cores + public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC public VSyncMode VSyncMode { get; set; } public bool CustomVSyncIntervalEnabled { get; set; } public int CustomVSyncInterval { get; set; } - public long TargetVSyncInterval { get; set; } = 60; public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; @@ -64,7 +73,7 @@ namespace Ryujinx.HLE Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags); Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks); System = new HOS.Horizon(this); - Statistics = new PerformanceStatistics(); + Statistics = new PerformanceStatistics(this); Hid = new Hid(this, System.HidStorage); Processes = new ProcessLoader(this); TamperMachine = new TamperMachine(); @@ -75,6 +84,7 @@ namespace Ryujinx.HLE VSyncMode = Configuration.VSyncMode; CustomVSyncInterval = Configuration.CustomVSyncInterval; + TickScalar = TurboMode ? Configuration.TickScalar : ITickSource.RealityTickScalar; System.State.DockedMode = Configuration.EnableDockedMode; System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; System.EnablePtc = Configuration.EnablePtc; @@ -126,6 +136,12 @@ namespace Ryujinx.HLE } } + public void ToggleTurbo() + { + TurboMode = !TurboMode; + TickScalar = TurboMode ? Configuration.TickScalar : ITickSource.RealityTickScalar; + } + public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile); public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId); public bool LoadNca(string ncaFile, BlitStruct? customNacpData = null) => Processes.LoadNca(ncaFile, customNacpData); diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 67c68d8ec..60120ab58 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -57,16 +57,19 @@ namespace Ryujinx.Input.SDL2 return null; } + // Remove the first 4 char of the guid (CRC part) to make it stable + string guidString = "0000" + guid.ToString().Substring(4); + string id; lock (_lock) { int guidIndex = 0; - id = guidIndex + "-" + guid; + id = guidIndex + "-" + guidString; while (_gamepadsIds.Contains(id)) { - id = (++guidIndex) + "-" + guid; + id = (++guidIndex) + "-" + guidString; } } diff --git a/src/Ryujinx/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs index 060c678d2..b6fb02f04 100644 --- a/src/Ryujinx/Common/KeyboardHotkeyState.cs +++ b/src/Ryujinx/Common/KeyboardHotkeyState.cs @@ -14,5 +14,6 @@ namespace Ryujinx.Ava.Common VolumeDown, CustomVSyncIntervalIncrement, CustomVSyncIntervalDecrement, + TurboMode, } } diff --git a/src/Ryujinx/Common/LocaleManager.cs b/src/Ryujinx/Common/LocaleManager.cs index d116fe709..9307532e7 100644 --- a/src/Ryujinx/Common/LocaleManager.cs +++ b/src/Ryujinx/Common/LocaleManager.cs @@ -61,6 +61,13 @@ namespace Ryujinx.Ava.Common.Locale } } + public static string GetUnformatted(LocaleKeys key) => Instance.Get(key); + + public string Get(LocaleKeys key) => + _localeStrings.TryGetValue(key, out string value) + ? value + : key.ToString(); + public string this[LocaleKeys key] { get diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs index 751a86571..a2f5af24c 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs @@ -11,6 +11,7 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.Vulkan; @@ -311,7 +312,7 @@ namespace Ryujinx.Headless return new OpenGLRenderer(); } - + private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) => new( new HleConfiguration( @@ -321,6 +322,7 @@ namespace Ryujinx.Headless options.VSyncMode, !options.DisableDockedMode, !options.DisablePTC, + ITickSource.RealityTickScalar, options.EnableInternetAccess, !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, options.FsGlobalAccessLogMode, diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs index 455afaf45..7b07000b4 100644 --- a/src/Ryujinx/Systems/AppHost.cs +++ b/src/Ryujinx/Systems/AppHost.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Threading; using DiscordRPC; +using Gommon; using LibHac.Common; using LibHac.Ns; using Ryujinx.Audio.Backends.Dummy; @@ -1115,11 +1116,23 @@ namespace Ryujinx.Ava.Systems LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), - Device.Statistics.FormatGameFrameRate(), + FormatGameFrameRate(), Device.Statistics.FormatFifoPercent(), _displayCount)); } + private string FormatGameFrameRate() + { + string frameRate = Device.Statistics.GetGameFrameRate().ToString("00.00"); + string frameTime = Device.Statistics.GetGameFrameTime().ToString("00.00"); + + return Device.TurboMode + ? LocaleManager.GetUnformatted(LocaleKeys.FpsTurboStatusBarText) + .Format(frameRate, frameTime, Device.TickScalar) + : LocaleManager.GetUnformatted(LocaleKeys.FpsStatusBarText) + .Format(frameRate, frameTime); + } + public async Task ShowExitPrompt() { bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; @@ -1215,6 +1228,12 @@ namespace Ryujinx.Ava.Systems if (currentHotkeyState != _prevHotkeyState) { + if (ConfigurationState.Instance.Hid.Hotkeys.Value.TurboModeWhileHeld && + _keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.TurboMode) != Device.TurboMode) + { + Device.ToggleTurbo(); + } + switch (currentHotkeyState) { case KeyboardHotkeyState.ToggleVSyncMode: @@ -1226,6 +1245,12 @@ namespace Ryujinx.Ava.Systems case KeyboardHotkeyState.CustomVSyncIntervalIncrement: _viewModel.CustomVSyncInterval = Device.IncrementCustomVSyncInterval(); break; + case KeyboardHotkeyState.TurboMode: + if (!ConfigurationState.Instance.Hid.Hotkeys.Value.TurboModeWhileHeld) + { + Device.ToggleTurbo(); + } + break; case KeyboardHotkeyState.Screenshot: ScreenshotRequested = true; break; @@ -1355,6 +1380,10 @@ namespace Ryujinx.Ava.Systems { state = KeyboardHotkeyState.CustomVSyncIntervalDecrement; } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.TurboMode)) + { + state = KeyboardHotkeyState.TurboMode; + } return state; } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs index c5315ab12..470749674 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs @@ -258,6 +258,11 @@ namespace Ryujinx.Ava.Systems.Configuration /// Enables or disables low-power profiled translation cache persistency loading /// public bool EnableLowPowerPtc { get; set; } + + /// + /// Clock tick scalar, in percent points (100 = 1.0). + /// + public long TickScalar { get; set; } /// /// Enables or disables guest Internet access diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs index b10cc3926..20c4c6414 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs @@ -93,6 +93,7 @@ namespace Ryujinx.Ava.Systems.Configuration System.EnableDockedMode.Value = cff.DockedMode; System.EnablePtc.Value = cff.EnablePtc; System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc; + System.TickScalar.Value = cff.TickScalar; System.EnableInternetAccess.Value = cff.EnableInternetAccess; System.EnableFsIntegrityChecks.Value = cff.EnableFsIntegrityChecks; System.FsGlobalAccessLogMode.Value = cff.FsGlobalAccessLogMode; @@ -438,9 +439,27 @@ namespace Ryujinx.Ava.Systems.Configuration (64, static cff => cff.LoggingEnableAvalonia = false), (65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off), (66, static cff => cff.DisableInputWhenOutOfFocus = false), - (67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing) - // 68 was the version that added per-game configs; the file structure did not change - // the version was increased so external tools could know that your Ryujinx version has per-game config capabilities. + (67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing), + (68, static cff => + { + cff.TickScalar = 200; + cff.Hotkeys = new KeyboardHotkeys + { + ToggleVSyncMode = cff.Hotkeys.ToggleVSyncMode, + Screenshot = cff.Hotkeys.Screenshot, + ShowUI = cff.Hotkeys.ShowUI, + Pause = cff.Hotkeys.Pause, + ToggleMute = cff.Hotkeys.ToggleMute, + ResScaleUp = cff.Hotkeys.ResScaleUp, + ResScaleDown = cff.Hotkeys.ResScaleDown, + VolumeUp = cff.Hotkeys.VolumeUp, + VolumeDown = cff.Hotkeys.VolumeDown, + CustomVSyncIntervalIncrement = cff.Hotkeys.CustomVSyncIntervalIncrement, + CustomVSyncIntervalDecrement = cff.Hotkeys.CustomVSyncIntervalDecrement, + TurboMode = Key.Unbound, + TurboModeWhileHeld = false + }; + }) ); } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs index b52c624e3..205054474 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs @@ -335,6 +335,11 @@ namespace Ryujinx.Ava.Systems.Configuration /// Enables or disables persistent profiled translation cache /// public ReactiveObject EnablePtc { get; private set; } + + /// + /// Clock tick scalar, in percent points (100 = 1.0). + /// + public ReactiveObject TickScalar { get; set; } /// /// Enables or disables low-power persistent profiled translation cache loading @@ -415,6 +420,15 @@ namespace Ryujinx.Ava.Systems.Configuration EnableLowPowerPtc.LogChangesToValue(nameof(EnableLowPowerPtc)); EnableLowPowerPtc.Event += (_, evnt) => Optimizations.LowPower = evnt.NewValue; + TickScalar = new ReactiveObject(); + TickScalar.LogChangesToValue(nameof(TickScalar)); + TickScalar.Event += (_, evnt) => + { + if (Switch.Shared is null) + return; + + Switch.Shared.Configuration.TickScalar = evnt.NewValue; + }; EnableInternetAccess = new ReactiveObject(); EnableInternetAccess.LogChangesToValue(nameof(EnableInternetAccess)); EnableFsIntegrityChecks = new ReactiveObject(); @@ -842,6 +856,7 @@ namespace Ryujinx.Ava.Systems.Configuration Graphics.VSyncMode, System.EnableDockedMode, System.EnablePtc, + System.TickScalar, System.EnableInternetAccess, System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid @@ -860,8 +875,8 @@ namespace Ryujinx.Ava.Systems.Configuration Multiplayer.Mode, Multiplayer.DisableP2p, Multiplayer.LdnPassphrase, - Instance.Multiplayer.GetLdnServer(), - Instance.Graphics.CustomVSyncInterval, - Instance.Hacks.ShowDirtyHacks ? Instance.Hacks.EnabledHacks : null); + Multiplayer.GetLdnServer(), + Graphics.CustomVSyncInterval, + Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null); } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs index 6fe35c744..65e8e02ce 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Ava.Systems.Configuration EnableColorSpacePassthrough = Graphics.EnableColorSpacePassthrough, EnablePtc = System.EnablePtc, EnableLowPowerPtc = System.EnableLowPowerPtc, + TickScalar = System.TickScalar, EnableInternetAccess = System.EnableInternetAccess, EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, @@ -260,6 +261,10 @@ namespace Ryujinx.Ava.Systems.Configuration ResScaleDown = Key.Unbound, VolumeUp = Key.Unbound, VolumeDown = Key.Unbound, + CustomVSyncIntervalIncrement = Key.Unbound, + CustomVSyncIntervalDecrement = Key.Unbound, + TurboMode = Key.Unbound, + TurboModeWhileHeld = false }; Hid.RainbowSpeed.Value = 1f; Hid.InputConfig.Value = diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs index 40f53c673..9e557d7b1 100644 --- a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -28,6 +28,10 @@ namespace Ryujinx.Ava.UI.Models.Input [ObservableProperty] private Key _customVSyncIntervalDecrement; + [ObservableProperty] private Key _turboMode; + + [ObservableProperty] private bool _turboModeWhileHeld; + public HotkeyConfig(KeyboardHotkeys config) { if (config == null) @@ -44,6 +48,8 @@ namespace Ryujinx.Ava.UI.Models.Input VolumeDown = config.VolumeDown; CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement; CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement; + TurboMode = config.TurboMode; + TurboModeWhileHeld = config.TurboModeWhileHeld; } public KeyboardHotkeys GetConfig() => @@ -60,6 +66,8 @@ namespace Ryujinx.Ava.UI.Models.Input VolumeDown = VolumeDown, CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement, CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement, + TurboMode = TurboMode, + TurboModeWhileHeld = TurboModeWhileHeld }; } } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index a092e97f2..0be6ab3fe 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -60,6 +60,7 @@ namespace Ryujinx.Ava.UI.ViewModels private bool _enableCustomVSyncInterval; private int _customVSyncIntervalPercentageProxy; private VSyncMode _vSyncMode; + private long _turboModeMultiplier; public event Action CloseWindow; public event Action SaveSettingsEvent; @@ -206,6 +207,25 @@ namespace Ryujinx.Ava.UI.ViewModels } public bool EnablePptc { get; set; } public bool EnableLowPowerPptc { get; set; } + + + public long TurboMultiplier + { + get => _turboModeMultiplier; + set + { + if (_turboModeMultiplier != value) + { + _turboModeMultiplier = value; + + OnPropertyChanged(); + OnPropertyChanged((nameof(TurboMultiplierPercentageText))); + } + } + } + + public string TurboMultiplierPercentageText => $"{TurboMultiplier}%"; + public bool EnableInternetAccess { get; set; } public bool EnableFsIntegrityChecks { get; set; } public bool IgnoreMissingServices { get; set; } @@ -592,6 +612,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableLowPowerPptc = config.System.EnableLowPowerPtc; MemoryMode = (int)config.System.MemoryManagerMode.Value; UseHypervisor = config.System.UseHypervisor; + TurboMultiplier = config.System.TickScalar; // Graphics GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value; @@ -694,6 +715,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc; config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; config.System.UseHypervisor.Value = UseHypervisor; + config.System.TickScalar.Value = TurboMultiplier; // Graphics config.Graphics.VSyncMode.Value = VSyncMode; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml index 62f087510..b3a4b66f9 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml @@ -7,6 +7,7 @@ xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" mc:Ignorable="d" x:DataType="viewModels:SettingsViewModel"> @@ -76,6 +77,57 @@ ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" /> + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml index 87b6dda7d..917177fb5 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -19,7 +19,7 @@ - @@ -47,71 +47,79 @@ Classes="h1" Text="{ext:Locale SettingsTabHotkeysHotkeys}" /> - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index 54ef00c38..82e01b609 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -110,6 +110,9 @@ namespace Ryujinx.Ava.UI.Views.Settings ViewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = buttonValue.AsHidType(); break; + case "TurboMode": + ViewModel.KeyboardHotkey.TurboMode = buttonValue.AsHidType(); + break; } }); }