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:
2026-07-16 17:48:44 +02:00
parent 55e59458a2
commit a738de6cd8
3 changed files with 101 additions and 6 deletions
@@ -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
@@ -129,6 +129,16 @@ pub unsafe extern "C" fn parse_monitor_description2(
return STATUS_NOT_FOUND;
};
let count = crate::monitor::flatten(&modes).count() as u32;
// Bring-up/diagnostic visibility (P2): does the OS ever RE-parse the description after an
// UPDATE_MODES? The head mode names which list generation this call served.
if let Some(head) = crate::monitor::flatten(&modes).next() {
dbglog!(
"[pf-vd] parse_monitor_description2(id={id}): {count} modes, head {}x{}@{}",
head.width,
head.height,
head.refresh_rate
);
}
out_args.MonitorModeBufferOutputCount = count;
if in_args.MonitorModeBufferInputCount < count {
// A zero input count is a count-only probe (success); a non-zero too-small buffer is an error.
@@ -204,6 +214,17 @@ pub unsafe extern "C" fn monitor_query_modes2(
return STATUS_NOT_FOUND;
};
let count = crate::monitor::flatten(&modes).count() as u32;
// Diagnostic visibility (P2): shows whether/when the OS re-queries target modes after an
// UPDATE_MODES (the head mode names the list generation this call served).
if let Some(head) = crate::monitor::flatten(&modes).next() {
dbglog!(
"[pf-vd] monitor_query_modes2: {count} modes, head {}x{}@{} (fill={})",
head.width,
head.height,
head.refresh_rate,
in_args.TargetModeBufferInputCount >= count
);
}
out_args.TargetModeBufferOutputCount = count;
if in_args.TargetModeBufferInputCount >= count {
// SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE2 entries.