From 68b71f10926220e4bcc6cc4515b005cf0c4a3527 Mon Sep 17 00:00:00 2001
From: jcm <6864788+jcm93@users.noreply.github.com>
Date: Mon, 13 May 2024 11:52:24 -0500
Subject: [PATCH] Add custom refresh rate mode to VSync option
---
.../Configuration/Hid/KeyboardHotkeys.cs | 4 +-
src/Ryujinx.Common/Configuration/VSyncMode.cs | 9 ++
src/Ryujinx.Graphics.GAL/IWindow.cs | 2 +-
.../Multithreading/ThreadedWindow.cs | 2 +-
src/Ryujinx.Graphics.GAL/VSyncMode.cs | 9 ++
src/Ryujinx.Graphics.OpenGL/Window.cs | 2 +-
src/Ryujinx.Graphics.Vulkan/Window.cs | 13 +-
src/Ryujinx.Graphics.Vulkan/WindowBase.cs | 2 +-
src/Ryujinx.HLE/HLEConfiguration.cs | 18 ++-
.../Services/SurfaceFlinger/SurfaceFlinger.cs | 20 ++-
src/Ryujinx.HLE/Switch.cs | 38 +++++-
src/Ryujinx.Headless.SDL2/Options.cs | 7 +-
src/Ryujinx.Headless.SDL2/Program.cs | 5 +-
.../StatusUpdatedEventArgs.cs | 4 +-
src/Ryujinx.Headless.SDL2/WindowBase.cs | 2 +-
.../Configuration/ConfigurationFileFormat.cs | 20 ++-
.../ConfigurationState.Migration.cs | 45 +++++--
.../Configuration/ConfigurationState.Model.cs | 24 +++-
.../Configuration/ConfigurationState.cs | 10 +-
src/Ryujinx/AppHost.cs | 105 +++++++++++++--
src/Ryujinx/Assets/Locales/en_US.json | 19 ++-
src/Ryujinx/Assets/Styles/Themes.xaml | 5 +-
src/Ryujinx/Common/KeyboardHotkeyState.cs | 4 +-
src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 38 +++++-
src/Ryujinx/UI/Models/SaveModel.cs | 2 +-
.../UI/Models/StatusUpdatedEventArgs.cs | 7 +-
.../UI/ViewModels/MainWindowViewModel.cs | 123 ++++++++++++++++--
.../UI/ViewModels/SettingsViewModel.cs | 82 +++++++++++-
.../UI/Views/Main/MainStatusBarView.axaml | 64 ++++++++-
.../UI/Views/Main/MainStatusBarView.axaml.cs | 7 +-
.../Views/Settings/SettingsHotkeysView.axaml | 20 ++-
.../Settings/SettingsHotkeysView.axaml.cs | 10 +-
.../Views/Settings/SettingsSystemView.axaml | 81 +++++++++++-
src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 4 +-
34 files changed, 697 insertions(+), 110 deletions(-)
create mode 100644 src/Ryujinx.Common/Configuration/VSyncMode.cs
create mode 100644 src/Ryujinx.Graphics.GAL/VSyncMode.cs
diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
index 0cb49ca8ce..6b8152b9db 100644
--- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
+++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
@@ -2,7 +2,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
public class KeyboardHotkeys
{
- public Key ToggleVsync { get; set; }
+ public Key ToggleVSyncMode { get; set; }
public Key Screenshot { get; set; }
public Key ShowUI { get; set; }
public Key Pause { get; set; }
@@ -11,5 +11,7 @@ public class KeyboardHotkeys
public Key ResScaleDown { get; set; }
public Key VolumeUp { get; set; }
public Key VolumeDown { get; set; }
+ public Key CustomVSyncIntervalIncrement { get; set; }
+ public Key CustomVSyncIntervalDecrement { get; set; }
}
}
diff --git a/src/Ryujinx.Common/Configuration/VSyncMode.cs b/src/Ryujinx.Common/Configuration/VSyncMode.cs
new file mode 100644
index 0000000000..ca93b5e1c2
--- /dev/null
+++ b/src/Ryujinx.Common/Configuration/VSyncMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Common.Configuration
+{
+ public enum VSyncMode
+ {
+ Switch,
+ Unbounded,
+ Custom
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/IWindow.cs b/src/Ryujinx.Graphics.GAL/IWindow.cs
index 83418e7090..12686cb281 100644
--- a/src/Ryujinx.Graphics.GAL/IWindow.cs
+++ b/src/Ryujinx.Graphics.GAL/IWindow.cs
@@ -8,7 +8,7 @@ public interface IWindow
void SetSize(int width, int height);
- void ChangeVSyncMode(bool vsyncEnabled);
+ void ChangeVSyncMode(VSyncMode vSyncMode);
void SetAntiAliasing(AntiAliasing antialiasing);
void SetScalingFilter(ScalingFilter type);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
index acda37ef36..102fdb1bb3 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
@@ -31,7 +31,7 @@ public void SetSize(int width, int height)
_impl.Window.SetSize(width, height);
}
- public void ChangeVSyncMode(bool vsyncEnabled) { }
+ public void ChangeVSyncMode(VSyncMode vSyncMode) { }
public void SetAntiAliasing(AntiAliasing effect) { }
diff --git a/src/Ryujinx.Graphics.GAL/VSyncMode.cs b/src/Ryujinx.Graphics.GAL/VSyncMode.cs
new file mode 100644
index 0000000000..c5794b8f77
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/VSyncMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum VSyncMode
+ {
+ Switch,
+ Unbounded,
+ Custom
+ }
+}
diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs
index 285ab725e2..1dc8a51f60 100644
--- a/src/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Window.cs
@@ -54,7 +54,7 @@ public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
}
- public void ChangeVSyncMode(bool vsyncEnabled) { }
+ public void ChangeVSyncMode(VSyncMode vSyncMode) { }
public void SetSize(int width, int height)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index 3dc6d4e191..3e8d3b375a 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -29,7 +29,7 @@ class Window : WindowBase, IDisposable
private int _width;
private int _height;
- private bool _vsyncEnabled;
+ private VSyncMode _vSyncMode;
private bool _swapchainIsDirty;
private VkFormat _format;
private AntiAliasing _currentAntiAliasing;
@@ -139,7 +139,7 @@ private unsafe void CreateSwapchain()
ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform,
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
- PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
+ PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
Clipped = true,
};
@@ -279,9 +279,9 @@ private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKH
}
}
- private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
+ private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
{
- if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
+ if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
{
return PresentModeKHR.ImmediateKhr;
}
@@ -634,9 +634,10 @@ public override void SetSize(int width, int height)
_swapchainIsDirty = true;
}
- public override void ChangeVSyncMode(bool vsyncEnabled)
+ public override void ChangeVSyncMode(VSyncMode vSyncMode)
{
- _vsyncEnabled = vsyncEnabled;
+ _vSyncMode = vSyncMode;
+ //present mode may change, so mark the swapchain for recreation
_swapchainIsDirty = true;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
index edb9c688c9..ca06ec0b86 100644
--- a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
@@ -10,7 +10,7 @@ internal abstract class WindowBase : IWindow
public abstract void Dispose();
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
public abstract void SetSize(int width, int height);
- public abstract void ChangeVSyncMode(bool vsyncEnabled);
+ public abstract void ChangeVSyncMode(VSyncMode vSyncMode);
public abstract void SetAntiAliasing(AntiAliasing effect);
public abstract void SetScalingFilter(ScalingFilter scalerType);
public abstract void SetScalingFilterLevel(float scale);
diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs
index 70fcf278db..f75ead5886 100644
--- a/src/Ryujinx.HLE/HLEConfiguration.cs
+++ b/src/Ryujinx.HLE/HLEConfiguration.cs
@@ -9,6 +9,7 @@
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.UI;
using System;
+using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE
{
@@ -84,9 +85,14 @@ public class HLEConfiguration
internal readonly RegionCode Region;
///
- /// Control the initial state of the vertical sync in the SurfaceFlinger service.
+ /// Control the initial state of the present interval in the SurfaceFlinger service (previously Vsync).
///
- internal readonly bool EnableVsync;
+ internal readonly VSyncMode VSyncMode;
+
+ ///
+ /// Control the custom VSync interval, if enabled and active.
+ ///
+ internal readonly int CustomVSyncInterval;
///
/// Control the initial state of the docked mode.
@@ -195,7 +201,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
IHostUIHandler hostUIHandler,
SystemLanguage systemLanguage,
RegionCode region,
- bool enableVsync,
+ VSyncMode vSyncMode,
bool enableDockedMode,
bool enablePtc,
bool enableInternetAccess,
@@ -212,7 +218,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
MultiplayerMode multiplayerMode,
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
- string multiplayerLdnServer)
+ string multiplayerLdnServer,
+ int customVSyncInterval)
{
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
@@ -225,7 +232,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
HostUIHandler = hostUIHandler;
SystemLanguage = systemLanguage;
Region = region;
- EnableVsync = enableVsync;
+ VSyncMode = vSyncMode;
+ CustomVSyncInterval = customVSyncInterval;
EnableDockedMode = enableDockedMode;
EnablePtc = enablePtc;
EnableInternetAccess = enableInternetAccess;
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
index 4c17e7aedc..601e858674 100644
--- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
@@ -10,13 +10,12 @@
using System.Diagnostics;
using System.Linq;
using System.Threading;
+using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
class SurfaceFlinger : IConsumerListener, IDisposable
{
- private const int TargetFps = 60;
-
private readonly Switch _device;
private readonly Dictionary _layers;
@@ -32,6 +31,9 @@ class SurfaceFlinger : IConsumerListener, IDisposable
private readonly long _spinTicks;
private readonly long _1msTicks;
+ private VSyncMode _vSyncMode;
+ private long _targetVSyncInterval;
+
private int _swapInterval;
private int _swapIntervalDelay;
@@ -88,7 +90,8 @@ private void UpdateSwapInterval(int swapInterval)
}
else
{
- _ticksPerFrame = Stopwatch.Frequency / TargetFps;
+ _ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval;
+ _targetVSyncInterval = _device.TargetVSyncInterval;
}
}
@@ -370,15 +373,20 @@ public void Compose()
if (acquireStatus == Status.Success)
{
- // If device vsync is disabled, reflect the change.
- if (!_device.EnableDeviceVsync)
+ if (_device.VSyncMode == VSyncMode.Unbounded)
{
if (_swapInterval != 0)
{
UpdateSwapInterval(0);
+ _vSyncMode = _device.VSyncMode;
}
}
- else if (item.SwapInterval != _swapInterval)
+ else if (_device.VSyncMode != _vSyncMode)
+ {
+ UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
+ _vSyncMode = _device.VSyncMode;
+ }
+ else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
{
UpdateSwapInterval(item.SwapInterval);
}
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index d12cb8f777..4663521527 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -27,7 +27,11 @@ public class Switch : IDisposable
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
- public bool EnableDeviceVsync { get; set; }
+ public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
+ public bool CustomVSyncIntervalEnabled { get; set; } = false;
+ public int CustomVSyncInterval { get; set; }
+
+ public long TargetVSyncInterval { get; set; } = 60;
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
@@ -59,12 +63,14 @@ public Switch(HLEConfiguration configuration)
System.State.SetLanguage(Configuration.SystemLanguage);
System.State.SetRegion(Configuration.Region);
- EnableDeviceVsync = Configuration.EnableVsync;
+ VSyncMode = Configuration.VSyncMode;
+ CustomVSyncInterval = Configuration.CustomVSyncInterval;
System.State.DockedMode = Configuration.EnableDockedMode;
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
System.EnablePtc = Configuration.EnablePtc;
System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel;
System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode;
+ UpdateVSyncInterval();
#pragma warning restore IDE0055
}
@@ -75,6 +81,34 @@ public void ProcessFrame()
Gpu.GPFifo.DispatchCalls();
}
+ public void IncrementCustomVSyncInterval()
+ {
+ CustomVSyncInterval += 1;
+ UpdateVSyncInterval();
+ }
+
+ public void DecrementCustomVSyncInterval()
+ {
+ CustomVSyncInterval -= 1;
+ UpdateVSyncInterval();
+ }
+
+ public void UpdateVSyncInterval()
+ {
+ switch (VSyncMode)
+ {
+ case VSyncMode.Custom:
+ TargetVSyncInterval = CustomVSyncInterval;
+ break;
+ case VSyncMode.Switch:
+ TargetVSyncInterval = 60;
+ break;
+ case VSyncMode.Unbounded:
+ TargetVSyncInterval = 1;
+ break;
+ }
+ }
+
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) => Processes.LoadNca(ncaFile);
diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs
index 8078ca5e4e..4e2ad5b586 100644
--- a/src/Ryujinx.Headless.SDL2/Options.cs
+++ b/src/Ryujinx.Headless.SDL2/Options.cs
@@ -115,8 +115,11 @@ public class Options
[Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")]
public int FsGlobalAccessLogMode { get; set; }
- [Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")]
- public bool DisableVSync { get; set; }
+ [Option("vsync-mode", Required = false, Default = VSyncMode.Switch, HelpText = "Sets the emulated VSync mode (Switch, Unbounded, or Custom).")]
+ public VSyncMode VSyncMode { get; set; }
+
+ [Option("custom-refresh-rate", Required = false, Default = 90, HelpText = "Sets the custom refresh rate target value (integer).")]
+ public int CustomVSyncInterval { get; set; }
[Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")]
public bool DisableShaderCache { get; set; }
diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs
index e3bbd1e515..ff87a38457 100644
--- a/src/Ryujinx.Headless.SDL2/Program.cs
+++ b/src/Ryujinx.Headless.SDL2/Program.cs
@@ -563,7 +563,7 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re
window,
options.SystemLanguage,
options.SystemRegion,
- !options.DisableVSync,
+ options.VSyncMode,
!options.DisableDockedMode,
!options.DisablePTC,
options.EnableInternetAccess,
@@ -580,7 +580,8 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
"",
- "");
+ "",
+ options.CustomVSyncInterval);
return new Switch(configuration);
}
diff --git a/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs
index cd7715712e..c1dd3805f8 100644
--- a/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs
+++ b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs
@@ -3,7 +3,7 @@
namespace Ryujinx.Headless.SDL2
{
class StatusUpdatedEventArgs(
- bool vSyncEnabled,
+ string vSyncMode,
string dockedMode,
string aspectRatio,
string gameStatus,
@@ -11,7 +11,7 @@ class StatusUpdatedEventArgs(
string gpuName)
: EventArgs
{
- public bool VSyncEnabled = vSyncEnabled;
+ public string VSyncMode = vSyncMode;
public string DockedMode = dockedMode;
public string AspectRatio = aspectRatio;
public string GameStatus = gameStatus;
diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs
index 6d681e100d..2479ec1272 100644
--- a/src/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -314,7 +314,7 @@ public void Render()
}
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
- Device.EnableDeviceVsync,
+ Device.VSyncMode.ToString(),
dockedMode,
Device.Configuration.AspectRatio.ToText(),
$"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
index 80ba1b1866..027e1052b0 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
@@ -16,7 +17,7 @@ public class ConfigurationFileFormat
///
/// The current version of the file format
///
- public const int CurrentVersion = 56;
+ public const int CurrentVersion = 57;
///
/// Version of the configuration file format
@@ -191,8 +192,25 @@ public class ConfigurationFileFormat
///
/// Enables or disables Vertical Sync
///
+ /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)
+ /// TODO: Remove this when those older versions aren't in use anymore.
public bool EnableVsync { get; set; }
+ ///
+ /// Current VSync mode; 60 (Switch), unbounded ("Vsync off"), or custom
+ ///
+ public VSyncMode VSyncMode { get; set; }
+
+ ///
+ /// Enables or disables the custom present interval
+ ///
+ public bool EnableCustomVSyncInterval { get; set; }
+
+ ///
+ /// The custom present interval value
+ ///
+ public int CustomVSyncInterval { get; set; }
+
///
/// Enables or disables Shader cache
///
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs
index 65dd881068..a41ea2cd73 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs
@@ -82,7 +82,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = Key.F1,
+ ToggleVSyncMode = Key.F1,
};
configurationFileUpdated = true;
@@ -276,7 +276,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = Key.F1,
+ ToggleVSyncMode = Key.F1,
Screenshot = Key.F8,
};
@@ -289,7 +289,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = Key.F1,
+ ToggleVSyncMode = Key.F1,
Screenshot = Key.F8,
ShowUI = Key.F4,
};
@@ -332,7 +332,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode,
Screenshot = configurationFileFormat.Hotkeys.Screenshot,
ShowUI = configurationFileFormat.Hotkeys.ShowUI,
Pause = Key.F5,
@@ -347,7 +347,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode,
Screenshot = configurationFileFormat.Hotkeys.Screenshot,
ShowUI = configurationFileFormat.Hotkeys.ShowUI,
Pause = configurationFileFormat.Hotkeys.Pause,
@@ -421,7 +421,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode,
Screenshot = configurationFileFormat.Hotkeys.Screenshot,
ShowUI = configurationFileFormat.Hotkeys.ShowUI,
Pause = configurationFileFormat.Hotkeys.Pause,
@@ -448,7 +448,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
- ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode,
Screenshot = configurationFileFormat.Hotkeys.Screenshot,
ShowUI = configurationFileFormat.Hotkeys.ShowUI,
Pause = configurationFileFormat.Hotkeys.Pause,
@@ -611,6 +611,33 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
configurationFileUpdated = true;
}
+ if (configurationFileFormat.Version < 57)
+ {
+ Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 57.");
+
+ configurationFileFormat.VSyncMode = VSyncMode.Switch;
+ configurationFileFormat.EnableCustomVSyncInterval = false;
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVSyncMode = Key.F1,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUI = configurationFileFormat.Hotkeys.ShowUI,
+ Pause = configurationFileFormat.Hotkeys.Pause,
+ ToggleMute = configurationFileFormat.Hotkeys.ToggleMute,
+ ResScaleUp = configurationFileFormat.Hotkeys.ResScaleUp,
+ ResScaleDown = configurationFileFormat.Hotkeys.ResScaleDown,
+ VolumeUp = configurationFileFormat.Hotkeys.VolumeUp,
+ VolumeDown = configurationFileFormat.Hotkeys.VolumeDown,
+ CustomVSyncIntervalIncrement = Key.Unbound,
+ CustomVSyncIntervalDecrement = Key.Unbound,
+ };
+
+ configurationFileFormat.CustomVSyncInterval = 120;
+
+ configurationFileUpdated = true;
+ }
+
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@@ -646,7 +673,9 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu
ShowTitleBar.Value = configurationFileFormat.ShowTitleBar;
EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration;
HideCursor.Value = configurationFileFormat.HideCursor;
- Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
+ Graphics.VSyncMode.Value = configurationFileFormat.VSyncMode;
+ Graphics.EnableCustomVSyncInterval.Value = configurationFileFormat.EnableCustomVSyncInterval;
+ Graphics.CustomVSyncInterval.Value = configurationFileFormat.CustomVSyncInterval;
Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;
Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression;
Graphics.EnableMacroHLE.Value = configurationFileFormat.EnableMacroHLE;
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs
index 9be8f4df7a..f28ce0348c 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs
@@ -1,4 +1,4 @@
-using ARMeilleure;
+using ARMeilleure;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@@ -474,9 +474,19 @@ public class GraphicsSection
public ReactiveObject ShadersDumpPath { get; private set; }
///
- /// Enables or disables Vertical Sync
+ /// Toggles the present interval mode. Options are Switch (60Hz), Unbounded (previously Vsync off), and Custom, if enabled.
///
- public ReactiveObject EnableVsync { get; private set; }
+ public ReactiveObject VSyncMode { get; private set; }
+
+ ///
+ /// Enables or disables the custom present interval mode.
+ ///
+ public ReactiveObject EnableCustomVSyncInterval { get; private set; }
+
+ ///
+ /// Changes the custom present interval.
+ ///
+ public ReactiveObject CustomVSyncInterval { get; private set; }
///
/// Enables or disables Shader cache
@@ -536,8 +546,12 @@ public GraphicsSection()
AspectRatio = new ReactiveObject();
AspectRatio.LogChangesToValue(nameof(AspectRatio));
ShadersDumpPath = new ReactiveObject();
- EnableVsync = new ReactiveObject();
- EnableVsync.LogChangesToValue(nameof(EnableVsync));
+ VSyncMode = new ReactiveObject();
+ VSyncMode.LogChangesToValue(nameof(VSyncMode));
+ EnableCustomVSyncInterval = new ReactiveObject();
+ EnableCustomVSyncInterval.LogChangesToValue(nameof(EnableCustomVSyncInterval));
+ CustomVSyncInterval = new ReactiveObject();
+ CustomVSyncInterval.LogChangesToValue(nameof(CustomVSyncInterval));
EnableShaderCache = new ReactiveObject();
EnableShaderCache.LogChangesToValue(nameof(EnableShaderCache));
EnableTextureRecompression = new ReactiveObject();
diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
index b3012568e8..badb047df2 100644
--- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs
@@ -64,7 +64,9 @@ public ConfigurationFileFormat ToFileFormat()
ShowTitleBar = ShowTitleBar,
EnableHardwareAcceleration = EnableHardwareAcceleration,
HideCursor = HideCursor,
- EnableVsync = Graphics.EnableVsync,
+ VSyncMode = Graphics.VSyncMode,
+ EnableCustomVSyncInterval = Graphics.EnableCustomVSyncInterval,
+ CustomVSyncInterval = Graphics.CustomVSyncInterval,
EnableShaderCache = Graphics.EnableShaderCache,
EnableTextureRecompression = Graphics.EnableTextureRecompression,
EnableMacroHLE = Graphics.EnableMacroHLE,
@@ -179,7 +181,9 @@ public void LoadDefault()
ShowTitleBar.Value = !OperatingSystem.IsWindows();
EnableHardwareAcceleration.Value = true;
HideCursor.Value = HideCursorMode.OnIdle;
- Graphics.EnableVsync.Value = true;
+ Graphics.VSyncMode.Value = VSyncMode.Switch;
+ Graphics.CustomVSyncInterval.Value = 120;
+ Graphics.EnableCustomVSyncInterval.Value = false;
Graphics.EnableShaderCache.Value = true;
Graphics.EnableTextureRecompression.Value = false;
Graphics.EnableMacroHLE.Value = true;
@@ -240,7 +244,7 @@ public void LoadDefault()
Hid.EnableMouse.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys
{
- ToggleVsync = Key.F1,
+ ToggleVSyncMode = Key.F1,
ToggleMute = Key.F2,
Screenshot = Key.F8,
ShowUI = Key.F4,
diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs
index 7246be4b91..456b349ed8 100644
--- a/src/Ryujinx/AppHost.cs
+++ b/src/Ryujinx/AppHost.cs
@@ -57,6 +57,8 @@
using MouseButton = Ryujinx.Input.MouseButton;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Size = Avalonia.Size;
+using Switch = Ryujinx.HLE.Switch;
+using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.Ava
{
@@ -203,6 +205,9 @@ public AppHost(
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
+ ConfigurationState.Instance.Graphics.VSyncMode.Event += UpdateVSyncMode;
+ ConfigurationState.Instance.Graphics.CustomVSyncInterval.Event += UpdateCustomVSyncIntervalValue;
+ ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Event += UpdateCustomVSyncIntervalEnabled;
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
@@ -295,6 +300,66 @@ private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs
_renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value);
}
+ public void UpdateVSyncMode(object sender, ReactiveEventArgs e)
+ {
+ if (Device != null)
+ {
+ Device.VSyncMode = e.NewValue;
+ Device.UpdateVSyncInterval();
+ }
+ _renderer.Window?.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)e.NewValue);
+
+ _viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom);
+ }
+
+ public void VSyncModeToggle()
+ {
+ VSyncMode oldVSyncMode = Device.VSyncMode;
+ VSyncMode newVSyncMode = VSyncMode.Switch;
+ bool customVSyncIntervalEnabled = ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Value;
+
+ switch (oldVSyncMode)
+ {
+ case VSyncMode.Switch:
+ newVSyncMode = VSyncMode.Unbounded;
+ break;
+ case VSyncMode.Unbounded:
+ if (customVSyncIntervalEnabled)
+ {
+ newVSyncMode = VSyncMode.Custom;
+ }
+ else
+ {
+ newVSyncMode = VSyncMode.Switch;
+ }
+
+ break;
+ case VSyncMode.Custom:
+ newVSyncMode = VSyncMode.Switch;
+ break;
+ }
+
+ UpdateVSyncMode(this, new ReactiveEventArgs(oldVSyncMode, newVSyncMode));
+ }
+
+ private void UpdateCustomVSyncIntervalValue(object sender, ReactiveEventArgs e)
+ {
+ if (Device != null)
+ {
+ Device.TargetVSyncInterval = e.NewValue;
+ Device.UpdateVSyncInterval();
+ }
+ }
+
+ private void UpdateCustomVSyncIntervalEnabled(object sender, ReactiveEventArgs e)
+ {
+ if (Device != null)
+ {
+ Device.CustomVSyncIntervalEnabled = e.NewValue;
+ Device.UpdateVSyncInterval();
+ }
+ }
+
private void ShowCursor()
{
Dispatcher.UIThread.Post(() =>
@@ -509,12 +574,6 @@ private void UpdateDisableP2pState(object sender, ReactiveEventArgs e)
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
}
- public void ToggleVSync()
- {
- Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
- _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
- }
-
public void Stop()
{
_isActive = false;
@@ -868,7 +927,7 @@ private void InitializeSwitchInstance()
_viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value,
- ConfigurationState.Instance.Graphics.EnableVsync,
+ ConfigurationState.Instance.Graphics.VSyncMode,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
@@ -885,7 +944,8 @@ private void InitializeSwitchInstance()
ConfigurationState.Instance.Multiplayer.Mode,
ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
- ConfigurationState.Instance.Multiplayer.LdnServer));
+ ConfigurationState.Instance.Multiplayer.LdnServer,
+ ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value));
}
private static IHardwareDeviceDriver InitializeAudio()
@@ -1006,7 +1066,7 @@ private void RenderLoop()
Device.Gpu.SetGpuThread();
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
- _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
+ _renderer.Window.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)Device.VSyncMode);
while (_isActive)
{
@@ -1067,6 +1127,7 @@ public void UpdateStatus()
{
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued.
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
+ string vSyncMode = Device.VSyncMode.ToString();
UpdateShaderCount();
@@ -1076,7 +1137,7 @@ public void UpdateStatus()
}
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
- Device.EnableDeviceVsync,
+ vSyncMode,
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
dockedMode,
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
@@ -1179,8 +1240,16 @@ private bool UpdateFrame()
{
switch (currentHotkeyState)
{
- case KeyboardHotkeyState.ToggleVSync:
- ToggleVSync();
+ case KeyboardHotkeyState.ToggleVSyncMode:
+ VSyncModeToggle();
+ break;
+ case KeyboardHotkeyState.CustomVSyncIntervalDecrement:
+ Device.DecrementCustomVSyncInterval();
+ _viewModel.CustomVSyncInterval -= 1;
+ break;
+ case KeyboardHotkeyState.CustomVSyncIntervalIncrement:
+ Device.IncrementCustomVSyncInterval();
+ _viewModel.CustomVSyncInterval += 1;
break;
case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true;
@@ -1267,9 +1336,9 @@ private KeyboardHotkeyState GetHotkeyState()
{
KeyboardHotkeyState state = KeyboardHotkeyState.None;
- if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync))
+ if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVSyncMode))
{
- state = KeyboardHotkeyState.ToggleVSync;
+ state = KeyboardHotkeyState.ToggleVSyncMode;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
{
@@ -1303,6 +1372,14 @@ private KeyboardHotkeyState GetHotkeyState()
{
state = KeyboardHotkeyState.VolumeDown;
}
+ else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CustomVSyncIntervalIncrement))
+ {
+ state = KeyboardHotkeyState.CustomVSyncIntervalIncrement;
+ }
+ else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CustomVSyncIntervalDecrement))
+ {
+ state = KeyboardHotkeyState.CustomVSyncIntervalDecrement;
+ }
return state;
}
diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json
index 9354c8a412..7452387072 100644
--- a/src/Ryujinx/Assets/Locales/en_US.json
+++ b/src/Ryujinx/Assets/Locales/en_US.json
@@ -142,9 +142,19 @@
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish",
"SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese",
"SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese",
- "SettingsTabSystemSystemTimeZone": "System TimeZone:",
+ "SettingsTabSystemSystemTimeZone": "System Time Zone:",
"SettingsTabSystemSystemTime": "System Time:",
- "SettingsTabSystemEnableVsync": "VSync",
+ "SettingsTabSystemVSyncMode": "VSync:",
+ "SettingsTabSystemEnableCustomVSyncInterval": "Enable custom refresh rate (Experimental)",
+ "SettingsTabSystemVSyncModeSwitch": "Switch",
+ "SettingsTabSystemVSyncModeUnbounded": "Unbounded",
+ "SettingsTabSystemVSyncModeCustom": "Custom Refresh Rate",
+ "SettingsTabSystemVSyncModeTooltip": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.",
+ "SettingsTabSystemVSyncModeTooltipCustom": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom' emulates the specified custom refresh rate.",
+ "SettingsTabSystemEnableCustomVSyncIntervalTooltip": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.",
+ "SettingsTabSystemCustomVSyncIntervalValueTooltip": "The custom refresh rate target value.",
+ "SettingsTabSystemCustomVSyncIntervalSliderTooltip": "The custom refresh rate, as a percentage of the normal Switch refresh rate.",
+ "SettingsTabSystemCustomVSyncIntervalValue": "Custom Refresh Rate Value:",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC cache",
"SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks",
@@ -153,6 +163,7 @@
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2",
+ "SettingsTabSystemCustomVSyncInterval": "Interval",
"SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": "May cause instability",
"SettingsTabSystemDramSize": "DRAM size:",
@@ -719,11 +730,13 @@
"RyujinxUpdater": "Ryujinx Updater",
"SettingsTabHotkeys": "Keyboard Hotkeys",
"SettingsTabHotkeysHotkeys": "Keyboard Hotkeys",
- "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:",
+ "SettingsTabHotkeysToggleVSyncModeHotkey": "Toggle VSync mode:",
"SettingsTabHotkeysScreenshotHotkey": "Screenshot:",
"SettingsTabHotkeysShowUiHotkey": "Show UI:",
"SettingsTabHotkeysPauseHotkey": "Pause:",
"SettingsTabHotkeysToggleMuteHotkey": "Mute:",
+ "SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey": "Raise custom refresh rate",
+ "SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey": "Lower custom refresh rate",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings",
"SettingsSelectThemeFileDialogTitle": "Select Theme File",
diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml
index 0f323f84b2..056eba2282 100644
--- a/src/Ryujinx/Assets/Styles/Themes.xaml
+++ b/src/Ryujinx/Assets/Styles/Themes.xaml
@@ -26,8 +26,9 @@
#b3ffffff
#80cccccc
#A0000000
- #FF2EEAC9
- #FFFF4554
+ #FF2EEAC9
+ #FFFF4554
+ #6483F5
_toggleVsync;
+ get => _toggleVSyncMode;
set
{
- _toggleVsync = value;
+ _toggleVSyncMode = value;
OnPropertyChanged();
}
}
@@ -104,11 +104,33 @@ public Key VolumeDown
}
}
+ private Key _customVSyncIntervalIncrement;
+ public Key CustomVSyncIntervalIncrement
+ {
+ get => _customVSyncIntervalIncrement;
+ set
+ {
+ _customVSyncIntervalIncrement = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private Key _customVSyncIntervalDecrement;
+ public Key CustomVSyncIntervalDecrement
+ {
+ get => _customVSyncIntervalDecrement;
+ set
+ {
+ _customVSyncIntervalDecrement = value;
+ OnPropertyChanged();
+ }
+ }
+
public HotkeyConfig(KeyboardHotkeys config)
{
if (config != null)
{
- ToggleVsync = config.ToggleVsync;
+ ToggleVSyncMode = config.ToggleVSyncMode;
Screenshot = config.Screenshot;
ShowUI = config.ShowUI;
Pause = config.Pause;
@@ -117,6 +139,8 @@ public HotkeyConfig(KeyboardHotkeys config)
ResScaleDown = config.ResScaleDown;
VolumeUp = config.VolumeUp;
VolumeDown = config.VolumeDown;
+ CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
+ CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
}
}
@@ -124,7 +148,7 @@ public KeyboardHotkeys GetConfig()
{
var config = new KeyboardHotkeys
{
- ToggleVsync = ToggleVsync,
+ ToggleVSyncMode = ToggleVSyncMode,
Screenshot = Screenshot,
ShowUI = ShowUI,
Pause = Pause,
@@ -133,6 +157,8 @@ public KeyboardHotkeys GetConfig()
ResScaleDown = ResScaleDown,
VolumeUp = VolumeUp,
VolumeDown = VolumeDown,
+ CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
+ CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
};
return config;
diff --git a/src/Ryujinx/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs
index 55408ac3ad..cfc397c6e5 100644
--- a/src/Ryujinx/UI/Models/SaveModel.cs
+++ b/src/Ryujinx/UI/Models/SaveModel.cs
@@ -47,7 +47,7 @@ public SaveModel(SaveDataInfo info)
TitleId = info.ProgramId;
UserId = info.UserId;
- var appData = App.MainWindow.ViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase));
+ var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase));
InGameList = appData != null;
diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs
index 40f783c448..6f0f5ab5d3 100644
--- a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs
+++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs
@@ -4,18 +4,17 @@ namespace Ryujinx.Ava.UI.Models
{
internal class StatusUpdatedEventArgs : EventArgs
{
- public bool VSyncEnabled { get; }
+ public string VSyncMode { get; }
public string VolumeStatus { get; }
public string AspectRatio { get; }
public string DockedMode { get; }
public string FifoStatus { get; }
public string GameStatus { get; }
-
public uint ShaderCount { get; }
- public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount)
+ public StatusUpdatedEventArgs(string vSyncMode, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount)
{
- VSyncEnabled = vSyncEnabled;
+ VSyncMode = vSyncMode;
VolumeStatus = volumeStatus;
DockedMode = dockedMode;
AspectRatio = aspectRatio;
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index f1587a0ff3..824fdd717a 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -63,6 +63,7 @@ public class MainWindowViewModel : BaseModel
private string _searchText;
private Timer _searchTimer;
private string _dockedStatusText;
+ private string _vSyncModeText;
private string _fifoStatusText;
private string _gameStatusText;
private string _volumeStatusText;
@@ -80,7 +81,7 @@ public class MainWindowViewModel : BaseModel
private bool _showStatusSeparator;
private Brush _progressBarForegroundColor;
private Brush _progressBarBackgroundColor;
- private Brush _vsyncColor;
+ private Brush _vSyncModeColor;
private byte[] _selectedIcon;
private bool _isAppletMenuActive;
private int _statusBarProgressMaximum;
@@ -111,6 +112,8 @@ public class MainWindowViewModel : BaseModel
private WindowState _windowState;
private double _windowWidth;
private double _windowHeight;
+ private int _customVSyncInterval;
+ private int _customVSyncIntervalPercentageProxy;
private bool _isActive;
private bool _isSubMenuOpen;
@@ -145,6 +148,7 @@ public MainWindowViewModel()
Volume = ConfigurationState.Instance.System.AudioVolume;
}
+ CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
}
public void Initialize(
@@ -447,17 +451,87 @@ public Brush ProgressBarForegroundColor
}
}
- public Brush VsyncColor
+ public Brush VSyncModeColor
{
- get => _vsyncColor;
+ get => _vSyncModeColor;
set
{
- _vsyncColor = value;
+ _vSyncModeColor = value;
OnPropertyChanged();
}
}
+ public bool ShowCustomVSyncIntervalPicker
+ {
+ get
+ {
+ if (_isGameRunning)
+ {
+ return AppHost.Device.VSyncMode ==
+ VSyncMode.Custom;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ set
+ {
+ OnPropertyChanged();
+ }
+ }
+
+ public int CustomVSyncIntervalPercentageProxy
+ {
+ get => _customVSyncIntervalPercentageProxy;
+ set
+ {
+ int newInterval = (int)((value / 100f) * 60);
+ _customVSyncInterval = newInterval;
+ _customVSyncIntervalPercentageProxy = value;
+ if (_isGameRunning)
+ {
+ AppHost.Device.CustomVSyncInterval = newInterval;
+ AppHost.Device.UpdateVSyncInterval();
+ }
+ OnPropertyChanged((nameof(CustomVSyncInterval)));
+ OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
+ }
+ }
+
+ public string CustomVSyncIntervalPercentageText
+ {
+ get
+ {
+ string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
+ return text;
+ }
+ set
+ {
+
+ }
+ }
+
+ public int CustomVSyncInterval
+ {
+ get => _customVSyncInterval;
+ set
+ {
+ _customVSyncInterval = value;
+ int newPercent = (int)((value / 60f) * 100);
+ _customVSyncIntervalPercentageProxy = newPercent;
+ if (_isGameRunning)
+ {
+ AppHost.Device.CustomVSyncInterval = value;
+ AppHost.Device.UpdateVSyncInterval();
+ }
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
+ OnPropertyChanged();
+ }
+ }
+
public byte[] SelectedIcon
{
get => _selectedIcon;
@@ -578,6 +652,17 @@ public string BackendText
}
}
+ public string VSyncModeText
+ {
+ get => _vSyncModeText;
+ set
+ {
+ _vSyncModeText = value;
+
+ OnPropertyChanged();
+ }
+ }
+
public string DockedStatusText
{
get => _dockedStatusText;
@@ -1292,17 +1377,18 @@ private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
- Application.Current!.Styles.TryGetResource(args.VSyncEnabled
- ? "VsyncEnabled"
- : "VsyncDisabled",
+ Application.Current!.Styles.TryGetResource(args.VSyncMode,
Application.Current.ActualThemeVariant,
out object color);
if (color is Color clr)
{
- VsyncColor = new SolidColorBrush(clr);
+ VSyncModeColor = new SolidColorBrush(clr);
}
+ VSyncModeText = args.VSyncMode == "Custom" ? "Custom" : "VSync";
+ ShowCustomVSyncIntervalPicker =
+ args.VSyncMode == VSyncMode.Custom.ToString();
DockedStatusText = args.DockedMode;
AspectRatioStatusText = args.AspectRatio;
GameStatusText = args.GameStatus;
@@ -1495,6 +1581,27 @@ public void ToggleDockMode()
}
}
+ public void ToggleVSyncMode()
+ {
+ AppHost.VSyncModeToggle();
+ OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker));
+ }
+
+ public void VSyncModeSettingChanged()
+ {
+ if (_isGameRunning)
+ {
+ AppHost.Device.CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
+ AppHost.Device.UpdateVSyncInterval();
+ }
+
+ CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
+ OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker));
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
+ OnPropertyChanged(nameof(CustomVSyncInterval));
+ }
+
public async Task ExitCurrentState()
{
if (WindowState is WindowState.FullScreen)
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index 2da252d002..a5abeb36b5 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -52,6 +52,10 @@ public partial class SettingsViewModel : BaseModel
private int _graphicsBackendIndex;
private int _scalingFilter;
private int _scalingFilterLevel;
+ private int _customVSyncInterval;
+ private bool _enableCustomVSyncInterval;
+ private int _customVSyncIntervalPercentageProxy;
+ private VSyncMode _vSyncMode;
public event Action CloseWindow;
public event Action SaveSettingsEvent;
@@ -154,7 +158,74 @@ public bool AutoloadDirectoryChanged
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
- public bool EnableVsync { get; set; }
+ public VSyncMode VSyncMode
+ {
+ get => _vSyncMode;
+ set
+ {
+ if (value == VSyncMode.Custom ||
+ value == VSyncMode.Switch ||
+ value == VSyncMode.Unbounded)
+ {
+ _vSyncMode = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public int CustomVSyncIntervalPercentageProxy
+ {
+ get => _customVSyncIntervalPercentageProxy;
+ set
+ {
+ int newInterval = (int)((value / 100f) * 60);
+ _customVSyncInterval = newInterval;
+ _customVSyncIntervalPercentageProxy = value;
+ OnPropertyChanged((nameof(CustomVSyncInterval)));
+ OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
+ }
+ }
+
+ public string CustomVSyncIntervalPercentageText
+ {
+ get
+ {
+ string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
+ return text;
+ }
+ }
+
+ public bool EnableCustomVSyncInterval
+ {
+ get => _enableCustomVSyncInterval;
+ set
+ {
+ _enableCustomVSyncInterval = value;
+ if (_vSyncMode == VSyncMode.Custom && !value)
+ {
+ VSyncMode = VSyncMode.Switch;
+ }
+ else if (value)
+ {
+ VSyncMode = VSyncMode.Custom;
+ }
+ OnPropertyChanged();
+ }
+ }
+
+ public int CustomVSyncInterval
+ {
+ get => _customVSyncInterval;
+ set
+ {
+ _customVSyncInterval = value;
+ int newPercent = (int)((value / 60f) * 100);
+ _customVSyncIntervalPercentageProxy = newPercent;
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
+ OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
+ OnPropertyChanged();
+ }
+ }
public bool EnablePptc { get; set; }
public bool EnableLowPowerPptc { get; set; }
public bool EnableInternetAccess { get; set; }
@@ -484,7 +555,9 @@ public void LoadCurrentConfiguration()
CurrentDate = currentDateTime.Date;
CurrentTime = currentDateTime.TimeOfDay;
- EnableVsync = config.Graphics.EnableVsync;
+ EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval.Value;
+ CustomVSyncInterval = config.Graphics.CustomVSyncInterval;
+ VSyncMode = config.Graphics.VSyncMode;
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
DramSize = config.System.DramSize;
IgnoreMissingServices = config.System.IgnoreMissingServices;
@@ -590,7 +663,9 @@ public void SaveSettings()
}
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
- config.Graphics.EnableVsync.Value = EnableVsync;
+ config.Graphics.VSyncMode.Value = VSyncMode;
+ config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
+ config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.DramSize.Value = DramSize;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
@@ -660,6 +735,7 @@ public void SaveSettings()
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
+ MainWindow.MainWindowViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke();
diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml
index 0e0526f494..66c5c12594 100644
--- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml
+++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml
@@ -79,15 +79,69 @@
MaxHeight="18"
Orientation="Horizontal">
+ PointerReleased="VSyncMode_PointerReleased"
+ Text="{Binding VSyncModeText}"
+ TextAlignment="Start"/>
+
-
-
-
+
+
+
@@ -103,6 +103,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs
index fb0fe2bb12..609f616335 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs
+++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs
@@ -82,8 +82,8 @@ private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
switch (button.Name)
{
- case "ToggleVsync":
- viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType();
+ case "ToggleVSyncMode":
+ viewModel.KeyboardHotkey.ToggleVSyncMode = buttonValue.AsHidType();
break;
case "Screenshot":
viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType();
@@ -109,6 +109,12 @@ private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
case "VolumeDown":
viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType();
break;
+ case "CustomVSyncIntervalIncrement":
+ viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = buttonValue.AsHidType();
+ break;
+ case "CustomVSyncIntervalDecrement":
+ viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = buttonValue.AsHidType();
+ break;
}
}
};
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml
index 4fe57b4258..99819fdff2 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
@@ -181,11 +182,78 @@
Width="350"
ToolTip.Tip="{ext:Locale TimeTooltip}" />
-
+
-
+ VerticalAlignment="Center"
+ Text="{ext:Locale SettingsTabSystemVSyncMode}"
+ ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}"
+ Width="250" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
index 829db4bc98..059f99a60a 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
@@ -38,6 +38,8 @@ namespace Ryujinx.Ava.UI.Windows
{
public partial class MainWindow : StyleableAppWindow
{
+ internal static MainWindowViewModel MainWindowViewModel { get; private set; }
+
public MainWindowViewModel ViewModel { get; }
internal readonly AvaHostUIHandler UiHandler;
@@ -73,7 +75,7 @@ public partial class MainWindow : StyleableAppWindow
public MainWindow()
{
- DataContext = ViewModel = new MainWindowViewModel
+ DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel
{
Window = this
};