
简介本资源聚焦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 compositorRK3588 的 DRM/KMS 驱动早已原生支持多图层硬件合成其中 OSDOn-Screen DisplayPlane 就是专为此类轻量、高频更新、需独立变换的覆盖图层设计的。它不走 CPU 绘图路径不依赖 X11 或 Weston直接由 Rockchip VOP2Video 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 而非 framebuffer2.1 DRM 中的 Plane 是什么OSD Plane 和 Primary/Cursor 的本质区别在 DRM/KMS 模型中plane是独立的图像数据源可被叠加到crtc显示控制器输出的最终帧上。RK3588 的 VOP2 支持最多 4 个 plane1 个 primary主图层通常承载 framebuffer 或 GPU 渲染结果、1 个 cursor光标、2 个 OSDOSD0 和 OSD1。关键区别在于Primary Plane绑定固定 framebuffer如/dev/fb0内容更新需 mmap memcpy缩放/旋转靠 GPU 或软件插值开销大OSD Plane接受 DMA-BUF 或 ION buffer 地址内容由用户态写入显存VOP2 硬件直接读取并执行坐标映射、双线性缩放、90°整数倍旋转全程不经过 CPUCursor Plane仅支持固定尺寸通常 64×64、无缩放、仅平移用于鼠标指针。提示OSD Plane 的 buffer 必须是连续物理内存通过ion_alloc分配且格式严格限定为DRM_FORMAT_ARGB8888或DRM_FORMAT_XRGB8888Alpha 通道决定透明度。其他格式如 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确保高于 primaryzpos0。2.3 Rockchip DRM 驱动对 OSD 的扩展属性rockchip,rotation与rockchip,scale标准 DRMDRM_MODE_ROTATE_*仅支持 0/90/180/270°但 RK3588 的rockchip_drv额外暴露了两个私有 property用于精确控制硬件行为Property NameTypeRange / Values说明rockchip,rotationenum0,90,180,270硬件级旋转单位为度仅整数倍90°有效设置后DRM_MODE_ROTATE_*自动同步rockchip,scalerange0x10000(1.0x) to0x40000(4.0x)缩放系数以 16.16 固定点格式表示0x20000 2.0x,0x18000 1.5x注意rockchip,scale是源图像的缩放比例即若原始 buffer 为 100×100设scale0x20000则硬件将其拉伸为 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 buffer2MB 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通常 ID31将其绑定到目标 CRTC并设置rockchip,rotation和rockchip,scale// 6. 获取 plane 列表定位 OSD0zpos1 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 是否为 1OSD0 典型值 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. 设置 rotation90° 和 scale2.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-pointCRTC_*是最终在屏幕上的显示位置与尺寸。当rockchip,scale0x20000时若SRC_W51216则CRTC_W应设为1024512×2否则硬件会自动按比例缩放填充。CRTC_X/Y决定图层左上角在屏幕坐标系中的位置不受旋转影响——旋转由硬件在合成阶段完成。3.3 参数对照表常见旋转缩放组合与对应设置需求场景rockchip,rotationrockchip,scaleSRC_W/SRC_H(hex)CRTC_W/CRTC_H关键说明原尺寸正向显示00x100000x200000000(512)512最简配置顺时针旋转90°等比900x100000x200000000(512)512输出区域变为 512×512但内容已旋转旋转90° 放大2倍900x200000x200000000(512)1024硬件先旋转源图再按2x缩放至1024×1024区域旋转180° 缩小0.5x1800x080000x200000000(512)2560x08000 0.5输出为256×256内容倒置仅缩放无旋转00x300000x200000000(512)15360x30000 3.0纯拉伸无几何变形注意SRC_X/Y/W/H必须是 16.16 fixed-point 格式即值 × 0x10000例如 512 →0x200000000512 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→ 与 PSRPanel Self Refresh冲突临时禁用echo 0 /sys/class/drm/card0-DSI-1/psr_enabledvop_win_set_format: invalid format 0x10000000→DRM_FORMAT_ARGB8888未被识别检查内核是否启用CONFIG_DRM_ROCKCHIP_DW_HDMIy及CONFIG_DRM_ROCKCHIP_VOP2yfailed to set property rockchip,rotation→rockchip_drv未加载或版本过旧需 Linux 5.10out of memory→ ION 分配失败检查/proc/meminfo中ION相关字段增大 ION heap size修改 device treeionff770000 { heap0 { size 0x4000000; }; }。4.2 使用 modetest 验证 plane 属性是否生效手动设置属性绕过应用代码验证# 获取 plane ID假设为31和 property ID假设 rotation42, scale43 modetest -M rockchip -P 31:4290 -P 31:430x20000 -s 31:100x100100,100若此时 OSD 出现且旋转缩放正确则问题在用户态 buffer 分配或 atomic commit 流程若仍无效则硬件或驱动层异常。4.3 缩放模糊的根本原因与锐化方案硬件双线性缩放RK3588 默认在缩小scale 1.0时易产生模糊。解决方案启用硬件最近邻插值仅限缩小向rockchip_drv提交 patch 或使用内核参数rockchip.vop2_scale_filter00最近邻1双线性。需重新编译内核。预处理源图在用户态用 OpenCV 对 buffer 进行 Lanczos 重采样后再提交牺牲 CPU 换清晰度避免过度缩小rockchip,scale下限为0x040000.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 支持 OSD0zpos1和 OSD1zpos2两个独立 plane。典型用法OSD0静态背景如半透明底纹rockchip,scale0x10000rotation0OSD1动态前景如旋转图标rockchip,scale0x180001.5xrotation90zpos2。控制要点两个 plane 必须绑定到同一crtc_idCRTC_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: en1, fmtARGB8888, scale0x20000, rot90若clk_rate长期低于 300MHz说明 OSD 未成为瓶颈若layer_status中en0则 plane 已被禁用。至此你已掌握 RK3588 DRM OSD 的核心控制链路从 ION buffer 分配、plane 属性设置到旋转缩放的 fixed-point 参数计算再到零拷贝更新与多图层协同。所有操作均基于标准 Linux DRM 接口无需定制内核模块可直接集成进嵌入式 Qt 应用或裸机图形框架。本文还有配套的精品资源点击获取