fix(encode): delete the write-only EncoderCaps::supports_hdr_metadata
A caps field nothing reads is a contract nobody honors — and this one shipped write-only: its single reader anywhere in the workspace was a hardware-gated assertion inside pf-encode's own AMF smoke test. Both planes send the static HDR grade out-of-band unconditionally (the native 0xCE datagram per keyframe, the GameStream 0x010e control message), every first-party client reads exclusively that path, and none parse in-band SEI — so the host decision the field was reserved for (suppress out-of-band when the encoder embeds) can never validly exist. The field's doc contract had also rotted in two directions: it claimed set_hdr_meta no-ops when false (native AMF and QSV consume it regardless) and that only Windows direct-NVENC attaches in-band metadata (AMF and QSV do too). The in-band SEI/OBU emission itself is untouched — it stays a bonus for stock decoders, documented at the emit sites; the trait docs now describe the real routing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -249,9 +249,16 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static capabilities an [`Encoder`] declares so the session glue routes loss-recovery and HDR
|
/// Static capabilities an [`Encoder`] declares so the session glue routes loss-recovery and
|
||||||
/// plumbing by *query* rather than relying on a method's no-op/`false` default. Cheap `Copy`; fixed
|
/// cursor plumbing by *query* rather than relying on a method's no-op/`false` default. Cheap
|
||||||
/// for the session (an HDR toggle re-initialises the encoder — re-query if that matters).
|
/// `Copy`; fixed for the session (an HDR toggle re-initialises the encoder — re-query if that
|
||||||
|
/// matters).
|
||||||
|
///
|
||||||
|
/// (There is deliberately NO `supports_hdr_metadata` cap: in-band HDR SEI/OBU embedding needs no
|
||||||
|
/// host-side routing — every first-party client reads the static grade exclusively out-of-band
|
||||||
|
/// (the native 0xCE datagram / the GameStream 0x010e control message), both planes send it
|
||||||
|
/// unconditionally, and the in-band grade is a decoder-side bonus for stock clients. A cap field
|
||||||
|
/// nothing reads is a contract nobody honors; it was deleted after shipping write-only.)
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct EncoderCaps {
|
pub struct EncoderCaps {
|
||||||
/// The encoder can perform real reference-frame invalidation — i.e.
|
/// The encoder can perform real reference-frame invalidation — i.e.
|
||||||
@@ -261,10 +268,6 @@ pub struct EncoderCaps {
|
|||||||
/// AMF (user-LTR force-reference, when the driver accepted the LTR slots at open). The
|
/// AMF (user-LTR force-reference, when the driver accepted the LTR slots at open). The
|
||||||
/// libavcodec paths (Linux NVENC, VAAPI, QSV) can't express it and always keyframe.
|
/// libavcodec paths (Linux NVENC, VAAPI, QSV) can't express it and always keyframe.
|
||||||
pub supports_rfi: bool,
|
pub supports_rfi: bool,
|
||||||
/// The encoder emits in-band HDR mastering/CLL SEI from [`set_hdr_meta`](Encoder::set_hdr_meta).
|
|
||||||
/// When `false`, `set_hdr_meta` is a no-op and no in-band grade reaches the client. Only the
|
|
||||||
/// Windows direct-NVENC path attaches it today.
|
|
||||||
pub supports_hdr_metadata: bool,
|
|
||||||
/// The opened encoder is actually producing a full-chroma 4:4:4 (`chroma_format_idc = 3`) stream.
|
/// The opened encoder is actually producing a full-chroma 4:4:4 (`chroma_format_idc = 3`) stream.
|
||||||
/// `false` on every 4:2:0 session (the default) and on a backend that declined 4:4:4. Set by the
|
/// `false` on every 4:2:0 session (the default) and on a backend that declined 4:4:4. Set by the
|
||||||
/// NVENC backends (Linux + Windows). The chroma is committed to the wire (`Welcome::chroma_format`)
|
/// NVENC backends (Linux + Windows). The chroma is committed to the wire (`Welcome::chroma_format`)
|
||||||
@@ -333,11 +336,10 @@ pub trait Encoder: Send {
|
|||||||
let _ = wire_index;
|
let _ = wire_index;
|
||||||
self.submit(frame)
|
self.submit(frame)
|
||||||
}
|
}
|
||||||
/// This encoder's static [capabilities](EncoderCaps) (RFI, HDR SEI), so the session glue can
|
/// This encoder's static [capabilities](EncoderCaps) (RFI, intra-refresh, chroma, cursor
|
||||||
/// route by query rather than rely on the no-op/`false` defaults of
|
/// blending), so the session glue can route by query rather than rely on the no-op/`false`
|
||||||
/// [`invalidate_ref_frames`](Self::invalidate_ref_frames) / [`set_hdr_meta`](Self::set_hdr_meta).
|
/// defaults of methods like [`invalidate_ref_frames`](Self::invalidate_ref_frames).
|
||||||
/// Default: no optional capabilities (the SDR / libavcodec backends) — only the direct-NVENC
|
/// Default: no optional capabilities (the software / libavcodec backends).
|
||||||
/// path overrides it.
|
|
||||||
fn caps(&self) -> EncoderCaps {
|
fn caps(&self) -> EncoderCaps {
|
||||||
EncoderCaps::default()
|
EncoderCaps::default()
|
||||||
}
|
}
|
||||||
@@ -345,10 +347,12 @@ pub trait Encoder: Send {
|
|||||||
/// reference-frame-invalidation request). Default: no-op.
|
/// reference-frame-invalidation request). Default: no-op.
|
||||||
fn request_keyframe(&mut self) {}
|
fn request_keyframe(&mut self) {}
|
||||||
/// Set the source's static HDR mastering metadata (from the capturer). An HDR encoder emits it
|
/// Set the source's static HDR mastering metadata (from the capturer). An HDR encoder emits it
|
||||||
/// as in-band SEI (`mastering_display_colour_volume` + `content_light_level_info`) on each
|
/// in-band (HEVC/H.264 `mastering_display_colour_volume` + `content_light_level_info` SEI, or
|
||||||
/// keyframe so any decoder — including stock Moonlight — tone-maps from the source's real grade.
|
/// AV1 metadata OBUs) on keyframes so a stock decoder — e.g. stock Moonlight — tone-maps from
|
||||||
/// Default: no-op (SDR encoders / libavcodec paths that don't attach it yet). Cheap to call
|
/// the source's real grade. Default: no-op (SDR encoders / paths that don't attach it).
|
||||||
/// every frame; only the direct-NVENC path consumes it.
|
/// Cheap to call every frame; consumed by Windows direct-NVENC, native AMF, and native QSV.
|
||||||
|
/// Every first-party client reads the grade out-of-band (the 0xCE datagram) regardless, so
|
||||||
|
/// this is a bonus for stock decoders, never the primary channel.
|
||||||
fn set_hdr_meta(&mut self, _meta: Option<punktfunk_core::quic::HdrMeta>) {}
|
fn set_hdr_meta(&mut self, _meta: Option<punktfunk_core::quic::HdrMeta>) {}
|
||||||
/// Invalidate a contiguous range of previously-encoded reference frames (client frame numbers
|
/// Invalidate a contiguous range of previously-encoded reference frames (client frame numbers
|
||||||
/// — WIRE frame indexes, the domain [`submit_indexed`](Self::submit_indexed) pins the encoder's
|
/// — WIRE frame indexes, the domain [`submit_indexed`](Self::submit_indexed) pins the encoder's
|
||||||
|
|||||||
@@ -1850,7 +1850,6 @@ impl Encoder for NvencCudaEncoder {
|
|||||||
// Composites `frame.cursor` via the SPIR-V blend over the Vulkan-allocated input slot.
|
// Composites `frame.cursor` via the SPIR-V blend over the Vulkan-allocated input slot.
|
||||||
blends_cursor: true,
|
blends_cursor: true,
|
||||||
supports_rfi: self.rfi_supported,
|
supports_rfi: self.rfi_supported,
|
||||||
supports_hdr_metadata: self.hdr,
|
|
||||||
chroma_444: self.chroma_444,
|
chroma_444: self.chroma_444,
|
||||||
intra_refresh: false,
|
intra_refresh: false,
|
||||||
intra_refresh_recovery: false,
|
intra_refresh_recovery: false,
|
||||||
|
|||||||
@@ -2014,9 +2014,6 @@ impl Encoder for AmfEncoder {
|
|||||||
// frame, force a later one to re-reference it). True only when the live driver accepted
|
// frame, force a later one to re-reference it). True only when the live driver accepted
|
||||||
// the LTR slots at open — otherwise loss recovery falls back to a full IDR.
|
// the LTR slots at open — otherwise loss recovery falls back to a full IDR.
|
||||||
supports_rfi: self.ltr_active,
|
supports_rfi: self.ltr_active,
|
||||||
// In-band mastering/CLL via `*InHDRMetadata` (HEVC SEI / AV1 metadata OBU); AVC has
|
|
||||||
// no such property (and no HDR sessions negotiate H.264).
|
|
||||||
supports_hdr_metadata: self.ten_bit && self.props.hdr_metadata.is_some(),
|
|
||||||
// Permanent: VCN hardware does not encode 4:4:4.
|
// Permanent: VCN hardware does not encode 4:4:4.
|
||||||
chroma_444: false,
|
chroma_444: false,
|
||||||
// True only when `PUNKTFUNK_INTRA_REFRESH` asked for the wave AND the live driver
|
// True only when `PUNKTFUNK_INTRA_REFRESH` asked for the wave AND the live driver
|
||||||
@@ -2715,10 +2712,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
enc.set_hdr_meta(Some(sample_hdr_meta()));
|
enc.set_hdr_meta(Some(sample_hdr_meta()));
|
||||||
assert!(
|
|
||||||
enc.caps().supports_hdr_metadata,
|
|
||||||
"HEVC 10-bit reports HDR SEI capability"
|
|
||||||
);
|
|
||||||
let mut aus: Vec<EncodedFrame> = Vec::new();
|
let mut aus: Vec<EncodedFrame> = Vec::new();
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
let frame = CapturedFrame {
|
let frame = CapturedFrame {
|
||||||
|
|||||||
@@ -1690,17 +1690,14 @@ impl Encoder for NvencD3d11Encoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn caps(&self) -> EncoderCaps {
|
fn caps(&self) -> EncoderCaps {
|
||||||
// RFI is probed once at open (`rfi_supported`); HDR SEI rides keyframes whenever the
|
// RFI is probed once at open (`rfi_supported`) — the real capability the session glue
|
||||||
// session is in HDR mode. Both are the real capabilities the session glue routes on.
|
// routes on. (In-band HDR SEI needs no cap: it rides keyframes on HEVC/H.264 HDR
|
||||||
|
// sessions — see `submit` — and every first-party client reads the grade out-of-band
|
||||||
|
// via the 0xCE datagram regardless.)
|
||||||
EncoderCaps {
|
EncoderCaps {
|
||||||
// The Windows capture path composites the pointer; this backend never reads `frame.cursor`.
|
// The Windows capture path composites the pointer; this backend never reads `frame.cursor`.
|
||||||
blends_cursor: false,
|
blends_cursor: false,
|
||||||
supports_rfi: self.rfi_supported,
|
supports_rfi: self.rfi_supported,
|
||||||
// In-band mastering/CLL is attached as keyframe SEI on HEVC/H.264 only — AV1 carries
|
|
||||||
// it in METADATA OBUs (`HDR_MDCV`/`HDR_CLL`), which this backend doesn't emit yet
|
|
||||||
// (see `submit`); the grade still reaches punktfunk clients out-of-band via the 0xCE
|
|
||||||
// datagram. Don't claim a capability the AV1 path doesn't have.
|
|
||||||
supports_hdr_metadata: self.hdr && self.codec != Codec::Av1,
|
|
||||||
// Reflects what the session actually configured (cleared in `query_caps` if the GPU lacks
|
// Reflects what the session actually configured (cleared in `query_caps` if the GPU lacks
|
||||||
// YUV444 encode), so the glue can confirm 4:4:4 vs the negotiated request.
|
// YUV444 encode), so the glue can confirm 4:4:4 vs the negotiated request.
|
||||||
chroma_444: self.chroma_444,
|
chroma_444: self.chroma_444,
|
||||||
|
|||||||
@@ -1471,9 +1471,6 @@ impl Encoder for QsvEncoder {
|
|||||||
// As Windows NVENC: the capturer composites; this backend never reads `frame.cursor`.
|
// As Windows NVENC: the capturer composites; this backend never reads `frame.cursor`.
|
||||||
blends_cursor: false,
|
blends_cursor: false,
|
||||||
supports_rfi: self.ltr_active,
|
supports_rfi: self.ltr_active,
|
||||||
// In-band mastering/CLL at IDR (HEVC prefix SEI / AV1 metadata OBU); AVC sessions
|
|
||||||
// are never HDR.
|
|
||||||
supports_hdr_metadata: self.ten_bit && self.codec != Codec::H264,
|
|
||||||
chroma_444: false,
|
chroma_444: false,
|
||||||
intra_refresh: self.ir_active,
|
intra_refresh: self.ir_active,
|
||||||
// Unvalidated on-glass — the host keeps the IDR recovery path until then.
|
// Unvalidated on-glass — the host keeps the IDR recovery path until then.
|
||||||
|
|||||||
Reference in New Issue
Block a user