VRR: optional per-game FPS cap
Adds a per-game FPS cap for the VRR present mode so games whose logic is tied to the frame rate (e.g. TOTK's menu cursor running wild at 200 fps) can be limited, while keeping G-Sync/FreeSync smoothing. The cap is off by default (VRR stays unbounded); 30/60/120 are exposed. Reuses the existing custom-VSync-interval plumbing: when VSyncMode is Vrr and the custom interval is enabled, SurfaceFlinger paces frames at that value (forcing a non-zero swap interval even when a Dynamic FPS mod submits 0) instead of running unbounded, while Window.cs keeps FifoRelaxed so the display still follows the frame rate. EnableCustomVSyncInterval is now read at Switch construction so the per-game setting applies at launch. UI: the VSync dropdown now offers Switch / Unbounded / VRR, with a VRR FPS cap picker shown only for VRR. The legacy Custom mode is hidden (kept in the enum for config compatibility) and the percentage slider / experimental checkbox are removed.
This commit is contained in:
@@ -13,10 +13,10 @@ namespace Ryujinx.Common.Configuration
|
|||||||
public static VSyncMode Next(this VSyncMode vsync, bool customEnabled = false) =>
|
public static VSyncMode Next(this VSyncMode vsync, bool customEnabled = false) =>
|
||||||
vsync switch
|
vsync switch
|
||||||
{
|
{
|
||||||
VSyncMode.Switch => customEnabled ? VSyncMode.Custom : VSyncMode.Unbounded,
|
VSyncMode.Switch => VSyncMode.Unbounded,
|
||||||
VSyncMode.Custom => VSyncMode.Unbounded,
|
|
||||||
VSyncMode.Unbounded => VSyncMode.Vrr,
|
VSyncMode.Unbounded => VSyncMode.Vrr,
|
||||||
VSyncMode.Vrr => VSyncMode.Switch,
|
VSyncMode.Vrr => VSyncMode.Switch,
|
||||||
|
VSyncMode.Custom => VSyncMode.Switch, // legacy mode, no longer in the cycle
|
||||||
_ => VSyncMode.Switch
|
_ => VSyncMode.Switch
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,7 +372,25 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
if (acquireStatus == Status.Success)
|
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)
|
if (_swapInterval != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ namespace Ryujinx.HLE
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly int CustomVSyncInterval;
|
internal readonly int CustomVSyncInterval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the custom VSync interval is enabled. Also acts as the per-game VRR FPS cap toggle.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly bool EnableCustomVSyncInterval;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the docked mode.
|
/// Control the initial state of the docked mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -241,6 +246,7 @@ namespace Ryujinx.HLE
|
|||||||
ushort gdbStubPort,
|
ushort gdbStubPort,
|
||||||
bool debuggerSuspendOnStart,
|
bool debuggerSuspendOnStart,
|
||||||
int customVSyncInterval,
|
int customVSyncInterval,
|
||||||
|
bool enableCustomVSyncInterval = false,
|
||||||
EnabledDirtyHack[] dirtyHacks = null)
|
EnabledDirtyHack[] dirtyHacks = null)
|
||||||
{
|
{
|
||||||
MemoryConfiguration = memoryConfiguration;
|
MemoryConfiguration = memoryConfiguration;
|
||||||
@@ -248,6 +254,7 @@ namespace Ryujinx.HLE
|
|||||||
Region = region;
|
Region = region;
|
||||||
VSyncMode = vSyncMode;
|
VSyncMode = vSyncMode;
|
||||||
CustomVSyncInterval = customVSyncInterval;
|
CustomVSyncInterval = customVSyncInterval;
|
||||||
|
EnableCustomVSyncInterval = enableCustomVSyncInterval;
|
||||||
EnableDockedMode = enableDockedMode;
|
EnableDockedMode = enableDockedMode;
|
||||||
EnablePtc = enablePtc;
|
EnablePtc = enablePtc;
|
||||||
TickScalar = tickScalar;
|
TickScalar = tickScalar;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ namespace Ryujinx.HLE
|
|||||||
|
|
||||||
VSyncMode = Configuration.VSyncMode;
|
VSyncMode = Configuration.VSyncMode;
|
||||||
CustomVSyncInterval = Configuration.CustomVSyncInterval;
|
CustomVSyncInterval = Configuration.CustomVSyncInterval;
|
||||||
|
CustomVSyncIntervalEnabled = Configuration.EnableCustomVSyncInterval;
|
||||||
TickScalar = TurboMode ? Configuration.TickScalar : ITickSource.RealityTickScalar;
|
TickScalar = TurboMode ? Configuration.TickScalar : ITickSource.RealityTickScalar;
|
||||||
System.State.DockedMode = Configuration.EnableDockedMode;
|
System.State.DockedMode = Configuration.EnableDockedMode;
|
||||||
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
|
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
|
||||||
@@ -147,6 +148,13 @@ namespace Ryujinx.HLE
|
|||||||
case VSyncMode.Unbounded:
|
case VSyncMode.Unbounded:
|
||||||
TargetVSyncInterval = 1;
|
TargetVSyncInterval = 1;
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -987,6 +987,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Debug.GdbStubPort,
|
Debug.GdbStubPort,
|
||||||
Debug.DebuggerSuspendOnStart,
|
Debug.DebuggerSuspendOnStart,
|
||||||
Graphics.CustomVSyncInterval,
|
Graphics.CustomVSyncInterval,
|
||||||
|
Graphics.EnableCustomVSyncInterval,
|
||||||
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,45 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged();
|
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 EnablePptc { get; set; }
|
||||||
public bool EnableLowPowerPptc { get; set; }
|
public bool EnableLowPowerPptc { get; set; }
|
||||||
|
|
||||||
@@ -743,9 +782,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
MatchSystemTime = config.System.MatchSystemTime;
|
MatchSystemTime = config.System.MatchSystemTime;
|
||||||
|
|
||||||
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
|
|
||||||
CustomVSyncInterval = config.Graphics.CustomVSyncInterval;
|
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;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
DramSize = config.System.DramSize;
|
DramSize = config.System.DramSize;
|
||||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||||
@@ -879,9 +932,22 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.TickScalar.Value = TurboMultiplier;
|
config.System.TickScalar.Value = TurboMultiplier;
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
config.Graphics.VSyncMode.Value = VSyncMode;
|
config.Graphics.VSyncMode.Value = _vSyncMode;
|
||||||
config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
|
|
||||||
config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
|
// 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.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||||
|
|||||||
@@ -178,21 +178,7 @@
|
|||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}"
|
ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}"
|
||||||
Width="250" />
|
Width="250" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
IsVisible="{Binding EnableCustomVSyncInterval}"
|
SelectedIndex="{Binding VSyncModeIndex}"
|
||||||
SelectedIndex="{Binding VSyncMode}"
|
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltipCustom}"
|
|
||||||
HorizontalContentAlignment="Left"
|
|
||||||
Width="350">
|
|
||||||
<ComboBoxItem
|
|
||||||
Content="{ext:Locale SettingsTabSystemVSyncModeSwitch}" />
|
|
||||||
<ComboBoxItem
|
|
||||||
Content="{ext:Locale SettingsTabSystemVSyncModeUnbounded}" />
|
|
||||||
<ComboBoxItem
|
|
||||||
Content="{ext:Locale SettingsTabSystemVSyncModeCustom}" />
|
|
||||||
</ComboBox>
|
|
||||||
<ComboBox
|
|
||||||
IsVisible="{Binding !EnableCustomVSyncInterval}"
|
|
||||||
SelectedIndex="{Binding VSyncMode}"
|
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}"
|
ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}"
|
||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
Width="350">
|
Width="350">
|
||||||
@@ -200,32 +186,26 @@
|
|||||||
Content="{ext:Locale SettingsTabSystemVSyncModeSwitch}" />
|
Content="{ext:Locale SettingsTabSystemVSyncModeSwitch}" />
|
||||||
<ComboBoxItem
|
<ComboBoxItem
|
||||||
Content="{ext:Locale SettingsTabSystemVSyncModeUnbounded}" />
|
Content="{ext:Locale SettingsTabSystemVSyncModeUnbounded}" />
|
||||||
|
<ComboBoxItem
|
||||||
|
Content="VRR" />
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel IsVisible="{Binding EnableCustomVSyncInterval}"
|
<StackPanel IsVisible="{Binding ShowVrrCapPicker}"
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{ext:Locale SettingsTabSystemCustomVSyncIntervalPercentage}"
|
Text="Plafond VRR (FPS)"
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemCustomVSyncIntervalValueTooltip}"
|
|
||||||
Width="250" />
|
Width="250" />
|
||||||
<Slider Value="{Binding CustomVSyncIntervalPercentageProxy}"
|
<ComboBox
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemCustomVSyncIntervalSliderTooltip}"
|
SelectedIndex="{Binding VrrFpsCapIndex}"
|
||||||
MinWidth="175"
|
HorizontalContentAlignment="Left"
|
||||||
Margin="10,-3,0,0"
|
Width="350">
|
||||||
Height="32"
|
<ComboBoxItem Content="Off" />
|
||||||
Padding="0,-5"
|
<ComboBoxItem Content="30" />
|
||||||
TickFrequency="1"
|
<ComboBoxItem Content="60" />
|
||||||
IsSnapToTickEnabled="True"
|
<ComboBoxItem Content="120" />
|
||||||
LargeChange="10"
|
</ComboBox>
|
||||||
SmallChange="1"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Minimum="10"
|
|
||||||
Maximum="400" />
|
|
||||||
<TextBlock Margin="5,0"
|
|
||||||
Width="40"
|
|
||||||
Text="{Binding CustomVSyncIntervalPercentageText}"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
|
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -282,11 +262,6 @@
|
|||||||
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
|
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox
|
|
||||||
IsChecked="{Binding EnableCustomVSyncInterval}"
|
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabSystemEnableCustomVSyncIntervalTooltip}">
|
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemEnableCustomVSyncInterval}" />
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding SkipUserProfiles}"
|
IsChecked="{Binding SkipUserProfiles}"
|
||||||
ToolTip.Tip="{ext:Locale SkipUserProfilesTooltip}">
|
ToolTip.Tip="{ext:Locale SkipUserProfilesTooltip}">
|
||||||
|
|||||||
Reference in New Issue
Block a user