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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user