Skip to content

Commit

Permalink
Add custom refresh rate mode to VSync option (#238)
Browse files Browse the repository at this point in the history
Rebased @jcm93's refreshinterval branch:
https://github.com/jcm93/Ryujinx/tree/refreshinterval

The option is placed under System/Hacks. Disabled, it's the default
Ryujinx behavior. Enabled, the behavior is shown in the attached
screenshots. If a framerate is too high or low, you can adjust the value
where you normally toggle VSync on and off. It will also cycle through
the default on/off toggles.

Also, in order to reduce clutter, I made an adjustment to remove the
target FPS and only show the percentage.

---------

Co-authored-by: jcm <6864788+jcm93@users.noreply.github.com>
  • Loading branch information
KeatonTheBot and jcm93 authored Nov 25, 2024
1 parent 7e16fcc commit 2e6794e
Show file tree
Hide file tree
Showing 34 changed files with 678 additions and 110 deletions.
4 changes: 3 additions & 1 deletion src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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; }
}
}
9 changes: 9 additions & 0 deletions src/Ryujinx.Common/Configuration/VSyncMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Ryujinx.Common.Configuration
{
public enum VSyncMode
{
Switch,
Unbounded,
Custom
}
}
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.GAL/IWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }

Expand Down
9 changes: 9 additions & 0 deletions src/Ryujinx.Graphics.GAL/VSyncMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum VSyncMode
{
Switch,
Unbounded,
Custom
}
}
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.OpenGL/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
13 changes: 7 additions & 6 deletions src/Ryujinx.Graphics.Vulkan/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Graphics.Vulkan/WindowBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 13 additions & 5 deletions src/Ryujinx.HLE/HLEConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.UI;
using System;
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;

namespace Ryujinx.HLE
{
Expand Down Expand Up @@ -84,9 +85,14 @@ public class HLEConfiguration
internal readonly RegionCode Region;

/// <summary>
/// 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).
/// </summary>
internal readonly bool EnableVsync;
internal readonly VSyncMode VSyncMode;

/// <summary>
/// Control the custom VSync interval, if enabled and active.
/// </summary>
internal readonly int CustomVSyncInterval;

/// <summary>
/// Control the initial state of the docked mode.
Expand Down Expand Up @@ -195,7 +201,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
IHostUIHandler hostUIHandler,
SystemLanguage systemLanguage,
RegionCode region,
bool enableVsync,
VSyncMode vSyncMode,
bool enableDockedMode,
bool enablePtc,
bool enableInternetAccess,
Expand All @@ -212,7 +218,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem,
MultiplayerMode multiplayerMode,
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
string multiplayerLdnServer)
string multiplayerLdnServer,
int customVSyncInterval)
{
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
Expand All @@ -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;
Expand Down
20 changes: 14 additions & 6 deletions src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<long, Layer> _layers;
Expand All @@ -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;

Expand Down Expand Up @@ -88,7 +90,8 @@ private void UpdateSwapInterval(int swapInterval)
}
else
{
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
_ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval;
_targetVSyncInterval = _device.TargetVSyncInterval;
}
}

Expand Down Expand Up @@ -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);
}
Expand Down
38 changes: 36 additions & 2 deletions src/Ryujinx.HLE/Switch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
}

Expand All @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/Ryujinx.Headless.SDL2/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
5 changes: 3 additions & 2 deletions src/Ryujinx.Headless.SDL2/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -580,7 +580,8 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
"",
"");
"",
options.CustomVSyncInterval);

return new Switch(configuration);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
namespace Ryujinx.Headless.SDL2
{
class StatusUpdatedEventArgs(
bool vSyncEnabled,
string vSyncMode,
string dockedMode,
string aspectRatio,
string gameStatus,
string fifoStatus,
string gpuName)
: EventArgs
{
public bool VSyncEnabled = vSyncEnabled;
public string VSyncMode = vSyncMode;
public string DockedMode = dockedMode;
public string AspectRatio = aspectRatio;
public string GameStatus = gameStatus;
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx.Headless.SDL2/WindowBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
20 changes: 19 additions & 1 deletion src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
Expand All @@ -16,7 +17,7 @@ public class ConfigurationFileFormat
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 56;
public const int CurrentVersion = 57;

/// <summary>
/// Version of the configuration file format
Expand Down Expand Up @@ -191,8 +192,25 @@ public class ConfigurationFileFormat
/// <summary>
/// Enables or disables Vertical Sync
/// </summary>
/// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
/// TODO: Remove this when those older versions aren't in use anymore.
public bool EnableVsync { get; set; }

/// <summary>
/// Current VSync mode; 60 (Switch), unbounded ("Vsync off"), or custom
/// </summary>
public VSyncMode VSyncMode { get; set; }

/// <summary>
/// Enables or disables the custom present interval
/// </summary>
public bool EnableCustomVSyncInterval { get; set; }

/// <summary>
/// The custom present interval value
/// </summary>
public int CustomVSyncInterval { get; set; }

/// <summary>
/// Enables or disables Shader cache
/// </summary>
Expand Down
Loading

0 comments on commit 2e6794e

Please sign in to comment.