3. SurfaceFlinger核心架构
说到SurfaceFlinger的核心工作,其实就是一件事——合成。把多个图层叠在一起,变成一帧完整的画面,送出去显示。
但怎么合成,这里头门道就多了。我最早接触这个模块时,以为合成就是GPU干活,后来才发现,硬件合成器(HWC)才是真正的"主力军"。GPU反而像个替补队员,平时不怎么上场,关键时刻才顶上去。
这一章,我们就来拆开看看:HWC到底在干什么?GPU合成又是怎么跑的?系统什么时候用HWC,什么时候切到GPU?
3.1 硬件合成器(HWC)的作用
HWC,全称Hardware Composer,是显示子系统里的一个硬件模块。它不是一个单独的芯片,而是集成在显示控制器(Display Controller)里的一组专用电路。
说白了,HWC就是一块"硬连线"的合成器。它不需要CPU或GPU参与,就能把多个图层直接叠加成最终画面。
HWC的核心能力:
- 图层叠加:支持2~8个硬件图层(取决于芯片设计)
- 透明度处理:硬件级Alpha Blending
- 旋转/缩放:部分HWC支持硬件旋转和缩放
- 零拷贝:直接操作显示缓冲区,不需要额外内存拷贝
我在做某款车机项目时,遇到过一个问题:中控屏显示导航地图时,GPU合成帧率只有30fps,但切到HWC后直接跑到60fps。原因很简单——HWC不需要把图层数据搬来搬去,直接在显示控制器里就完成了叠加。
HWC的工作模式,其实是一个"协商"的过程:
- SurfaceFlinger把图层列表发给HWC
- HWC检查自己能处理哪些图层
- 能处理的,HWC直接合成
- 不能处理的,打回给SurfaceFlinger,让GPU来干
嗯,这里要注意:HWC不是万能的。它支持的图层数量有限,而且对图层格式有要求。比如有些HWC不支持RGBA_8888以外的格式,有些不支持超过1080p的分辨率。
3.2 GPU合成流程
当HWC说"我搞不定"的时候,GPU就上场了。
GPU合成,本质上就是用OpenGL ES或Vulkan把多个纹理(texture)渲染到一个帧缓冲(Frame Buffer)里。这个过程,我习惯叫它"软合成"——不是说GPU软,而是说它走的是通用计算路线,不是专用硬件。
GPU合成的典型流程:
- 创建FBO:Frame Buffer Object,作为合成目标
- 绑定纹理:把每个图层的BufferQueue里的GraphicBuffer绑定为纹理
- 渲染:按Z-order顺序,把纹理绘制到FBO上
- 提交:把FBO的内容提交到显示缓冲区
代码层面,SurfaceFlinger里负责GPU合成的是RenderEngine类。它的核心方法长这样:
// 简化后的GPU合成调用
status_t SurfaceFlinger::doComposeSurfaces(const sp<DisplayDevice>& hw) {
// 创建合成目标
auto& renderEngine = getRenderEngine();
// 遍历所有可见图层
for (auto& layer : mDrawingState.layersSortedByZ()) {
if (!layer->isVisible()) continue;
// 绑定图层纹理
renderEngine.bindLayerTexture(layer);
// 设置混合参数
renderEngine.setupLayerBlending(layer);
// 绘制到FBO
renderEngine.drawLayer(layer);
}
// 提交到显示
renderEngine.finishComposition();
return NO_ERROR;
}
我曾经踩过一个坑:GPU合成时,如果图层数量多,纹理绑定操作会非常耗时。有一次在某个低端芯片上,12个图层的合成,光绑定纹理就占了8ms。后来我改成只绑定可见区域,才把时间压到3ms以内。
小技巧:GPU合成时,尽量用GL_TEXTURE_EXTERNAL_OES而不是GL_TEXTURE_2D。前者可以直接操作硬件视频帧,省掉一次内存拷贝。我在多个项目里验证过,能省1~2ms。
3.3 合成策略的选择与切换
系统怎么决定用HWC还是GPU?
答案在SurfaceFlinger的chooseCompositionStrategy方法里。每次Vsync到来时,SurfaceFlinger都会重新评估一次。
决策逻辑其实不复杂:
| 条件 | 策略 | 原因 |
|---|---|---|
| 图层数 ≤ HWC支持数 | 全HWC合成 | 性能最优,功耗最低 |
| 图层数 > HWC支持数 | GPU合成部分图层 | HWC能力不足,GPU兜底 |
| 存在HWC不支持的格式 | GPU合成该图层 | HWC硬件限制 |
| 存在旋转/缩放需求 | GPU合成或HWC处理 | 取决于HWC是否支持 |
| 客户端强制GPU合成 | 全GPU合成 | 调试或特殊需求 |
你想想看,这个决策每帧都在做。如果画面变化不大,策略基本稳定。但一旦有窗口弹出、动画开始,策略就可能切换。
切换本身是有代价的。从HWC切到GPU,需要重新分配纹理、重建FBO。从GPU切回HWC,需要重新配置显示控制器。这个切换过程,我见过最严重的情况,会掉一帧。
避坑指南:我曾经在一个项目中,发现每次打开应用时都会卡一下。排查后发现,是因为应用启动时创建了多个图层,导致合成策略从HWC切到GPU。切换过程中,SurfaceFlinger花了将近20ms做资源重分配。解决方案是预分配GPU资源,让切换时的开销降到最低。
实际项目中,我建议你关注几个关键指标:
- HWC合成占比:理想情况是80%以上
- 策略切换频率:每秒不超过3次
- GPU合成耗时:单帧不超过8ms
这些数据可以从dumpsys SurfaceFlinger里拿到。我习惯写个脚本,每5秒采样一次,跑个10分钟,就能看出系统的合成策略是否稳定。
最后说一个经验:座舱场景下,仪表盘和中控的合成策略最好分开配置。仪表盘画面稳定,适合全HWC合成;中控交互频繁,可能需要GPU兜底。分开配置后,仪表盘的帧率稳定性明显提升。