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

@ -98,6 +98,37 @@ public sealed class DisplayFramePacingControllerTests
Assert.Equal(new FramePacingPolicy(false, 144d), controller.Policy);
}
[Fact]
public void Unavailable_monitor_during_display_handoff_uses_fallback_policy()
{
using var profiler = new FrameProfiler();
using var controller = new DisplayFramePacingController(
uncappedRendering: false,
profiler,
new FramePacingController(
new FakeClock(1_000),
new RecordingWaiter()));
var surface = new FakeSurface
{
VSync = false,
ActiveMonitorRefreshHz = 144,
};
controller.InitializeStartup(requestedVSync: false);
controller.BindSurface(surface);
controller.RefreshActiveMonitor();
Assert.Equal(new FramePacingPolicy(false, 144d), controller.Policy);
surface.MonitorAvailable = false;
controller.OnWindowMoved(default);
Assert.Equal(
new FramePacingPolicy(
false,
FramePacingPolicy.FallbackRefreshHz),
controller.Policy);
Assert.False(surface.VSync);
}
[Fact]
public void Explicit_uncapped_mode_disables_both_pacing_mechanisms()
{
@ -172,16 +203,22 @@ public sealed class DisplayFramePacingControllerTests
public int RefreshReadCount { get; private set; }
public bool MonitorAvailable { get; set; } = true;
public int? ActiveMonitorRefreshHz
{
get
{
RefreshReadCount++;
return _activeMonitorRefreshHz;
}
get => _activeMonitorRefreshHz;
set => _activeMonitorRefreshHz = value;
}
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
{
RefreshReadCount++;
refreshHz = _activeMonitorRefreshHz.GetValueOrDefault();
return MonitorAvailable &&
_activeMonitorRefreshHz is > 0;
}
public void Dispose() => Disposed = true;
}

View file

@ -789,13 +789,11 @@ public sealed class SilkWindowCallbackBindingTests
public bool VSync { get; set; }
public int RefreshReadCount { get; private set; }
public int? ActiveMonitorRefreshHz
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
{
get
{
RefreshReadCount++;
return 144;
}
RefreshReadCount++;
refreshHz = 144;
return true;
}
}
}

View file

@ -1171,13 +1171,16 @@ public sealed class RuntimeSettingsControllerTests
public int? ActiveMonitorRefreshHz
{
get
{
RefreshReadCount++;
return _refreshRate;
}
get => _refreshRate;
set => _refreshRate = value;
}
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
{
RefreshReadCount++;
refreshHz = _refreshRate.GetValueOrDefault();
return _refreshRate is > 0;
}
}
private sealed class NullKeyboardSource : IKeyboardSource