feat(clients): HDR Steps 2-3 — apply mastering metadata + display capability-gate
Continues docs/hdr-pipeline-plan.md. Steps 0/1 + Step 2 (Windows/Android) already landed in 3526517; this is Step 2 (Apple) + Step 3 (all clients). Client-only — no core/host/ABI change (the 0xCE/next_hdr_meta/color_info surfaces shipped in Step 0). Step 2 — clients APPLY the host's HDR metadata (each remaps from the wire form: ST.2086 G,B,R order, mastering luminance in 0.0001 cd/m2): - Apple: connect via punktfunk_connect_ex5 (resurrects the previously-dead HDR pipeline); nextHdrMeta/colorInfo wrappers + HdrMeta SEI-blob builders; the pump drains nextHdrMeta -> VideoDecoder.setHdrMeta -> CVBufferSetAttachment of MasteringDisplayColorVolume (24B BE) + ContentLightLevelInfo (4B BE) on each HDR pixel buffer (correct for the itur_2100_PQ layer; CAEDRMetadata avoided as ambiguous there). Step 3 — capability-gate: advertise HDR caps ONLY when the display can present it, so an SDR display gets a proper BT.709 stream instead of PQ it would mis-tone-map; an HDR display self-tone-maps from the Step-1/2 mastering metadata. - Windows: present::display_supports_hdr() (DXGI any IDXGIOutput6 colour space == G2084), ANDed with the user HDR setting in session.rs; logs the SDR drop. - Apple: NSScreen.maximumExtendedDynamicRangeColorComponentValue>1 (macOS) / UIScreen.main.potentialEDRHeadroom>1 (iOS) in SessionModel. - Android: Settings.displaySupportsHdr (Display.getHdrCapabilities HDR10/HDR10+) passed through a new hdr_enabled jboolean on nativeConnect; session.rs gates the caps. Validation: Android native (incl. the jboolean gate) builds + clippy clean via cargo-ndk; fmt clean. Windows (MSVC), Apple (Swift) and the Kotlin side are CI/on-glass validated — not compilable on the Linux dev box. Deferred to the RTX box: mid-session Reconfigure SDR-downgrade on monitor move, and confirming the host emits SDR for an SDR client off an HDR desktop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -619,6 +619,36 @@ fn blob_bytes(blob: &ID3DBlob) -> &[u8] {
|
||||
}
|
||||
}
|
||||
|
||||
/// True if any attached display is currently in HDR (BT.2020 PQ) mode. The client advertises HDR
|
||||
/// caps only when this holds, so an SDR display gets a proper 8-bit BT.709 stream instead of PQ it
|
||||
/// would mis-tone-map (the washed-out/dark failure); an HDR display self-tone-maps from the
|
||||
/// mastering metadata. Coarse — checks ANY output, not the app's specific monitor; a mid-session
|
||||
/// monitor move to/from HDR is a follow-up (the `Reconfigure` downgrade).
|
||||
pub fn display_supports_hdr() -> bool {
|
||||
unsafe {
|
||||
let factory: IDXGIFactory1 = match CreateDXGIFactory1() {
|
||||
Ok(f) => f,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let mut ai = 0u32;
|
||||
while let Ok(adapter) = factory.EnumAdapters1(ai) {
|
||||
ai += 1;
|
||||
let mut oi = 0u32;
|
||||
while let Ok(output) = adapter.EnumOutputs(oi) {
|
||||
oi += 1;
|
||||
if let Ok(o6) = output.cast::<IDXGIOutput6>() {
|
||||
if let Ok(desc) = o6.GetDesc1() {
|
||||
if desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Generic HDR10 mastering metadata: BT.2020 primaries + D65 white, a 1000-nit mastering display,
|
||||
/// MaxCLL 1000 / MaxFALL 400. The fallback used only until the host's real `0xCE` metadata arrives.
|
||||
fn generic_hdr10_metadata() -> DXGI_HDR_METADATA_HDR10 {
|
||||
|
||||
@@ -107,13 +107,19 @@ fn pump(
|
||||
params.compositor,
|
||||
params.gamepad,
|
||||
params.bitrate_kbps,
|
||||
// Advertise 10-bit + HDR10 (when enabled): the presenter handles BT.2020 PQ frames (P010 on
|
||||
// the GPU path, X2BGR10 on software), so the host may upgrade HDR content to a Main10/PQ
|
||||
// stream — it still only does so for actual HDR content with its own 10-bit gate. 8-bit SDR
|
||||
// is unaffected. A client that turns HDR off advertises `0` and always gets the 8-bit stream.
|
||||
if params.hdr_enabled {
|
||||
// Advertise 10-bit + HDR10 only when the user enabled HDR AND a display is actually in HDR
|
||||
// mode: the host then upgrades HDR content to a Main10/PQ stream (its own 10-bit gate still
|
||||
// applies). On an SDR display we advertise `0` so the host sends a proper 8-bit BT.709 stream
|
||||
// rather than PQ the panel would mis-tone-map (washed-out/dark). An HDR display self-tone-maps
|
||||
// from the mastering metadata we apply. The presenter handles BT.2020 PQ frames (P010 / X2BGR10).
|
||||
if params.hdr_enabled && crate::present::display_supports_hdr() {
|
||||
punktfunk_core::quic::VIDEO_CAP_10BIT | punktfunk_core::quic::VIDEO_CAP_HDR
|
||||
} else {
|
||||
if params.hdr_enabled {
|
||||
tracing::info!(
|
||||
"HDR enabled in settings but no HDR display detected — requesting SDR"
|
||||
);
|
||||
}
|
||||
0
|
||||
},
|
||||
None, // launch: the Windows client has no library picker yet
|
||||
|
||||
Reference in New Issue
Block a user