在视频处理领域,CPU 与 GPU 之间的数据拷贝开销长期是性能瓶颈。FFmpeg 8.1 引入的 Vulkan Compute Shader 编解码方案,通过将整个编解码管线迁移至 GPU 端执行,实现了真正的全 GPU 驻留架构。这一设计不仅绕过了传统的内存拷贝延迟,还为专业级高分辨率视频处理提供了可扩展的并行计算能力。
传统混合编解码的失败教训
在 Vulkan Compute 方案之前,社区曾尝试混合编解码路径:将串行步骤保留在 CPU,将并行步骤卸载至 GPU。这种方案在理论上利用了 CPU 处理串行逻辑的优势和 GPU 处理并行计算的能力,但实际效果往往不尽如人意。dav1d 尝试将最后的滤波 Pass 卸载到 GPU,即使在移动设备上也未能获得性能提升;x264 添加的 OpenCL 支持同样因帧上传延迟而告吹。这些失败案例的核心原因在于:GPU 与系统内存之间的物理距离导致往返延迟往往抵消了并行计算带来的收益。
混合方案的失败揭示了一个关键洞察:要实现持续的性能优势,计算着色器编解码必须完全 GPU 化,即整个管线驻留在 GPU 内存中,无需任何 CPU 参与数据搬运。FFmpeg 的 Vulkan Compute 实现正是遵循这一原则,将解析、调度、帧处理等全部逻辑在 GPU 上完成。
全 GPU 驻留的架构设计
FFmpeg 的硬件加速架构建立在软件编解码器之上。头部解析、线程调度、帧分配、错误校正等逻辑仍在软件层处理,只有视频数据解码部分卸载到 GPU。这种设计结合了经过充分测试的软件逻辑与硬件加速能力,同时允许用户动态切换软件和硬件实现。更重要的是,通过调度多个独立帧并行解码,FFmpeg 能够充分利用 GPU 的并行能力。
在具体实现中,工作组(Workgroup)大小的选择至关重要。以 FFv1 的 Range Coder 为例,由于每个符号的每一位都需要独立的 8 位自适应值,编码或解码时需要从数千个值中随机查找 32 个连续值。实现采用了 32 的工作组大小,每个本地调用并行执行查找和自适应更新,而单个调用负责实际的编解码操作。这种设计将内存访问延迟隐藏于并行执行中。
对于 RGB 视频的可逆颜色变换(RCT),最初使用单独的着色器将结果编码到独立图像中。但对于极高分辨率图像,带宽开销抵消了并行优势。优化方案改为仅需要 2 行即可编解码,分配宽度乘以水平切片数再乘以 2 的图像数量,在编码每行前使用 32 个辅助调用并行执行 RCT。
已实现与在研的编解码器
FFmpeg 8.1 已实现四种编解码器的 Vulkan Compute 支持。FFv1 作为存档领域的标准编解码器,其软件实现因高分辨率 RGB 视频的巨大带宽需求和熵编码瓶颈而运行缓慢,通过 Compute Shader 实现了显著加速。ProRes 和 ProRes RAW 作为事实上的专业母版编解码器,ProRes 编码器通过着色器查找合适的量化器以满足帧的位预算。ProRes RAW 采用两 Pass 解码方式:第一着色器解码每个瓦片,第二着色器使用行并行配置转换每个瓦片内的所有块。
DPX 作为电影扫描仪常用的无压缩像素打包容器,其解析核心在于在着色器中编写启发式规则来识别不同供应商的特殊实现方式。VC-2、JPEG 和 APV 的编解码器仍在开发中。
并行化策略的技术细节
JPEG 的并行化提供了一个独特的攻击向量:尽管 VLC 流缺乏并行化方法,但 VLC 解码器实际上可以伪同步。经过简短延迟后,VLC 解码器倾向于输出有效数据。实现方案是运行 4 个着色器逐步同步每个 JPEG 流的起始点。DC 预测可通过并行前缀和实现,这是 Compute Shader 最常见的操作之一。DCT 则使用与其他编解码器相同的 shred 配置。
APV 从设计之初就考虑了并行性。每个帧被细分为组件,每个组件细分为瓦片,每个瓦片包含多个块。每个块简单变换后通过标量量化器量化,再通过可变长度编码。实现时,一个着色器处理每个瓦片的解码,另一个着色器将每个调用转换单个块的行。
实践参数与监控要点
在实际部署中,工作组大小应与目标 GPU 的子组大小对齐,通常为 32 或 64。对于需要频繁内存查找的熵编码操作,建议使用本地共享内存缓存热点数据。监控方面应关注 GPU 利用率、内存带宽占用和着色器执行时间。Vulkan 提供了全面的调试和优化工具生态系统,全球开发者社区已积累了大量的优化经验。
Vulkan Compute 的优势不仅在于图形计算,更在于其对现代 GPU 能力的完整利用:指针、子组操作、共享内存别名、原生位操作、明确定义的内存模型、着色器特化、64 位寻址以及矩阵单元的直接访问。这些特性使程序员能够在比抽象 API 更低的层次进行优化,同时保持跨平台的兼容性。
资料来源:Khronos 官方博客《Video Encoding and Decoding with Vulkan Compute Shaders in FFmpeg》