Home

2019Rust图形库之路(译) —— ggez 作者

2019 Rust 图形库之路

Rust 图形库指南

截至 2019 年 5 月。

介绍

因为,在Unofficial Rust Discord的游戏开发频道上,大家在谈论图形 API 的问题,哪个到了什么地步以及做什么的,人们相互争执和相互纠正,“雨水来得过于猛烈”,一切的一切都变得有些模糊,混乱。所以在这里,我准备 尝试 捋一捋记录。本文旨在为希望使用 Rust,编写图形东西(视频游戏,动画,炫酷的可视化等)的人,但又不知道他们应从哪里开始,提供些背景知识。

你为什么要相信我?因为我需要知道这些东西才能选择ggez的伙伴,所以最近几年我一直在关注事物的状态。而且因为我对这些东西真的很感兴趣; 而且因为我喜欢写作。就说,虽然我远不是专家,但更像是一个兴致勃勃的观察家。

但是,在深入研究 Rust 细节之前,让我们先了解一下图形 API 的奇妙世界……

到底事情是怎么的

没有人直接为 GPU 编写代码。硬件接口,指令集以及它们实际工作方式的细节受到制造商的严密保护,好吧,除了三个主要制造商中的两位 —— 现在开源了(大部分)驱动程序。因此,实际上只剩下 NVidia 是锁源的。GPU 硬件可以快速发展,而不必担心向后兼容性,但最近十年左右,看起来 GPU 和 CPU 相互通信的基本设计和折衷方法已经达到了更稳定的状态。Anyway,不管实际原因,操作系统的 GPU 驱动程序会与硬件进行所有对话,并为(可以是你写的)程序提供与之对话的 API。实际上,有很多这样的 API,您以前可能已经听说过其中一些,所以让我们来看看明星选手:

OpenGL

基本上,称得上是图形 API 界的 Javascript。OpenGL 最初是 SGI 在 1992 年提出的一个合理的好主意,从那时起,它便开始生长,融合,变异,发芽怪异的副枝等。历史累赘比 JS 还要糟糕,原因可以一直追溯到 90 年代初,那时专用 GPU 是唯一的至高科技,而 GPU 硬件从那之后却快速发展。那时,您要为 GPU 的每个函数调用都提供一个 vertex ,且像 per-face shading 这样的的事情是非常重要。当前版本为 4.6,低于 3.2 的版本均不值得使用。

OpenGL 是由工业组织 Khronos 开发的开放标准,并且在 Windows,Linux 和 Mac 上均受支持,此外,它的变体还可以在移动(OpenGL ES)和 Web 浏览器(WebGL)上运行。它有一百万个不同的版本,亿个扩展,并且在 3.2 版之前,没有办法选择要使用的哪个版本。大多数时候,您实际使用的版本是“GPU 驱动程序想给您的任何东西”,而 GPU 驱动程序的实现是 多为 awful(该文章已有几年历史了,现在情况已经好了一些)。它的 API 设计具有完整向后的兼容性,而这可是将近三十年的负重,其中充满了烦人的边缘情况和漏洞抽象,这些抽象在 1997 年当年完全有效,但命不长。这里有programming techniques that get around them(PDF 链接),但这仍然很复杂。总的来说,就像 Javascript 一样,它是所有主要平台支持的唯一 API,经过精心设计才能运作良好,并且短时间内不会有变化。

DirectX

与 OpenGL 类似,但由 Microsoft 拥有。他们设计了 API,仅在 Windows 和 XBox 上运行,并且我认为 GPU 供应商与 Microsoft 合作编写了驱动程序。坦白地说,我对此并不了解很多,但是据我所知,它与 OpenGL 很像,除了不向后兼容的能力。这意味着,旧功能可能实际上已被弃用,新版本可能进行了重大的重新设计,从而改变了工作方式的基本要素,总的来说,它与时俱进。另外,由于它得到了 Microsoft 的支持,因此在制造游戏在其系统上运行良好这方面下了很大功夫,因此驱动程序通常不会出现故障,而工具则更好。如果游戏仅 Windows 玩耍,那么它将在 DirectX 上运行。9 之前的 DirectX 版本可能不再值得使用,而 9~11 版本在功能上与 OpenGL 4 大致相当。但是,最新版本的 DirectX 12 是新一代,更底层 API 的一部分,该 API 使程序员可以更好地控制 GPU 执行和排列事物的细节。而我们也迎来了一个…

Vulkan

新的热点。Vulkan 由 OpenGL 所在的行业组织开发,于 2016 年首次发布,当前版本为 1.1。如果 OpenGL 是 GPU Javascript,那 Vulkan 就是 GPU C。底层级,更广泛的用途,和(潜在的)比 OpenGL 容易编写快速代码。当然,多数时候这也可能不是你想直接地使用的东西,因为它真的非常具体且冗长。它不是一个图形 API,而是一个与 GPU 对话的接口;而实际的图形 API 是您使用 Vulkan,来创建的。

我们为什么需要这个东东?我的理解 is not the best,但我将尝试总结一下:OpenGL 中的许多东西(可能还有 DirectX pre-DX12)涉及到 GPU 驱动程序中的 state changes(状态改变),比如告诉它“切换到这个混合模式”或“加载这个 shader”。这些事情只会影响到一个又大又长的多步骤渲染过程的一部分,但是如果你不小心就会发生这种问题:GPU 同时忙于处理依赖该部分状态的一些事情(而你,改变了这个状态)。每当发生重大变化时,GPU 必须等待 1000 多个线程完成它们的工作、同步、等待输入,以及然后告诉它们新的操作模式是什么,并重新使它们走上快乐流程。而这(问题)会导致大量的死亡时间,其中大多数 GPU 都无所事事。OpenGL 意外改变一个本不需要影响其他东西,但却实际改变了的设置,结果就是,使得创建这些死亡时间变得非常容易。另外,由于它是如此 stateful(富有状态),所以 GPU 驱动程序很难提供帮助。驱动程序的工作会试图将 state changes 合并成块,有效地组织它们,并创建大量命令供 GPU 一次完成所有操作,但此时,驱动程序不知道接下来你要告诉它做什么,所以很多都是灵光一闪、猜测和权衡。与 JIT-编译 语言一样,没有明显的原因,它就能简单用一个相对无害的更改,将你扔出“fast path”。

而 Vulkan 根本就不会这么做。使用 Vulkan,你可以创建一个巨大的树结构,它的各个部分之间有很多非常明确的设置和关联,准确点来说:如何运行一系列的处理步骤以及如何处理结果,加载上 vertex 数据等的巨大数组,把它放到一个大箱子里,然后把它送到 GPU。然后第二天(就计算机世界的时间),GPU 会给你的前门送上一个漂亮的、闪亮的渲染图形帧,按响你的门铃,然后继续下一个帧的工作,因为它已经拿到它的工单。由于 GPU 一次基本上获得了绘制一个帧所需的所有信息,所以,所有内容都被显式地指定了,如果程序员若是说“这一帧快,而那一帧慢,两者之间改变了什么?” —— 而“事实上,他们就可以知道发生了什么变化。此外,GPU 驱动程序有一个更简单的流程,因为它需要猜测您打算做什么的工作更少了;它有了更多的信息,可以尝试调度命令,用于 GPU 的实际执行。除了让你的程序运行得更快之外,这还将引出更快、更高效(希望更少的 bug)的 GPU 驱动程序。

Metal

基本上可以说是 DirectX 12,但由苹果公司制造。它(连同 AMD 现在已经被弃用的 Mantle 原型)实际上早于 Vulkan 和 DirectX 12,可以说是开启了新一代的底层图形系统。像 DirectX 12 我对它了解不多;它是一个类似于 Vulkan 的底层 API,那些尝试过它的人说它非常适合使用。不过,苹果过去所有的图形都是通过 OpenGL 完成的。他们的工作鼓舞了 Vulkan,这样的话,他们当然可以在它的发展指导上,大有作为。但是,在其他人都已经加盟 Vulkan 之后,苹果宣布他们将不再使用 Vulkan,他们制造了一种大家都应该使用的,很酷的新东西叫做 Metal,噢顺便说一下,无论怎样,他们会在某个时候停止支持 OpenGL,如此除了他们之外,没有人可以为他们的硬件编写图形驱动程序。所以,我不在乎 Metal 有多好,它的唯一目的就是加强苹果的锁。

说真的。我愿意原谅 DirectX 的 亿点点恶作剧,但我们真的不需要更多的 proprietary crap 活在宇宙中。我想写一个游戏,它能在任何人的系统上运行,所以如果我可以避免的话,我永远不会使用 Metal。And I can。这让我们走向了…

真真真 Rust!

好吧,尽管上面列出了所有的不同点,但上面所有的东东都是痛苦的低层,按照一次一行“获取一个 vertices 数组和一堆配置数据,并生成一个渲染帧”的执行操作。如果你真的只想把模型推进去,选择一些材质设置,放置一些灯光,得到漂亮的渲染图形,你就需要使用一些更高层次的东西。“它“可能是Amethyst游戏引擎。可能kiss3dthree。也许你自己写。或者像一个理智的人一样使用 Godot 或 Unity。“但这不是我摇滚的方式,所以让我们继续滚吧”。

所有这些基本图形 API 都是 in, by and for(在,通过,和 为) C 编写的,在某些地方可能有一些 C++,但它们通常作为 C 库被公开。而使用 Rust 中的 raw C API 是相当痛苦的,所以人们为它们编写了 Rust 包装器,来让生活变得更美好、更安全。但是还是那句,这些仍是相当底层的系统,并且要做到 C API 与 Rust 结合时的绝对安全性,既昂贵又困难,而且it is not always worth the trouble。也就是说,即使是 Unsafe Rust 仍然比 C 要酷得多,所以我们还是希望使用更好的 Rust 绑定库来编写我们的图形代码。

有许多用于图形的 Rust 库,具有安全性和合理性的不同级别。包装 OpenGL 的是gliumluminance,包装 Vulkan 的是vulkano。还有一些底层包装,只是呈现了一个不错的 Rust-y,但为相同系统的不安全版本:winapid3d12-rs for DirectX,metal-rs对于 Metal,gl-rs for OpenGL,以及ash for Vulkan。这样的话,如您所见,似乎有更多的人在底层库之上,制作更高级别的跨平台 API 箱子,以及一个更酷的名称。这很可能是为了让我们这些 Rustaceans 可以用整洁的功能 play around,和看看我们可以制作一个到底有多安全的 API,以至于你不得不说“这些完全独立的程序恰好共享一大块内存,如果程序 A 在其中写入一个 float,那么程序 B 将仅会尝试从同一个位置读取一个 float,绝对可靠”。假设,有那么一群人,只编写在单一平台上运行的程序,而他们实际上都忙于完成有用的工作,而没功夫担心那些高大上的高级抽象,这样的话,下面让我们揭开那些不仅仅是简单包装的库的面纱。

但是,如果仅仅以一个底层 C API 编写成的 Rust 程序,对您来说还不够好呢?那这时,gfx来到你身边,它打算成为一个全能图形库,可以使用任何图形 API 作为图形后端。你可以拿上一个程序,在 Windows 上编译它,那它将使用 DirectX,然后在 Mac 上编译相同的程序,那它将使用 Metal。且它还很底层,所以人们很容易在上面编写更高级别的工具,并且工作迅速。 嗯,如是说,运行时性能会很快;编译它可能需要一段时间…

不管怎样!gfx有点复杂的历史。它是开源的,但它背后的许多推动力来自 Mozilla。它最初是一个研究项目,在大部分的目标后端上都做得非常得当,这本身就不是一个小的壮举,它的目的是作为 Mozilla 实验性Servo浏览器引擎的后端。但是这个系统一直在发展,最终开发人员清楚地意识到必须做出选择:它必须变得不安全,或者必须变得更慢,强制运行时安全。开发人员认为,在现有的不安全 API 上创建一个安全但速度较慢的包装器,要比反过来做容易得多,因此决定放弃纯粹的内存安全性。事实上,他们基本上重组了所有的东西并重新命名gfx-hal,全称“图形硬件抽象层(Graphics Hardware Abstraction Layer)”,采用了与 Vulkan 97% 相同的 API。剩下的 3% 左右是一些额外的摆动空间,来处理任何需要的边缘情况,如使它与 DirectX12 和 Metal 配合更灵活一些,和那些不合适的部分。但总的来说,gfx-hal基本上和使用 Vulkan 是一样的。

这一过程来之不易,但就在 2018 年圣诞节之后gfx-hal0.1 已发布。而且…很管用!令人惊讶。当然还没有完成,但它瞄准了一个已知的目标,而且基本上来说,是击中了。Vulkan 后端工作得很好(应该如此),它在 DirectX12 和 Metal 上也能正常工作。不过,让它在 OpenGL 和 DirectX pre-12 运行良好,仍然是一项正在进行的工作。搞定那些个后端真的不容易,因为 gfx 可以说是,要在更高级别的 API 之上,实现一个底层 API,比如将 C 编译成 Javascript。我敢打赌,在更高级别的 API 中,使用后端可能总会有一些性能问题,但我也希望它们最终会变得非常好,而且尖端人才现在已加入 gfx 豪华大家桶。

所以,我们现在基本上有一个 Rust Vulkan 实现,可以在任何平台上运行,并且可以在将来添加更多。事实上,还有gfx-portability,它将纯净的Vulkan 作为提个 C API,并成为在其之上的一个"精瘦"层,类似于MoltenVK。同样,这还没有完全完成,但实际上在一些挺重要的用例中,工作的不错。当它更接近完成体,你就可以把使用了 Vulkan 的任何程序,接入gfx-portability,ok,运作在任何平台。

好了,你是 gfx 粉丝仔,那其他的呢

哦,对了,还有其他的箱子!实际上,它们都非常好。这不是一个详尽的清单,我只想列出最重要的事情。我也没有投入足够的时间和精力,像gfx一样使用他们,所以我的知识并没有那么深,但这里有:

glium

原安全 OpenGL 库,由尊敬的 Tomaka 编写。关于它死亡的传言被大大地夸大了,因为它现在是由它的用户社区维护的,尽管我承认它的维护很"温柔”。它的安全性在于它比纯 OpenGL 具有更高的级别,引入一些更高级别的抽象,并自动地将它们组合在一起,这样就不必手动,瞎处理状态变量了。尽管如此,它还是有一些众所周知的安全漏洞,而主要的维护者把所有的时间都花在了愚蠢的赚钱上(他怎么敢!),没有人做过研究,重新设计,以找出如何关闭漏洞。据我所知,98%的人可能不会注意到这些洞洞。而作为比较其他事物的基准,glium仍挺着。

luminance

另一个安全的 OpenGL 库,由 Phaazon 编写,基本上是一个人的项目。我承认我除了那里看看这里瞧瞧之外,实际上还没用过它,but that playing around was quite nice。对我来说,主要的卖点是作者:我使用了 Phaazon 编写的其他一些库,文档简洁但完整,功能集非常实用,他总是愿意帮忙解释工作原理,并听取更改或添加内容的建议。还有就是维护更频繁,比起glium某个做自己的东西的人来说。在抽象和安全级别上,它大约相当于glium。一个区别是glium打算支持全部的OpenGL 的版本,而luminance只打算支持 OpenGL 3.3+,这是一种更明智的方法。

ash

你是否通关了https://vulkan-tutorial.com/,并说“这很好,但我希望它是 Rust 的,加上好结构和Copy trait 在正确的地方,还有还有 enums 等等等?噢,这就是你想要的。这是 Vulkan API 的一个非常简单的包装起,Rust 化。这就是它的目的,这就是它所做的一切,最后它做得很好。虽然它非常不安全,但也不会妨碍您的工作;如果您可以使用纯 C 在 Vulkan 中,编写一些东西,那么在ash同样的事情可以直截了当。我已经通关了上面的 C++教程,并把所有的东西都用 Rust ash 写出来了。库是由一个 MaikKlein 维护的,一个我没交流乐趣的人,但是 Rust graphics/gamedev 社区中,各种各样的知名人士似乎一有机会,就时不时地插手,所以它基本上是 go-to Vulkan 绑定库。

同样,如果您想使用底层 Metal、OpenGL 或 DirectX11/DirectX12,并且只要那最基本的方便,那么metal-rs, gl-rswinapi/d3d12-rs会是你的去哪里。我所说的关于ash的一切,或多或少也适用。

vulkano

另一个特别的 Tomaka,在精神上类似于glium, vulkano是一种完全安全的 Vulkan 包装器。唉,它也有点被它最初的创造者抛弃了,但也许当他中彩票时他会回来,现在由 Rukai 来维护。我自己还没有使用过它,所以我只能说文档中的内容:“Vulkano 仍在大量开发中,还没有达到非常健壮的目标。不过,库的总体结构很可能是确定的,未来所有突破性的更改都可能直接在用户代码中修复。”

rendy

我在作弊,我们又回到了gfx-hal。编写原始的 Vulkan 代码是相当乏味和烦琐的,并且有相当数量的东西你必须为亲手写上。比如内存管理。所以,为什么不有一个好的助手库,可以处理一堆常见的和死记硬背的 bits 呢?完全控制 GPU 的功能是很好的,但是 300 行代码,仅仅是为了完成 95%的用户基本相同的基本设置时,很明显,此时应该大开方便 bit 门。方便即是rendy打算提供的:一套模块化的、相当基本的工具,使与gfx-hal工作生活更美好。您可以在图形中,创建 nodes 并告诉它们依赖是什么,而不是一次将渲染过程中的各个阶段细心地连接到一个数组索引。而从一堆渲染目标,“交通手势和胶水”,拼凑出一个交换链,只是,拜托,只是为了在屏幕上显示已经搞定的东西??,而rendy的方便帮你搞定那一堆。但它是非常模块化和无定向,当然你提起胆气向gfx-hal进发。而 rendy 更像是一个工具包而不是包装器。

它是由 Amethyst 游戏引擎(Viral, Frizi,和Fusha)后面的一些开发人员编写的,我正在认真考虑是否将其用于a future version of ggez。我真的很怀疑rendy一开始,部分原因是我觉得 Amethyst 有点像 (雾件 —— 宣布要上市但尚未设计生产的电脑软硬件产品) —— 它是如此超级雄心勃勃和创新,而这使它很难有循序渐进的过程,从中你能在设计错误中学习。但后来,我真的试过rendy,我不得不说,这正是我想要的。

未来在继续书写 (in Rust)

但是等等,还有…

尤其,是在 Vulkan 发布后,anal 图形编程人员(即我)非常放心,他们提供了一种不像 OpenGL 那样糟糕,编写图形代码的方法。事实上,在 Webassembly 发布之后,这种只有那些专门 anal 编程语言的呆子(还是我)感受到的放心受到了"敌袭",因为他们提供了一种不需要 Javascript,就能为 web 编写前端代码的方法。显然,我不是单着的,在 Vulkan 1.0 发布后不久,就有人开始问我这样的问题:“我们能不能有比 OpenGL 更好的东西,能在互联网上做 3D 图形?”?而当苹果、谷歌、微软、Mozilla 和英特尔的人开始问这样的问题时,不管是好是坏他们聚集在一起,并且造出了它,and so we get WebGPU

所以,第一件事是,截至 2019 年 5 月,WebGPU 是未完成品。这是多个供应商之间的巨大努力之一,因此在每个人都试图找到最佳解决方案时,有很多技术问题需要解决。再加上很多政治问题需要解决,因为每个人都试图压垮其他人,所以他们拥有技术优势和更多的供应商锁源(惊讶,这似乎主要是苹果做的)。第二,WebGPU必须完全安全。浏览器执行不受信任的代码,因此即使是一个相当底层的 API 的不好的事情发生,也要变为不可能。第三,它非常让人联想到 Vulkan,但实际上不是 Vulkan,更像是 Vulkan 的一个合理的子集,有着一堆剪下来的’毛发’功能。最后,有一个由gfx-rs社区制作与使用gfx-hal制图的 Rust 功能型原型,叫做wgpu

注意标准、WebGPU 和wgpu实现之间的区别。还有其他 WebGPU 的原型实现:Dawn, by Google和苹果的另一个原型可能不是开源的。

作为一个简单的,安全的,像 Vulkan-ish 一样的东西,wgpu实际上是一个相当有吸引力的库,还能用于在桌面上编写图形代码。事实上,为便于跨不同桌面平台的可移植性而设计,它正被一些相关团体,作为’标准’所推动。着已经有了,使用它的实验性的游戏框架 —— 事实上,一个真漂亮粒子演示,你应该百分百下载试试,即使框架本身是相当不成熟的。我个人抱有小小疑虑:现在确实是实验性的原型,制作实际东西,尽管 kvark(一个主要的wgpu(devs)向我保证,这不是真的,这是一件不会死的实事,而事情会变得 awesome,必然在某天完成。他在gfx-hal 2017 年末也是这么说的,老实说,他是对的,除了“某天完成”已过了大约一年半。:-P 我们拭目以待。

另一个有趣的可能性是相反的方向:会有什么让gfx-hal有一天不在 WebGPU 之上运行?Nothing,这就是什么。可能不是特别容易,但是gfx-hal已经做了很多不容易的事情,而且效果很好。事实上,Amethyst 计划获得了一个准许,让rendy在网络上工作得更好。而rendy作为一个gfx-hal的工具箱,至此他们的努力可能包括gfx-hal也在网上工作。所以gfx-hal在任何最终的 WebGPU API 上运行良好的机会真的非常大大。

所以,尽管所有的网络标准都很糟糕,但在 WebAssembly 和 WebGPU 之间,网络游戏的近期前景看起来非常非常有趣。

所以,我要用什么,让一个三角形图出现在屏幕上呢?

就像我说的,整篇文章都是为 crazy people 写的。如果你只想在 Blender 中建模,然后编写一个程序加载它,并显示在屏幕上,那你应该使用一个现有的游戏引擎,如 Godot 或 Unity。我们还没有 Rust 对标产物(尽管 Amethyst 已经到了那里),只有一大堆零件。把零件组装成漂亮的东西不是很难,但这仍然是一个需要一些肘部润滑脂和电动工具的项目,而不是宜家家具,你可以简单地拼凑在一起。目前,生态系统大致如下:

如果你想做你自己的 triangle-slinging,并希望它马上能在所有平台上工作,但不想编写 1200 行安装代码,请使用glium, luminance,或其他 OpenGL 绑定之一。

如果你不害怕让你的手入到渲染管道的五脏六腑,并希望它马上在所有平台上工作,使用gfx-hal。除非你真的决定亲力亲为,不然的话,推荐你使用rendy,将 1200 行设置代码减少到 400 行。

如果上面所说的适用,但你不想让所有的,这些gfx-hal奇特方式,和如果你只想让它在 Linux 和 Windows 上运行,那么使用ashvulkano。最坏的情况是,无论如何它都能在gfx-portability运行。不过,rendy不能用就是呐;是否有其他类似rendy的工具,让 Vulkan 变得更好,我(还)不知道。如果你找到了就告诉我!

如果你想要一个比 OpenGL 更酷、更具未来感,但又不像 Vulkan 那么惊悚的东西,(愿意下注)并且你想让它运行在任何东西上,包括某天来临的 web 原生的话,使用wgpu

附录: 游戏框架

我喜欢制作流程图,所以我会给你另一个我知道的各种 Rust 游戏引擎/框架箱子,以及它们在图形上的使用/能用于:

快速、有偏见的精简列表: