news 2026/9/10 5:17:08

RK3588 DRM OSD图层硬件合成:旋转缩放零拷贝实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RK3588 DRM OSD图层硬件合成:旋转缩放零拷贝实现

简介:本资源聚焦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_ARGB8888DRM_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,rotationrockchip,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,设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,rotationrockchip,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,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 →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 0x10000000DRM_FORMAT_ARGB8888未被识别,检查内核是否启用CONFIG_DRM_ROCKCHIP_DW_HDMI=yCONFIG_DRM_ROCKCHIP_VOP2=y
  • failed to set property rockchip,rotationrockchip_drv未加载或版本过旧(需 Linux 5.10+);
  • out of memory→ ION 分配失败,检查/proc/meminfoION相关字段,增大 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,rotationrockchip,scale保持不变,仅刷新像素数据,CPU 占用降低 70%,且无撕裂。

5.2 OSD0 与 OSD1 协同:实现分层动画(如背景+前景)

RK3588 支持 OSD0(zpos=1)和 OSD1(zpos=2)两个独立 plane。典型用法:

  • OSD0:静态背景(如半透明底纹),rockchip,scale=0x10000rotation=0
  • OSD1:动态前景(如旋转图标),rockchip,scale=0x18000(1.5x),rotation=90zpos=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_statusen=0,则 plane 已被禁用。

至此,你已掌握 RK3588 DRM OSD 的核心控制链路:从 ION buffer 分配、plane 属性设置,到旋转缩放的 fixed-point 参数计算,再到零拷贝更新与多图层协同。所有操作均基于标准 Linux DRM 接口,无需定制内核模块,可直接集成进嵌入式 Qt 应用或裸机图形框架。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 5:16:36

Django 5 + Vue3 + PostgreSQL:六周落地运维管理平台实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 5:13:58

家庭医生健康服务管理系统毕设全攻略:从需求到实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 5:13:10

deer-flow实操指南:可视化编排AI工作流,从部署到落地

1. deer-flow是什么&#xff1a;一个专注AI工作流的可视化编排平台1.1 核心需求解析——为什么我需要一个工作流引擎先聊聊我接触deer-flow的起因。做了几年AI应用开发&#xff0c;我发现自己有个很典型的痛点&#xff1a;单次调大模型写个demo没问题&#xff0c;可一旦业务变复…

作者头像 李华
网站建设 2026/9/10 5:09:53

红外动物检测实战:YOLO预处理、Anchor重聚类与长尾优化

简介&#xff1a;本资源是一套专为红外场景下野生动物识别设计的高质量目标检测数据集&#xff0c;面向计算机视觉方向的研究者、算法工程师及深度学习初学者&#xff0c;助力YOLO系列模型在低光照、热成像等特殊条件下的动物检测任务快速验证与调优。数据集共9568张带标注图像…

作者头像 李华