fix(apple/tvOS): guard EDR HDR APIs unavailable on tvOS
apple / swift (push) Successful in 1m12s
release / apple (push) Successful in 9m11s
apple / screenshots (push) Successful in 4m49s
ci / web (push) Successful in 1m1s
ci / docs-site (push) Successful in 1m19s
ci / rust (push) Successful in 4m45s
deb / build-publish (push) Successful in 3m11s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 6m51s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m55s
docker / deploy-docs (push) Successful in 5s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m49s
android / android (push) Successful in 3m10s

The tvOS archive failed compiling PunktfunkKit: a recent presenter HDR change
dropped the `#if os(macOS)` guard around the EDR calls and applied them "on all
platforms", but `wantsExtendedDynamicRangeContent`, `CAEDRMetadata`, and
`CAMetalLayer.edrMetadata` are all explicitly unavailable on tvOS.

Wrap the EDR usage (and the makeEDR helper, whose return type is the unavailable
CAEDRMetadata) in `#if !os(tvOS)`. macOS + iOS keep the reference-white-anchored
EDR path unchanged; tvOS now sets only the rgba16Float pixel format + itur_2100_PQ
colour space and lets its compositor tone-map from those. The 0xCE grade is still
cached on tvOS (harmless), it just can't be pushed to the layer there.

tvOS Simulator build: BUILD SUCCEEDED (PunktfunkKit Swift compile, the step that
failed). macOS build + test green (49 tests); iOS compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 11:12:18 +02:00
parent fc35ea8c31
commit 915f11a712
@@ -223,32 +223,39 @@ public final class MetalVideoPresenter {
} }
/// Set the layer's pixel format + colour config for SDR or HDR. MAIN THREAD ONLY. EDR is requested /// Set the layer's pixel format + colour config for SDR or HDR. MAIN THREAD ONLY. EDR is requested
/// on ALL platforms the property is available on macOS/iOS/tvOS at our deployment floor, and the /// on macOS + iOS (the old `#if os(macOS)` guard left iOS EDR half-engaged). tvOS has NO EDR API
/// old `#if os(macOS)` guard left iOS/tvOS EDR half-engaged. /// (`wantsExtendedDynamicRangeContent`/`edrMetadata`/`CAEDRMetadata` are all unavailable there), so
/// it gets the PQ pixel format + colour space only the tvOS compositor tone-maps from those.
private func configureColor(hdr: Bool) { private func configureColor(hdr: Bool) {
if hdr { if hdr {
layer.pixelFormat = .rgba16Float layer.pixelFormat = .rgba16Float
layer.colorspace = CGColorSpace(name: CGColorSpace.itur_2100_PQ) layer.colorspace = CGColorSpace(name: CGColorSpace.itur_2100_PQ)
#if !os(tvOS)
layer.wantsExtendedDynamicRangeContent = true layer.wantsExtendedDynamicRangeContent = true
// Anchor reference white. Re-apply the real grade if one already arrived (0xCE before the // Anchor reference white. Re-apply the real grade if one already arrived (0xCE before the
// flip); otherwise the bare 203-nit anchor. Without this anchor the PQ signal is too bright. // flip); otherwise the bare 203-nit anchor. Without this anchor the PQ signal is too bright.
layer.edrMetadata = makeEDR(lastHdrMeta) layer.edrMetadata = makeEDR(lastHdrMeta)
#endif
} else { } else {
// SDR: gamma-encoded BT.709 [0,1] in an 8-bit drawable; a nil colorspace tags it device/sRGB // SDR: gamma-encoded BT.709 [0,1] in an 8-bit drawable; a nil colorspace tags it device/sRGB
// (the proven SDR path never showed the "too bright" issue, which was HDR-only). // (the proven SDR path never showed the "too bright" issue, which was HDR-only).
layer.pixelFormat = .bgra8Unorm layer.pixelFormat = .bgra8Unorm
layer.colorspace = nil layer.colorspace = nil
#if !os(tvOS)
layer.wantsExtendedDynamicRangeContent = false layer.wantsExtendedDynamicRangeContent = false
layer.edrMetadata = nil layer.edrMetadata = nil
#endif
} }
} }
#if !os(tvOS)
private func makeEDR(_ meta: PunktfunkConnection.HdrMeta?) -> CAEDRMetadata { private func makeEDR(_ meta: PunktfunkConnection.HdrMeta?) -> CAEDRMetadata {
CAEDRMetadata.hdr10( CAEDRMetadata.hdr10(
displayInfo: meta?.masteringDisplayColorVolume(), displayInfo: meta?.masteringDisplayColorVolume(),
contentInfo: meta?.contentLightLevelInfo(), contentInfo: meta?.contentLightLevelInfo(),
opticalOutputScale: hdrReferenceWhiteNits) opticalOutputScale: hdrReferenceWhiteNits)
} }
#endif
/// Update the HDR mastering metadata (drained from the host's 0xCE datagram) to refine the system /// Update the HDR mastering metadata (drained from the host's 0xCE datagram) to refine the system
/// tone-map from the real grade. Called from the PUMP thread, so the layer write is hopped to MAIN /// tone-map from the real grade. Called from the PUMP thread, so the layer write is hopped to MAIN
@@ -259,7 +266,11 @@ public final class MetalVideoPresenter {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { return } guard let self else { return }
self.lastHdrMeta = meta self.lastHdrMeta = meta
// tvOS has no edrMetadata the cached grade is still kept above (harmless), it just can't
// be applied to the layer there. macOS/iOS refine the system tone-map from the real grade.
#if !os(tvOS)
if self.hdrActive { self.layer.edrMetadata = self.makeEDR(meta) } if self.hdrActive { self.layer.edrMetadata = self.makeEDR(meta) }
#endif
} }
} }