跳转到内容

Android 绘图框架:应用画、SurfaceFlinger 统筹、HWC 分配 plane

结论:Android 的一帧画面由三类进程接力完成:应用进程把窗口画进自己的 buffer,SurfaceFlinger 收齐各窗口的 buffer 后决定怎么合成,厂商的 HWC 把图层分给显示控制器的 plane 并提交给 KMS。 GPU 只在两个地方出场:应用画窗口,以及合成 HWC 处理不了的图层。其余图层直接交给显示控制器叠加,这是 Android 省电的关键。

本文以 Android 13 及以后的版本为准:buffer 走 BLASTBufferQueue,HWC 接口是 AIDL 的 composer3。

逻辑视图:应用、SurfaceFlinger、厂商 HAL 三类进程各管一段

Section titled “逻辑视图:应用、SurfaceFlinger、厂商 HAL 三类进程各管一段”

逻辑视图:Android 绘图框架的进程、部件和内核接口

应用进程负责把自己的窗口画好:

  • UI 线程遍历 View 树,把要画的内容记录成显示列表,不直接调 GPU。
  • RenderThread(HWUI)用 Skia 回放显示列表,生成 GLES 或 Vulkan 命令。
  • 加载器(libEGL、libvulkan)按系统属性找到 Mali DDK 的 libGLES_mali.so,由它把命令交给 GPU。
  • BLASTBufferQueue 管理窗口的几块 buffer:向 gralloc 申请 buffer,画完后把 buffer 和 acquire fence 打包进 Transaction 交给 SurfaceFlinger。Android 12 起这个队列放在应用进程里,以前在 SurfaceFlinger 里。

SurfaceFlinger 进程负责统筹:

  • Scheduler 产生 VSYNC 节拍,分别唤醒应用(开始画下一帧)和自己(开始合成)。
  • 前端接收各应用的 Transaction,在合成时刻锁定每个图层这一帧用哪块 buffer(latch)。
  • CompositionEngine 把图层列表交给 HWC,问每层由谁合成。
  • RenderEngine 用 Skia 和同一个 Mali DDK,把 HWC 退回的图层合成到一块 buffer(client target)。

厂商 HAL 服务进程由 SoC 厂商实现:

  • gralloc allocator 按用途分配 buffer。比如同时要被 GPU 写、被显示控制器读的 buffer,要选双方都支持的格式和 modifier,内存从 dma-buf heaps 申请。
  • composer3 HAL(HWC)决定图层和 plane 的对应关系,调用 KMS 的 atomic 提交,再把 fence 交回 SurfaceFlinger。

内核:kbase 驱动 Mali,dma-buf heaps 提供内存,sync_file 让 fence 跨进程传递,KMS 驱动控制显示控制器。

进程视图:一帧画面走十步,跨三个进程

Section titled “进程视图:一帧画面走十步,跨三个进程”

一帧从 VSYNC 唤醒应用开始,到 buffer 回收结束:

sequenceDiagram
  participant SF as SurfaceFlinger
  participant UI as 应用 UI 线程
  participant RT as 应用 RenderThread
  participant GPU as Mali(DDK + kbase)
  participant HWC as HWC
  participant KMS as KMS 驱动
  SF->>UI: ① VSYNC-app
  UI->>RT: ② 同步显示列表
  RT->>GPU: ③ Skia 生成命令并提交
  RT->>SF: ④ Transaction:buffer + acquire fence
  Note over SF: ⑤ VSYNC-sf:锁定各层 buffer
  SF->>HWC: ⑥ validateDisplay:图层列表
  HWC-->>SF: 每层的合成方式
  SF->>GPU: ⑦ 有 CLIENT 层时,RenderEngine 合成
  SF->>HWC: ⑧ presentDisplay
  HWC->>KMS: ⑨ atomic 提交,各 plane 带 IN_FENCE_FD
  KMS-->>HWC: present fence
  HWC-->>SF: present fence、各层 release fence
  SF-->>RT: ⑩ release fence:buffer 可以重用
  1. SurfaceFlinger 的 Scheduler 向应用发 VSYNC-app,Choreographer 开始这一帧的输入、动画和绘制。
  2. UI 线程画完显示列表,同步给 RenderThread,然后可以去处理下一件事。
  3. RenderThread 用 Skia 生成 GPU 命令,经 DDK 和 kbase 提交给 Mali。
  4. 提交后立刻把 buffer 和 acquire fence 放进 Transaction 发给 SurfaceFlinger,不等 GPU 画完。
  5. 到 VSYNC-sf,SurfaceFlinger 锁定每个图层这一帧要用的 buffer。
  6. SurfaceFlinger 把图层列表交给 HWC 验证。HWC 返回每层的合成方式:DEVICE 由硬件叠加,CLIENT 交回 GPU 合成。
  7. 有 CLIENT 层时,RenderEngine 用 GPU 把它们合成到 client target。
  8. SurfaceFlinger 通知 HWC 正式出图。
  9. HWC 组装 atomic 请求提交给 KMS。每个 plane 都带上对应 buffer 的 acquire fence,内核等 fence 触发后才在 VSYNC 切换画面。
  10. 新画面上屏后,KMS 给出 present fence,各层旧 buffer 的 release fence 经 SurfaceFlinger 回到应用,应用收到后就能重用这块 buffer。

步骤 ① 到 ④ 和 ⑤ 到 ⑨ 由两个不同的 VSYNC 驱动,二者有固定的相位差。应用在这一拍画,SurfaceFlinger 在下一拍合成,流水线化以后,两边可以各用一整帧的时间。

第二层:HWC 用 TEST_ONLY 试分配,硬件叠不下的图层才交给 GPU

Section titled “第二层:HWC 用 TEST_ONLY 试分配,硬件叠不下的图层才交给 GPU”

上图第 ⑥ 步是 Android 省电的关键,下面单独展开。下图展开的是逻辑视图里的“composer3 HAL”。

第二层:HWC 把五个图层分给四个 plane

  • 输入:SurfaceFlinger 按 z 序给出图层列表,每层带 buffer、格式、位置、缩放、透明度。
  • 试分配:HWC 按自己的策略把图层映射到 plane,用 atomic 的 TEST_ONLY 问内核硬件能否接受。不能就换一种分配再试。
  • DEVICE:分到 plane 的图层,buffer 直接交给显示控制器。图中的视频层是 YUV 格式,格式转换和缩放都由显示控制器完成,GPU 完全不碰它。
  • CLIENT:分不到 plane 的图层交回 SurfaceFlinger,由 RenderEngine 用 GPU 合成成一块 client target,再占用一个 plane。图中 5 个图层、4 个 plane,最底下的壁纸和应用窗口被合成到一起。
  • 其他变成 CLIENT 的原因:模糊、圆角等特效,或者显示控制器不支持的格式、缩放比例。

分配策略写在厂商 HWC 里,是 Android 显示调优的主要工作。plane 越多、能力越强,GPU 合成越少,越省电。

厂商要实现的部件:HWC、gralloc,加上 KMS 驱动

Section titled “厂商要实现的部件:HWC、gralloc,加上 KMS 驱动”

Android 绘图框架里 Google 提供框架,厂商负责和硬件打交道的部分:

部件厂商做什么做不好会怎样
HWC(composer3 HAL)图层到 plane 的分配策略;组装 atomic 请求;传递 fence;上报 VSYNC 和热插拔GPU 合成增多,耗电、掉帧
gralloc(allocator + mapper)按用途选格式、modifier、内存类型;buffer 句柄要让 DDK 和 HWC 都能读懂GPU 写的 buffer 显示控制器读不了,或者被迫用不压缩的格式
KMS 显示驱动实现 plane、CRTC、connector 的 atomic 回调;接 vblank 中断;上报 plane 支持的格式和 modifierHWC 的 TEST_ONLY 结果不准,画面撕裂或黑屏
Mali DDK 集成放好库和固件,设置系统属性,kbase 与用户态版本对齐GPU 驱动加载失败,见下一篇

HWC 也有开源参照:drm_hwcomposer 是直接建在 KMS atomic 上的通用实现,支持 composer3。它的分配策略比较简单,适合作为对照或起步版本。