fix(host/windows): detect format/size change on the DDA acquire path

DDA only re-read the duplication format/size on rebuild (recreate_dupl) and
initial open. A mid-stream HDR<->SDR flip (FP16<->BGRA — e.g. the SudoVDA output
dropping out of HDR for the secure desktop) or a resolution change that does NOT
raise ACCESS_LOST left hdr_fp16/width/height stale, so present_acquired copied
into a mismatched-format/size target — the secure-desktop "works once, then HDR
breaks" symptom. Re-read the acquired texture's desc every frame (as Apollo does)
and rebuild on a real change instead of presenting a mismatched frame; throttled
like the ACCESS_LOST path so a flapping toggle can't hammer DuplicateOutput.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:18:53 +00:00
parent 3d04ce92a1
commit 995db69387
+32
View File
@@ -1570,6 +1570,38 @@ impl DuplCapturer {
Err(e) => return Err(e).context("AcquireNextFrame"),
}
let res = res.context("AcquireNextFrame: null resource")?;
// Detect a mode/format change on the hot path. The desktop can flip HDR<->SDR (FP16<->BGRA —
// e.g. the SudoVDA output dropping out of HDR for the secure desktop) or change resolution
// WITHOUT raising ACCESS_LOST; `hdr_fp16`/`width`/`height` would then be stale and
// `present_acquired` would CopyResource into a mismatched-format/size target — corruption, or
// the secure-desktop "works once, then HDR breaks" bug. Re-read the acquired texture's desc
// every frame (Apollo does this) and rebuild on a real change instead of presenting a
// mismatched frame. Throttled like the ACCESS_LOST path so a flapping toggle can't hammer
// DuplicateOutput.
if let Ok(tex) = res.cast::<ID3D11Texture2D>() {
let mut d = D3D11_TEXTURE2D_DESC::default();
tex.GetDesc(&mut d);
let now_hdr = d.Format == DXGI_FORMAT_R16G16B16A16_FLOAT;
if d.Width != self.width || d.Height != self.height || now_hdr != self.hdr_fp16 {
tracing::info!(
old = format!("{}x{} hdr={}", self.width, self.height, self.hdr_fp16),
new = format!("{}x{} hdr={}", d.Width, d.Height, now_hdr),
"DXGI capture format/size changed mid-stream — rebuilding"
);
let _ = self.dupl.ReleaseFrame();
let now = Instant::now();
let due = self
.last_rebuild
.map_or(true, |t| now.duration_since(t) >= Duration::from_millis(250));
if due {
self.last_rebuild = Some(now);
if self.recreate_dupl().is_ok() {
self.first_frame = true;
}
}
return Ok(None);
}
}
Ok(Some(self.present_acquired(res)?))
}