Highlight
Apple 显示与色彩技术团队介绍了 EDR(Extended Dynamic Range)在 iOS 和 iPadOS 上的原生支持,开发者可以用超过 SDR 白色 1.0 的像素值在 iPhone 与 iPad 的 HDR 显示屏上渲染更亮、更饱和的内容。
核心内容
很多 App 已经能处理 HDR 照片和视频,但渲染端常常卡在最后一步。文件里有高光细节,显示时却被压到 SDR 白色以内。火焰、太阳反光、金属高光都还在数据里,屏幕上看起来却像被剪平。
EDR(Extended Dynamic Range,扩展动态范围)解决的是这一步。它把 SDR 内容继续放在 0 到 1 的范围内,把超过 SDR 白色的亮度放在 1 以上。这样,同一套浮点像素表示可以同时承载 SDR 主体和 HDR 高光(00:24)。
这场 session 的新变化是:EDR API 来到 iOS 和 iPadOS(02:20)。如果你的渲染管线已经在 macOS 上用 CAMetalLayer 输出 EDR,迁移到 iPhone 和 iPad 时,核心开关、像素格式和色彩空间的思路保持一致。
Apple 还把 12.9 英寸 iPad Pro 的专业色彩工作流纳入这条链路。Reference Mode 会把 SDR 峰值固定在 100 nits,把 HDR 峰值固定在 1000 nits,得到 10 倍 EDR headroom,并关闭 True Tone、自动亮度、Night Shift 这类动态显示调整(02:40)。Sidecar 在 Reference Mode 下也可以把 iPad Pro 用作 Apple silicon Mac 的参考显示器(04:37)。
详细内容
1. EDR 像素值如何表示亮度
(06:09)传统 SDR 浮点表示把 0 作为黑色,把 1 作为 SDR 白色。EDR 沿用这段范围:0 到 1 仍然是 SDR 内容,超过 1 的值表示比 SDR 白色更亮的内容。
let sdrWhite: Float = 1.0
let campfireHighlight: Float = 2.4
let displayableHighlight = min(campfireHighlight, screen.currentEDRHeadroom)
关键点:
sdrWhite对应 transcript 中的 EDR 1.0,也就是 SDR 白色。campfireHighlight是概念示例,用大于 1 的浮点值表示高光。screen.currentEDRHeadroom是 iOS 上查询当前 headroom 的 API,session 用它判断当前显示环境能显示到多亮。min表示默认 EDR 行为的核心约束:超过当前 headroom 的值会被裁剪。
EDR 值在线性空间里表示。2.0 EDR 不是视觉上两倍亮。session 特别强调,EDR 不会把 HDR 值 tone map 回 0 到 1;0 到 1 的 SDR 内容总能显示,1 以上的内容会在当前 headroom 以内显示,超出的部分裁剪(06:24)。
2. 把 HDR 图片解码到浮点 bitmap
(08:18)示例工作流从一张 HDR still image 开始。原始图片通常是 biplanar YUV。直接操作这种格式比较困难,所以 session 先用 Image I/O 读出 CGImage,再用 CGBitmapContext 转成更适合 Metal 的 16-bit 浮点 bitmap。
// Create CGImage from HDR Image
let isr = CGImageSourceCreateWithURL(HDRImageURL, nil)
let img = CGImageSourceCreateImageAtIndex(isr, 0, nil)
// Draw into floating point bitmap context
let width = img.width
let height = img.height
let info = CGBitmapInfo(rawValue: kCGBitmapByteOrder16Host |CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue)
let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 16,
bytesPerRow: 0, space: layer.colorspace, bitmapInfo: info.rawValue)
ctx?.draw(in: img,
image: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)))
关键点:
CGImageSourceCreateWithURL从 HDR 图片 URL 创建 Image I/O source。CGImageSourceCreateImageAtIndex从 source 中取出第一张CGImage。width和height直接来自CGImage,用于建立同尺寸 bitmap。CGBitmapInfo.floatComponents指定 bitmap 使用浮点分量。bitsPerComponent: 16配合浮点分量,得到 16-bit floating point context。space: layer.colorspace让 bitmap context 的色彩空间匹配后面要渲染到的CAMetalLayer。ctx?.draw把CGImage解码并绘制进浮点 bitmap context。
如果需要让某些 HDR 格式产生浮点 buffer,session 提到可以设置新的 kCGImageSourceShouldAllowFloat 选项(09:35)。
3. 把 bitmap 数据装进 Metal texture
(10:28)Bitmap 已经是 16-bit 浮点格式,Metal texture 也要选择匹配的格式。session 的示例使用 .rgba16Float。
// Create floating point texture
let desc = MTLTextureDescriptor()
desc.pixelFormat = .rgba16Float
desc.textureType = .type2D
let texture = layer.device.makeTexture(descriptor: desc)
// Load EDR bitmap into texture
let data = ctx.data
texture.replace(region: MTLRegionMake2D(0, 0, width, height),
mipmapLevel: 0,
withBytes: &data,
bytesPerRow: ctx.bytesPerRow)
关键点:
MTLTextureDescriptor()描述将要创建的 Metal texture。desc.pixelFormat = .rgba16Float让 texture 使用半精度浮点 RGBA,匹配前面的 bitmap context。desc.textureType = .type2D表示这是二维纹理。layer.device.makeTexture使用CAMetalLayer的 Metal device 创建 texture。ctx.data取出 bitmap context 背后的像素数据。texture.replace把整张 bitmap 拷贝进 Metal texture。bytesPerRow: ctx.bytesPerRow保持源 bitmap 的行跨度。
到这里,App 已经有一张包含 EDR 值的 Metal texture,可以交给自己的 Metal pipeline 渲染(11:08)。
4. 在 CAMetalLayer 上开启 EDR
(11:27)iOS 和 iPadOS 的开启方式与 macOS 一致:使用 CAMetalLayer,打开 wantsExtendedDynamicRangeContent,选择支持 EDR 的 pixel format 和 color space。
// Opt into using EDR
var layer = CAMetalLayer()
layer?.wantsExtendedDynamicRangeContent = true
// Use supported pixel format and color spaces
layer.pixelFormat = MTLPixelFormatRGBA16Float
layer.colorspace = CGColorSpace(name: kCGColorSpaceExtendedLinearDisplayP3)
关键点:
CAMetalLayer是这条渲染路径的输出层。wantsExtendedDynamicRangeContent = true明确让 layer 进入 EDR 渲染。pixelFormat要和内容格式匹配;示例使用 16-bit floating point。kCGColorSpaceExtendedLinearDisplayP3是 extended linear Display P3。- session 强调,如果使用线性色彩空间,要使用 extended 变体,否则内容会被裁剪到 SDR(12:22)。
iOS 支持两类 EDR 渲染组合:16-bit floating point pixel buffer 搭配 linear color space,以及 10-bit packed BGRA pixel buffer 搭配 PQ 或 HLG color space(12:22)。
5. 根据 headroom 决定渲染策略
(13:11)EDR 的可用亮度不是固定值。当前 headroom 会受到设备显示技术、屏幕亮度等因素影响。iOS 把相关查询放在 UIScreen 上。
// Query potential headroom
let screen = windowScene.screen
let maxPotentialEDR = screen.potentialEDRHeadroom
if (maxPotentialEDR < 1.5) {
// SDR path
}
// Query current headroom
func draw(_ rect: CGRect) {
let maxEDR = screen.currentEDRHeadroom
// Tone-map to maxEDR
}
// Register for Reference Mode notifications
let notification = NotificationCenter.default
notification.addObserver(self,
selector: #selector(screenChangedEvent(_:)),
name: UIScreen.referenceDisplayModeStatusDidChangeNotification,
object: nil)
// Query for latest status and headroom
func screenChangedEvent(_ notification: Notification?) {
let status = screen.referenceDisplayModeStatus
let maxPotentialEDR = screen.potentialEDRHeadroom
}
关键点:
windowScene.screen拿到当前 scene 所在的UIScreen。potentialEDRHeadroom表示这块显示屏可能提供的最大 headroom。maxPotentialEDR < 1.5是策略判断:如果潜在 headroom 太低,可以走 SDR 路径,节省处理成本。currentEDRHeadroom表示当前时刻可用的 headroom,适合在 draw call 里读取。UIScreen.referenceDisplayModeStatusDidChangeNotification会在 Reference Mode 状态变化时发送通知。referenceDisplayModeStatus告诉 App Reference Mode 是否启用、受限、未启用或不支持。
UIScreen 没有 EDR headroom 变化通知。session 的建议是在渲染回调里查询 currentEDRHeadroom,并在 Reference Mode 状态变化时重新读取状态和潜在 headroom(14:17)。
6. 用 CAEDRMetadata 交给系统 tone mapping
(16:24)如果处理的是视频内容,App 可以使用 Apple 的内建 tone mapping。入口是 CAEDRMetadata,它提供 HLG 和 HDR10 的 metadata 构造方式。
// Check if EDR metadata is available
let isAvailable = CAEDRMetadata.isAvailable
// Instantiate EDR metadata
// ...
// Apply EDR metadata to layer
let layer: CAMetalLayer? = nil
layer?.edrMetadata = metadata
关键点:
CAEDRMetadata.isAvailable用来检查当前平台是否支持这套 tone mapping 能力。metadata代表由 HLG 或 HDR10 构造器创建的 EDR metadata。layer?.edrMetadata = metadata把 metadata 设置到CAMetalLayer。- 设置后,这个 layer 上渲染的内容会由系统 tone mapper 按 metadata 处理。
构造 metadata 时,HLG 不需要参数。HDR10 有两种构造器:一种传 mastering display 的最小亮度、最大亮度和 opticalOutputScale;另一种传 MasterDisplayColourVolume SEI、ContentLightLevelInformation SEI 和 opticalOutputScale(17:22)。
// HLG
let edrMetadata = CAEDRMetadata.hlg
// HDR10 (Mastering Display luminance)
let edrMetaData = CAEDRMetadata.hdr10(minLuminance: minLuminance,
maxLuminance: maxContentMasteringDisplayBrightness,
opticalOutputScale: outputScale)
// HDR10 (Supplemental Enhancement Information)
let edrMetaData = CAEDRMetadata.hdr10(displayInfo: displayData,
contentInfo: contentInfo,
opticalOutputScale: outputScale)
关键点:
CAEDRMetadata.hlg用于 HLG color space 的内容。hdr10(minLuminance:maxLuminance:opticalOutputScale:)用 mastering display 亮度信息描述 HDR10 内容。minLuminance和maxLuminance单位是 nits。opticalOutputScale描述 EDR 1.0 对应多少 nits,session 提到通常设为 100。hdr10(displayInfo:contentInfo:opticalOutputScale:)使用内容自带的 SEI messages,更适合保留作者意图。
核心启发
-
做一个 HDR 照片对比查看器。 让用户在同一张照片上切换 SDR 和 EDR 显示。EDR 开启时使用
CAMetalLayer.wantsExtendedDynamicRangeContent和.rgba16Floattexture;关闭时把高光压回 SDR 路径。这样能直观看到高光细节是否仍在图像数据里。 -
给视频剪辑 App 加 Reference Mode 状态提示。 在 12.9 英寸 iPad Pro 上监听
UIScreen.referenceDisplayModeStatusDidChangeNotification。状态是 enabled 时显示参考预览;状态是 limited 时提示用户当前环境无法保证参考响应。 -
按 headroom 自动选择渲染成本。 在渲染前读取
potentialEDRHeadroom。如果潜在 headroom 太低,走 SDR 预览;如果足够,再加载浮点 texture 并读取currentEDRHeadroom做 tone mapping。 -
给 HDR10 播放层接入系统 tone mapping。 如果内容有 mastering display 或 SEI metadata,先检查
CAEDRMetadata.isAvailable,再创建 HDR10 metadata 并设置到CAMetalLayer.edrMetadata,减少自己实现 tone mapping 的成本。
关联 Session
- Display EDR content with Core Image, Metal, and SwiftUI — 继续讲怎样在 Core Image、SwiftUI 和 Metal view 架构里显示 EDR 内容。
- Explore HDR rendering with EDR — 介绍 macOS 上的 EDR 渲染模型和 API,是理解本场 iOS API 的前置材料。
- Edit and play back HDR video with AVFoundation — 讲 HDR 视频播放与编辑中的色彩空间、传输函数和 metadata。
评论
GitHub Issues · utterances