RenderingDevice重构;基于DAG的渲染调度系统;微软平台上的原生DX12渲染后端;苹果平台上的原生Metal渲染后端。新的全局动态光照系统HDDAGI。
2024.02.26:细化DAG的分析,更新Metal后端现状。
概况
Godot的 2D 渲染管线比较成熟,有不少成功应用案例,广受好评;3D管线则一直处于能用但不好用的状态。近期Godot背后的主力贡献者成立了商业公司 W4,手上资源宽裕不少,有迹象对 3D 渲染管线进行大修。
W4公司雇佣了 Darío Banini 来牵头渲染系统重构,在2023年底合并了 DirectX 12渲染后端。这一系列 PR 首先将渲染后端 RenderingDevice 拆分成两层:API 无关的 RenderingDevice 和 API 相关的 RenderingDeviceDriver,然后分别实现基于Vulkan,DX12,Metal等API的版本。
当前支持 Vulkan 和 DirectX 12的版本已经合并;Metal 和 WebGPU 版本已经有贡献者表示在开发,其中Metal版已经提交等待审核。另外W4公司还会开发支持各大主机平台,如 Switch、PS、Xbox 的版本,作为收费服务提供给有需要的用户。
这次重构不但为全平台支持铺平了道路,同时也让API无关的改进能惠及所有平台。最近我们就看到这样的一个例子。
基于DAG的渲染调度系统
Darío Banini 提交的PR:基于 DAG 的渲染调度系统最近已经合并,这个 PR 意义同样重大,不但提高渲染系统的效率,还能够简化代码,降低后续开发难度。
DAG 是“有向无环图”(Directed Acyclic Graph)的缩写,广泛应用于任务调度系统。刨除各种细节,渲染管线本质上正是一个任务调度系统,而这个 PR 的目的就是用 DAG 来优化渲染任务的调度。
为了渲染某一帧游戏画面渲染系统需要提交大量渲染任务,每一个任务可以抽象的分为三步:从资源读取输入、处理、输出写入资源。这里资源一般指Buffer或Texture,本质上都是一块显存。为了保证渲染结果正确,所有渲染任务对资源的使用必须同步,避免多个任务同时读写同一个资源。
实现同步最简单的方法是所有任务顺序执行,前一个任务完成后开始下一个任务。这种方式保证绝对不出同步错误,但浪费了显卡的并行处理能力,效率非常地下。因此这种顺序执行只在调试阶段使用,部署阶段要尽量并行化。
任务并行化执行时必须想办法追踪任务之间的依赖关系。当B任务的输入是A任务的输出时,B依赖于A,必须在A之后执行。存在依赖关系的任务顺序执行,不存在依赖关系的任务并行执行。这样即保证正确性,有最大化发挥显卡的性能。
之前 Godot 的 3D 渲染系统是靠程序员手工对渲染任务排序,这种方式不但繁琐、易出错,而且让代码臃肿,难以修改。DAG 系统的思路是自动化管理依赖关系。提交渲染任务时程序员只需要标明该任务的管线、所需的输入输出资源。DAG 将每个任务存储为图中的一个节点;然后会标记所用资源,同时检查这些资源是否有其他任务标记,如果有则两个任务间建立依赖关系。所有任务提交完成后得到任务之间的依赖关系图,然后通过拓扑排序算法找出任务的正确开工顺序。
采用 DAG 系统后不但简化了代码,解决了几个存在多年的渲染错误,而且渲染速度提高了 10% 左右,后续随着优化有望继续提高。更多细节参见作者本人的博文。
新的全局动态光照系统 HDDAGI
Godot 现在的动态全局光照系统称为 SDFGI。基本思路是用 SDF(Signed Distance Function) 近似描述场景,在近似场景上进行全局光照计算。新的系统基本思路是将近似场景描述替换为 HDDA(Hierarchical Digital Differential Analyzer),最大的好处是镜头位置改变时不需要重新生成近似场景,减少计算量。详情见审核中的PR,HDDA 的原理参见论文。