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:
@@ -140,11 +140,15 @@ fun ConnectScreen(settings: Settings, onConnected: (Long) -> Unit) {
|
||||
status = "Connecting to $targetHost:$targetPort…"
|
||||
discovery.stop() // free the Wi-Fi radio before the stream session
|
||||
scope.launch {
|
||||
// Advertise HDR only when this device's display can present it (else the host sends a
|
||||
// proper SDR stream rather than PQ the panel would mis-tone-map).
|
||||
val hdrEnabled = displaySupportsHdr(context)
|
||||
val handle = withContext(Dispatchers.IO) {
|
||||
NativeBridge.nativeConnect(
|
||||
targetHost, targetPort, w, h, hz,
|
||||
id.certPem, id.privateKeyPem, pinHex ?: "",
|
||||
settings.bitrateKbps, settings.compositor, settings.gamepad,
|
||||
hdrEnabled,
|
||||
)
|
||||
}
|
||||
connecting = false
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.unom.punktfunk
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Display
|
||||
|
||||
/**
|
||||
* User-tunable stream settings, persisted in `SharedPreferences`. A `0` resolution/refresh means
|
||||
@@ -76,6 +77,21 @@ fun nativeDisplayMode(context: Context): Triple<Int, Int, Int> {
|
||||
return Triple(maxOf(w, h), minOf(w, h), hz)
|
||||
}
|
||||
|
||||
/**
|
||||
* True when this device's display can actually present HDR10, so we should advertise HDR to the
|
||||
* host. On an SDR panel we advertise `0` instead — the host then sends a proper 8-bit BT.709 stream
|
||||
* rather than BT.2020 PQ the panel would mis-tone-map (the washed-out/dark failure). Mirrors the
|
||||
* capability gate the Apple/Windows clients apply.
|
||||
*/
|
||||
fun displaySupportsHdr(context: Context): Boolean {
|
||||
val display = runCatching { context.display }.getOrNull() ?: return false
|
||||
@Suppress("DEPRECATION") // hdrCapabilities is the supported query on minSdk 31
|
||||
val caps = display.hdrCapabilities ?: return false
|
||||
return caps.supportedHdrTypes.any {
|
||||
it == Display.HdrCapabilities.HDR_TYPE_HDR10 || it == Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve [Settings] (with its 0=native placeholders) to the concrete mode to request. */
|
||||
fun Settings.effectiveMode(context: Context): Triple<Int, Int, Int> {
|
||||
val native = nativeDisplayMode(context)
|
||||
|
||||
Reference in New Issue
Block a user