perf(host): replace the Windows bring-up/resize fixed sleeps with verified-state waits
Latency plan P0.2/P0.3/P0.5 (design/first-frame-and-resize-latency.md): - topology settle: the unconditional 1500 ms sleeps after create_monitor's group-topology apply and re_add's reisolate become a 25 ms poll for the committed state (active path + active mode == requested), ceiling 1500 ms — worst case identical, typical case saves ~1.2-1.4 s on every fresh create AND every mid-stream resize. The experimental pnp_disable_monitors sweep keeps the full settle as its floor (it reads OTHER displays' active flags, which the target-scoped wait doesn't verify). - monitor departure: the fixed 400 ms REMOVE settles (re_add + both preempt paths) become a 25 ms poll until the target leaves the active CCD set (2 consecutive absent samples), ceiling 400 ms; the driver-side ghost-reap ADD retry stays the backstop. - activation ladder: 200 ms -> 50 ms sampling, same 3 s per-stage ceilings and the same 3-stage structure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,8 @@ use windows::Win32::System::Threading::{
|
|||||||
use super::{DisplayOwnership, Mode, VirtualOutput};
|
use super::{DisplayOwnership, Mode, VirtualOutput};
|
||||||
use crate::win_display::{
|
use crate::win_display::{
|
||||||
count_other_active, force_extend_topology, isolate_displays_ccd, resolve_gdi_name,
|
count_other_active, force_extend_topology, isolate_displays_ccd, resolve_gdi_name,
|
||||||
restore_displays_ccd, set_active_mode, set_virtual_primary_ccd, SavedConfig,
|
restore_displays_ccd, set_active_mode, set_virtual_primary_ccd, wait_mode_settled,
|
||||||
|
wait_target_departed, SavedConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The per-backend REMOVE key the driver stamps on ADD and consumes on REMOVE. SudoVDA keys monitors by
|
/// The per-backend REMOVE key the driver stamps on ADD and consumes on REMOVE. SudoVDA keys monitors by
|
||||||
@@ -511,9 +512,10 @@ impl VirtualDisplayManager {
|
|||||||
if let Some(SlotState::Lingering { mon, .. } | SlotState::Pinned { mon }) =
|
if let Some(SlotState::Lingering { mon, .. } | SlotState::Pinned { mon }) =
|
||||||
inner.slots.remove(&slot)
|
inner.slots.remove(&slot)
|
||||||
{
|
{
|
||||||
|
let old_target = mon.target_id;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
slot,
|
slot,
|
||||||
old_target = mon.target_id,
|
old_target,
|
||||||
"IDD-push reconnect — preempting the kept (lingering/pinned) monitor, recreating a fresh one"
|
"IDD-push reconnect — preempting the kept (lingering/pinned) monitor, recreating a fresh one"
|
||||||
);
|
);
|
||||||
// SAFETY: `teardown_removed` requires `dev` to be a valid control handle; `dev` is the
|
// SAFETY: `teardown_removed` requires `dev` to be a valid control handle; `dev` is the
|
||||||
@@ -523,7 +525,16 @@ impl VirtualDisplayManager {
|
|||||||
unsafe { self.teardown_removed(dev, &mut inner, mon) };
|
unsafe { self.teardown_removed(dev, &mut inner, mon) };
|
||||||
// Let the OS finish the ASYNC monitor departure before the next ADD; a back-to-back
|
// Let the OS finish the ASYNC monitor departure before the next ADD; a back-to-back
|
||||||
// REMOVE→ADD races the teardown and the ADD IOCTL is rejected under reconnect churn.
|
// REMOVE→ADD races the teardown and the ADD IOCTL is rejected under reconnect churn.
|
||||||
thread::sleep(Duration::from_millis(400));
|
// Verified-state wait, ceiling = the old fixed 400 ms settle (latency plan P0.3).
|
||||||
|
// SAFETY: CCD query FFI over a `Copy` target id, under the held `state` lock.
|
||||||
|
let departed =
|
||||||
|
unsafe { wait_target_departed(old_target, Duration::from_millis(400)) };
|
||||||
|
if !departed {
|
||||||
|
tracing::debug!(
|
||||||
|
old_target,
|
||||||
|
"preempted monitor still in the active CCD set after the departure ceiling"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,9 +550,10 @@ impl VirtualDisplayManager {
|
|||||||
if matches!(inner.slots.get(&slot), Some(SlotState::Active { mon, .. }) if !wudf_alive(mon.wudf_pid))
|
if matches!(inner.slots.get(&slot), Some(SlotState::Active { mon, .. }) if !wudf_alive(mon.wudf_pid))
|
||||||
{
|
{
|
||||||
if let Some(SlotState::Active { mon, .. }) = inner.slots.remove(&slot) {
|
if let Some(SlotState::Active { mon, .. }) = inner.slots.remove(&slot) {
|
||||||
|
let old_target = mon.target_id;
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
slot,
|
slot,
|
||||||
old_target = mon.target_id,
|
old_target,
|
||||||
wudf_pid = mon.wudf_pid,
|
wudf_pid = mon.wudf_pid,
|
||||||
"virtual monitor's WUDFHost is gone — preempting the dead monitor, recreating"
|
"virtual monitor's WUDFHost is gone — preempting the dead monitor, recreating"
|
||||||
);
|
);
|
||||||
@@ -550,8 +562,9 @@ impl VirtualDisplayManager {
|
|||||||
// retired, kept alive; see `DeviceSlot`). `mon` was just removed from the map, so it
|
// retired, kept alive; see `DeviceSlot`). `mon` was just removed from the map, so it
|
||||||
// is exclusively owned here — no aliasing.
|
// is exclusively owned here — no aliasing.
|
||||||
unsafe { self.teardown_removed(dev, &mut inner, mon) };
|
unsafe { self.teardown_removed(dev, &mut inner, mon) };
|
||||||
// Same async-departure settle as the reconnect preempt above.
|
// Same async-departure settle as the reconnect preempt above (verified wait, P0.3).
|
||||||
thread::sleep(Duration::from_millis(400));
|
// SAFETY: CCD query FFI over a `Copy` target id, under the held `state` lock.
|
||||||
|
let _ = unsafe { wait_target_departed(old_target, Duration::from_millis(400)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,8 +845,12 @@ impl VirtualDisplayManager {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
/// Runs the CCD (QueryDisplayConfig / SetDisplayConfig) FFI; call under the `state` lock.
|
/// Runs the CCD (QueryDisplayConfig / SetDisplayConfig) FFI; call under the `state` lock.
|
||||||
unsafe fn resolve_target_gdi(&self, target_id: u32) -> Option<String> {
|
unsafe fn resolve_target_gdi(&self, target_id: u32) -> Option<String> {
|
||||||
for _ in 0..15 {
|
// 50 ms sampling (latency plan P0.5): the SAME 3 s per-stage ceilings — the 3-stage ladder
|
||||||
thread::sleep(Duration::from_millis(200));
|
// structure encodes real failure modes (headless auto-activate, integrated-panel clone,
|
||||||
|
// lid-closed path activation) and is untouched — but a typical activation resolves on an
|
||||||
|
// early poll, so finer sampling shaves ~150 ms off every stage crossing.
|
||||||
|
for _ in 0..60 {
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
// SAFETY: `resolve_gdi_name` is `unsafe` for its CCD FFI; it takes a plain `Copy` `u32`
|
// SAFETY: `resolve_gdi_name` is `unsafe` for its CCD FFI; it takes a plain `Copy` `u32`
|
||||||
// target id by value and returns an owned `String`, so no caller memory is borrowed.
|
// target id by value and returns an owned `String`, so no caller memory is borrowed.
|
||||||
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
||||||
@@ -842,8 +859,8 @@ impl VirtualDisplayManager {
|
|||||||
}
|
}
|
||||||
// SAFETY: `force_extend_topology` only calls `SetDisplayConfig` (CCD) with no borrowed memory.
|
// SAFETY: `force_extend_topology` only calls `SetDisplayConfig` (CCD) with no borrowed memory.
|
||||||
unsafe { force_extend_topology() };
|
unsafe { force_extend_topology() };
|
||||||
for _ in 0..15 {
|
for _ in 0..60 {
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(50));
|
||||||
// SAFETY: as the resolve loop above.
|
// SAFETY: as the resolve loop above.
|
||||||
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
||||||
return Some(n);
|
return Some(n);
|
||||||
@@ -852,8 +869,8 @@ impl VirtualDisplayManager {
|
|||||||
// SAFETY: `activate_target_path` runs the CCD query/apply FFI with owned local buffers; the
|
// SAFETY: `activate_target_path` runs the CCD query/apply FFI with owned local buffers; the
|
||||||
// `Copy` target id is passed by value, under the `state` lock — the sole topology mutator.
|
// `Copy` target id is passed by value, under the `state` lock — the sole topology mutator.
|
||||||
if unsafe { crate::win_display::activate_target_path(target_id) } {
|
if unsafe { crate::win_display::activate_target_path(target_id) } {
|
||||||
for _ in 0..15 {
|
for _ in 0..60 {
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(50));
|
||||||
// SAFETY: as the resolve loops above.
|
// SAFETY: as the resolve loops above.
|
||||||
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
if let Some(n) = unsafe { resolve_gdi_name(target_id) } {
|
||||||
return Some(n);
|
return Some(n);
|
||||||
@@ -1015,19 +1032,40 @@ impl VirtualDisplayManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture opens
|
// Topology settle before capture opens: verified-state wait (latency plan P0.2) —
|
||||||
|
// poll until the target's path + active mode are committed, ceiling = the old fixed
|
||||||
|
// 1500 ms sleep (a rejected mode / slow third-party CCD-lock holder burns the
|
||||||
|
// ceiling and proceeds, exactly like the sleep it replaces).
|
||||||
|
let settle_start = std::time::Instant::now();
|
||||||
|
// SAFETY: CCD/GDI query FFI over a `Copy` target id, under the held `state` lock.
|
||||||
|
let settled = unsafe {
|
||||||
|
wait_mode_settled(added.target_id, mode, Duration::from_millis(1500))
|
||||||
|
};
|
||||||
|
tracing::info!(
|
||||||
|
settle_ms = settle_start.elapsed().as_millis() as u64,
|
||||||
|
verified = settled,
|
||||||
|
"topology settle (verified-state wait)"
|
||||||
|
);
|
||||||
|
|
||||||
// EXPERIMENTAL `pnp_disable_monitors`, second selector (ANY topology): monitors
|
// EXPERIMENTAL `pnp_disable_monitors`, second selector (ANY topology): monitors
|
||||||
// that are connected but NOT part of the desktop — the standby TV/monitor the
|
// that are connected but NOT part of the desktop — the standby TV/monitor the
|
||||||
// deactivated-set selector above structurally misses (it never had an active path
|
// deactivated-set selector above structurally misses (it never had an active path
|
||||||
// to deactivate), yet whose periodic standby wake events drive the same Windows
|
// to deactivate), yet whose periodic standby wake events drive the same Windows
|
||||||
// reaction cascade (rationale in `windows/monitor_devnode.rs`). Runs AFTER the
|
// reaction cascade (rationale in `windows/monitor_devnode.rs`). Runs AFTER the
|
||||||
// settle sleep so the active flags it reads are the committed ones (a display
|
// settle so the active flags it reads are the committed ones (a display still
|
||||||
// still mid-activation from the primary topology's force-EXTEND must not read as
|
// mid-activation from the primary topology's force-EXTEND must not read as
|
||||||
// inactive and get disabled); in Extend the active physical panels are untouched
|
// inactive and get disabled) — and since the verified wait above only confirms
|
||||||
// by construction. First member only — the sweep is group-scoped like the
|
// OUR target (not a physical still lighting up from force-EXTEND), this opt-in
|
||||||
// isolate; later members join an already-swept desktop.
|
// sweep keeps the old FULL settle as its floor before reading those flags.
|
||||||
|
// In Extend the active physical panels are untouched by construction. First
|
||||||
|
// member only — the sweep is group-scoped like the isolate; later members join
|
||||||
|
// an already-swept desktop.
|
||||||
if first_member && crate::vdisplay::policy::prefs().pnp_disable_monitors() {
|
if first_member && crate::vdisplay::policy::prefs().pnp_disable_monitors() {
|
||||||
|
if let Some(rest) =
|
||||||
|
Duration::from_millis(1500).checked_sub(settle_start.elapsed())
|
||||||
|
{
|
||||||
|
thread::sleep(rest);
|
||||||
|
}
|
||||||
let mut keep = inner.target_ids();
|
let mut keep = inner.target_ids();
|
||||||
keep.push(added.target_id);
|
keep.push(added.target_id);
|
||||||
for id in crate::monitor_devnode::disable_connected_inactive(&keep) {
|
for id in crate::monitor_devnode::disable_connected_inactive(&keep) {
|
||||||
@@ -1109,9 +1147,17 @@ impl VirtualDisplayManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Let the OS finish the ASYNC monitor departure before the ADD — a back-to-back REMOVE→ADD
|
// Let the OS finish the ASYNC monitor departure before the ADD — a back-to-back REMOVE→ADD
|
||||||
// races the teardown and the ADD is rejected under churn (same 400 ms settle as the reconnect
|
// races the teardown and the ADD is rejected under churn. Verified departure wait, ceiling =
|
||||||
// preempt path).
|
// the old fixed 400 ms settle (latency plan P0.3); the driver's ghost-reap ADD retry remains
|
||||||
thread::sleep(Duration::from_millis(400));
|
// the backstop for a departure the CCD reports early.
|
||||||
|
let depart_start = std::time::Instant::now();
|
||||||
|
// SAFETY: CCD query FFI over a `Copy` target id, under the held `state` lock.
|
||||||
|
let departed = unsafe { wait_target_departed(old.target_id, Duration::from_millis(400)) };
|
||||||
|
tracing::info!(
|
||||||
|
depart_ms = depart_start.elapsed().as_millis() as u64,
|
||||||
|
verified = departed,
|
||||||
|
"re-arrival: old monitor departure settle"
|
||||||
|
);
|
||||||
// 2. ADD a fresh monitor at the NEW mode, reusing the slot as the preferred (stable) id.
|
// 2. ADD a fresh monitor at the NEW mode, reusing the slot as the preferred (stable) id.
|
||||||
let render_pin = resolve_render_pin();
|
let render_pin = resolve_render_pin();
|
||||||
// SAFETY: `dev` is the live control handle; `render_pin`/`client_hdr` are owned `Copy`/`Option`
|
// SAFETY: `dev` is the live control handle; `render_pin`/`client_hdr` are owned `Copy`/`Option`
|
||||||
@@ -1138,7 +1184,18 @@ impl VirtualDisplayManager {
|
|||||||
// the group's first-member restore snapshot.
|
// the group's first-member restore snapshot.
|
||||||
// SAFETY: CCD FFI over borrowed Copy target ids, under the `state` lock.
|
// SAFETY: CCD FFI over borrowed Copy target ids, under the `state` lock.
|
||||||
unsafe { self.reisolate_after_swap(inner, added.target_id) };
|
unsafe { self.reisolate_after_swap(inner, added.target_id) };
|
||||||
thread::sleep(Duration::from_millis(1500)); // let the topology settle before capture reopens
|
// Topology settle before capture reopens: verified-state wait, ceiling = the old
|
||||||
|
// fixed 1500 ms sleep (latency plan P0.2 — the re-arrival twin).
|
||||||
|
let settle_start = std::time::Instant::now();
|
||||||
|
// SAFETY: CCD/GDI query FFI over a `Copy` target id, under the held `state` lock.
|
||||||
|
let settled = unsafe {
|
||||||
|
wait_mode_settled(added.target_id, mode, Duration::from_millis(1500))
|
||||||
|
};
|
||||||
|
tracing::info!(
|
||||||
|
settle_ms = settle_start.elapsed().as_millis() as u64,
|
||||||
|
verified = settled,
|
||||||
|
"re-arrival topology settle (verified-state wait)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
None => tracing::warn!(
|
None => tracing::warn!(
|
||||||
"re-arrival target {} not yet an active display path (auto-activate, EXTEND preset \
|
"re-arrival target {} not yet an active display path (auto-activate, EXTEND preset \
|
||||||
|
|||||||
@@ -226,6 +226,68 @@ pub(crate) unsafe fn active_resolution(target_id: u32) -> Option<(u32, u32)> {
|
|||||||
Some((dm.dmPelsWidth, dm.dmPelsHeight))
|
Some((dm.dmPelsWidth, dm.dmPelsHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verified-state topology-settle wait (latency plan P0.2): poll the CCD state until the target is
|
||||||
|
/// actually COMMITTED — an active path exists (the GDI name resolves) and the active resolution
|
||||||
|
/// equals the requested one — instead of sleeping a fixed interval. The conditions are exactly what
|
||||||
|
/// `resolve_gdi_name`/`set_active_mode` already established once; this waits until the OS reports
|
||||||
|
/// them stable. `ceiling` (the old fixed sleep) is the worst-case bound: a mode the driver rejected
|
||||||
|
/// (`set_active_mode` left the OS default) or a slow third-party CCD-lock holder (SteelSeries
|
||||||
|
/// class) burns the ceiling and proceeds — behavior identical to the fixed sleep it replaces.
|
||||||
|
/// Returns `true` when the state verified (typical: one or two 25 ms polls), `false` on ceiling.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Runs the CCD/GDI query FFI; call under the manager `state` lock like the callers it serves.
|
||||||
|
pub(crate) unsafe fn wait_mode_settled(
|
||||||
|
target_id: u32,
|
||||||
|
mode: Mode,
|
||||||
|
ceiling: std::time::Duration,
|
||||||
|
) -> bool {
|
||||||
|
let deadline = std::time::Instant::now() + ceiling;
|
||||||
|
loop {
|
||||||
|
// SAFETY (both calls): CCD/GDI FFI over a `Copy` target id, owned returns — the callers'
|
||||||
|
// own safety contract (under the `state` lock) covers them.
|
||||||
|
if resolve_gdi_name(target_id).is_some()
|
||||||
|
&& active_resolution(target_id) == Some((mode.width, mode.height))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if std::time::Instant::now() >= deadline {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(25));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Monitor-departure wait (latency plan P0.3): after a REMOVE, poll until the target has left the
|
||||||
|
/// ACTIVE CCD set — two consecutive absent samples, so one transient query failure mid-teardown
|
||||||
|
/// can't read as "gone" — instead of sleeping the fixed departure settle. `ceiling` (the old fixed
|
||||||
|
/// sleep) bounds the worst case. The OS-side departure may still be finishing driver-side when the
|
||||||
|
/// CCD stops listing the target; the ADD path's ghost-reap retry (pf_vdisplay) remains the backstop
|
||||||
|
/// for that rare race, exactly as it was for a settle that expired. Returns `true` when departure
|
||||||
|
/// was observed, `false` on ceiling.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Runs the CCD query FFI; call under the manager `state` lock like the callers it serves.
|
||||||
|
pub(crate) unsafe fn wait_target_departed(target_id: u32, ceiling: std::time::Duration) -> bool {
|
||||||
|
let deadline = std::time::Instant::now() + ceiling;
|
||||||
|
let mut absent_streak = 0u32;
|
||||||
|
loop {
|
||||||
|
// SAFETY: CCD FFI over a `Copy` target id, owned return, under the caller's `state` lock.
|
||||||
|
if resolve_gdi_name(target_id).is_none() {
|
||||||
|
absent_streak += 1;
|
||||||
|
if absent_streak >= 2 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
absent_streak = 0;
|
||||||
|
}
|
||||||
|
if std::time::Instant::now() >= deadline {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(25));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggle the virtual-display target's advanced-color (HDR) state via the CCD API. Disabling HDR while on the
|
/// Toggle the virtual-display target's advanced-color (HDR) state via the CCD API. Disabling HDR while on the
|
||||||
/// secure (Winlogon) desktop makes it render SDR/composed so DXGI Desktop Duplication can capture it
|
/// secure (Winlogon) desktop makes it render SDR/composed so DXGI Desktop Duplication can capture it
|
||||||
/// (the HDR fullscreen independent-flip otherwise storms `ACCESS_LOST` → black); re-enable on return so
|
/// (the HDR fullscreen independent-flip otherwise storms `ACCESS_LOST` → black); re-enable on return so
|
||||||
|
|||||||
Reference in New Issue
Block a user