From 816401835631197a44f9d31f6b11e5e0a14c2b07 Mon Sep 17 00:00:00 2001
From: Cody Mullins <1738479+codymullins@users.noreply.github.com>
Date: Tue, 16 Jun 2026 19:45:24 -0400
Subject: [PATCH] feat(window): expose the display refresh rate
Add AppWindow.RefreshRate (Hz) so a shell can pace animation to the
display. On macOS it is the window's screen rate; on GLFW it is the
window's monitor rate when known (set in fullscreen), otherwise 60.
Addresses #11 (the vsync-clock piece). Wheel coalescing and normalized
scroll deltas remain.
---
src/Skyline.Apple/AppKitWindowBackend.cs | 2 ++
src/Skyline/AppWindow.cs | 7 +++++++
src/Skyline/GlfwWindowBackend.cs | 3 +++
src/Skyline/IWindowBackend.cs | 3 +++
tests/Skyline.Tests/WindowBackendFactoryTests.cs | 1 +
tests/Skyline.WindowedTests/Program.cs | 2 ++
6 files changed, 18 insertions(+)
diff --git a/src/Skyline.Apple/AppKitWindowBackend.cs b/src/Skyline.Apple/AppKitWindowBackend.cs
index 91524de..33ac103 100644
--- a/src/Skyline.Apple/AppKitWindowBackend.cs
+++ b/src/Skyline.Apple/AppKitWindowBackend.cs
@@ -112,6 +112,8 @@ private void UpdateDrawableSize()
}
}
+ public double RefreshRate => (_window.Screen ?? NSScreen.MainScreen)?.MaximumFramesPerSecond ?? 60;
+
public string Title
{
get => _window.Title;
diff --git a/src/Skyline/AppWindow.cs b/src/Skyline/AppWindow.cs
index 962a94a..56e956d 100644
--- a/src/Skyline/AppWindow.cs
+++ b/src/Skyline/AppWindow.cs
@@ -158,6 +158,13 @@ public string? ClipboardText
set => _backend.ClipboardText = value;
}
+ ///
+ /// The display's refresh rate in Hz. Pace animation to this. On macOS it
+ /// is the window's screen rate; on GLFW it is the window's monitor rate
+ /// when known (set in fullscreen), otherwise 60.
+ ///
+ public double RefreshRate => _backend.RefreshRate;
+
public FrameInfo CurrentFrame => Frame(0);
/// True when this window has been adopted by an . Hosted windows render on the host's thread, so use AppHost.Run, not .
diff --git a/src/Skyline/GlfwWindowBackend.cs b/src/Skyline/GlfwWindowBackend.cs
index 3d0d94f..bb234c0 100644
--- a/src/Skyline/GlfwWindowBackend.cs
+++ b/src/Skyline/GlfwWindowBackend.cs
@@ -72,6 +72,9 @@ public unsafe GlfwWindowBackend(AppWindowOptions options)
public (int Width, int Height) FramebufferSize => (_window.FramebufferSize.X, _window.FramebufferSize.Y);
public (int Width, int Height) LogicalSize => (_window.Size.X, _window.Size.Y);
+ // The window's monitor when GLFW knows it (set in fullscreen), else 60.
+ public double RefreshRate => _window.Monitor?.VideoMode.RefreshRate ?? 60;
+
public string Title
{
get => _window.Title;
diff --git a/src/Skyline/IWindowBackend.cs b/src/Skyline/IWindowBackend.cs
index e1b01c9..f562b30 100644
--- a/src/Skyline/IWindowBackend.cs
+++ b/src/Skyline/IWindowBackend.cs
@@ -17,6 +17,9 @@ internal interface IWindowBackend : IDisposable
/// Window size in logical points.
(int Width, int Height) LogicalSize { get; }
+ /// The display's refresh rate in Hz, for pacing animation.
+ double RefreshRate { get; }
+
string Title { get; set; }
string? ClipboardText { get; set; }
diff --git a/tests/Skyline.Tests/WindowBackendFactoryTests.cs b/tests/Skyline.Tests/WindowBackendFactoryTests.cs
index a7b3ff2..167ea51 100644
--- a/tests/Skyline.Tests/WindowBackendFactoryTests.cs
+++ b/tests/Skyline.Tests/WindowBackendFactoryTests.cs
@@ -14,6 +14,7 @@ private sealed class FakeBackend : IWindowBackend
{
public (int Width, int Height) FramebufferSize => (200, 100);
public (int Width, int Height) LogicalSize => (100, 50);
+ public double RefreshRate => 60;
public string Title { get; set; } = "fake";
public string? ClipboardText { get; set; }
public bool IsClosing => false;
diff --git a/tests/Skyline.WindowedTests/Program.cs b/tests/Skyline.WindowedTests/Program.cs
index 8694c73..ed1b848 100644
--- a/tests/Skyline.WindowedTests/Program.cs
+++ b/tests/Skyline.WindowedTests/Program.cs
@@ -38,6 +38,7 @@ void Check(bool condition, string what)
Check(frame.Dpr > 0, "CurrentFrame has a device pixel ratio");
Check(Math.Abs(frame.LogicalWidth - frame.PixelWidth / frame.Dpr) < 0.01, "logical width derives from Dpr");
Check(win.Surface is not null, "native surface source is exposed");
+Check(win.RefreshRate > 0, "RefreshRate reports a positive display rate");
win.ClipboardText = "skyline-test";
Check(win.ClipboardText == "skyline-test", "clipboard round-trips");
@@ -58,6 +59,7 @@ void Check(bool condition, string what)
});
var cf = chromed.CurrentFrame;
Check(cf.PixelWidth > 0 && cf.PixelHeight > 0, $"{chrome} window constructs with a real surface");
+ Check(chromed.RefreshRate > 0, $"{chrome} GLFW window reports a refresh rate");
}
var soloResized = false;