fix(capture): stop stranding a PipeWire buffer on a caught panic; invalidate the PyroWave CSC on ring recreate

Two medium findings from the round-1 sweep.

- The `.process` callback dequeued a buffer INSIDE its `catch_unwind`, and every
  requeue site was inside too. `newest` is a raw pointer with no Drop, so any
  caught panic (update_cursor_meta / consume_frame) unwound past all three
  requeues and permanently stranded that buffer. Once the stream's fixed pool
  drained, `dequeue_raw_buffer` returned null every call and capture silently
  wedged while still reporting negotiated/active — defeating the very
  catch_unwind that was meant to keep a panic survivable. The drain loop now runs
  OUTSIDE the catch (dequeue/queue are non-panicking C FFI pointer ops) and
  `newest` is requeued exactly once after it, on every path: normal,
  corrupted-skip, or caught panic.

- `recreate_ring` invalidated `video_conv` and `hdr_p010_conv` but not
  `pyro_conv`. That converter is mode-baked — BgraToYuvPlanes selects entirely
  different shaders and output formats for SDR (8-bit BT.709 → R8/R8G8) vs HDR
  (scRGB→PQ BT.2020 → R16/R16G16) — and `ensure_pyro_conv` only builds when None,
  so a display_hdr flip reused the stale SDR converter against a freshly
  HDR-formatted pyro ring, corrupting every frame. Reachable at the documented
  "Downgrade point D": a PyroWave session with client_10bit=true that opens on a
  box where HDR can't enable, then flips once the display comes up.

Linux .21: pf-capture 1/0. Windows .173: `cargo check -p pf-capture` clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 23:16:25 +02:00
parent 26ff005e70
commit fe4af1761e
2 changed files with 43 additions and 37 deletions
@@ -1341,6 +1341,12 @@ impl IddPushCapturer {
self.out_ring.clear(); // the output format changed → rebuild lazily at the new format
self.video_conv = None; // converters are sized + HDR-specific → rebuild at the new mode
self.hdr_p010_conv = None;
// The PyroWave CSC is mode-baked too (BgraToYuvPlanes picks different SDR vs HDR shaders
// and R8/R8G8 vs R16/R16G16 outputs). Without this, a display_hdr flip (Downgrade point D:
// client_10bit=true but HDR couldn't enable at open) reused the stale SDR converter against
// the freshly HDR-formatted pyro ring — every frame corrupted. `ensure_pyro_conv` only
// builds when None, so it must be reset here like its siblings.
self.pyro_conv = None;
self.pyro_ring.clear(); // PyroWave two-plane ring is sized → rebuild at the new mode
self.pyro_last = None;
self.out_idx = 0;