feat(windows-host): IDD-push attach fallback to DDA, not the 20s black bail (audit §5.1)
open() now hands the keepalive BACK on failure (the WGC attach_keepalive pattern) so the caller can fall back instead of tearing the virtual display down. Added a bounded wait_for_attach() that polls the driver's DRV_STATUS_OPENED — it checks ATTACH status, not frame arrival, so it never false-fails on an idle desktop that has composed no frame yet.
An attach failure (e.g. a hybrid-GPU render mismatch -> DRV_STATUS_TEX_FAIL, or the driver never opening the ring within 4s) now fails open() -> capture.rs falls back to DDA, instead of next_frame's 20s deadline leaving the session black. Pairs with the driver SET_RENDER_ADAPTER fix (0a7ae5e).
Verified: host clippy (nvenc) clean on the RTX box. Behavioral validation (fallback trigger + happy-path attach timing) needs an on-glass session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -351,8 +351,20 @@ pub fn capture_virtual_output(
|
|||||||
// the host can't revive it. The driver's recreate crash (target id resolved to 0) is fixed by
|
// the host can't revive it. The driver's recreate crash (target id resolved to 0) is fixed by
|
||||||
// stamping target_id onto the monitor context. The ring is always FP16 (the driver composes
|
// stamping target_id onto the monitor context. The ring is always FP16 (the driver composes
|
||||||
// the IDD in FP16); `want_hdr` selects the per-frame conversion (FP16 → Rgb10a2 vs Bgra).
|
// the IDD in FP16); `want_hdr` selects the per-frame conversion (FP16 → Rgb10a2 vs Bgra).
|
||||||
return idd_push::IddPushCapturer::open(target, pref, want_hdr, keep)
|
// If IDD-push can't open OR the driver doesn't attach to the ring within a few seconds (e.g. a
|
||||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
// hybrid-GPU render mismatch), fall back to DDA so the session is NEVER left black (audit §5.1).
|
||||||
|
// `open()` hands the keepalive back on failure so DDA can take ownership of the virtual display.
|
||||||
|
match idd_push::IddPushCapturer::open(target.clone(), pref, want_hdr, keep) {
|
||||||
|
Ok(c) => return Ok(Box::new(c) as Box<dyn Capturer>),
|
||||||
|
Err((e, keep)) => {
|
||||||
|
tracing::warn!(
|
||||||
|
error = %format!("{e:#}"),
|
||||||
|
"IDD-push open/attach failed — falling back to DDA"
|
||||||
|
);
|
||||||
|
return dxgi::DuplCapturer::open(target, pref, keep, false)
|
||||||
|
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// WGC (Windows.Graphics.Capture) is the default: it captures the COMPOSED desktop including the
|
// WGC (Windows.Graphics.Capture) is the default: it captures the COMPOSED desktop including the
|
||||||
// overlay/independent-flip planes DXGI Desktop Duplication misses (the frozen-HDR-animation bug),
|
// overlay/independent-flip planes DXGI Desktop Duplication misses (the frozen-HDR-animation bug),
|
||||||
|
|||||||
@@ -208,7 +208,12 @@ pub fn open_or_reuse(
|
|||||||
client_10bit,
|
client_10bit,
|
||||||
"IDD push: creating the persistent capturer (first session)"
|
"IDD push: creating the persistent capturer (first session)"
|
||||||
);
|
);
|
||||||
*slot = Some(IddPushCapturer::open(target, preferred, client_10bit, keepalive)?);
|
// (dead persistent path) open() now returns the keepalive on failure; this path has no
|
||||||
|
// fallback, so discard it on error.
|
||||||
|
*slot = Some(
|
||||||
|
IddPushCapturer::open(target, preferred, client_10bit, keepalive)
|
||||||
|
.map_err(|(e, _keepalive)| e)?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Box::new(IddReuseHandle))
|
Ok(Box::new(IddReuseHandle))
|
||||||
@@ -331,11 +336,29 @@ impl IddPushCapturer {
|
|||||||
Ok(slots)
|
Ok(slots)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open the IDD-push capturer. On success the caller's `keepalive` is attached (the capturer owns the
|
||||||
|
/// virtual display); on FAILURE the keepalive is handed BACK so the caller can fall back to DDA
|
||||||
|
/// instead of tearing the display down (audit §5.1 — no more 20 s black bail). "Failure" includes the
|
||||||
|
/// driver not attaching to the ring within a few seconds (e.g. a hybrid-GPU render mismatch).
|
||||||
pub fn open(
|
pub fn open(
|
||||||
target: WinCaptureTarget,
|
target: WinCaptureTarget,
|
||||||
preferred: Option<(u32, u32, u32)>,
|
preferred: Option<(u32, u32, u32)>,
|
||||||
client_10bit: bool,
|
client_10bit: bool,
|
||||||
keepalive: Box<dyn Send>,
|
keepalive: Box<dyn Send>,
|
||||||
|
) -> std::result::Result<Self, (anyhow::Error, Box<dyn Send>)> {
|
||||||
|
match Self::open_inner(target, preferred, client_10bit) {
|
||||||
|
Ok(mut me) => {
|
||||||
|
me._keepalive = keepalive;
|
||||||
|
Ok(me)
|
||||||
|
}
|
||||||
|
Err(e) => Err((e, keepalive)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_inner(
|
||||||
|
target: WinCaptureTarget,
|
||||||
|
preferred: Option<(u32, u32, u32)>,
|
||||||
|
client_10bit: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let (w, h, _hz) = preferred
|
let (w, h, _hz) = preferred
|
||||||
.context("IDD push needs the negotiated mode (WxH) to size the shared ring")?;
|
.context("IDD push needs the negotiated mode (WxH) to size the shared ring")?;
|
||||||
@@ -451,7 +474,7 @@ impl IddPushCapturer {
|
|||||||
ring_fp16 = display_hdr,
|
ring_fp16 = display_hdr,
|
||||||
"IDD push(host): created shared ring; waiting for the driver to attach + publish"
|
"IDD push(host): created shared ring; waiting for the driver to attach + publish"
|
||||||
);
|
);
|
||||||
Ok(Self {
|
let me = Self {
|
||||||
device,
|
device,
|
||||||
context,
|
context,
|
||||||
target_id: target.target_id,
|
target_id: target.target_id,
|
||||||
@@ -474,8 +497,43 @@ impl IddPushCapturer {
|
|||||||
last_present: None,
|
last_present: None,
|
||||||
status_logged: false,
|
status_logged: false,
|
||||||
my_gen: crate::vdisplay::sudovda::CURRENT_MON_GEN.load(Ordering::Relaxed),
|
my_gen: crate::vdisplay::sudovda::CURRENT_MON_GEN.load(Ordering::Relaxed),
|
||||||
_keepalive: keepalive,
|
// Placeholder; `open()` attaches the real keepalive on success, so a FAILED open can hand
|
||||||
})
|
// it back to the caller for the DDA fallback (audit §5.1).
|
||||||
|
_keepalive: Box::new(()),
|
||||||
|
};
|
||||||
|
// Bounded wait for the driver to ATTACH to the ring (it writes DRV_STATUS_OPENED). An attach
|
||||||
|
// failure (e.g. the OS rendered the IDD on a different GPU than our ring → DRV_STATUS_TEX_FAIL)
|
||||||
|
// becomes an open failure the caller falls back from, instead of next_frame's 20 s deadline.
|
||||||
|
me.wait_for_attach()?;
|
||||||
|
Ok(me)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block (bounded) until the driver attaches to the host ring, else fail so the caller can fall back
|
||||||
|
/// to DDA (audit §5.1). Checks `driver_status` (NOT frame arrival — an idle desktop may present no
|
||||||
|
/// frame yet), so it never falsely fails on the happy path: the driver writes `DRV_STATUS_OPENED` as
|
||||||
|
/// soon as it opens the ring textures, regardless of whether DWM has composed a frame.
|
||||||
|
fn wait_for_attach(&self) -> Result<()> {
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(4);
|
||||||
|
loop {
|
||||||
|
// Plain read: the driver writes this u32; an aligned u32 read can't tear (same access as
|
||||||
|
// log_driver_status_once).
|
||||||
|
let st = unsafe { (*self.header).driver_status };
|
||||||
|
match st {
|
||||||
|
DRV_STATUS_OPENED => return Ok(()),
|
||||||
|
DRV_STATUS_TEX_FAIL | DRV_STATUS_NO_DEVICE1 => {
|
||||||
|
let detail = unsafe { (*self.header).driver_status_detail };
|
||||||
|
bail!(
|
||||||
|
"IDD-push driver failed to attach (driver_status={st} detail=0x{detail:08x} — \
|
||||||
|
render-adapter mismatch?)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if Instant::now() > deadline {
|
||||||
|
bail!("IDD-push driver did not attach within 4s (driver_status={st})");
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(20));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -796,7 +854,7 @@ pub fn spawn_observer(target: WinCaptureTarget, preferred: Option<(u32, u32, u32
|
|||||||
);
|
);
|
||||||
cap.log_debug_block();
|
cap.log_debug_block();
|
||||||
}
|
}
|
||||||
Err(e) => tracing::warn!(
|
Err((e, _keep)) => tracing::warn!(
|
||||||
target_id = tid,
|
target_id = tid,
|
||||||
"IDD push OBSERVER: ring open failed: {e:#}"
|
"IDD push OBSERVER: ring open failed: {e:#}"
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user