fix(host): force a CCD mode re-enumeration after UPDATE_MODES (in-place resize)
First on-glass run: the driver accepted every UpdateModes2 (0x0 in the driver log) but the OS never re-enumerated the target's settable modes on its own — 'OS did not advertise 800x1050 within 2s' → re-arrival fallback every time. Re-commit the current config with SDC_FORCE_MODE_ENUMERATION (the same nudge the isolate/layout paths already rely on) before the advertised-wait, re-kick up to 3x, and log the actually-offered resolutions when it still misses. Driver: dbglog the *2 mode-query/parse callbacks so the re-enumeration story is visible in pfvd-driver.log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1189,15 +1189,33 @@ impl VirtualDisplayManager {
|
||||
// SAFETY: `dev` is the live control handle (this fn's contract); `update_modes` forwards it
|
||||
// to a synchronous IOCTL with owned/borrowed locals only.
|
||||
unsafe { self.driver.update_modes(dev, &mon.key, mode) }?;
|
||||
// The OS re-evaluates the target's settable modes asynchronously after UpdateModes2 — wait
|
||||
// (bounded) for the new resolution to become enumerable before forcing it, else the
|
||||
// CDS_TEST inside `set_active_mode` would reject it and silently keep the old mode.
|
||||
// The OS does NOT re-evaluate an indirect display's settable modes on its own after
|
||||
// UpdateModes2 (on-glass: the new mode never became enumerable within 2 s) — force a mode
|
||||
// re-enumeration by re-committing the current config (the same SDC_FORCE_MODE_ENUMERATION
|
||||
// re-commit the isolate/layout paths use), then wait for the new resolution to appear,
|
||||
// re-kicking a couple of times. Without it the CDS_TEST inside `set_active_mode` would
|
||||
// reject the mode and silently keep the old one.
|
||||
let t0 = Instant::now();
|
||||
if !crate::win_display::wait_mode_advertised(&gdi, mode, Duration::from_millis(2000)) {
|
||||
let mut advertised = false;
|
||||
for kick in 0..3u32 {
|
||||
// SAFETY: CCD query/apply FFI under the held `state` lock (this fn's contract).
|
||||
unsafe { crate::win_display::force_mode_reenumeration() };
|
||||
if crate::win_display::wait_mode_advertised(&gdi, mode, Duration::from_millis(1000)) {
|
||||
advertised = true;
|
||||
break;
|
||||
}
|
||||
tracing::debug!(
|
||||
kick,
|
||||
"in-place resize: new mode not yet enumerable — forcing another mode re-enumeration"
|
||||
);
|
||||
}
|
||||
if !advertised {
|
||||
anyhow::bail!(
|
||||
"OS did not advertise {}x{} within 2s of the driver mode-list update",
|
||||
"OS did not advertise {}x{} within {}ms of the driver mode-list update (offers: {:?})",
|
||||
mode.width,
|
||||
mode.height
|
||||
mode.height,
|
||||
t0.elapsed().as_millis(),
|
||||
crate::win_display::advertised_resolutions(&gdi)
|
||||
);
|
||||
}
|
||||
let advertised_ms = t0.elapsed().as_millis() as u64;
|
||||
|
||||
@@ -258,6 +258,62 @@ pub(crate) unsafe fn wait_mode_settled(
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-commit the CURRENT active config with `SDC_FORCE_MODE_ENUMERATION` — the nudge that makes
|
||||
/// the OS re-query an indirect display's target modes. Observed on-glass (P2): after
|
||||
/// `IddCxMonitorUpdateModes2` the OS did NOT re-enumerate on its own within 2 s, so a freshly
|
||||
/// advertised mode never became settable; the isolate/layout paths already re-commit with this
|
||||
/// flag for the same "the OS won't re-evaluate unless told" class. Best-effort.
|
||||
///
|
||||
/// # Safety
|
||||
/// Runs the CCD query/apply FFI; call under the manager `state` lock (sole topology mutator).
|
||||
pub(crate) unsafe fn force_mode_reenumeration() -> bool {
|
||||
let Some((paths, modes)) = query_active_config() else {
|
||||
return false;
|
||||
};
|
||||
let rc = SetDisplayConfig(
|
||||
Some(paths.as_slice()),
|
||||
Some(modes.as_slice()),
|
||||
SDC_APPLY
|
||||
| SDC_USE_SUPPLIED_DISPLAY_CONFIG
|
||||
| SDC_ALLOW_CHANGES
|
||||
| SDC_FORCE_MODE_ENUMERATION,
|
||||
);
|
||||
if rc != 0 {
|
||||
tracing::debug!("force mode re-enumeration: SetDisplayConfig rc={rc:#x}");
|
||||
}
|
||||
rc == 0
|
||||
}
|
||||
|
||||
/// The distinct resolutions `gdi_name` currently advertises (diagnostics for the in-place-resize
|
||||
/// path: what the OS actually offers when a requested mode never shows up).
|
||||
pub(crate) fn advertised_resolutions(gdi_name: &str) -> Vec<(u32, u32)> {
|
||||
let wname: Vec<u16> = gdi_name.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let mut set = std::collections::BTreeSet::new();
|
||||
let mut i = 0u32;
|
||||
loop {
|
||||
let mut dm = DEVMODEW {
|
||||
dmSize: size_of::<DEVMODEW>() as u16,
|
||||
..Default::default()
|
||||
};
|
||||
// SAFETY: `wname` is a live NUL-terminated UTF-16 device name; `&mut dm` is a live,
|
||||
// size-stamped DEVMODEW the API fills for mode index `i`. Both outlive the call.
|
||||
let ok = unsafe {
|
||||
EnumDisplaySettingsW(
|
||||
PCWSTR(wname.as_ptr()),
|
||||
ENUM_DISPLAY_SETTINGS_MODE(i),
|
||||
&mut dm,
|
||||
)
|
||||
}
|
||||
.as_bool();
|
||||
if !ok {
|
||||
break;
|
||||
}
|
||||
set.insert((dm.dmPelsWidth, dm.dmPelsHeight));
|
||||
i += 1;
|
||||
}
|
||||
set.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Wait (bounded) until `gdi_name` ADVERTISES `mode`'s resolution in its display-mode list — the
|
||||
/// gate between a driver-side mode-list refresh (`IOCTL_UPDATE_MODES`, latency plan P2) and the
|
||||
/// CCD/GDI force-set: the OS re-evaluates an indirect display's settable modes asynchronously after
|
||||
|
||||
Reference in New Issue
Block a user