4. drm_plane_state 结构体:平面状态、坐标、格式、Fence
drm_plane_state 是 DRM 原子提交中描述单个平面(Plane)瞬时状态的核⼼结构体。它记录了该平面在某一帧的所有配置参数,包括绑定的 Framebuffer、显示坐标、像素格式、裁剪区域以及同步 Fence。原子提交的核心工作就是构造、校验并提交这些状态对象。
4.1 结构体核心字段解析
以下表格列出了 drm_plane_state 中最关键的字段及其语义:
| 字段名 | 类型 | 说明 |
|---|---|---|
crtc |
struct drm_crtc * |
该平面当前绑定的 CRTC,NULL 表示关闭 |
fb |
struct drm_framebuffer * |
当前显示的 Framebuffer,包含像素数据与格式信息 |
fb_damage_clips |
struct drm_property_blob * |
脏区域(Damage)提示,用于优化局部刷新 |
crtc_x, crtc_y |
int32_t |
平面内容在 CRTC 坐标空间中的位置(左上角) |
crtc_w, crtc_h |
uint32_t |
平面在 CRTC 上的显示宽度和高度(缩放后尺寸) |
src_x, src_y |
uint32_t |
从 Framebuffer 中采样的起始坐标(16.16 定点数) |
src_w, src_h |
uint32_t |
从 Framebuffer 中采样的源尺寸(16.16 定点数) |
rotation |
unsigned int |
旋转和镜像标志位(DRM_MODE_ROTATE_* / REFLECT_*) |
zpos |
unsigned int |
平面 Z 序,值越小越靠近背景 |
color_encoding |
enum drm_color_encoding |
颜色编码标准(BT.601 / BT.709 等) |
color_range |
enum drm_color_range |
颜色量化范围(有限 / 全范围) |
fence |
struct dma_fence * |
出站 Fence,表示 GPU 渲染完成信号 |
state |
struct drm_plane_state * |
指向全局原子状态(用于状态所有权跟踪) |
关键概念:
src_* 字段使用 16.16 定点数格式(左移 16 位),允许亚像素精度的裁剪和缩放。驱动在 atomic_check 中必须验证这些坐标不超出 Framebuffer 边界。
4.2 坐标与裁剪模型
平面状态定义了从 Framebuffer 到 CRTC 显示区域的完整映射链路。下图展示了源裁剪、缩放和显示定位的关系:
校验要点: 驱动在
atomic_check 回调中必须检查:
src_x + src_w不超过 Framebuffer 宽度(左移 16 位后比较)crtc_x + crtc_w不超过 CRTC 模式设定的显示宽度- 缩放比例不超过硬件限制(如最大缩小 1/4 或最大放大 8 倍)
4.3 像素格式与颜色属性
Framebuffer 的像素格式由 drm_framebuffer 的 format 字段描述,而 drm_plane_state 通过 fb 指针间接引用。平面支持的格式列表在 drm_plane 的 format_types 数组中声明。
常见像素格式分类:
| 格式类型 | 示例 FOURCC | 位深 | 典型用途 |
|---|---|---|---|
| ARGB8888 | DRM_FORMAT_ARGB8888 |
32 bpp | UI 图层、光标平面 |
| XRGB8888 | DRM_FORMAT_XRGB8888 |
32 bpp | 无透明通道的主平面 |
| NV12 | DRM_FORMAT_NV12 |
12 bpp | 视频解码输出、摄像头预览 |
| P010 | DRM_FORMAT_P010 |
15 bpp | HDR 视频、10-bit 精度 |
格式校验陷阱: 原子提交时,用户空间可能设置一个平面不支持的格式。驱动必须在
atomic_check 中遍历 plane->format_types 进行匹配,不匹配则返回 -EINVAL。此外,对于 NV12 等半平面格式,还需验证偏移量和行距(pitch)的对齐要求。
4.4 Fence 同步机制
drm_plane_state 中的 fence 字段用于实现 GPU 与 Display 之间的流水线同步。它本质上是一个 dma_fence,表示该平面的 Framebuffer 内容何时可以被 Display 硬件安全读取。
Fence 的工作流程如下:
- 出站 Fence(Out-fence): 用户空间在提交原子 commit 时,可以为每个平面关联一个 out-fence。当 Display 硬件完成对该平面的扫描输出后,该 fence 被 signal。
- 入站 Fence(In-fence): 用户空间可以传入一个 in-fence,表示 GPU 渲染尚未完成。Display 驱动必须等待该 fence signal 后才能开始扫描该平面。
- 驱动处理: 在
atomic_commit的commit_tail阶段,驱动调用drm_atomic_set_fence_for_plane()将 in-fence 安装到 plane_state,并在硬件编程前等待。
实现建议: 在
atomic_commit 的 prepare_fb 回调中,驱动应调用 dma_fence_get() 获取 in-fence 的引用,并在硬件编程前通过 dma_fence_wait() 或硬件等待机制阻塞。out-fence 则在 cleanup_fb 或中断处理中 signal。
4.5 状态校验流程示例
以下代码片段展示了驱动在 atomic_check 中如何校验 drm_plane_state 的坐标和格式:
static int my_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_state *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
struct drm_crtc_state *crtc_state;
struct drm_framebuffer *fb = new_state->fb;
int ret;
if (!fb || !new_state->crtc)
return 0; /* 平面关闭,跳过校验 */
crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
/* 1. 校验像素格式是否在平面支持列表中 */
ret = drm_plane_check_pixel_format(plane, fb->format->format);
if (ret)
return ret;
/* 2. 校验源裁剪坐标不越界(16.16 定点数) */
if (new_state->src_x + new_state->src_w > fb->width << 16 ||
new_state->src_y + new_state->src_h > fb->height << 16)
return -EINVAL;
/* 3. 校验显示坐标不超出 CRTC 尺寸 */
if (new_state->crtc_x + new_state->crtc_w > crtc_state->mode.hdisplay ||
new_state->crtc_y + new_state->crtc_h > crtc_state->mode.vdisplay)
return -EINVAL;
/* 4. 校验缩放比例在硬件限制内 */
if (new_state->crtc_w > (new_state->src_w >> 16) * 8 ||
new_state->crtc_h > (new_state->src_h >> 16) * 8)
return -EINVAL;
return 0;
}
校验顺序建议: 先检查格式和基本参数,再检查坐标边界,最后检查硬件约束(缩放、对齐、带宽)。这样可以在早期快速拒绝无效请求,减少不必要的计算。
4.6 状态所有权与引用计数
drm_plane_state 的生命周期由 DRM 核心管理,但驱动需要理解以下所有权规则:
- 分配: 驱动通过
drm_atomic_helper_plane_duplicate_state()或自定义atomic_duplicate_state回调创建新状态。 - 销毁: 原子提交完成后,旧状态通过
drm_atomic_helper_plane_destroy_state()释放。 - Framebuffer 引用:
fb字段在状态复制时自动增加引用计数,销毁时减少。驱动不应手动干预。 - Fence 引用:
fence字段在设置时通过dma_fence_get()获取引用,在状态清理时通过dma_fence_put()释放。
常见错误: 驱动在
atomic_check 中修改 plane_state 后,必须确保所有修改在 atomic_commit 中可见。不要直接修改 plane->state,始终通过 drm_atomic_get_new_plane_state() 获取新状态。