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:
parent
10ccce3f2d
commit
14fbe92b08
4 changed files with 76 additions and 22 deletions
|
|
@ -8,7 +8,7 @@ internal interface IDisplayFramePacingSurface
|
||||||
{
|
{
|
||||||
bool VSync { get; set; }
|
bool VSync { get; set; }
|
||||||
|
|
||||||
int? ActiveMonitorRefreshHz { get; }
|
bool TryGetActiveMonitorRefreshHz(out int refreshHz);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Narrow Silk window adapter for display pacing.</summary>
|
/// <summary>Narrow Silk window adapter for display pacing.</summary>
|
||||||
|
|
@ -27,12 +27,25 @@ internal sealed class SilkDisplayFramePacingSurface : IDisplayFramePacingSurface
|
||||||
set => _window.VSync = value;
|
set => _window.VSync = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? ActiveMonitorRefreshHz
|
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
|
||||||
{
|
{
|
||||||
get
|
refreshHz = 0;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
int? refreshHz = _window.Monitor?.VideoMode.RefreshRate;
|
int? activeRefreshHz = _window.Monitor?.VideoMode.RefreshRate;
|
||||||
return refreshHz is > 0 ? refreshHz : null;
|
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()
|
public void RefreshActiveMonitor()
|
||||||
{
|
{
|
||||||
_activeMonitorRefreshHz = _surface?.ActiveMonitorRefreshHz;
|
_activeMonitorRefreshHz =
|
||||||
|
_surface?.TryGetActiveMonitorRefreshHz(out int refreshHz) == true
|
||||||
|
? refreshHz
|
||||||
|
: null;
|
||||||
ApplyResolvedPolicy();
|
ApplyResolvedPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,37 @@ public sealed class DisplayFramePacingControllerTests
|
||||||
Assert.Equal(new FramePacingPolicy(false, 144d), controller.Policy);
|
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]
|
[Fact]
|
||||||
public void Explicit_uncapped_mode_disables_both_pacing_mechanisms()
|
public void Explicit_uncapped_mode_disables_both_pacing_mechanisms()
|
||||||
{
|
{
|
||||||
|
|
@ -172,14 +203,20 @@ public sealed class DisplayFramePacingControllerTests
|
||||||
|
|
||||||
public int RefreshReadCount { get; private set; }
|
public int RefreshReadCount { get; private set; }
|
||||||
|
|
||||||
|
public bool MonitorAvailable { get; set; } = true;
|
||||||
|
|
||||||
public int? ActiveMonitorRefreshHz
|
public int? ActiveMonitorRefreshHz
|
||||||
{
|
{
|
||||||
get
|
get => _activeMonitorRefreshHz;
|
||||||
|
set => _activeMonitorRefreshHz = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
|
||||||
{
|
{
|
||||||
RefreshReadCount++;
|
RefreshReadCount++;
|
||||||
return _activeMonitorRefreshHz;
|
refreshHz = _activeMonitorRefreshHz.GetValueOrDefault();
|
||||||
}
|
return MonitorAvailable &&
|
||||||
set => _activeMonitorRefreshHz = value;
|
_activeMonitorRefreshHz is > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => Disposed = true;
|
public void Dispose() => Disposed = true;
|
||||||
|
|
|
||||||
|
|
@ -789,13 +789,11 @@ public sealed class SilkWindowCallbackBindingTests
|
||||||
public bool VSync { get; set; }
|
public bool VSync { get; set; }
|
||||||
public int RefreshReadCount { get; private set; }
|
public int RefreshReadCount { get; private set; }
|
||||||
|
|
||||||
public int? ActiveMonitorRefreshHz
|
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
RefreshReadCount++;
|
RefreshReadCount++;
|
||||||
return 144;
|
refreshHz = 144;
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1171,12 +1171,15 @@ public sealed class RuntimeSettingsControllerTests
|
||||||
|
|
||||||
public int? ActiveMonitorRefreshHz
|
public int? ActiveMonitorRefreshHz
|
||||||
{
|
{
|
||||||
get
|
get => _refreshRate;
|
||||||
|
set => _refreshRate = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetActiveMonitorRefreshHz(out int refreshHz)
|
||||||
{
|
{
|
||||||
RefreshReadCount++;
|
RefreshReadCount++;
|
||||||
return _refreshRate;
|
refreshHz = _refreshRate.GetValueOrDefault();
|
||||||
}
|
return _refreshRate is > 0;
|
||||||
set => _refreshRate = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue