diff --git a/UI/Utilities/DisplayHelper.cs b/UI/Utilities/DisplayHelper.cs
new file mode 100644
index 000000000..27129dfd4
--- /dev/null
+++ b/UI/Utilities/DisplayHelper.cs
@@ -0,0 +1,145 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Mesen.Utilities
+{
+ public class DisplayHelper
+ {
+ [DllImport("user32.dll")]
+ private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool EnumDisplaySettings(string? lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);
+
+ private const uint MONITOR_DEFAULTTONEAREST = 2;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct RECT
+ {
+ public int left, top, right, bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ private struct MONITORINFOEX
+ {
+ public int cbSize;
+ public RECT rcMonitor;
+ public RECT rcWork;
+ public int dwFlags;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string szDevice;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ private struct DEVMODE
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string dmDeviceName;
+
+ public short dmSpecVersion;
+ public short dmDriverVersion;
+ public short dmSize;
+ public short dmDriverExtra;
+ public int dmFields;
+ public int dmPositionX;
+ public int dmPositionY;
+ public int dmDisplayOrientation;
+ public int dmDisplayFixedOutput;
+ public short dmColor;
+ public short dmDuplex;
+ public short dmYResolution;
+ public short dmTTOption;
+ public short dmCollate;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string dmFormName;
+
+ public short dmLogPixels;
+ public int dmBitsPerPel;
+ public int dmPelsWidth;
+ public int dmPelsHeight;
+ public int dmDisplayFlags;
+ public int dmDisplayFrequency;
+ public int dmICMMethod;
+ public int dmICMIntent;
+ public int dmMediaType;
+ public int dmDitherType;
+ public int dmReserved1;
+ public int dmReserved2;
+ public int dmPanningWidth;
+ public int dmPanningHeight;
+ }
+
+ ///
+ /// Returns an array of supported refresh rates for the monitor where the main Avalonia window currently resides.
+ /// Assumes the caller has already verified this is executing on a Windows environment.
+ ///
+ public static UInt32[] GetRefreshRatesForCurrentMonitor()
+ {
+ IntPtr hwnd = IntPtr.Zero;
+
+ // 1. Safely grab the Avalonia Main Window handle
+ if(Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ var window = desktop.MainWindow;
+ if(window != null) {
+ var platformHandle = window.TryGetPlatformHandle();
+ if(platformHandle != null) {
+ hwnd = platformHandle.Handle;
+ }
+ }
+ }
+
+ string? deviceName = null;
+
+ // 2. If we successfully got the window handle, find out which monitor it's on
+ if(hwnd != IntPtr.Zero) {
+ IntPtr hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFOEX monitorInfo = new MONITORINFOEX();
+ monitorInfo.cbSize = Marshal.SizeOf();
+
+ if(GetMonitorInfo(hMonitor, ref monitorInfo)) {
+ deviceName = monitorInfo.szDevice;
+ }
+ }
+
+ // 3. Query the refresh rates (passing 'null' as deviceName falls back to primary display)
+ HashSet refreshRates = new HashSet();
+ DEVMODE vDevMode = new DEVMODE();
+ vDevMode.dmSize = (short)Marshal.SizeOf();
+
+ int modeIndex = 0;
+ while(EnumDisplaySettings(deviceName, modeIndex, ref vDevMode)) {
+ if(vDevMode.dmDisplayFrequency > 0) {
+ refreshRates.Add((UInt32)vDevMode.dmDisplayFrequency);
+ }
+
+ modeIndex++;
+ }
+
+ UInt32[] result = refreshRates.ToArray();
+ Array.Sort(result);
+ return result;
+ }
+
+ ///
+ /// Returns the lowest refresh rate that is equal to or a multiple of .
+ /// Falls back to the highest available rate if no compatible rate is found, or if is 0.
+ ///
+ public static UInt32 GetCompatibleRefreshRate(UInt32[] availableRates, UInt32 targetRate)
+ {
+ if(targetRate == 0) return availableRates.LastOrDefault();
+
+ var compatibleRate = availableRates.FirstOrDefault(rate => rate % targetRate == 0);
+
+ return compatibleRate == 0 ? availableRates.LastOrDefault() : compatibleRate;
+ }
+ }
+}
diff --git a/UI/ViewModels/VideoConfigViewModel.cs b/UI/ViewModels/VideoConfigViewModel.cs
index d2cff1911..109de1555 100644
--- a/UI/ViewModels/VideoConfigViewModel.cs
+++ b/UI/ViewModels/VideoConfigViewModel.cs
@@ -55,6 +55,23 @@ public VideoConfigViewModel()
//Exclusive fullscreen is only supported on Windows currently
IsWindows = OperatingSystem.IsWindows();
+ // Detect available refresh rates on Windows and ensure the configured refresh rates are valid
+ if(IsWindows) {
+ UInt32[] monitorSupportedRefreshRates = DisplayHelper.GetRefreshRatesForCurrentMonitor();
+
+ if(monitorSupportedRefreshRates.Length > 0) {
+ AvailableRefreshRates = monitorSupportedRefreshRates;
+
+ if(!AvailableRefreshRates.Contains(Config.ExclusiveFullscreenRefreshRatePal)) {
+ Config.ExclusiveFullscreenRefreshRatePal = DisplayHelper.GetCompatibleRefreshRate(AvailableRefreshRates, Config.ExclusiveFullscreenRefreshRatePal);
+ }
+
+ if(!AvailableRefreshRates.Contains(Config.ExclusiveFullscreenRefreshRateNtsc)) {
+ Config.ExclusiveFullscreenRefreshRateNtsc = DisplayHelper.GetCompatibleRefreshRate(AvailableRefreshRates, Config.ExclusiveFullscreenRefreshRateNtsc);
+ }
+ }
+ }
+
//MacOS only supports the software renderer
IsMacOs = OperatingSystem.IsMacOS();