fix(vdisplay): first-frame guarantee — republish a retained frame at ring attach

DWM composes a display only when something dirties it, so a session opened
onto an idle desktop never produced a first frame: the host's synthetic-input
"compose kick" (cursor wiggle / sibling-display jump) was the only source, and
it is inherently unreliable — blocked on the secure desktop, defeated by a
fullscreen game's ClipCursor, user-visible, and dead in service contexts. The
field symptom: connect → black stream until something repaints the desktop.

Reconstruct DDA's first-frame semantics at the driver instead (DDA seeds a new
duplication with the current desktop image; IDD-push never had an equivalent):

* frame_transport.rs: new FrameStash — the retained last composed frame, a
  driver-private copy-only texture. publish() now reports Published /
  DescMismatch / Dropped, and harvest_into() pulls the last-published ring
  slot into the stash (keyed-mutex guarded, freshness-checked) before a
  superseded publisher is dropped — between sessions the driver keeps writing
  the host-side-dead old ring, so that slot IS the current desktop image.
* swap_chain_processor.rs: the worker stashes every frame the ring can NOT
  take (unattached, or descriptor-mismatched during a mode/HDR-flip race),
  harvests before a supersede, and REPUBLISHES the stash into every freshly
  attached ring — the host sees a normal seq=1 publish milliseconds after
  channel delivery, no compose needed. Zero steady-state cost: matched
  publishes touch only the ring. The frame-channel stash is now polled every
  iteration (attach latency = first-frame latency; it was 1-in-30).
* monitor.rs: preserved_stash (LUID-tagged) so the retained frame survives
  swap-chain unassign→reassign flaps, alongside the preserved publisher.
* host idd_push.rs: kick_dwm_compose demoted to documented last-resort
  fallback for pre-stash drivers; a debug log now fires when a kick actually
  runs so field logs show whether the stash path is working.

No proto change: the republish is an ordinary publish, so old host + new
driver and new host + old driver both keep working (the latter via the kick).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 15:16:36 +02:00
parent 5748706631
commit 89a08f83af
4 changed files with 362 additions and 56 deletions
@@ -69,6 +69,12 @@ pub struct MonitorObject {
/// ([`take_preserved_publisher`]) and resumes publishing into the same ring. Dropped with the
/// `MonitorObject` on teardown (closing its ring handles) if no worker ever reclaims it.
pub preserved_publisher: Option<crate::frame_transport::FramePublisher>,
/// The worker's [`FrameStash`](crate::frame_transport::FrameStash) (the retained last composed
/// frame — the first-frame guarantee) preserved across a swap-chain unassign→reassign flap,
/// tagged with the render-adapter LUID its texture lives on: the next worker re-adopts it only
/// on the SAME adapter (same pooled device — a cross-device texture would be unusable). Dropped
/// with the `MonitorObject` on teardown (it holds only a driver-private texture, no handles).
pub preserved_stash: Option<(crate::frame_transport::FrameStash, u32, i32)>,
/// When the entry was created — the watchdog skips still-initializing monitors.
pub created_at: Instant,
}
@@ -372,6 +378,46 @@ pub fn take_preserved_publisher(
.take()
}
/// Preserve an EXITING worker's [`FrameStash`](crate::frame_transport::FrameStash) on its monitor
/// across a swap-chain unassign→reassign flap, tagged with the render-adapter LUID it lives on
/// (see [`MonitorObject::preserved_stash`]). An empty stash, or one for a monitor that no longer
/// exists (genuine teardown), is simply dropped — unlike a publisher it owns no handles, so there
/// is nothing to hand back.
pub fn preserve_stash(
target_id: u32,
luid_low: u32,
luid_high: i32,
stash: crate::frame_transport::FrameStash,
) {
if target_id == 0 || stash.texture().is_none() {
return;
}
let mut lock = lock_monitors();
if let Some(m) = lock.iter_mut().find(|m| m.target_id == target_id) {
m.preserved_stash = Some((stash, luid_low, luid_high));
}
}
/// Take (remove) the preserved [`FrameStash`](crate::frame_transport::FrameStash) for a freshly-
/// (re)assigned swap-chain worker — returned only when the worker's render adapter matches the one
/// the stash was preserved on (same pooled device); a mismatched stash is dropped (its texture
/// would be cross-device).
pub fn take_preserved_stash(
target_id: u32,
luid_low: u32,
luid_high: i32,
) -> Option<crate::frame_transport::FrameStash> {
if target_id == 0 {
return None;
}
let (stash, low, high) = lock_monitors()
.iter_mut()
.find(|m| m.target_id == target_id)?
.preserved_stash
.take()?;
((low, high) == (luid_low, luid_high)).then_some(stash)
}
/// Install a swap-chain processor on the monitor whose handle matches, returning any PREVIOUS processor
/// for the caller to drop OUTSIDE the lock. Dropping a processor RAII-joins its worker thread, so it must
/// never happen while holding `MONITOR_MODES` (the worker would block the whole control plane / risk a
@@ -461,6 +507,7 @@ pub fn create_monitor(
swap_chain_processor: None,
frame_channel: None,
preserved_publisher: None,
preserved_stash: None,
created_at: Instant::now(),
});
id