将开源 PowerVR Vulkan 驱动扩展至新一代 GPU 架构

作者:Ashish Chauhan

本文介绍了我们近期在 Mesa 开源 PowerVR Vulkan 驱动中开展的一项基础性工作——为驱动建立对“多 GPU 架构(Multi-Architecture,简称 Multi-Arch)”的支持框架。

需要说明的是,这项工作并不会立即让驱动支持新的 GPU 架构,但它已经搭建好了整体框架,使我们未来能够更加高效、规范地集成新一代硬件。按照目前的计划,我们预计将在明年率先支持 Volcanic GPU 架构。


什么是 Multi-Arch?

Multi-Arch(多架构支持),是指同一套 Vulkan 驱动代码库和同一个驱动软件包即可支持多个 PowerVR GPU 架构。

目前,Mesa 中的开源 PowerVR Vulkan 驱动仅支持 Rogue 架构。当驱动只面向一种 GPU 架构时,原本应当保持通用的代码,往往会逐渐混入大量架构特定的实现细节,并产生一些不易察觉的隐式依赖。

因此,我们此次工作的重点并不是增加某一款新 GPU 的支持,而是为未来更多 GPU 架构(例如 Volcanic)的加入奠定基础。借助这一新的架构设计,未来新增硬件时无需重写整个驱动,也无需在代码中到处添加架构分发逻辑(architecture-specific dispatch),更无需充斥大量类似 if (rogue) ... else ... 的条件判断,从而避免代码复杂度不断攀升。

我们的目标是将架构相关的实现集中管理,并显式地处理架构差异,使驱动能够在持续演进的过程中保持清晰的代码结构和良好的可维护性。

我们采用的是 Mesa PowerVR Multi-Arch 设计文档[^1] 中所定义的实现方案。

其核心思路是:一小部分源文件会针对每种 GPU 架构分别编译一次。在 PowerVR 驱动中,这些文件主要是 pvr_arch_*.c。

这些源文件中定义了一系列以 pvr_arch_ 为前缀的函数,其中 arch 并不是固定名称,而是一个占位符,会通过对应头文件中的宏定义进行展开,并由 pvr_macros.h 中提供的宏机制完成具体实现。

这样做的目的是,将这些函数作为架构相关(architecture-specific)的入口,再通过 Vulkan 通用的 Dispatch Table(分发表)路由调用。借助这种机制,驱动既能够调用架构专属代码,也能够调用架构无关(architecture-agnostic)的公共代码,同时保持两者职责清晰、彼此解耦,而不是将所有逻辑混杂在一起。

需要稍加注意的情况是,当架构无关的代码需要调用架构特定代码时。为此,我们使用 PVR_ARCH_DISPATCH 和 PVR_ARCH_RET。这些宏需要访问所有架构的特定定义,实现这一功能的模式是在每个需要使用分派宏的源文件中定义一个 PER_ARCH_FUNCS(arch) 宏,并针对每个架构实例化一次。


PowerVR特定的选择

我们的方法与Mesa中其他Vulkan驱动的路径类似,但有一些PowerVR特定的选择。例如,架构使用符号命名而非数字命名,因为不存在一个能清晰跨越几代产品的数字分割方式。我们还使用PVR_PER_ARCH()风格的宏作为连接机制的一部分。为了减少基础设施落地过程中的代码变动,许多函数被引入为PVR_PER_ARCH(foo),并临时通过#define pvr_foo PVR_PER_ARCH(foo)进行别名定义,这样调用点就不必立即更改。长期方向是转向“代码块风格”的宏定义,以便在不过度增加函数调用噪音的情况下,清晰地看出哪些是逐架构的。


搭建基本结构

在驱动能够合理地实现多架构之前,我们首先分离出那些不能是架构特定的部分。pvr_device的大部分内容是架构特定的,但物理设备和实例不能是架构特定的,因此我们将pvr_instance和pvr_physical_device从pvr_device.c中拆分出来,放入它们自己的模块。我们还将一些架构特定的细节从头文件移到了源文件中,这样驱动中需要以硬件特定方式构建的部分就更少了,这项工作包括移除了对广泛包含的pvr_private.h的需求。

另一项促成性改动是将架构信息存储在device-info中。目前,所有支持的设备都是Rogue,但一旦我们添加更多架构,就需要在运行时知道正在处理的是哪种架构。这将使我们能够干净地选择逐架构的表和逐架构的操作,并避免将架构知识推入到不应该需要它的地方。


设置硬件定义

基本结构就绪后,下一个重点是减少共享代码中意外产生的对Rogue的依赖。大量的准备工作在于严格管理硬件定义,因此硬件定义的头文件包含和辅助函数被移到了它们真正需要的地方(例如,移到源文件的末尾)。这减少了共享代码在不知不觉中与Rogue绑定的可能性。我们目前还没有完美的方法来处理跨架构共用的硬件定义。因此,在少数几个地方,代码有意定义了PVR_BUILD_ARCH_ROGUE,并包含了需要定义架构的头文件。

在清理整个代码库中硬件定义的同时,我们重新组织了硬件定义文件的布局,这样将来添加新GPU时,就不会因为所有文件都堆在单个目录下而变得笨拙。这项工作移除了rogue_hwdefs.h,改为在pvr_csb.h中包含独立的文件,目的是使为每个GPU架构单独设立目录变得更容易。


处理架构特定的数据

一旦启用了不止一种架构,另一个实际问题很快显现:某些架构特定的数据被存放在并非架构特定的结构体中。我们针对边界表(border table)和清除状态(clear state)解决了这个问题。在这两种情况下,问题是一样的:数据是架构特定的,大小随架构变化,且不能以一种固定的方式直接存放在共享结构体中。解决方案是改为存储指向架构特定数据的指针,这样共享的设备对象保持共享,而逐架构的数据可以变化。

格式表(format-table)的工作是另一个多架构的准备步骤。我们对pvr_formats.[ch]进行了重构,以便可以在不需要硬件细节的情况下查询和推理格式。PBE细节从主表中拆分出来,因为它们并非对所有GPU都相关。绑定支持从单个支持位改为每个绑定标志(顶点缓冲、采样器视图、渲染目标、深度模板、存储图像)。像压缩的PBE格式这类总是无效的条目则不予存储。添加了辅助函数,用于基于device-info查询限制。


优化winsys

在这些部分就位后,我们简化了winsys侧,它抽象了所使用的内核驱动,这样多架构就不需要在驱动中到处使用派发逻辑。在powervr winsys中,PVR_ARCH_DISPATCH的使用主要是为了获取KMD_STREAM_HDR的内核流定义。在这里使用逐架构基础设施收益不大,因为代码在任务上下文初始化期间运行,且头部本身主要是一个简单字段加上填充,而真正的固件接口内容已经是展开编码的。为了解决这个问题,我们移除了该路径中的派发,并且winsys不再依赖于内核流定义。

对于pvrsrvkm winsys,情况有所不同,因为内核驱动并未对相同的硬件细节进行抽象。我们没有试图完全移除架构知识,而是提供了专门的逐架构pvr_winsys_ops结构体来处理每个架构。这将派发从各个winsys函数中移出,转为按架构选择正确的操作结构体,这降低了复杂性,并使得在添加更多架构时winsys更易于维护。


为Volcanic GPU添加新定义

在新硬件方面,我们为Volcanic添加了初始的硬件定义集,这些定义或多或少与Rogue已有的定义直接对应。这项工作尚不足以驱动GPU,但它是前进的一步。一些代码路径也被限定为仅Rogue使用(如blitting和clearing),因为对于Volcanic,计划是使用vk_meta,所以提前花时间清理旧路径没有意义。

总体而言,这些改动的方向是一致的:将架构特定的部分保持在清晰的逐架构模型之后(pvr_arch_*.c、PVR_PER_ARCH以及派发或操作模式),限制硬件定义的使用范围以确保共享代码保持共享,并将架构特定的决策移至device-info、逐架构表和逐架构操作中。Volcanic的定义开始以有助于启动活动的方式加入,而winsys的变更减少了不必要的派发,同时仍然处理了内核接口需要架构感知设置的地方。


未来工作与致谢

基础工作已经就绪,下一步将在此基础之上,补充剩余的架构特定部分,并利用新框架来启动对Volcanic的支持。特别感谢Collabora的Erik Faye-Lund,他领导了整体设计并推动了在PowerVR Vulkan驱动中建立多架构结构的改动。我还要感谢支持团队的成员们在整项工作中的贡献、讨论和审阅。

支持性代码变更:


英文链接:https://blog.imaginationtech.com/scaling-the-open-source-powervr-vulkan-driver-to-new-gpu-architectures

声明:本文为原创文章,转载需注明作者、出处及原文链接。

最新文章