diff --git a/Prowl.Runtime/Graphics/Graphics.cs b/Prowl.Runtime/Graphics/Graphics.cs index af122eb20..fd3792b3c 100644 --- a/Prowl.Runtime/Graphics/Graphics.cs +++ b/Prowl.Runtime/Graphics/Graphics.cs @@ -232,6 +232,12 @@ private static void RenderThreadLoop() if (job.IsFrameEnd) { + // VSync changes are requested from the main thread but SwapInterval needs the context, + // which lives on this thread for the whole run. Applied next to SwapBuffers because that + // is the call the interval governs. + try { Window.ApplyPendingSwapInterval(); } + catch (Exception ex) { Debug.LogError($"SwapInterval failed: {ex}"); } + try { Window.InternalWindow.GLContext!.SwapBuffers(); } catch (Exception ex) { Debug.LogError($"SwapBuffers failed: {ex}"); } finally { s_renderFrameDone.Set(); } diff --git a/Prowl.Runtime/Window.cs b/Prowl.Runtime/Window.cs index 69bf4883c..2078a4f46 100644 --- a/Prowl.Runtime/Window.cs +++ b/Prowl.Runtime/Window.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using Silk.NET.Input; using Silk.NET.Maths; @@ -127,10 +128,35 @@ public static bool IsVisible set { InternalWindow.IsVisible = value; } } + // Requested swap interval, or -1 for "nothing pending". + private static int s_pendingSwapInterval = -1; + public static bool VSync { get { return InternalWindow.VSync; } - set { InternalWindow.VSync = value; } + set + { + InternalWindow.VSync = value; + + // IWindow.VSync is only read by Silk.NET's automatic Render path, which does not run under the manual loop - so setting it alone changes nothing. + // SwapInterval is the call that matters, but it needs the GL context current, and Start() hands the context to the render thread without it ever returning to main. + // So record the request and let the render thread apply it. + Interlocked.Exchange(ref s_pendingSwapInterval, value ? 1 : 0); + } + } + + /// + /// Applies a pending change. **Render thread only** - the caller must hold the GL context current. + /// + internal static void ApplyPendingSwapInterval() + { + int pending = Interlocked.Exchange(ref s_pendingSwapInterval, -1); + if (pending < 0) + { + return; + } + + InternalWindow.GLContext?.SwapInterval(pending); } public static float FramesPerSecond