fix(rendering): survive display topology handoff

Model active-monitor refresh telemetry as optional so GLFW display replacement during console/RDP transitions cannot escape the native move callback and terminate the client. Preserve the existing software pacing fallback until a later monitor event succeeds.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 13:15:24 +02:00
parent 10ccce3f2d
commit 14fbe92b08
4 changed files with 76 additions and 22 deletions

View file

@ -8,7 +8,7 @@ internal interface IDisplayFramePacingSurface
{
bool VSync { get; set; }
int? ActiveMonitorRefreshHz { get; }
bool TryGetActiveMonitorRefreshHz(out int refreshHz);
}
/// <summary>Narrow Silk window adapter for display pacing.</summary>
@ -27,12 +27,25 @@ internal sealed class SilkDisplayFramePacingSurface : IDisplayFramePacingSurface
set => _window.VSync = value;
}
public int? ActiveMonitorRefreshHz
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
{
get
refreshHz = 0;
try
{
int? refreshHz = _window.Monitor?.VideoMode.RefreshRate;
return refreshHz is > 0 ? refreshHz : null;
int? activeRefreshHz = _window.Monitor?.VideoMode.RefreshRate;
if (activeRefreshHz is not > 0)
return false;
refreshHz = activeRefreshHz.Value;
return true;
}
catch (Silk.NET.GLFW.GlfwException)
{
// GLFW can retain a monitor handle while Windows is replacing the
// display topology during a console/RDP handoff. Refresh-rate
// telemetry is optional; report it as unavailable until the next
// move/state event rather than terminating the render host.
return false;
}
}
}
@ -96,7 +109,10 @@ internal sealed class DisplayFramePacingController : IDisposable
public void RefreshActiveMonitor()
{
_activeMonitorRefreshHz = _surface?.ActiveMonitorRefreshHz;
_activeMonitorRefreshHz =
_surface?.TryGetActiveMonitorRefreshHz(out int refreshHz) == true
? refreshHz
: null;
ApplyResolvedPolicy();
}