From b0d28380b50a802affaa7dcabd202571b39bbd40 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 25 Jun 2026 13:18:05 +0000 Subject: [PATCH] =?UTF-8?q?feat(windows-host):=20rotate=20out-ring=20on=20?= =?UTF-8?q?repeat=20+=20size=20HDR=20ring=20at=20open=20(audit=20=C2=A75.3?= =?UTF-8?q?/=C2=A75.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §5.3 (C3): repeat_last() now copies the last frame into a FRESH rotated out-ring slot instead of re-handing last_present's slot, so a repeat (static desktop) never re-hands a slot still encoding under pipeline_depth>1. OUT_RING(3) > max depth(2) keeps the rotated slot free — the out-ring rotation contract now holds for repeats too, not just the synchronous-loop assumption. §5.4 (C4): when enabling advanced color for a 10-bit client, trust set_advanced_color success and size the ring FP16 directly, instead of racing the advanced_color_enabled poll (which could size SDR while the driver composes FP16 -> format mismatch -> an immediate ring recreate + dropped first frames). Verified: host clippy (nvenc) clean on the RTX box. On-glass to confirm: HDR-client first-frame + static-desktop pipelining. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/capture/idd_push.rs | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/punktfunk-host/src/capture/idd_push.rs b/crates/punktfunk-host/src/capture/idd_push.rs index c9d4e0f..1ffcfcd 100644 --- a/crates/punktfunk-host/src/capture/idd_push.rs +++ b/crates/punktfunk-host/src/capture/idd_push.rs @@ -371,12 +371,18 @@ impl IddPushCapturer { // SDR-only client leaves the display alone (and still gets a tone-mapped picture, never a freeze, // if the user does enable HDR). unsafe { - if client_10bit && crate::vdisplay::sudovda::set_advanced_color(target.target_id, true) - { + // If we ENABLE advanced color for a 10-bit client, trust it (the driver will compose FP16) and + // size the ring FP16 directly — don't race the advanced_color_enabled poll, which may not have + // settled within 250 ms and would size the ring SDR while the driver composes FP16 → a format + // mismatch → an immediate ring recreate + dropped first frames (audit §5.4). + let enabled_hdr = + client_10bit && crate::vdisplay::sudovda::set_advanced_color(target.target_id, true); + if enabled_hdr { // Let the colorspace change settle before the driver composes + we size the ring. std::thread::sleep(Duration::from_millis(250)); } - let display_hdr = crate::vdisplay::sudovda::advanced_color_enabled(target.target_id); + let display_hdr = + enabled_hdr || crate::vdisplay::sudovda::advanced_color_enabled(target.target_id); let ring_fmt = if display_hdr { DXGI_FORMAT_R16G16B16A16_FLOAT } else { @@ -810,14 +816,28 @@ impl IddPushCapturer { })) } - fn repeat_last(&self) -> Option { - self.last_present.as_ref().map(|(tex, pf)| CapturedFrame { + fn repeat_last(&mut self) -> Option { + // Copy the last presented frame into a FRESH rotated out-ring slot so a repeat (static desktop, no + // new driver frame) never re-hands a slot that may still be encoding under pipeline_depth>1 — the + // out-ring rotation IS the texture-ownership contract, and repeats must honor it too (audit §5.3). + // OUT_RING(3) > the max pipeline_depth(2) guarantees the rotated slot is not in flight. + let (src, pf) = self.last_present.clone()?; + let i = self.out_idx; + let dst = self.out_ring.get(i)?.0.clone(); + // SAFETY: GPU copy on the owning thread's immediate context; src/dst are our out-ring textures of + // identical format/size (src is a previous out-ring slot; dst the next). + unsafe { + self.context.CopyResource(&dst, &src); + } + self.out_idx = (i + 1) % self.out_ring.len(); + self.last_present = Some((dst.clone(), pf)); + Some(CapturedFrame { width: self.width, height: self.height, pts_ns: now_ns(), - format: *pf, + format: pf, payload: FramePayload::D3d11(D3d11Frame { - texture: tex.clone(), + texture: dst, device: self.device.clone(), }), })