feat(windows-host): IDD-push first-frame failover to DDA (game-capture bug GB1 pt1)
wait_for_attach now requires the driver to publish a FIRST frame, not just attach (DRV_STATUS_OPENED). A fullscreen game can leave the virtual display in a format/size the driver's publish() guard rejects -> the driver ATTACHES but silently drops every frame; previously the host sailed past open() and only died on next_frame's 20s deadline (the 'reconnect = black + working audio' symptom). Now open() fails -> capture.rs falls back to DDA (reusing the C1 fallback) -> the game is captured + visible after a reconnect. Safe at open: the OS composites the freshly-activated virtual display, so a frame arrives within ~1s — a normal/idle open isn't false-failed; only a genuinely-broken display (no frame in 4s) falls back (and DDA is a working path, so even a false-positive degrades gracefully). GB1 Stage 1a (docs/windows-host-rewrite-game-capture-bug.md P3). The mid-session-without-reconnect live failover (composing capturer) is the next piece. Verified: host clippy (nvenc) clean on the RTX box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -507,37 +507,47 @@ impl IddPushCapturer {
|
|||||||
// it back to the caller for the DDA fallback (audit §5.1).
|
// it back to the caller for the DDA fallback (audit §5.1).
|
||||||
_keepalive: Box::new(()),
|
_keepalive: Box::new(()),
|
||||||
};
|
};
|
||||||
// Bounded wait for the driver to ATTACH to the ring (it writes DRV_STATUS_OPENED). An attach
|
// Bounded wait for the driver to ATTACH to the ring AND publish a first frame. An attach
|
||||||
// failure (e.g. the OS rendered the IDD on a different GPU than our ring → DRV_STATUS_TEX_FAIL)
|
// failure (DRV_STATUS_TEX_FAIL) or an attach-but-no-frames (a game left the display in a
|
||||||
// becomes an open failure the caller falls back from, instead of next_frame's 20 s deadline.
|
// format/size the ring can't match) becomes an open failure the caller falls back from (→ DDA),
|
||||||
|
// instead of next_frame's 20 s black-then-bail.
|
||||||
me.wait_for_attach()?;
|
me.wait_for_attach()?;
|
||||||
Ok(me)
|
Ok(me)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block (bounded) until the driver attaches to the host ring, else fail so the caller can fall back
|
/// Block (bounded) until the driver has ATTACHED to the host ring (`DRV_STATUS_OPENED`) **and published
|
||||||
/// to DDA (audit §5.1). Checks `driver_status` (NOT frame arrival — an idle desktop may present no
|
/// a first frame**, else fail so the caller can fall back to DDA (audit §5.1 +
|
||||||
/// frame yet), so it never falsely fails on the happy path: the driver writes `DRV_STATUS_OPENED` as
|
/// `docs/windows-host-rewrite-game-capture-bug.md` P3/Stage 1).
|
||||||
/// soon as it opens the ring textures, regardless of whether DWM has composed a frame.
|
///
|
||||||
|
/// Requiring the first frame — not just the attach — catches the *reconnect-into-a-broken-state* case:
|
||||||
|
/// a fullscreen game can leave the virtual display in a format/size that the driver's `publish()` guard
|
||||||
|
/// rejects, so the driver ATTACHES but silently drops every frame; without this the host sails past
|
||||||
|
/// `open()` and only dies on `next_frame`'s 20 s deadline (the "reconnect = black + audio" symptom). At
|
||||||
|
/// session open the OS activates the virtual display → DWM composites it → a frame arrives within ~1 s,
|
||||||
|
/// so this does not false-fail a normal (even idle) open; no frame within the window = genuinely broken.
|
||||||
fn wait_for_attach(&self) -> Result<()> {
|
fn wait_for_attach(&self) -> Result<()> {
|
||||||
let deadline = Instant::now() + Duration::from_secs(4);
|
let deadline = Instant::now() + Duration::from_secs(4);
|
||||||
loop {
|
loop {
|
||||||
// Plain read: the driver writes this u32; an aligned u32 read can't tear (same access as
|
// Plain read: the driver writes this u32; an aligned u32 read can't tear (same access as
|
||||||
// log_driver_status_once).
|
// log_driver_status_once).
|
||||||
let st = unsafe { (*self.header).driver_status };
|
let st = unsafe { (*self.header).driver_status };
|
||||||
match st {
|
if matches!(st, DRV_STATUS_TEX_FAIL | DRV_STATUS_NO_DEVICE1) {
|
||||||
DRV_STATUS_OPENED => return Ok(()),
|
|
||||||
DRV_STATUS_TEX_FAIL | DRV_STATUS_NO_DEVICE1 => {
|
|
||||||
let detail = unsafe { (*self.header).driver_status_detail };
|
let detail = unsafe { (*self.header).driver_status_detail };
|
||||||
bail!(
|
bail!(
|
||||||
"IDD-push driver failed to attach (driver_status={st} detail=0x{detail:08x} — \
|
"IDD-push driver failed to attach (driver_status={st} detail=0x{detail:08x} — \
|
||||||
render-adapter mismatch?)"
|
render-adapter mismatch?)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
// Attached AND a frame has been published — the publish token's seq advances past 0.
|
||||||
|
if st == DRV_STATUS_OPENED && frame::FrameToken::unpack(self.latest()).seq != 0 {
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
if Instant::now() > deadline {
|
if Instant::now() > deadline {
|
||||||
bail!("IDD-push driver did not attach within 4s (driver_status={st})");
|
bail!(
|
||||||
|
"IDD-push: driver_status={st} but no frame published within 4s — the virtual display \
|
||||||
|
is likely in a format/size the ring can't match (fullscreen game?); falling back"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(20));
|
std::thread::sleep(Duration::from_millis(20));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user