ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3588 RGA核拓扑详解:RGA2与RGA3能力差异及开发实践

RK3588 RGA核拓扑详解:RGA2与RGA3能力差异及开发实践 做 RK3588 图像处理久了你会发现这颗 SoC 里的 RGA 并不是一个简单的 2D 加速引擎而是一组能力完全不同的硬件加速核心集合。很多第一次接触 RK3588 的朋友在设备树里看到rga2、rga3_core0、rga3_core1这几个节点时都会愣一下不是只有一个 RGA 吗怎么冒出来三个这正是 RK3588 的核拓扑设计与前代最明显的区别——三个执行单元、两代架构、各管一摊。这篇就专门把 RK3588 上 RGA 的核拓扑和能力差异讲透为什么这样设计、每个核能做什么不能做什么以及这些差异会怎样影响你实际的开发路径比如 YOLOv8 这些模型的前处理、MIPI 屏幕适配、视频解码后的格式转换。1. 先说结论RK3588 的 RGA 不是一个核1.1 三核拓扑从哪来RK3588 内部集成了 1 个 RGA2 核心和 2 个 RGA3 核心core0、core1。RGA2 是延续前代架构的全功能 2D 引擎负责那些复杂的图像变换、旋转、混合、颜色空间转换RGA3 则是 Rockchip 后来主推的轻量型加速核心目标非常明确在尽可能低的延迟下完成大量图像数据的搬运、拷贝和格式转换。为什么把 RGA3 做成双核因为 RGA3 的指令集和功能集设计得相对“窄”窄意味着内部逻辑简单、面积小、功耗低也意味着可以更激进地堆吞吐。在同样的芯片面积预算里塞进去两个窄而快的核心比塞一个又宽又慢的核心划算得多。两个 core 可以各自处理独立的 buffer也能在同一帧的不同区域并行干活在视频帧处理、显示合成这类高并发场景里直接用双核就摊开了带宽压力。RGA2 保留下来也不是因为它比 RGA3 快恰恰相反在纯搬运场景里 RGA3 往往更快。RGA2 的核心价值在于“覆盖能力”——它能处理 RGA3 消化不了的那些复杂格式组合和变换需求。你可以把 RGA2 理解成一个通用工具箱把 RGA3 理解成两条专用流水线。专用流水线速度快但只处理固定套路一旦任务出了套路还得回到工具箱。1.2 设备树里怎么认在 RK3588 的 SDK 设备树里RGA 相关节点的命名和 compatible 一般是这样的rga2ff680000 { compatible rockchip,rga2; reg 0x0 0xff680000 0x0 0x1000; interrupts GIC_SPI 159 IRQ_TYPE_LEVEL_HIGH; clocks cru ACLK_RGA2, cru HCLK_RGA2; clock-names aclk, hclk; ... }; rga3_core0ff6c0000 { compatible rockchip,rga3-core0; reg 0x0 0xff6c0000 0x0 0x1000; interrupts GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH; ... }; rga3_core1ff6e0000 { compatible rockchip,rga3-core1; reg 0x0 0xff6e0000 0x0 0x1000; interrupts GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH; ... };具体 reg 地址和中断号在不同批次 SDK 里可能会变你不用死记硬背真正重要的是那三个 compatiblerockchip,rga2、rockchip,rga3-core0、rockchip,rga3-core1。驱动和 librga 都是靠这几个字符串来识别核心的。你在自己的代码里如果要识别当前平台上有几个 RGA 核、分别是什么最可靠的方式就是遍历设备树或读取 /dev 下的设备而不是自己硬编码地址。有一点要提醒不同 SDK 的节点名可能不一样比如有的叫rga3有的叫rga3_core0但 compatible 基本保持稳定。所以排查问题或者写通用适配代码时一定以 compatible 为准不要用节点名做匹配。1.3 核间关系调度与协同这三个核在硬件上是并列的没有主从关系也没有固定的“必须谁先谁后”的流水线顺序。它们共用同一个 RGA 驱动框架但用户态大多数时候只看到一个/dev/rga设备节点。真正决定一个任务落到哪个核上执行的是 librga 或者内核驱动里的调度逻辑。调度策略大致是这样驱动拿到一个任务先检查参数是否满足 RGA3 的能力范围。如果满足优先交给 RGA3 处理如果不满足再降级到 RGA2。这个策略的设计逻辑很直接——RGA3 快、省电、并发能力强优先使用它才能把 RGA2 的算力留给更复杂的任务。不过这并不代表调度一定是完美的。实际开发中如果你对某条链路的性能有极致要求完全可以在用户态干预选核让简单转换任务固定走 RGA3复杂合成任务固定走 RGA2绕开自动调度的不确定性。2. RGA2 和 RGA3 的能力差异一张表讲清楚2.1 RGA2功能最全的“多边形战士”RGA2 支持的格式非常广。RGB 家族里常见的 RGB565、RGB888、ARGB8888、RGBA8888、XRGB8888 基本都能处理YUV 家族里 NV12、NV21、NV16、NV61 以及 I420、YV12 这类 planar 格式也在覆盖范围内。变换方面RGA2 支持 0、90、180、270 度旋转支持水平垂直镜像支持任意比例缩放还支持 alpha 混合、全局和局部 alpha、颜色填充、ROP 光栅操作和颜色空间转换。这意味着你需要做一个“带旋转 缩放 格式转换 混合”的复合操作时RGA2 可以一把梭把源 buffer 直接处理成目标 buffer。这也是它作为“兜底核”的底气所在。但 RGA2 的弱点也很明显因为内部处理逻辑复杂同样的纯格式转换任务它的带宽利用率和延迟通常不如 RGA3。拿 1080p 的 NV12 转 RGB 来说如果走 RGA2一次调用耗时可能在小几百微秒到 1 毫秒这个量级还受时钟频率和内存带宽影响。这个表现并不是不能用但如果你要做 4K 多路并发RGA2 就会成为整条链路的瓶颈。2.2 RGA3为速度和带宽而生的“搬运工”RGA3 的设计目标非常纯粹以最快的速度把数据从 A 点搬到 B 点顺带做一次格式转换或者缩放。它支持的格式相对少以 RGB 为主部分型号也支持 NV12/NV16 这类半平面 YUV 格式。旋转能力通常只覆盖 0 度和 180 度这类基础姿态90 度和 270 度往往不支持或者支持得不好。混合、ROP、复杂 CSC 这些高级功能就更别指望了。当然能力少不代表没用。RGA3 的强项在于“单线程深度优化”——它的内部数据通路就是为快速 blit 设计的处理 RGB565/RGB888 拷贝、NV12 拷贝、RGB 缩放这一类任务时吞吐量非常可观。实测下来同样一个 1080p RGB565 的纯拷贝任务RGA3 相比 RGA2 往往能快 30% 到 50%。这个数字在不同驱动版本下会有浮动但趋势是稳定的凡是“搬运 简单变换”这种活儿RGA3 是首选。还有一个容易被忽略的点RGA3 有两个核意味着你可以把两路独立的“搬运任务”同时塞进去。比如一路做摄像头 1080p 帧的 NV12 转 RGB另一路做 UI 图层的 RGB 缩放两个任务互不干扰效果就像有两个独立的 DMA 通道在并行干活。2.3 差异速查表对比维度RGA2RGA3core0/core1定位全功能 2D 图形加速引擎轻量快速搬运 / 格式转换引擎核心数量1 个2 个主要格式RGB 家族 YUV 家族 部分压缩格式RGB 为主支持部分半平面 YUV旋转能力0 / 90 / 180 / 270含镜像通常仅 0 / 180 等基础姿态缩放能力全场景缩放支持缩放但组合能力有限Alpha 混合支持可做全局和局部 alpha一般不支持或不完整ROP / 填充支持完整 ROP 和填充填充可用ROP 覆盖少色彩空间转换支持可配置 CSC 矩阵有限支持典型场景UI 合成、竖屏旋转、复杂效果视频帧搬运、格式转换、AI 前处理并发吞吐单核压满时延较高双核并行纯搬运吞吐优势明显这张表只是经验层面的总结。不同 SDK 版本、不同批次芯片的固件实现可能对能力和格式支持做微调所以如果你在开发中遇到“文档说支持但实际调用返回错误”的情况不要跟驱动硬刚优先查 TRM 和内核里 RGA 驱动的能力位图。以实际枚举结果为准比靠记忆可靠得多。2.4 对性能的影响选错核的代价曾经我在 RK3588 上做一路 4K 视频帧格式转换最初直接用 librga 的默认接口任务能跑通但帧率一直上不去总感觉哪里被卡住了。后来在驱动里加了 trace 点发现这个简单任务被调度到了 RGA2 上而 RGA3 两个核全在空转。手动把任务指到 RGA3_core0 之后单次耗时几乎砍半整条流水线的帧率立刻提了上来。这个例子很能说明问题核拓扑的差异不是纸面上的规格区别而是直接影响你最终能跑多少路视频、多少路模型前处理的关键变量。如果你在上手阶段就把三个核的能力边界摸清楚后面做性能优化时至少能少走两天的弯路。3. 核拓扑如何影响你的 RK3588 开发3.1 librga 怎么选核能力枚举与自动回退新版本 librga 的 im2d API 对内部调度做了比较完整的封装。你写业务代码时大多数情况下只要提供源 buffer、目标 buffer、区域、格式和变换参数librga 会自动判断该用哪个核并且按“先 RGA3、再 RGA2都不行就报错”的顺序做回退。我在工程里的做法是每次调用前先用imcheck预检查一下当前参数能不能被 RGA 处理。预检查通过之后再调improcess或更上层的imresize、imcvtcolor这类函数。如果imcheck返回错误直接去核对格式组合别硬跑。硬跑的结果通常是驱动报EPERM或者EINVAL问题不会自己消失只会浪费你 Debug 的时间。一个简化的 im2d 调用流程长这样#include im2d.h #include rga.h rga_buffer_t src wrapbuffer_fd(src_fd, src_w, src_h, src_format); rga_buffer_t dst wrapbuffer_virtualaddr(dst_ptr, dst_w, dst_h, dst_format); im_rect srect {0, 0, src_w, src_h}; im_rect drect {0, 0, dst_w, dst_h}; if (imcheck(src, dst, srect, drect, {}) ! IM_STATUS_NOERROR) { // 根据错误码调整格式或变换参数 } IM_STATUS ret improcess(src, dst, srect, drect, {}); if (ret ! IM_STATUS_SUCCESS) { // 处理失败查日志和参数 }注意wrapbuffer_fd和wrapbuffer_virtualaddr的区别。fd 方式走 dma-buf驱动对缓存同步的处理更完整虚拟地址方式在某些 SDK 版本里容易踩 cache 同步的坑。能走 fd 就走 fd这是我在多个项目里踩过坑之后养成的习惯。3.2 典型场景一YOLOv8 这类模型的前处理最近 RK3588 部署 YOLOv8 的方案特别多整套流程里最容易被忽视的就是前处理。摄像头原始帧一般是 NV12模型输入要的是 RGB而且分辨率往往要缩放到 640x640 或 416x416。如果让 CPU 来做 resize 和颜色转换A76 大核会被吃掉不少算力NPU 那边还没开始跑模型CPU 先成了瓶颈。RGA 在这条链路里的作用就是“预加工”。把摄像头的 dma-buf fd 直接喂给 RGA让它一步完成“NV12 缩放 转 RGB”产出的 RGB buffer 再送去 NPU。选核上这种“格式转换 缩放”正是 RGA3 的甜点区优先交给 RGA3。有一个细节值得注意如果你要做 letterbox也就是在目标图四周补黑边不要指望一次调用同时完成“缩放 补边”。更稳妥的做法是先调一次填充操作把目标 buffer 铺成黑色再用 RGA3 把缩放后的图像拷贝到目标区域。分两步虽然多一次调用但逻辑清晰也不容易踩到核能力边界的坑。3.3 典型场景二MIPI 屏与 UI 合成做 RK3588 Linux 适配 MIPI 屏幕时DRM/KMS 负责最终的显示扫描输出但很多图层在进 CRTC 之前需要先经过 RGA 做旋转、格式转换和缩放。比如竖屏 UI 要旋转 90 度或者视频层 NV12 要转成 RGB 才能被显示通路正确处理。这种场景下RGA2 通常才是正确选择因为涉及完整的 90 度旋转 CSC 可能的 alpha 混合RGA3 的场景覆盖不了。如果你在调试中发现屏幕方向不对、颜色分量对调、或者 alpha 显示成纯黑先检查这条显示链路走的到底是 RGA2 还是 RGA3。很多显示问题归根结底不是驱动寄存器配错而是任务被分配到了不支持该操作的核上。3.4 典型场景三视频流水线的格式转换RK3588 的视频解码通路MPP/VPU输出一般是 NV12 这类格式但下游的推流、显示、NPU 各有各的格式偏好。解码后到消费端之间的这段“加工链”基本就是 RGA 的主场。你要么做 NV12 到 NV12 的分辨率缩放要么做 NV12 到 RGB 的格式转换要么是裁剪加缩放一起来。这种场景下我的分配策略是单纯的格式转换和缩放交给 RGA3 双核涉及水印叠加、多图层混合的合成操作交给 RGA2。这样分工之后三路 1080p 同时处理也就变成了常态而不是还要刻意去优化单次调用的延迟。3.5 多核并行如何把三个核用满很多人以为只要连续调用 librga三个核就会自动并行处理实际上不是这样。驱动内部有锁和队列如果你只是单线程顺序提交任务那么任务大概率还是串行执行的两个 RGA3 core 可能轮流空闲。想把核用满得主动一点把任务拆成两路独立 buffer分别交给两个线程提交或者用两个不同的 dma-buf fence 去触发。这样 core0 和 core1 才能同时跑起来。实际操作中还有一个容易被忽略的问题——带宽。三个核共享内存总线如果处理的数据量太大或者 buffer 分配在不同物理区域并发收益会明显小于 2 倍有时候甚至只有 10% 到 20% 的提升。所以开并发线程之前先确认内存分配在同一片 DDR 区域不然忙了半天性能还是卡在总线上。4. 实战踩坑记录与排查思路4.1 任务报错多半是核不支持你用的格式组合我见过最多的错误就是improcess返回IM_STATUS_FAILED然后内核日志里出现unsupported format或invalid argument。这类问题的根源通常是任务被调度到了不支持当前格式组合的核上。排查思路按顺序来先查你的格式枚举值是否和 TRM 里的 RGA3 支持列表一致再用imcheck预检查具体卡在哪个环节最后确认你传的是 dma-buf fd 而不是裸虚拟地址。如果是裸虚拟地址帧内容不对的概率很高因为 cache 同步没做好RGA 读到的是 CPU 尚未写回的数据。4.2 多核并发没有提速现象很典型开了两个线程分别提交两路 RGA 任务总耗时几乎没有变化。排查时先确认两路任务真的被分配到了不同核上驱动日志或者统计接口里能看出来。如果两个任务都堆在同一个核上排队那提速当然无从谈起。另一种情况是带宽饱和。RGA 是硬件单元但它也得从 DDR 读数据搬数据多了总线就堵了。这个时候再优化代码没意义想办法减少数据搬移量才是根治方案比如减少中间 buffer、合并多次小任务、或者用压缩格式直接在 RGA 内部传递。4.3 图像花屏 / 内容不对cache 同步RGA 写回来的 bufferCPU 读的时候发现数据是旧的或者花的这是我调试 RK3588 时踩过最多的坑。原因基本都是 cache 一致性问题。CPU 写了一段 buffer数据还留在 cache 里没回写 DDRRGA 从 DDR 拿到的就是旧数据反过来RGA 写完了CPU 的 cache 还缓存着旧内容读出来的也是花的。规避办法很明确RGA 要读写的 buffer优先走 dma-buf fd 传给驱动让驱动统一做 cache 的 flush 和 invalidate。非要走虚拟地址裸指针的话至少确保你的 librga 和内核驱动版本是配套的并且驱动实现里真的做了缓存同步。很多自己写的裸指针调用跑出来花屏最后查下来都是驱动没处理干净。4.4 如何定位 RGA 性能瓶颈定位 RGA 性能瓶颈我一般分三步。第一步统计单次 RGA 调用耗时多跑几次取中位数不要用一次的数据下结论。第二步用perf或者ftrace看驱动内部的执行耗时确认时间到底花在等待还是真正在执行。第三步对比不同格式组合在同一核上的耗时差异看是不是某个格式触发了降级路径。还有一个容易被忽略的问题RGA 任务的单次执行时间很短可能只有几十微秒到几毫秒但你每次调用 librga 的 API 都有帧开销。如果你处理的是小尺寸 buffer很可能真正执行只占一半时间另一半都花在 API 调用和参数校验上了。这种时候优化方向不是调 RGA 参数而是减少调用次数把多个操作合并到一次调用里。4.5 经验建议速查拿到新板子后第一件事跑能力枚举打印 RGA2、RGA3_core0、RGA3_core1 各自支持的格式和变换把结果存到工程文档里。默认信任 librga 的自动调度但性能敏感的路径一定要手动指定核别赌调度器每次都选对。设备树里不要随手禁用 RGA 节点。很多上层库和 librga 会遍历设备节点你禁掉一个核可能整个 RGA 通路都会报错。升级 SDK 之后重新跑一遍能力枚举。不同驱动版本对格式组合的支持可能会有调整旧结论不一定适用。多核并行前先确认内存分配位置和总线占用别一上来就开线程。线程开了性能没上去最后还得排查带宽。最后说一个我自己的习惯拿到一块新的 RK3588 板子我第一件事不是跑 hello world而是写一个小的能力枚举和压力测试程序把三个核各自支持什么格式、同样的任务各耗时多少全部打出来。这份结果会直接决定后面整个图像链路的选核策略也能让我在同事问“为什么这里这么慢”的时候直接拿数据说话而不是靠猜。RGA 的核拓扑不算复杂但确实值得你在动手前花半小时摸透。这半小时花下去后面省出来的排查时间可不止半天。
返回列表