
聊到 RK3588 的 2D 加速RGA 是绕不开的话题。系列上一篇已经把 RGA 能干什么做了个铺垫这一篇我们直接钻进这颗芯片的 RGA 模块内部把它的核拓扑结构和两代核心的能力差异拆开看。说直白点RK3588 里不只有一个 RGA不同的核支持的格式、性能上限、能做的几何变换都不一样选不对轻则预处理卡顿重则功能缺失。这篇内容适合正在做 RK3588 平台图像处理的工程师尤其是做 ISP 串接、视频编解码前处理、NPU 推理前处理这类需要频繁做缩放和格式转换的场景。你如果只是把 RGA 当成一个硬件缩放器来用也可以看但建议重点看第二章和第三章能帮你少走不少弯路。1. 为什么先看核拓扑RGA 在 RK3588 里的位置1.1 从 RGA1 到 RGA32D 加速的演进RGA 的全称是 Raster Graphic Acceleration Unit也就是光栅图形加速单元。瑞芯微做它的目的很直接把 2D 图形操作从 CPU 上卸载下来。缩放、格式转换、旋转、裁剪、镜像、alpha 混合这些操作在嵌入式平台上用 CPU 做既费电又慢尤其是图像尺寸一大CPU 占用率直接拉满别的任务就别想跑了。早期 IC 上集成的 RGA1 能力很基础主要就是缩放和简单的 RGB/YUV 转换分辨率上限也低。到了 RGA2 一代功能明显增多旋转、镜像、色彩空间转换都进来了分辨率上限也拉高到接近 4K 级别这代核在 RK3288、RK3399 上大面积使用积累了非常多的软件适配经验。再往后就是 RGA3这代主要补的是格式支持和性能上限YUV 家族格式覆盖更全分辨率也往 8K 方向走同时寄存器模型和描述符结构重新设计过驱动也是单独的一套。也就是说RGA 不是一个一个版本打天下的 IP而是跟着 SoC 迭代在演进。到了 RK3588 这一代芯片里同时放了两代核心这就引出了标题里说的核拓扑问题。1.2 两个核心并存RK3588 RGA 的硬件布局RK3588 的 RGA 模块不是单核而是包含两个独立的核心。在技术参考手册TRM和 Linux SDK 驱动里通常分别叫 RGA2 和 RGA3也有资料会把它们表述为 RGA_2 和 RGA_3。两个核都能做 2D 加速但不是简单的 A/B 冗余关系而是能力各有侧重的异构组合。我最初接触 RK3588 的 RGA 时想当然地以为驱动里只有一个统一的RGA后来翻 TRM 和内核源码才确认RGA2 和 RGA3 是物理上独立的两套硬件拥有各自的寄存器基地址、各自的中断线、各自的 DMA 通道。在 Linux 系统中它们对应到同一个用户空间设备节点 /dev/rga由驱动层根据任务属性做分发。这个设计乍看有点奇怪为什么不在 SoC 里只放一个更强的 RGA3我的理解是一方面 RGA2 的驱动模型经历了多代产品验证很多老代码和用户态库是直接对着 RGA2 调的保留它可以实现软件平滑兼容另一方面两个核并存意味着在任务繁忙时可以并行处理一个核做格式转换另一个核做缩放混合这在高负载媒体管线里是有实际意义的。1.3 怎么看懂 TRM 里的 RGA 框图如果你手上有 RK3588 的 TRM找到 RGA 章节一般会看到类似这样的描述模块包含 RGA2 主机接口、RGA3 主机接口、公共的 AXI 总线接口和配置寄存器区。画到系统层面RGA2 和 RGA3 各自通过 AXI 端口挂到内存总线上访问 DDR 时走各自的通道。实操中第一件事是先在板子上确认这两个核是否都被识别。以 Linux SDK 为例可以这样查ls -l /dev/rga* cat /proc/device-tree/rga*/compatible dmesg | grep -i rga正常启动日志里能看到类似 rga2 和 rga3 驱动的 probe 信息/proc/device-tree 下也会出现对应节点。不同 SDK 版本的节点命名可能有差异有的叫 rgaxxxxxxx有的直接叫 rga2/rga3这个不影响使用重点是确认两套驱动都加载成功。别小看这一步。我遇到过一块定制板的 SDK 只启用了其中一个核的情况跑高分辨率格式转换时另一核的资源被浪费排查半天才发现是内核配置或者设备树裁剪导致某些节点被关掉了。2. RGA2 和 RGA3 的能力差异一张表拉齐2.1 输入输出格式谁的主场RGA2 和 RGA3 最直观的差异体现在输入输出格式支持上。RGA2 的核心接口围绕经典 YUV 和 RGB 展开NV12、NV21、YUYV、UVYV 这类常见视频帧格式、RGB565、RGB888、ARGB8888 这类 UI 渲染格式基本都覆盖日常 99% 的 2D 操作它都能接。RGA3 则在格式支持上做了明显扩展一个典型的区别是它支持更多 YUV 采样格式包括 YUV444、YUV422 的多种打包排列同时也支持 10 位深度的格式这点在做 HDR 或高精度视频处理时很关键。另一个区别是 RGA3 对部分格式之间转换时内部走的是可配置的色彩空间转换矩阵精度比 RGA2 的固定系数更高。为了直观对比我整理了一个常见格式支持速查表注意这是基于 RK3588 SDK 通用配置的总结具体到你手里的 BSP 版本可能还会略有增减一切以 SDK 头文件里定义的 format 枚举为准功能/格式RGA2RGA3输入 NV12/NV21支持支持输入 YUV422YUYV/UYVY支持支持输入 YUV444部分型号不支持支持输入 RGB888/RGB565/ARGB支持支持输出 NV12/NV21支持支持输出 YUV444部分型号不支持支持10 位深度格式部分型号不支持支持色彩空间矩阵可配置固定/有限可配置工程上经常遇到的一个问题是想把 YUV444 的 IPC 帧先转成 NV12 再送给编码器。如果你的平台只有 RGA2这个操作就做不了必须走 CPU 或者另想办法如果会用 RGA3一条 blit 指令就解决了。这也印证了标题里说的能力差异——它不是性能差异而是能力范围的差异。2.2 几何变换与混合操作谁的工具全缩放、旋转、裁剪、镜像这些几何操作RGA2 和 RGA3 都支持旋转角度常规的 0 度、90 度、180 度、270 度都可以做镜像也支持水平、垂直。但有一点要注意RGA2 在做缩放时水平方向和垂直方向的缩放系数有独立上限超出就会报错RGA3 在这一代做了一些调整对于非整数倍缩放的边界条件处理更宽松效果也更好。alpha 混合方面两者都支持。RGA2 主要面向经典的 2D 合成场景比如把 UI 层叠加到视频层、在画面上叠加 OSD 等操作数支持两个输入和一个输出。RGA3 也支持这些并且对混合模式和边界条件的控制粒度更细比如可以控制逐通道的混合系数这对某些特殊效果实现有帮助。再补充一个容易踩坑的点虽然名字都叫旋转 90 度但 RGA2 和 RGA3 在旋转后的输出尺寸计算逻辑并不完全一致。RGA3 的核心在处理旋转时会先根据目标缓冲区尺寸和旋转角度进行内部坐标重映射如果配置不当同一个目标 buffer 在 RGA2 上能跑通换成 RGA3 上可能因为 stride 校验不过而返回错误。所以写代码时最好统一走 librga 的接口而不是直接对着寄存器或者私有 ioctl 操作。2.3 性能上限与典型应用场景性能差异也是选核的重要参考。RGA3 在时钟和内存带宽利用上明显优于 RGA2单次大尺寸格式转换的耗时更短这在 4K/8K 分辨率场景下会拉开差距。SDK 文档中的数据一般是按像素吞吐率来算的实际应用中我建议不要只看理论值而是拿典型尺寸直接做一次 RgaBlit 计时测试因为结果还受内存位姿、cache 策略、总线上其他 DMA 设备占用情况影响。基于两者的能力特性我总结出比较合理的分工方式RGA2 适合做轻量级的日常 2D 加速比如 UI 合成、小尺寸区域旋转、RGB 之间的转换、OSD 混合这类不涉及复杂 YUV 格式的操作。RGA3 适合做视频数据流的主干道处理比如 NV12 到 RGB888 的大尺寸转换、YUV444 到 NV12 的下采样、4K 以上分辨率的缩放、10 位格式处理等。在 RK3588 上做 yolov8 推理部署时预处理阶段经常需要把摄像头进来的 NV12 帧缩放到 640x640 并转成 RGB这个操作直接丢给 RGA3 会比用 RGA2 更快也比用 CPU 做省出大把算力。说白了核拓扑决定了你手上有什么牌能力差异则决定了哪张牌在哪种牌局里最好用。3. 工程调用与核选择从驱动到 librga3.1 先确认你的系统认了几个核实际开发中用户态程序通常不需要直接操作寄存器而是通过 librga 库调 RgaBlit 接口。但要想用好它得先确认底层驱动状态。我建议拿到板子后按下面流程做一次体检# 查看设备节点是否存在 ls -l /dev/rga* # 查看内核日志中的 rga 驱动初始化信息 dmesg | grep -i rga # 查看设备树中的节点状态 cat /proc/device-tree/rga*/status节点存在且 status 为 okay、设备节点 /dev/rga 有正常的读写权限说明驱动基础是正常的。有些 SDK 里还能通过 debugfs 查到版本信息路径通常在 /sys/kernel/debug/rga/ 下面可以看一下有没有类似 version 的文件cat /sys/kernel/debug/rga/version如果拿不到那大概率是内核没打开 rga 的 debugfs 配置。这只是提示信息不影响功能但会影响你排查问题时的信息量。3.2 librga 调用与核选择策略用户态调用 librga 时最核心的结构体是 rga_info_t你需要把源图像格式、目标格式、宽高、地址信息填进去然后调用 RgaBlit。示例代码如下#include rga.h #include RgaApi.h rga_info_t src; rga_info_t dst; memset(src, 0, sizeof(src)); memset(dst, 0, sizeof(dst)); src.fd -1; src.virAddr (uint64_t)input_buffer; // 虚拟地址方式 src.format RK_FORMAT_NV12; src.width 1920; src.height 1080; src.wstride 1920; src.hstride 1080; dst.fd -1; dst.virAddr (uint64_t)output_buffer; // 需要预先分配好输出空间 dst.format RK_FORMAT_RGB_888; dst.width 640; dst.height 640; dst.wstride 640; dst.hstride 640; int ret RgaBlit(src, dst, NULL); if (ret) { printf(RgaBlit failed: %s\n, imStrError(ret)); }关键点在于librga 在内部会自动根据输入输出的格式和尺寸判断由哪个核处理。大多数情况下你不用手动指定核心直接调用 RgaBlit 即可。但在某些特殊场景——比如你明确知道某个操作只有在 RGA3 上才能完成——你需要确认你用的 librga 版本是否暴露了核选择的参数。新版本 librga 在 rga_info_t 结构中增加了 core 相关字段可以通过设置它来强制指定 RGA2 或 RGA3。不过我个人的习惯是优先让 librga 自动决策。因为 SDK 里的 librga 已经针对各芯片做过匹配自动模式通常会选到更合适、更稳定的路径。手动指定核心一般用于调试定位问题比如怀疑某个格式转换走 RGA2 失败但走 RGA3 成功这时候切换核再做对比实验能快速锁定问题边界。3.3 实战YOLOv8 图像预处理走 RGA3写一个贴近实际需求的小例子。很多人在 RK3588 上部署 YOLOv8预处理就两个动作把摄像头帧缩放到 640x640再做颜色空间转换。用 RGA 做这件事的效率远高于 CPU。首先是获取摄像头帧。通常是 NV12 格式宽 1920高 1080。期望输出是 640x640 的 RGB888 连续内存方便后续喂给 NPU 或者自己写的推理代码。示例中直接构造一个 NV12 的虚拟地址源输出到预先分配的 RGB buffer 上核心就是设定 src、dst 的 width/height/format让 RgaBlit 完成缩放加转换。注意 wstride 和 hstride 这一对参数。如果你的源数据每行并不是紧贴着的比如从硬件解码器拿到的 buffer 行与行之间有对齐 padding那么 wstride 要填实际的一行字节数对应的像素宽度hstride 同理。填错这个画面会出现斜切或者拉伸这是一个非常常见的低级错误。实测下来一次 1920x1080 NV12 到 640x640 RGB888 的转换在 RK3588 上用 RGA3 大约只需要几毫秒CPU 占用几乎为零。如果你用 CPU 直接做双线性缩放和颜色空间转换同样的尺寸大概要几十毫秒差距非常明显这也是为什么做视频分析项目时 RGA 几乎是必选项。4. 开发中踩过的坑与问题速查4.1 三个典型异常现象和处理记录第一个典型问题是输出图像花屏或者颜色错乱。现象是转换后图像有绿色条纹或者红蓝对调。排查思路从格式匹配开始先确认源格式描述是否正确。NV12 是 Y 平面加 UV 交错平面但有些摄像头驱动给出来的 buffer 并不是标准的 NV12 排列而是带行 padding 对齐的变体这时候必须正确设置 wstride。其次是色彩空间标准NV12 可能是 BT.601 也可能是 BT.709如果 librga 版本支持色彩空间设置要手动指定不然转出来会偏色。第二个典型问题是 RgaBlit 返回错误码 -22 之类。常见原因有两个一是目标缓冲区尺寸小于 RGA 计算出的最小输出尺寸二是指定了不支持的格式组合。我的排查顺序是先用最简单的 RGB 到 RGB 缩放测试环境是否正常再逐步扩展到 YUV 输入。这样可以快速区分是基本通路问题还是格式组合问题。第三个典型问题是性能并不比 CPU 快。这种情况多发生在小图操作上比如做 100x100 的旋转RGA 的驱动开销和 DMA 延迟反而比 CPU 直接算更耗时。所以别盲目把一切 2D 操作都搬上 RGA小尺寸简单操作直接用 CPU 更划算。我建议的分界线大致在 200x200 以上或者操作步骤复杂缩放加转换加旋转时RGA 优势才明显。4.2 RGA 异常排查速查表给出一张实际排查表按现象、可能原因、处理方向来组织现象可能原因处理方向输出图像斜切/错位wstride/hstride 配置错误核对源 buffer 的 stride 信息按实际一行像素数填写颜色偏色/通道互换格式枚举填错重新确认 YUV/RGB 格式和字节序ARGB 和 ABGR 都要区分花屏且有叠加感源 buffer 不是连续内存或 cache 未同步使用 dma-buf fd 方式传递或者操作前做 cache flush返回 -22 或无效参数输入输出格式组合不支持或尺寸超限查看 librga 的 imStrError缩小格式组合测试范围大小图性能反而慢DMA 开销高于计算收益小图操作留在 CPU大图操作走 RGA请求卡死无返回内核驱动的等待超时检查是否同时两个核操作了同一块内存区域避免竞争这张表是在 RK3588 多个项目里沉淀下来的每次遇到新问题我都会往里面补一行时间长了就是一份活的排错手册。4.3 与 VPU/NPU 配合的管线设计建议RK3588 平台上的 RGA 不是孤立存在的。典型的媒体处理流程是摄像头传感器通过 ISP 输出 NV12 帧VPU 负责 H.264/H.265 编解码NPU 负责模型推理而 RGA 负责它们之间琐碎但高频的图像变换。你可以把 RGA 理解成一个图像格式翻译官它不生产画面但所有需要格式和尺寸对齐的环节都会用到它。一个常见的设计误区是把 RGA 操作放在 CPU 数据拷贝之后。比如先 memcpy 一份 buffer再做 RgaBlit多了一步不需要的内存拷贝还多占用一次 DDR 带宽。更好的做法是直接传递内存句柄比如用 dma-buf 的 fd 传给 RgaBlit这样 RGA 可以直接访问到源数据省去 CPU 中转。在手写多线程处理时还要注意两个核同时对同一块 buffer 操作的风险。RGA2 和 RGA3 如果同时对一个输出区域写数据结果是不可预期的。我的建议是在软件层做缓冲区分区或者放到不同帧轮转避免共享写。VPU 和 RGA 的配合也值得多说一句VPU 解码出来的是压缩帧解码后的 YUV 帧通常分辨率较大比如 3840x2160。如果 NPU 需要输入 640x640 的 RGB那中间必有一道缩放加格式转换。这时候把 RGA 操作放在 VPU 输出之后、NPU 输入之前是一个很合理的处理链设计。有人担心 RGA 转换后的精度影响模型效果实测下来只要色彩空间设置正确RGA 的处理精度对 YOLO 这类目标检测网络的精度影响基本可以忽略。最后是个小建议每次拿到新 SDK 或者新板卡我会习惯性做一件事先跑一遍 SDK 里自带的 RGA 测试用例比如 rga_test 这类可执行文件把 RGA 能支持的格式、旋转模式、缩放范围都过一遍然后把输出结果打印存进工程文档。这样做的好处是后面遇到问题你可以很快确认是这个核本来就不支持该操作还是你的配置有问题省掉大量无意义的排查时间。再补充一个实用习惯在用 RgaBlit 之前尽量把源和目标 buffer 都固定到物理连续内存里。如果 malloc 普通用户态内存一定要在处理完成后、读取数据前做一次 cache 同步或者直接走 dma-buf 方式让 RGA 与 CPU 之间的 cache 一致性由框架保证。这一步通常能规避掉我在 4.1 节里提到的花屏和错位问题。RK3588 的 RGA 核拓扑看起来是一个硬件细节但实际决定的是你的软件架构怎么写、性能指标能不能达到。搞清楚每个核心的能力边界你的 RK3588 图像处理方案才算是真正站住了脚。如果后面你碰上了更具体的 RGA 问题欢迎顺着这套排查思路再往前走一步。