feat(host/windows): capture the secure desktop in HDR via DDA (no SDR drop)
ci / web (push) Successful in 32s
ci / rust (push) Successful in 1m26s
android / android (push) Failing after 43s
apple / swift (push) Successful in 55s
deb / build-publish (push) Successful in 2m24s
decky / build-publish (push) Successful in 22s
ci / bench (push) Successful in 4m30s
ci / docs-site (push) Successful in 28s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4m1s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m31s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m15s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 7m46s
docker / deploy-docs (push) Successful in 21s

The secure-desktop DDA leg went black with HDR on: legacy DuplicateOutput (the SDR-era
API) can't capture an FP16/HDR desktop, and dropping the SudoVDA out of HDR is denied on
the Winlogon desktop (so the SDR-drop attempt just churned and stayed black).

Instead capture HDR natively on the DDA path — the capturer already has the full
FP16→BT.2020 PQ→R10G10B10A2 conversion (hdr_fp16 path), it just never requested FP16.
Thread a want_hdr flag into duplicate_output: for an HDR session request
DuplicateOutput1 with FP16 first and retry it (5×) instead of bailing to the
HDR-incapable legacy fallback. The secure-desktop mux now reads the monitor's real HDR
state and opens DDA in HDR when set — no advanced-color toggling at all. The
normal-desktop DDA overlay/flip issues that pushed us to WGC don't apply to the composed
Winlogon UI. want_hdr is threaded through every (re)duplication incl. ACCESS_LOST recovery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 22:11:07 +00:00
parent 69765bad93
commit ad0cb1b582
3 changed files with 77 additions and 109 deletions
+42 -93
View File
@@ -2385,33 +2385,37 @@ fn virtual_stream_relay(
}
// Note: takes the dimensions as args rather than capturing `cur_mode` — `cur_mode` is reassigned
// on reconfig, and a closure holding a shared borrow of it for the whole fn would forbid that.
let open_dda = |target: &WinCaptureTarget, w: u32, h: u32, hz: u32| -> Result<DdaPipe> {
// The host already holds the real keepalive (sole isolation owner), so DDA gets a no-op one.
let mut cap = crate::capture::dxgi::DuplCapturer::open(
target.clone(),
Some((w, h, hz)),
Box::new(()),
)
.context("open DDA for secure desktop")?;
cap.set_active(true);
let frame = cap.next_frame().context("DDA first frame")?;
let enc = crate::encode::open_video(
crate::encode::Codec::H265,
frame.format,
frame.width,
frame.height,
hz,
bitrate_kbps as u64 * 1000,
frame.is_cuda(),
bit_depth,
)
.context("open NVENC for DDA")?;
Ok(DdaPipe {
cap: Box::new(cap),
enc,
frame,
})
};
let open_dda =
|target: &WinCaptureTarget, w: u32, h: u32, hz: u32, hdr: bool| -> Result<DdaPipe> {
// The host already holds the real keepalive (sole isolation owner), so DDA gets a no-op one.
// `hdr` requests an FP16 DuplicateOutput1 so the secure desktop is captured in HDR (→ BT.2020
// PQ Main10) instead of black — legacy DuplicateOutput can't capture an HDR/FP16 desktop.
let mut cap = crate::capture::dxgi::DuplCapturer::open(
target.clone(),
Some((w, h, hz)),
Box::new(()),
hdr,
)
.context("open DDA for secure desktop")?;
cap.set_active(true);
let frame = cap.next_frame().context("DDA first frame")?;
let enc = crate::encode::open_video(
crate::encode::Codec::H265,
frame.format,
frame.width,
frame.height,
hz,
bitrate_kbps as u64 * 1000,
frame.is_cuda(),
bit_depth,
)
.context("open NVENC for DDA")?;
Ok(DdaPipe {
cap: Box::new(cap),
enc,
frame,
})
};
let perf = std::env::var("PUNKTFUNK_PERF").is_ok();
let burst_cap = std::env::var("PUNKTFUNK_PACE_BURST_KB")
@@ -2470,10 +2474,6 @@ fn virtual_stream_relay(
// decoder must resume on a keyframe — the two encoders keep independent infinite-GOP state).
let mut dda: Option<DdaPipe> = None;
let mut on_secure = false;
// Whether we dropped the SudoVDA out of HDR for the secure (DDA) leg, so we know to restore it on
// the way back. Keyed off the monitor's REAL HDR state at the moment of the switch (a user can
// toggle Windows HDR mid-session), not the handshake bit depth.
let mut dropped_hdr_for_secure = false;
let mut next = std::time::Instant::now();
let mut await_idr = false;
// Step 6 relaunch watchdog: how many times in a row the helper has died without producing a frame.
@@ -2563,46 +2563,17 @@ fn virtual_stream_relay(
"two-process: source switch"
);
if secure {
// SDR-while-secure (HDR sessions ONLY): drop the SudoVDA out of HDR so the secure
// (Winlogon) desktop renders SDR/composed — HDR fullscreen independent-flip is what made
// DDA storm ACCESS_LOST (black). Key off the monitor's REAL HDR state (a user may have
// toggled Windows HDR on the virtual display), not the negotiated bit depth — the pipeline
// streams HDR whenever the monitor is HDR regardless of the 8/10 handshake. For an SDR
// monitor this is a no-op (no needless topology change, nothing to restore).
dropped_hdr_for_secure =
// Capture the secure (Winlogon) desktop in its NATIVE colorspace. Don't try to drop the
// SudoVDA out of HDR for the DDA leg — display-config changes are denied on the secure
// desktop (the drop just churned + still went black). Instead, if the monitor is in HDR,
// open DDA in HDR (FP16 DuplicateOutput1 → BT.2020 PQ Main10); the normal-desktop DDA
// overlay/flip issues that drove us to WGC don't apply to the composed Winlogon UI.
let hdr =
unsafe { crate::vdisplay::sudovda::advanced_color_enabled(target.target_id) };
if dropped_hdr_for_secure {
// The DDA path is SDR-only (BGRA8) — leaving the SudoVDA in HDR makes the secure
// desktop capture black. Drop to SDR and VERIFY it actually took before opening DDA:
// the CCD advanced-color toggle can transiently fail (rc=5) or lag, so retry until
// advanced_color_enabled() reads false (or we give up and open DDA regardless).
let mut off = false;
for attempt in 0..6 {
unsafe {
crate::vdisplay::sudovda::set_advanced_color(target.target_id, false);
}
std::thread::sleep(std::time::Duration::from_millis(200));
if !unsafe {
crate::vdisplay::sudovda::advanced_color_enabled(target.target_id)
} {
off = true;
tracing::info!(
attempt,
"SudoVDA dropped to SDR for the secure DDA leg"
);
break;
}
}
if !off {
tracing::warn!(
"could not drop the SudoVDA out of HDR for the secure desktop — DDA may \
be black (display-config change likely denied on the Winlogon desktop)"
);
}
}
dda = None; // reopen so we capture the (SDR) output
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz) {
dda = None; // reopen to capture the secure desktop
match open_dda(&target, cur_mode.width, cur_mode.height, effective_hz, hdr) {
Ok(mut p) => {
tracing::info!(hdr, "two-process: opened DDA for the secure desktop");
p.enc.request_keyframe();
dda = Some(p);
}
@@ -2622,30 +2593,8 @@ fn virtual_stream_relay(
dda = None; // free the secure DDA encoder; the relay (helper) is the source again
while relay.try_recv().is_ok() {} // drop secure-dwell backlog
relay.request_keyframe(); // client decoder resumes on the helper's next IDR
if dropped_hdr_for_secure {
// We dropped the SudoVDA to SDR for the DDA leg → restore HDR AND rebuild the helper
// so WGC re-detects the HDR colorspace. (An SDR session never changed the colorspace
// → dropped_hdr_for_secure is false → no rebuild, no recreate.)
dropped_hdr_for_secure = false;
unsafe {
crate::vdisplay::sudovda::set_advanced_color(target.target_id, true);
}
match build(&mut vd, cur_mode) {
Ok((ka, rl, tg, hz)) => {
relay = rl;
_keepalive = ka;
target = tg;
effective_hz = hz;
interval = std::time::Duration::from_secs_f64(1.0 / hz.max(1) as f64);
}
Err(e) => {
tracing::error!(error = %format!("{e:#}"),
"two-process: helper rebuild on secure-exit failed");
while relay.try_recv().is_ok() {}
relay.request_keyframe();
}
}
}
// Nothing to restore: we no longer toggle the SudoVDA's HDR state for the DDA leg, so the
// monitor's colorspace is unchanged and the still-alive WGC helper just resumes.
next = std::time::Instant::now();
}
}