简介:本资源聚焦RK3588平台DRM子系统中OSD图层叠加、旋转与缩放三大核心显示功能的实战实现,面向嵌入式Linux图形开发工程师、多媒体驱动开发者及进阶ARM平台学习者,解决多图层合成、硬件加速图像变换等典型显示开发难题,适用于智能电视UI叠加、车载HUD渲染、广告机多源画面融合等实际场景。压缩包为43KB的ZIP文件,共16个文件,含10个C源码(如modeset-plane-rotate.c、modeset-plane-scale.c、modeset-plane-test.c等)、2个Makefile构建脚本、1个README.md说明文档、1个camero_test测试程序及1个modeset-single-buffer示例,代码结构清晰,覆盖平面管理、像素格式配置、DRM ioctl调用与硬件寄存器协同等关键环节。已有1377人学习下载。读者可直接编译运行各测试用例,快速验证OSD多层叠加顺序、90/180/270度旋转效果及任意比例缩放的硬件加速行为,并结合源码深入理解RK3588 DRM Plane机制与VOP模块协同逻辑。
1. 在 RK3588 上用 DRM 原生能力做 OSD 叠加,不是“画上去”,而是硬件图层合成
你正在调试一块 RK3588 开发板,需要在 HDMI 或 MIPI-DSI 输出的主视频流(比如摄像头预览、解码器输出或 OpenGL 渲染画面)之上,叠加一个带旋转和缩放的 UI 图层——比如带角度的温度告警框、斜向滚动的字幕条、或适配异形屏的自定义状态栏。别急着上 Qt Quick 或 Wayland compositor:RK3588 的 DRM/KMS 驱动早已原生支持多图层硬件合成,其中 OSD(On-Screen Display)Plane 就是专为此类轻量、高频更新、需独立变换的覆盖图层设计的。它不走 CPU 绘图路径,不依赖 X11 或 Weston,直接由 Rockchip VOP2(Video Output Processor 2)硬件调度,叠加延迟低于 2ms,缩放/旋转全程无 CPU 拷贝、无内存带宽瓶颈。适合工业 HMI、车载仪表、安防终端等对实时性与功耗敏感的场景。本文聚焦于 Linux 6.1+ 内核下,通过标准 DRM ioctl 接口与 Rockchip 特定属性控制 OSD Plane 的叠加位置、90°/180°/270° 硬件旋转、以及 0.25x–4.0x 范围内任意比例缩放,所有操作均可在用户态完成,无需修改内核驱动。
2. 理解 RK3588 DRM 架构中的 OSD Plane:为什么必须用 Plane 而非 framebuffer
2.1 DRM 中的 Plane 是什么?OSD Plane 和 Primary/Cursor 的本质区别
在 DRM/KMS 模型中,plane是独立的图像数据源,可被叠加到crtc(显示控制器)输出的最终帧上。RK3588 的 VOP2 支持最多 4 个 plane:1 个 primary(主图层,通常承载 framebuffer 或 GPU 渲染结果)、1 个 cursor(光标)、2 个 OSD(OSD0 和 OSD1)。关键区别在于:
- Primary Plane:绑定固定 framebuffer(如
/dev/fb0),内容更新需 mmap + memcpy,缩放/旋转靠 GPU 或软件插值,开销大; - OSD Plane:接受 DMA-BUF 或 ION buffer 地址,内容由用户态写入显存,VOP2 硬件直接读取并执行坐标映射、双线性缩放、90°整数倍旋转,全程不经过 CPU;
- Cursor Plane:仅支持固定尺寸(通常 64×64)、无缩放、仅平移,用于鼠标指针。
提示:OSD Plane 的 buffer 必须是连续物理内存(通过
ion_alloc分配),且格式严格限定为DRM_FORMAT_ARGB8888或DRM_FORMAT_XRGB8888(Alpha 通道决定透明度)。其他格式(如 NV12)将被内核拒绝。
2.2 查看当前 DRM 设备与可用 Plane 的完整拓扑
使用modetest(来自libdrm-tests包)确认硬件能力是否就绪:
# 安装(Ubuntu/Debian) sudo apt install libdrm-tests # 列出所有 connector/crtc/plane modetest -M rockchip -c输出中需看到类似:
Connectors: id encoder name status encoders ... Planes: id fb CRTC format modifier size pos rotation zpos 30 0 29 ARGB8888 linear 1920x1080 0,0 0 0 31 0 29 XRGB8888 linear 1920x1080 0,0 0 1 ← 这是 OSD0 32 0 29 ARGB8888 linear 1920x1080 0,0 0 2 ← 这是 OSD1注意zpos值:数值越大,图层越靠前。OSD Plane 的zpos默认为 1 或 2,确保高于 primary(zpos=0)。
2.3 Rockchip DRM 驱动对 OSD 的扩展属性:rockchip,rotation与rockchip,scale
标准 DRMDRM_MODE_ROTATE_*仅支持 0/90/180/270°,但 RK3588 的rockchip_drv额外暴露了两个私有 property,用于精确控制硬件行为:
| Property Name | Type | Range / Values | 说明 |
|---|---|---|---|
rockchip,rotation | enum | 0,90,180,270 | 硬件级旋转,单位为度,仅整数倍90°有效;设置后DRM_MODE_ROTATE_*自动同步 |
rockchip,scale | range | 0x10000(1.0x) to0x40000(4.0x) | 缩放系数,以 16.16 固定点格式表示:0x20000 = 2.0x,0x18000 = 1.5x |
注意:
rockchip,scale是源图像的缩放比例,即:若原始 buffer 为 100×100,设scale=0x20000,则硬件将其拉伸为 200×200 后再叠加。该值不影响crtc输出分辨率,只作用于本 plane。
验证属性是否存在:
modetest -M rockchip -p | grep -E "(rockchip,rotation|rockchip,scale)"应返回两行,包含prop id(如 42 和 43)及对应 type。
3. 用 libdrm 直接控制 OSD Plane:从分配 buffer 到设置旋转缩放的最小可行代码
3.1 分配 ION buffer 并导入为 DRM FB(关键:物理连续内存)
RK3588 的 OSD Plane 不接受普通 malloc 内存,必须使用 ION 分配器获取 DMA-BUF。以下 C 代码片段(需链接-l drm -l ion)完成 buffer 创建与 FB 注册:
#include <xf86drm.h> #include <xf86drmMode.h> #include <drm_fourcc.h> #include <ion/ion.h> int drm_fd; struct ion_device *ion_dev; int ion_client; // 1. 打开 DRM 设备 drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); if (drm_fd < 0) { perror("open /dev/dri/card0"); return -1; } // 2. 初始化 ION(需 root 或 /dev/ion 权限) ion_dev = ion_open(); ion_client = ion_client_create(ion_dev, "rk3588-osd"); // 3. 分配 512x512 ARGB8888 buffer(2MB) size_t buf_size = 512 * 512 * 4; int ion_fd; unsigned char *map_addr; ion_allocation_data alloc = { .len = buf_size, .heap_mask = ION_HEAP_SYSTEM_MASK, // 确保系统堆(物理连续) .flags = ION_FLAG_CACHED, }; ion_alloc(ion_dev, ion_client, &alloc); ion_fd = alloc.handle; // 4. 映射到用户态,填充测试图案(红色方块+半透明文字) map_addr = mmap(NULL, buf_size, PROT_READ|PROT_WRITE, MAP_SHARED, ion_fd, 0); memset(map_addr, 0, buf_size); // 绘制 100x100 红色区域(ARGB: 0xFFFF0000) for (int y = 50; y < 150; y++) { uint32_t *row = (uint32_t*)(map_addr + y * 512 * 4); for (int x = 50; x < 150; x++) row[x] = 0xFFFF0000; } munmap(map_addr, buf_size); // 5. 导入 ION handle 为 DRM framebuffer uint32_t fb_id; struct drm_mode_fb_cmd2 fbcmd = { .width = 512, .height = 512, .pixel_format = DRM_FORMAT_ARGB8888, .flags = 0, .handles[0] = ion_fd, .pitches[0] = 512 * 4, .offsets[0] = 0, }; if (drmIoctl(drm_fd, DRM_IOCTL_MODE_ADDFB2, &fbcmd)) { perror("DRM_IOCTL_MODE_ADDFB2"); return -1; } fb_id = fbcmd.fb_id;3.2 获取 OSD Plane ID 并设置 DRM 属性(旋转 + 缩放)
接下来,找到 OSD0 plane(通常 ID=31),将其绑定到目标 CRTC,并设置rockchip,rotation和rockchip,scale:
// 6. 获取 plane 列表,定位 OSD0(zpos=1) drmModeRes *res = drmModeGetResources(drm_fd); drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm_fd); uint32_t osd_plane_id = 0; for (int i = 0; i < plane_res->count_planes; i++) { drmModePlane *plane = drmModeGetPlane(drm_fd, plane_res->planes[i]); if (plane && plane->count_formats > 0 && plane->possible_crtcs) { // 检查 zpos 是否为 1(OSD0 典型值) uint64_t zpos_val; drmModeObjectProperties *props = drmModeObjectGetProperties(drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); for (int j = 0; j < props->count_props; j++) { drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[j]); if (prop && !strcmp(prop->name, "zpos") && drmModeObjectGetPropertyBlob(drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE, props->props[j], &zpos_val) == 0) { if (zpos_val == 1) { osd_plane_id = plane->plane_id; drmModeFreeProperty(prop); break; } } drmModeFreeProperty(prop); } drmModeFreeObjectProperties(props); } drmModeFreePlane(plane); if (osd_plane_id) break; } // 7. 设置 rotation=90° 和 scale=2.0x (0x20000) uint32_t rot_prop_id = 0, scale_prop_id = 0; // ...(同上,遍历 plane properties 找到 "rockchip,rotation" 和 "rockchip,scale" 的 prop_id) drmModeAtomicReq *req = drmModeAtomicAlloc(); drmModeAtomicAddProperty(req, osd_plane_id, rot_prop_id, 90); drmModeAtomicAddProperty(req, osd_plane_id, scale_prop_id, 0x20000); // 2.0x drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "FB_ID", fb_id); drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_ID", res->crtcs[0]); // 绑定到第一个 CRTC drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "SRC_X", 0); drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "SRC_Y", 0); drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "SRC_W", 512 << 16); // fixed-point 512.0 drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "SRC_H", 512 << 16); drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_X", 100); // 屏幕坐标 X drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_Y", 100); // 屏幕坐标 Y drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_W", 1024); // 输出宽(缩放后) drmModeAtomicAddProperty(req, osd_plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_H", 1024); // 输出高(缩放后) // 提交原子请求 uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; drmModeAtomicCommit(drm_fd, req, flags, NULL); drmModeAtomicFree(req);逻辑说明:
SRC_*是源 buffer 的裁剪区域(单位为 16.16 fixed-point),CRTC_*是最终在屏幕上的显示位置与尺寸。当rockchip,scale=0x20000时,若SRC_W=512<<16,则CRTC_W应设为1024(512×2),否则硬件会自动按比例缩放填充。CRTC_X/Y决定图层左上角在屏幕坐标系中的位置,不受旋转影响——旋转由硬件在合成阶段完成。
3.3 参数对照表:常见旋转缩放组合与对应设置
| 需求场景 | rockchip,rotation | rockchip,scale | SRC_W/SRC_H(hex) | CRTC_W/CRTC_H | 关键说明 |
|---|---|---|---|---|---|
| 原尺寸,正向显示 | 0 | 0x10000 | 0x200000000(512) | 512 | 最简配置 |
| 顺时针旋转90°,等比 | 90 | 0x10000 | 0x200000000(512) | 512 | 输出区域变为 512×512,但内容已旋转 |
| 旋转90° + 放大2倍 | 90 | 0x20000 | 0x200000000(512) | 1024 | 硬件先旋转源图,再按2x缩放至1024×1024区域 |
| 旋转180° + 缩小0.5x | 180 | 0x08000 | 0x200000000(512) | 256 | 0x08000 = 0.5,输出为256×256,内容倒置 |
| 仅缩放(无旋转) | 0 | 0x30000 | 0x200000000(512) | 1536 | 0x30000 = 3.0,纯拉伸,无几何变形 |
注意:
SRC_X/Y/W/H必须是 16.16 fixed-point 格式(即值 × 0x10000),例如 512 →0x200000000(512 << 16)。错误的 fixed-point 值会导致图层错位或截断。
4. 调试与排错:为什么 OSD 不显示?旋转失效?缩放模糊?
4.1 必查的 5 个内核日志线索(dmesg 实时过滤)
OSD 失效时,第一时间运行:
dmesg -w | grep -i -E "(vop|osd|drm|rockchip|plane)"重点关注以下错误模式:
vop_set_win_enable: win[2] enable fail→ OSD0 对应 win2,表示 plane 启用失败,常见于fb_id无效或 buffer 格式不匹配;rockchip_drm_psr_disable: PSR disable failed→ 与 PSR(Panel Self Refresh)冲突,临时禁用:echo 0 > /sys/class/drm/card0-DSI-1/psr_enabled;vop_win_set_format: invalid format 0x10000000→DRM_FORMAT_ARGB8888未被识别,检查内核是否启用CONFIG_DRM_ROCKCHIP_DW_HDMI=y及CONFIG_DRM_ROCKCHIP_VOP2=y;failed to set property rockchip,rotation→rockchip_drv未加载或版本过旧(需 Linux 5.10+);out of memory→ ION 分配失败,检查/proc/meminfo中ION相关字段,增大 ION heap size(修改 device treeion@ff770000 { heap@0 { size = <0x4000000>; }; })。
4.2 使用 modetest 验证 plane 属性是否生效
手动设置属性,绕过应用代码验证:
# 获取 plane ID(假设为31)和 property ID(假设 rotation=42, scale=43) modetest -M rockchip -P 31:42=90 -P 31:43=0x20000 -s 31:100x100@100,100若此时 OSD 出现且旋转缩放正确,则问题在用户态 buffer 分配或 atomic commit 流程;若仍无效,则硬件或驱动层异常。
4.3 缩放模糊的根本原因与锐化方案
硬件双线性缩放(RK3588 默认)在缩小(scale < 1.0)时易产生模糊。解决方案:
- 启用硬件最近邻插值(仅限缩小):
向rockchip_drv提交 patch 或使用内核参数rockchip.vop2_scale_filter=0(0=最近邻,1=双线性)。需重新编译内核。 - 预处理源图:在用户态用 OpenCV 对 buffer 进行 Lanczos 重采样后再提交,牺牲 CPU 换清晰度;
- 避免过度缩小:
rockchip,scale下限为0x04000(0.25x),低于此值硬件可能降级为 nearest-neighbor,但效果仍差。
提示:旋转本身不引入模糊,因硬件采用整数像素映射(90°倍数旋转无插值)。模糊仅来自
rockchip,scale ≠ 0x10000。
5. 进阶技巧:动态更新 OSD 内容而不闪烁,以及多 OSD 协同控制
5.1 零拷贝更新:用 page-flip 避免 atomic commit 引起的撕裂
前述drmModeAtomicCommit每次提交都会触发全帧重绘,若频繁更新 OSD 内容(如每秒 30 帧字幕),可能造成撕裂。正确做法是复用同一fb_id,仅更新 buffer 内容并触发 page-flip:
// 假设已有 fb_id 和 ion_fd uint32_t *buf_ptr = mmap(NULL, buf_size, PROT_READ|PROT_WRITE, MAP_SHARED, ion_fd, 0); // 直接修改 buf_ptr 指向的内存(如绘制新文字) draw_new_text(buf_ptr, 512, 512); munmap(buf_ptr, buf_size); // 触发 page-flip,不改变 plane 属性 drmModePageFlip(drm_fd, crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL);此方式下,rockchip,rotation和rockchip,scale保持不变,仅刷新像素数据,CPU 占用降低 70%,且无撕裂。
5.2 OSD0 与 OSD1 协同:实现分层动画(如背景+前景)
RK3588 支持 OSD0(zpos=1)和 OSD1(zpos=2)两个独立 plane。典型用法:
- OSD0:静态背景(如半透明底纹),
rockchip,scale=0x10000,rotation=0; - OSD1:动态前景(如旋转图标),
rockchip,scale=0x18000(1.5x),rotation=90,zpos=2。
控制要点:
- 两个 plane 必须绑定到同一
crtc_id; CRTC_X/Y坐标系以屏幕左上角为原点,OSD1 可部分覆盖 OSD0;- 更新时分别对两个
fb_id调用drmModePageFlip,硬件自动按 zpos 合成。
5.3 性能监控:量化 OSD 硬件负载
通过 sysfs 实时查看 VOP2 负载:
# VOP2 频率(MHz) cat /sys/kernel/debug/clk/vop2_m0/clk_rate # OSD plane 活跃状态 cat /sys/kernel/debug/rockchip/vop2/layer_status # 输出类似:osd0: en=1, fmt=ARGB8888, scale=0x20000, rot=90若clk_rate长期低于 300MHz,说明 OSD 未成为瓶颈;若layer_status中en=0,则 plane 已被禁用。
至此,你已掌握 RK3588 DRM OSD 的核心控制链路:从 ION buffer 分配、plane 属性设置,到旋转缩放的 fixed-point 参数计算,再到零拷贝更新与多图层协同。所有操作均基于标准 Linux DRM 接口,无需定制内核模块,可直接集成进嵌入式 Qt 应用或裸机图形框架。
本文还有配套的精品资源,点击获取