feat(pyrowave): Windows host HDR + 4:4:4, Rust client HDR present

Phase 3 of design/pyrowave-444-hdr.md. A PyroWave session now negotiates HDR
(10-bit) and 4:4:4 on a Windows host exactly like HEVC/AV1, and the Linux
client presents it through the real HDR10 path.

Host (Windows): BgraToYuvPlanes becomes mode-aware — SDR/BGRA and HDR/scRGB
variants at half- or full-res chroma. The HDR passes reuse HdrP010Converter's
exact colour math (scRGB -> PQ BT.2020 limited studio codes, verified by
hdr_p010_selftest) but write P010-style MSB-packed codes into two separate
shareable R16_UNORM/R16G16_UNORM textures; chroma keeps the pyrowave family's
centre-sited 2x2 box. idd_push pins the composition to the NEGOTIATED depth
(SDR sessions force advanced color off as before; 10-bit sessions enable it
and ride the FP16 ring), and the descriptor poller re-asserts that state
instead of following display flips the fixed-format encoder can't. The
encoder imports 8/16-bit planes per session and stamps the sequence header's
BT.2020/PQ/matrix bits on HDR (stamp_color_bits, extending 574e3e4e's range
stamp); supports_10bit/can_encode_10bit/can_encode_444 gates open (HDR
Windows-only — Linux capture has no HDR source).

Client: the plane ring becomes R16_UNORM for 10-bit sessions (with a
STORAGE_IMAGE format probe), the planar CSC pass joins the HDR10 swapchain
rebuild (set_hdr_mode previously destroyed it without rebuilding — latent),
st.hdr follows frame.color.is_pq(), and the planar push constants carry
depth-10 MSB-packed rows + the PQ tonemap mode, identical to the NV12 arm.

Verified: .173 (RTX 4090) deploy-config clippy + fmt + wire tests + the
extended pyrowave_win_smoke (10-case {SDR,HDR}x{420,444} matrix incl. R16
imports and header stamps); .21 (RTX 5070 Ti) clippy across 4 crates, host
186 tests, client/presenter/encode tests, both Linux GPU smokes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 13:21:06 +02:00
parent 4861824e7d
commit 188edde2b3
14 changed files with 495 additions and 144 deletions
+19 -6
View File
@@ -428,9 +428,16 @@ fn open_video_backend(
if codec == Codec::PyroWave {
#[cfg(feature = "pyrowave")]
{
let _ = (format, cuda, bit_depth);
return pyrowave::PyroWaveEncoder::open(width, height, fps, bitrate_bps, chroma)
.map(|e| (Box::new(e) as Box<dyn Encoder>, "pyrowave"));
let _ = (format, cuda);
return pyrowave::PyroWaveEncoder::open(
width,
height,
fps,
bitrate_bps,
chroma,
bit_depth,
)
.map(|e| (Box::new(e) as Box<dyn Encoder>, "pyrowave"));
}
#[cfg(not(feature = "pyrowave"))]
anyhow::bail!(
@@ -872,9 +879,9 @@ pub fn can_encode_444(codec: Codec) -> bool {
if codec == Codec::PyroWave {
// PyroWave does its own RGB→YCbCr CSC (capture always hands it a full-chroma source),
// so 4:4:4 needs no GPU encode probe — only the full-res-chroma CSC variant:
// `rgb2yuv444.comp` on Linux (landed, design/pyrowave-444-hdr.md Phase 2); the
// Windows `BgraToYuvPlanes` twin is Phase 3.
return cfg!(target_os = "linux");
// `rgb2yuv444.comp` on Linux (Phase 2) and the mode-aware `BgraToYuvPlanes` on
// Windows (Phase 3) — both landed (design/pyrowave-444-hdr.md).
return true;
}
if codec != Codec::H265 {
return false;
@@ -961,6 +968,12 @@ pub fn can_encode_10bit(codec: Codec) -> bool {
if !codec.supports_10bit() {
return false;
}
if codec == Codec::PyroWave {
// PyroWave needs no GPU encode probe (the wavelet is depth-agnostic) — only the HDR
// capture CSC (scRGB FP16 → 16-bit studio-code planes), which exists on the Windows
// IDD-push path only (design/pyrowave-444-hdr.md Phase 3; Linux capture has no HDR).
return cfg!(target_os = "windows");
}
// Cached per (selected GPU, codec) — a web-console preference change re-probes on the newly
// selected adapter before the next Welcome, mirroring `can_encode_444`.
static CACHE: OnceLock<Mutex<HashMap<(String, &'static str), bool>>> = OnceLock::new();