diff --git a/src/Ryujinx.Common/Configuration/VSyncMode.cs b/src/Ryujinx.Common/Configuration/VSyncMode.cs index b2818eb7a..3cfc9549e 100644 --- a/src/Ryujinx.Common/Configuration/VSyncMode.cs +++ b/src/Ryujinx.Common/Configuration/VSyncMode.cs @@ -13,10 +13,10 @@ namespace Ryujinx.Common.Configuration public static VSyncMode Next(this VSyncMode vsync, bool customEnabled = false) => vsync switch { - VSyncMode.Switch => customEnabled ? VSyncMode.Custom : VSyncMode.Unbounded, - VSyncMode.Custom => VSyncMode.Unbounded, + VSyncMode.Switch => VSyncMode.Unbounded, VSyncMode.Unbounded => VSyncMode.Vrr, VSyncMode.Vrr => VSyncMode.Switch, + VSyncMode.Custom => VSyncMode.Switch, // legacy mode, no longer in the cycle _ => VSyncMode.Switch }; } diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs index 971778175..99e49af71 100644 --- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -372,7 +372,25 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger if (acquireStatus == Status.Success) { - if (_device.VSyncMode == VSyncMode.Unbounded) + bool vrrCapped = _device.VSyncMode == VSyncMode.Vrr + && _device.CustomVSyncIntervalEnabled + && _device.CustomVSyncInterval > 0; + + if (vrrCapped) + { + // VRR with a per-game FPS cap: force a paced interval (swap interval 1 -> + // _ticksPerFrame derived from TargetVSyncInterval) instead of running + // unbounded. The present mode stays FifoRelaxed (chosen from VSyncMode.Vrr + // in Window.cs), so the display still follows the framerate, just capped. + if (_swapInterval != 1 + || _device.TargetVSyncInterval != _targetVSyncInterval + || _vSyncMode != _device.VSyncMode) + { + UpdateSwapInterval(1); + _vSyncMode = _device.VSyncMode; + } + } + else if (_device.VSyncMode == VSyncMode.Unbounded || _device.VSyncMode == VSyncMode.Vrr) { if (_swapInterval != 0) { diff --git a/src/Ryujinx.HLE/HleConfiguration.cs b/src/Ryujinx.HLE/HleConfiguration.cs index e2f95ede7..f9999118a 100644 --- a/src/Ryujinx.HLE/HleConfiguration.cs +++ b/src/Ryujinx.HLE/HleConfiguration.cs @@ -93,6 +93,11 @@ namespace Ryujinx.HLE /// internal readonly int CustomVSyncInterval; + /// + /// Whether the custom VSync interval is enabled. Also acts as the per-game VRR FPS cap toggle. + /// + internal readonly bool EnableCustomVSyncInterval; + /// /// Control the initial state of the docked mode. /// @@ -241,6 +246,7 @@ namespace Ryujinx.HLE ushort gdbStubPort, bool debuggerSuspendOnStart, int customVSyncInterval, + bool enableCustomVSyncInterval = false, EnabledDirtyHack[] dirtyHacks = null) { MemoryConfiguration = memoryConfiguration; @@ -248,6 +254,7 @@ namespace Ryujinx.HLE Region = region; VSyncMode = vSyncMode; CustomVSyncInterval = customVSyncInterval; + EnableCustomVSyncInterval = enableCustomVSyncInterval; EnableDockedMode = enableDockedMode; EnablePtc = enablePtc; TickScalar = tickScalar; diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 90af47988..d60d7518f 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -98,6 +98,7 @@ namespace Ryujinx.HLE VSyncMode = Configuration.VSyncMode; CustomVSyncInterval = Configuration.CustomVSyncInterval; + CustomVSyncIntervalEnabled = Configuration.EnableCustomVSyncInterval; TickScalar = TurboMode ? Configuration.TickScalar : ITickSource.RealityTickScalar; System.State.DockedMode = Configuration.EnableDockedMode; System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; @@ -147,6 +148,13 @@ namespace Ryujinx.HLE case VSyncMode.Unbounded: TargetVSyncInterval = 1; break; + case VSyncMode.Vrr: + // VRR runs unbounded by default (interval 1). If the per-game FPS cap is + // enabled, pace to that value instead (e.g. TOTK capped at 60). + TargetVSyncInterval = (CustomVSyncIntervalEnabled && CustomVSyncInterval > 0) + ? CustomVSyncInterval + : 1; + break; } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs index c4d6ce273..66632e5d1 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs @@ -987,6 +987,7 @@ namespace Ryujinx.Ava.Systems.Configuration Debug.GdbStubPort, Debug.DebuggerSuspendOnStart, Graphics.CustomVSyncInterval, + Graphics.EnableCustomVSyncInterval, Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null); } } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 0f6e473e3..68ed064b0 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -260,6 +260,45 @@ namespace Ryujinx.Ava.UI.ViewModels OnPropertyChanged(); } } + + // --- Clean VSync / VRR UI (Roofer Edition) --- + // Dropdown positions: 0 = Switch, 1 = Unbounded, 2 = VRR. We translate position <-> enum + // here so we can skip the legacy "Custom" enum value (2) and still bind by SelectedIndex. + public int VSyncModeIndex + { + get => _vSyncMode switch + { + VSyncMode.Unbounded => 1, + VSyncMode.Vrr => 2, + _ => 0, + }; + set + { + _vSyncMode = value switch + { + 1 => VSyncMode.Unbounded, + 2 => VSyncMode.Vrr, + _ => VSyncMode.Switch, + }; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowVrrCapPicker)); + } + } + + // The VRR FPS cap picker is only relevant when VRR is the selected mode. + public bool ShowVrrCapPicker => _vSyncMode == VSyncMode.Vrr; + + // 0 = Off (unbounded VRR), 1 = 30, 2 = 60, 3 = 120. + public int VrrFpsCapIndex + { + get; + set + { + field = value; + OnPropertyChanged(); + } + } + public bool EnablePptc { get; set; } public bool EnableLowPowerPptc { get; set; } @@ -743,9 +782,23 @@ namespace Ryujinx.Ava.UI.ViewModels MatchSystemTime = config.System.MatchSystemTime; - EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval; CustomVSyncInterval = config.Graphics.CustomVSyncInterval; - VSyncMode = config.Graphics.VSyncMode; + + // VSync / VRR (Roofer clean UI): set the backing field directly so VRR loads (the + // legacy VSyncMode setter rejects it), and derive the VRR cap picker from the stored + // custom-interval values. + _vSyncMode = config.Graphics.VSyncMode.Value; + VrrFpsCapIndex = config.Graphics.EnableCustomVSyncInterval.Value + ? config.Graphics.CustomVSyncInterval.Value switch + { + 30 => 1, + 60 => 2, + 120 => 3, + _ => 0, + } + : 0; + OnPropertyChanged(nameof(VSyncModeIndex)); + OnPropertyChanged(nameof(ShowVrrCapPicker)); EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; DramSize = config.System.DramSize; IgnoreMissingServices = config.System.IgnoreMissingServices; @@ -879,9 +932,22 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.TickScalar.Value = TurboMultiplier; // Graphics - config.Graphics.VSyncMode.Value = VSyncMode; - config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval; - config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval; + config.Graphics.VSyncMode.Value = _vSyncMode; + + // VRR FPS cap -> reuse the custom-interval plumbing the engine already reads + // (EnableCustomVSyncInterval = cap on, CustomVSyncInterval = the capped fps). + bool vrrCapOn = _vSyncMode == VSyncMode.Vrr && VrrFpsCapIndex > 0; + config.Graphics.EnableCustomVSyncInterval.Value = vrrCapOn; + if (vrrCapOn) + { + config.Graphics.CustomVSyncInterval.Value = VrrFpsCapIndex switch + { + 1 => 30, + 2 => 60, + 3 => 120, + _ => 60, + }; + } config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex; config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); config.Graphics.EnableShaderCache.Value = EnableShaderCache; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml index 2414fb7f6..cd0fded02 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml @@ -178,21 +178,7 @@ ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}" Width="250" /> - - - - - @@ -200,32 +186,26 @@ Content="{ext:Locale SettingsTabSystemVSyncModeSwitch}" /> + - - - + + + + + + - - -