将 HDR 亮度映射到 SDR 兼容的范围

Android 13 中引入了一个名为 libtonemap 的供应商可配置静态库,用于定义色调映射操作,并与 SurfaceFlinger 进程和硬件混合渲染器 (HWC) 实现共享。借助此功能,原始设备制造商 (OEM) 可以在框架和供应商之间定义和共享其屏幕色调映射算法,从而减少色调映射中的不匹配问题。

在 Android 13 之前,屏幕专用色调映射操作未在 HWC、SurfaceFlinger 和应用之间共享。根据渲染路径,对于 HDR 内容,这会导致图片质量不匹配,即 HDR 内容以不同的方式色调映射到输出空间。这种情况在屏幕旋转等场景中较为明显,在此类场景中,合成策略会在 GPU 和 DPU 之间变化;这种情况还体现为 TextureView 和 SurfaceView 之间的渲染行为差异。

本页将介绍 libtonemap 库的接口、自定义和验证详情。

色调映射库的接口

libtonemap 库包含 CPU 支持的实现和 SkSL 着色器,可由 SurfaceFlinger 插入以进行 GPU 后端合成,并由 HWC 插入以生成色调映射查询表 (LUT)。libtonemap 的入口点是 android::tonemap::getToneMapper(),它会返回一个用于实现 ToneMapper 接口的对象。

ToneMapper 接口支持以下功能:

  • 生成色调映射 LUT

    接口 ToneMapper::lookupTonemapGainlibtonemap_LookupTonemapGain() 中定义的着色器的 CPU 实现。它供框架中的单元测试使用,并且可供合作伙伴用来协助在其颜色流水线中生成色调映射 LUT。

    libtonemap_LookupTonemapGain() 在线性 RGB 和 XYZ 中都会接受非标准化绝对线性空间中的颜色值,并返回一个浮点数,用于说明在线性空间中将输入颜色乘以多少。

  • 生成 SkSL 着色器

    给定源和目标数据空间时,接口 ToneMapper::generateTonemapGainShaderSkSL() 会返回一个 SkSL 着色器字符串。SkSL 着色器会插入 RenderEngine(适用于 SurfaceFlinger 的 GPU 加速合成组件)的 Skia 实现中。着色器也会插入 libhwui,以便针对 TextureView 高效地执行从 HDR 到 SDR 的色调映射。由于生成的字符串内嵌在 Skia 使用的其他 SkSL 着色器中,因此着色器必须遵循以下规则:

    • 着色器字符串必须具有带 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) 签名的入口点,其中 linearRGB 是线性空间中 RGB 像素的绝对尼特值,xyz 是已转换为 XYZ 的 linearRGB
    • 着色器字符串使用的任何辅助方法都必须以字符串 libtonemap_ 为前缀,以免框架着色器定义发生冲突。同样,输入 uniform 必须带有 in_libtonemap_ 前缀。
  • 生成 SkSL uniform

    鉴于描述不同 HDR 标准和显示条件的元数据的元数据 struct,接口 ToneMapper::generateShaderSkSLUniforms() 会返回以下内容:

    • 由 SkSL 着色器绑定的 uniform 的列表。

    • uniform 值 in_libtonemap_displayMaxLuminancein_libtonemap_inputMaxLuminance。将输入扩缩为 libtonemap 并将输出标准化(如果适用)时,框架着色器会使用这些值。

    目前,生成 uniform 的过程与输入和输出数据空间无关。

自定义

libtonemap 库的参考实现会生成可接受的结果。不过,由于 GPU 合成使用的色调映射算法可能与 DPU 合成使用的色调映射算法不同,因此在某些场景(例如旋转动画)中使用参考实现可能会导致闪烁。自定义可以解决此类供应商特有的图片质量问题。

强烈建议 OEM 替换 libtonemap 的实现,以定义自己的 ToneMapper 子类(由 getToneMapper() 返回)。自定义实现时,合作伙伴应执行以下操作之一:

  • 直接修改 libtonemap 的实现。
  • 定义自己的静态库,将该库编译为独立的库,并将 libtonemap 库的 .a 文件替换为通过其自定义库生成的文件。

供应商无需修改任何内核代码,但多个供应商必须传达有关 DPU 色调映射算法的详细信息,才能正确实现。

验证

请按照以下步骤验证您的实现:

  1. 在符合显示系统支持的任何 HDR 标准(例如 HLG、HDR10、HDR10+ 或 DolbyVision)的屏幕上播放 HDR 视频。

  2. 切换 GPU 合成,以确保用户不会察觉到闪烁。

    使用以下 adb 命令切换 GPU 合成:

    adb shell service call SurfaceFlinger 1008 i32 <0 to enable HWC composition,
    1 to force GPU composition>
    
    

常见问题

此实现可能会出现以下问题:

  • 当 GPU 合成使用的渲染目标精度低于 HDR 内容的典型值时,就会出现条带。例如,当 HWC 实现支持不透明的 10 位 HDR 格式(例如 RGBA1010102 或 P010),但要求 GPU 合成写入 8 位格式(如 RGBA8888)以支持 Alpha 通道时,可能会出现条带。

  • 如果 DPU 和 GPU 以不同的精度运行,就会出现量化差异,从而导致细微的色偏。

上述每个问题都与底层硬件的相对精度差异有关。典型的解决方法是确保精度较低的路径中存在一个抖动步骤,使所有精度差异都不易察觉。