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:
@@ -1009,7 +1009,10 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
||||
// else decodes the codec); only device loss ends the session.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
DecodedImage::PyroWave(f) => {
|
||||
st.hdr = false; // 8-bit SDR codec
|
||||
// The wavelet stream carries the negotiated ColorInfo (no VUI): an
|
||||
// HDR (PQ) pyrowave session presents through the HDR10 path exactly
|
||||
// like the H.26x codecs (design/pyrowave-444-hdr.md Phase 3).
|
||||
st.hdr = f.color.is_pq();
|
||||
match presenter.present(
|
||||
&window,
|
||||
FrameInput::PyroWave(f),
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Presenter {
|
||||
#[cfg(windows)]
|
||||
FrameInput::D3d11(d) => Some(d.color.is_pq()),
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
FrameInput::PyroWave(f) => Some(f.color.is_pq()), // always SDR today
|
||||
FrameInput::PyroWave(f) => Some(f.color.is_pq()),
|
||||
};
|
||||
if let Some(pq) = frame_pq {
|
||||
// A PQ stream we can only tone-map (no HDR10 surface) is the silent failure behind
|
||||
@@ -750,11 +750,31 @@ impl Presenter {
|
||||
&[planar.desc_set],
|
||||
&[],
|
||||
);
|
||||
let rows = csc_rows(color, 8, false);
|
||||
// An HDR (PQ) pyrowave session carries P010-style 10-bit studio codes MSB-packed
|
||||
// into 16-bit planes (design/pyrowave-444-hdr.md §2.2) — same sampling scale as
|
||||
// the P010 path; SDR sessions are plain 8-bit BT.709 limited. Depth follows the
|
||||
// colour contract (negotiation couples 10-bit ⟺ PQ for this codec).
|
||||
let (depth, msb_packed) = if color.is_pq() {
|
||||
(10, true)
|
||||
} else {
|
||||
(8, false)
|
||||
};
|
||||
let rows = csc_rows(color, depth, msb_packed);
|
||||
// Mode 1 = PQ→SDR tonemap (PQ stream without an HDR10 surface); mode 0 passes
|
||||
// the transfer through — identical to the NV12 arm above.
|
||||
let mode = if color.is_pq() && !self.hdr_active {
|
||||
1.0f32
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let peak = std::env::var("PUNKTFUNK_TONEMAP_PEAK")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<f32>().ok())
|
||||
.unwrap_or(4.9); // ≈1000 nits over the 203-nit reference
|
||||
let mut pc = [0f32; 16];
|
||||
pc[..12].copy_from_slice(bytemuck_rows(&rows));
|
||||
pc[12] = 0.0; // SDR passthrough — PyroWave has no PQ path
|
||||
pc[13] = 0.0;
|
||||
pc[12] = mode;
|
||||
pc[13] = peak;
|
||||
let bytes = std::slice::from_raw_parts(pc.as_ptr().cast::<u8>(), 64);
|
||||
self.device.cmd_push_constants(
|
||||
self.cmd_buf,
|
||||
|
||||
@@ -203,11 +203,15 @@ impl Presenter {
|
||||
vk::Format::R8G8B8A8_UNORM
|
||||
};
|
||||
self.csc.destroy(&self.device); // fence-safe: only our cmd bufs reference it
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
if let Some(p) = &self.csc_planar {
|
||||
p.destroy(&self.device);
|
||||
}
|
||||
self.csc = CscPass::new(&self.device, self.video_format)?;
|
||||
// The planar (PyroWave) pass renders to the same intermediate — rebuild it at the
|
||||
// new format too (an HDR pyrowave session needs the 10-bit intermediate exactly
|
||||
// like the H.26x path; 8-bit PQ bands visibly).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
if let Some(p) = self.csc_planar.take() {
|
||||
p.destroy(&self.device);
|
||||
self.csc_planar = Some(CscPass::new_planar(&self.device, self.video_format)?);
|
||||
}
|
||||
if let Some(v) = self.video.take() {
|
||||
unsafe {
|
||||
self.device.destroy_framebuffer(v.framebuffer, None);
|
||||
|
||||
@@ -315,7 +315,8 @@ impl Presenter {
|
||||
ext_mem_win32: ash::khr::external_memory_win32::Device::new(&instance, &device),
|
||||
});
|
||||
let csc = CscPass::new(&device, vk::Format::R8G8B8A8_UNORM)?;
|
||||
// PyroWave is 8-bit SDR only, so the planar pass never needs the HDR10 rebuild.
|
||||
// Starts SDR like `csc`; an HDR (PQ) pyrowave session rebuilds it at the 10-bit
|
||||
// intermediate via `set_hdr_mode`, exactly like the H.26x pass.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
let csc_planar = if pyrowave_ok {
|
||||
Some(CscPass::new_planar(&device, vk::Format::R8G8B8A8_UNORM)?)
|
||||
|
||||
Reference in New Issue
Block a user