WWDC Quick Look 💓 By SwiftGGTeam
Explore EDR on iOS

Explore EDR on iOS

观看原视频

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
  • widthheight 直接来自 CGImage,用于建立同尺寸 bitmap。
  • CGBitmapInfo.floatComponents 指定 bitmap 使用浮点分量。
  • bitsPerComponent: 16 配合浮点分量,得到 16-bit floating point context。
  • space: layer.colorspace 让 bitmap context 的色彩空间匹配后面要渲染到的 CAMetalLayer
  • ctx?.drawCGImage 解码并绘制进浮点 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 和 opticalOutputScale17: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 内容。
  • minLuminancemaxLuminance 单位是 nits。
  • opticalOutputScale 描述 EDR 1.0 对应多少 nits,session 提到通常设为 100。
  • hdr10(displayInfo:contentInfo:opticalOutputScale:) 使用内容自带的 SEI messages,更适合保留作者意图。

核心启发

  • 做一个 HDR 照片对比查看器。 让用户在同一张照片上切换 SDR 和 EDR 显示。EDR 开启时使用 CAMetalLayer.wantsExtendedDynamicRangeContent.rgba16Float texture;关闭时把高光压回 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

评论

GitHub Issues · utterances