4. Framebuffer 与显示流水线:Framebuffer 的创建、格式配置、与 Plane 的绑定,以及显示刷新流程(Page Flip)
Framebuffer 是显示子系统的“画布”,用户空间通过它提交渲染好的像素数据。在 DRM 框架中,Framebuffer 并不直接参与扫描输出,而是作为 内存中的像素数据容器,通过 Plane(图层) 与 CRTC(显示控制器) 协同工作,最终形成完整的显示流水线。本章将深入 Framebuffer 的创建、格式配置、与 Plane 的绑定机制,以及关键的显示刷新流程——Page Flip。
4.1 Framebuffer 的核心概念与生命周期
Framebuffer 本质上是一个 内存对象,它描述了一块或多块物理连续或非连续的内存区域(通常由 GEM 或 DMA-BUF 分配),并携带像素格式、分辨率、行跨度(pitch)等元数据。用户空间通过 drmModeAddFB2() 或 drmModeAddFB() 创建,通过 drmModeRmFB() 销毁。
4.1.1 Framebuffer 创建流程
- 分配内存: 用户空间通过
drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB)或 GEM 分配器创建 GEM 对象。 - 填充元数据: 调用
drmModeAddFB2(),传入 GEM handle、宽度、高度、像素格式(如 DRM_FORMAT_XRGB8888)、每个平面的 pitch 和偏移。 - 内核校验: DRM 核心调用
fb_create回调,驱动校验格式、内存对齐、硬件限制。 - 返回 FB ID: 成功后返回一个全局唯一的 Framebuffer ID,用户空间后续通过此 ID 引用该 Framebuffer。
| 参数 | 说明 | 典型值 |
|---|---|---|
width |
Framebuffer 的像素宽度 | 1920 |
height |
Framebuffer 的像素高度 | 1080 |
pixel_format |
四字符编码格式 | DRM_FORMAT_NV12 |
handles[4] |
每个平面的 GEM handle | handle[0] = 0x1234 |
pitches[4] |
每行字节数(行跨度) | 7680 (1920×4) |
offsets[4] |
每个平面在 GEM 对象中的偏移 | 0 |
4.1.2 像素格式配置
DRM 使用四字符编码(FourCC)标识像素格式,常见格式如下:
| FourCC | 描述 | 位深 | 典型用途 |
|---|---|---|---|
DRM_FORMAT_XRGB8888 |
32位 RGB,无 Alpha | 32 | 桌面 UI、视频叠加 |
DRM_FORMAT_ARGB8888 |
32位 RGB,带 Alpha | 32 | 窗口合成、透明叠加 |
DRM_FORMAT_NV12 |
YUV 420 半平面 | 12 | 视频解码、摄像头预览 |
DRM_FORMAT_RGB565 |
16位 RGB | 16 | 低功耗显示、嵌入式设备 |
4.2 Framebuffer 与 Plane 的绑定
Framebuffer 本身不参与显示,必须绑定到 Plane 上才能被 CRTC 扫描输出。Plane 是 DRM 中的图层抽象,每个 Plane 可以绑定一个 Framebuffer,并配置裁剪、缩放、位置等属性。
4.2.1 Plane 类型与绑定关系
- Primary Plane: 每个 CRTC 必须有一个,通常绑定主 Framebuffer(如桌面背景)。
- Overlay Plane: 可选,用于叠加视频、光标等,支持 Alpha 混合。
- Cursor Plane: 专用硬件光标,通常尺寸小(64×64),更新频率高。
绑定操作通过 drmModeAtomicAddProperty() 或 drmModeSetPlane() 完成,核心是设置 FB_ID 属性:
// 使用 Atomic API 绑定 Framebuffer 到 Plane
drmModeAtomicReq *req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, plane_id, plane_fb_id_prop, fb_id);
drmModeAtomicAddProperty(req, plane_id, plane_crtc_id_prop, crtc_id);
drmModeAtomicAddProperty(req, plane_id, plane_src_x_prop, 0 << 16);
drmModeAtomicAddProperty(req, plane_id, plane_src_y_prop, 0 << 16);
drmModeAtomicAddProperty(req, plane_id, plane_src_w_prop, width << 16);
drmModeAtomicAddProperty(req, plane_id, plane_src_h_prop, height << 16);
drmModeAtomicAddProperty(req, plane_id, plane_crtc_x_prop, 0);
drmModeAtomicAddProperty(req, plane_id, plane_crtc_y_prop, 0);
drmModeAtomicAddProperty(req, plane_id, plane_crtc_w_prop, display_width);
drmModeAtomicAddProperty(req, plane_id, plane_crtc_h_prop, display_height);
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
drmModeAtomicFree(req);
4.2.2 绑定关系示意图
4.3 显示刷新流程:Page Flip
Page Flip 是 DRM 中 无撕裂(tear-free) 显示更新的核心机制。它通过交换前后缓冲区的指针,在 VBlank 期间完成切换,避免屏幕撕裂。
4.3.1 Page Flip 的两种模式
| 模式 | API | 特点 | 适用场景 |
|---|---|---|---|
| Legacy Page Flip | drmModePageFlip() |
简单,仅支持单个 CRTC,无原子校验 | 简单桌面、单屏 |
| Atomic Page Flip | drmModeAtomicCommit() + DRM_MODE_ATOMIC_NONBLOCK |
支持多 CRTC、多 Plane、状态校验、异步提交 | 现代桌面、Wayland、复杂合成 |
4.3.2 Page Flip 完整流程
- 用户空间提交: 应用程序调用
drmModePageFlip()或 Atomic Commit,指定新的 Framebuffer ID。 - 内核校验: DRM 核心检查新 Framebuffer 的格式、尺寸是否与当前 CRTC 配置兼容。
- 等待 VBlank: 驱动注册一个 VBlank 事件回调,等待下一个垂直消隐期。
- 硬件切换: 在 VBlank 中断中,驱动更新 CRTC 的扫描缓冲区指针,指向新 Framebuffer。
- 事件通知: 驱动通过
drm_crtc_send_vblank_event()通知用户空间 Page Flip 完成。 - 旧缓冲区释放: 用户空间收到完成事件后,可以安全地复用或释放旧 Framebuffer。
4.3.3 Page Flip 时序图
4.4 显示流水线完整视图
将 Framebuffer、Plane、CRTC、Encoder、Connector 串联起来,形成完整的显示流水线:
4.5 调试与常见问题
- Page Flip 超时: 检查 VBlank 中断是否正常,使用
cat /sys/kernel/debug/dri/0/vblank查看计数。 - Framebuffer 格式不匹配: 确保用户空间设置的格式与硬件支持的格式一致,使用
modetest -M msm -e查看支持的格式。 - Plane 绑定失败: 检查 Plane 的
possible_crtcs掩码,确保 Plane 与目标 CRTC 兼容。
modetest:测试显示模式、Framebuffer 创建、Page Flip。drm_info:查看 DRM 设备拓扑、Plane 属性、Framebuffer 列表。ftrace:跟踪drm_atomic_commit、drm_vblank_event等关键函数。
4.6 本章小结
Framebuffer 是显示流水线的数据源头,通过 Plane 绑定到 CRTC,最终由 Encoder 和 Connector 输出到显示设备。Page Flip 机制保证了无撕裂的帧切换,是高性能显示的核心。理解 Framebuffer 的创建、格式配置、绑定关系以及 Page Flip 流程,是调试 DRM 显示驱动的基础。