3. Android图形栈概览
好,我们正式开始啃 DRM/KMS 这块硬骨头。说实话,我刚接触 Android 显示驱动时,也被这一堆缩写搞得头晕——CRTC、Encoder、Connector、Plane……每个词都认识,合在一起就不知道谁管谁了。
这一章,我会带你把这些概念一个一个拆开。你想想看,显示链路说白了就是一条流水线:图像数据从哪来,经过谁处理,最后送到哪去。DRM/KMS 就是这条流水线的标准化管理框架。
3.1 DRM 核心概念
DRM(Direct Rendering Manager)是 Linux 内核的显示子系统。它分两层:
- DRM Core:提供用户空间与内核的通用接口,比如
open()、ioctl()。 - KMS(Kernel Mode Setting):负责显示模式设置和画面合成,是我们驱动开发的重点。
我个人的习惯是,把 DRM 理解成「显示驱动的基础设施」,KMS 则是「具体的显示管线」。没有 KMS,你连屏幕分辨率都改不了。
关键认知:DRM 不等于 KMS。KMS 是 DRM 的一部分,但 DRM 还包含 GEM(图形内存管理)、FB(帧缓冲)等模块。做显示驱动,KMS 是主战场。
3.2 KMS 四大组件
KMS 的核心由四个对象组成:CRTC、Encoder、Connector、Plane。它们之间的关系,我画了张图,你先看个整体印象。
嗯,这张图你得记住。每次调试显示问题,我都会在脑子里过一遍这个流程。下面我们逐个拆解。
3.2.1 CRTC(显示控制器)
CRTC 是整条管线的「大脑」。它负责:
- 从内存中读取帧缓冲数据
- 生成时序信号(行同步、场同步、像素时钟)
- 控制画面刷新率、分辨率、色彩深度
我在项目中遇到过一个问题:换了块新屏幕,分辨率从 1080p 升到 4K,结果画面一直闪烁。查了两天才发现,是 CRTC 的像素时钟配置没跟上。说白了,CRTC 的带宽决定了你能跑多高的分辨率。
调试技巧:用 cat /sys/kernel/debug/dri/0/crtc-0/state 可以查看当前 CRTC 的状态,包括 active、mode、gamma 等信息。这是排查显示问题的第一站。
3.2.2 Encoder(编码器)
Encoder 负责把 CRTC 输出的并行数据转换成显示接口需要的串行信号。常见的编码方式有:
| 接口类型 | 编码方式 | 典型应用 |
|---|---|---|
| LVDS | 并行→差分对 | 老旧笔记本屏幕 |
| eDP | DisplayPort 编码 | 现代手机/平板 |
| HDMI | TMDS 编码 | 外接显示器 |
| MIPI DSI | D-PHY 串行 | 手机内置屏(最常见) |
Encoder 和 CRTC 通常是 1:1 绑定的,但也可以 1:N。比如一个 CRTC 同时输出给 HDMI 和 eDP 两个 Encoder——不过这种情况在手机上很少见。
3.2.3 Connector(连接器)
Connector 是物理接口的抽象。它描述的是「屏幕插在哪」。每个 Connector 对应一个实际的物理端口,比如:
HDMI-A-1:HDMI 接口eDP-1:内置 eDP 屏幕DSI-1:MIPI DSI 接口
Connector 还负责读取屏幕的 EDID(Extended Display Identification Data)信息。EDID 里包含了屏幕支持的分辨率、刷新率、物理尺寸等。我曾经踩过一个坑:某款国产屏幕的 EDID 里写错了最大分辨率,导致系统只能输出 720p。最后我们不得不写死一个 override 模式。
注意:Connector 的 status 属性有四种状态:connected、disconnected、unknown、hotplug。热插拔事件就是通过 Connector 的 hotplug 状态变化来触发的。如果你的 HDMI 插上去没反应,先查 Connector 状态。
3.2.4 Plane(图层)
Plane 是 KMS 里最灵活、也最容易让人困惑的组件。它代表一个独立的图像层。每个 Plane 可以有自己的:
- 帧缓冲(FB)
- 位置(crtc_x, crtc_y)
- 大小(crtc_w, crtc_h)
- 透明度(alpha)
- 旋转/缩放(rotation, scaling)
为什么需要 Plane?你想想看,手机屏幕上同时显示状态栏、桌面壁纸、悬浮窗——如果每次都要把所有图层合成成一个整图再送显,CPU/GPU 的负担会很重。Plane 允许硬件直接做图层叠加,这叫 硬件合成(Hardware Composition)。
Plane 分三种类型:
- Primary Plane:主图层,每个 CRTC 至少有一个。通常用来显示桌面或主界面。
- Overlay Plane:叠加层,可以有多个。用来显示视频、悬浮窗等。
- Cursor Plane:光标层,专门给鼠标指针用的。硬件上通常有独立的 cursor buffer。
实战经验:我在调试某款平板时,发现视频播放卡顿。查了硬件文档才发现,它的 Overlay Plane 只支持 NV12 格式,而播放器默认输出的是 RGBA。改成 NV12 后,视频播放直接走硬件合成,CPU 占用从 40% 降到了 5%。这就是 Plane 的威力。
3.3 组件之间的绑定关系
这四个组件不是各自为政的。它们通过 atomic state 绑定在一起。一个典型的绑定关系是:
// 伪代码:KMS 组件绑定
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_plane *plane;
// 绑定 CRTC 到 Encoder
encoder->crtc = crtc;
// 绑定 Encoder 到 Connector
connector->encoder = encoder;
// 绑定 Plane 到 CRTC
plane->crtc = crtc;
// 设置显示模式(分辨率、刷新率等)
drm_mode_set(crtc, connector, mode);
你可能会问:为什么不能直接 CRTC 连 Connector?非要中间插个 Encoder?
原因很简单:解耦。同一个 CRTC 可以接不同的 Encoder(比如 HDMI 或 DP),同一个 Encoder 也可以接不同的 Connector。这种分层设计让驱动更容易复用。我在做平台移植时,经常只需要换 Connector 的驱动,CRTC 和 Encoder 的代码基本不动。
3.4 模式设置流程
一个完整的 KMS 模式设置流程,大致分这几步:
- 枚举 Connector:遍历所有 Connector,检查哪些有屏幕插入。
- 读取 EDID:从 Connector 获取屏幕支持的显示模式列表。
- 选择 Mode:从列表里选一个合适的模式(比如 1920x1080@60Hz)。
- 绑定组件:把 Plane、CRTC、Encoder、Connector 串起来。
- 提交 Atomic Commit:一次性提交所有状态变更,保证画面不撕裂。
小技巧:用 modetest 工具可以手动测试模式设置。命令 modetest -M msm -s 32:1920x1080@60 就能把屏幕设成 1080p60。这是验证驱动是否正常的最快方法。
3.5 本章小结
DRM/KMS 的四个组件,说白了就是一条流水线上的四个工位:
- Plane 提供原料(图层数据)
- CRTC 负责加工(生成时序)
- Encoder 负责包装(编码成接口信号)
- Connector 负责发货(送到屏幕)
理解了这个流程,后面讲 atomic commit、vblank 同步、GPU 合成时,你就能快速定位问题出在哪个环节。嗯,这一章的内容就到这里。记住那张图,它会陪你走完整条显示驱动开发之路。