10个版本
0.4.0 | 2022年7月19日 |
---|---|
0.3.6 | 2021年7月11日 |
0.3.5 | 2021年2月21日 |
0.2.0 | 2021年2月15日 |
0.1.0 | 2021年1月18日 |
#322 在 图形API
每月27次下载
175KB
4.5K SLoC
Shades,Rust中的着色器EDSL
这个crate提供了一个EDSL来构建着色器,利用Rust编译器(rustc
)及其类型系统来确保正确性和类型。因为着色器是用Rust编写的,所以这个crate完全与语言无关:在理论上可以针对任何着色语言进行目标化 - 当前的第一级语言是GLSL。EDSL允许在运行时生成实际的着色代码的同时静态类型化着色器。
动机
在典型的图形库和引擎中,着色器是不透明的字符串 - 要么硬编码在程序中,要么在运行时从文件中读取,要么通过将字符串片段连接在一起构建,等等。这些字符串传递给图形驱动程序,驱动程序将在运行时编译和链接代码。检查错误并正确反应是运行时的责任(即图形库、引擎或应用程序)。着色语言也可以离线编译,然后它们的字节码在运行时使用(例如SPIR-V)。
对于很多人来说,几十年来这已经证明是可以接受的,甚至允许实时编码:因为着色代码是在运行时加载的,所以每次发生变化时都可以重新加载、重新编译和重新链接它。然而,这也带来了不可忽视的缺点
- 着色代码通常在运行时进行检查。在这种情况下,编写不良的着色器直到运行时执行,GPU驱动程序拒绝着色代码时,程序员才看不到。
- 离线编译为字节码时,需要额外的专业工具(如外部程序、语言扩展等)。
- 编写着色器意味着学习一门新的语言。最广泛使用的着色语言是GLSL,但其他语言也存在,这意味着人们将不得不学习特定的语言,并且大多数情况下,编译系统较弱。例如,GLSL没有内置的功能来包含其他GLSL文件,它是一种类似于C的旧语言。
- 尽管动态使用语言的吸引力可能看起来很有吸引力,但从一个动态语言过渡到静态使用并不容易。然而,反过来(从静态到动态)要简单得多。换句话说:借助低级系统原语,如
dlopen
、dlsym
等,可以实时重新加载编译后的语言。这需要更多的工作,但这是可能的。而且Rust也能做到这一点。
这个crate的作者(@phaazon)认为着色器代码仍然是代码,应该像代码一样对待。很容易看到实时编码/重新加载的力量,但更重要的是提供一个经过静态验证且错误较少的着色器代码,而没有静态检查的话则相反。此外,如上所述,使用编译方法并不会阻止编写可重定位的对象,编译后隔离并重新加载此对象,提供与实时编码大致相同的功能。
重要提示:这个crate 尽可能通过
rustc
在编译时捕获语义错误。然而,它可能仍然缺乏捕捉所有语义错误的机会。如果您发现这种情况,请随时提出问题,因为这被认为是错误/回归。
另一个重要点是选择使用EDSL。有些人可能会争辩说,Rust有其他有趣且强大的方式来实现相同的目标。重要的是要注意,这个crate不提供将Rust代码编译成着色语言的编译器。相反,它提供了一个在运行时生成着色器代码的Rust crate。其他替代方案包括使用proc-macro。有几个crate就是这样做的。
- 您可以使用glsl和[glsl-quasiquote] crates。第一个是一个GLSL解析器,第二个允许您在quasi-quoter中编写GLSL(
glsl! { /* 这里 */ }
)并在运行时编译和检查。尽管如此,它仍然是GLSL,而运行时组合的可能性远低于EDSL。 - 您可以使用rust-gpu项目。这是一个类似的项目,但他们使用proc-macro,将表示GPU代码的Rust代码编译。它需要一个特定的工具链,并且不在这个crate的同一级别——它甚至可以编译
core
库的大部分内容。
影响
- blaze-html,一个用于在Haskell中构建HTML的Haskell EDSL。
- selda,一个用于构建SQL查询并执行它们而不需要编写SQL字符串的Haskell 嵌入式领域特定语言。当前这个crate在方法上与之非常相似。
为什么你会喜欢这个
如果你喜欢类型系统、语言以及基本上的编译器黑客(为你的编译器编写代码以生成运行时代码!),那么你可能会喜欢这个crate。你将发现所有这些特性
- 使用纯Rust。因为这个crate是语言无关的,你开始所需了解的只是编写Rust。你不需要学习GLSL 来使用这个crate——尽管你仍然需要理解着色器概念,它们是什么,如何工作等。但现在,这些概念的编码已被一个本地的Rust crate封装。
- 用于表示着色类型的类型是基本的、原生的Rust类型,例如
bool
、f32
或[T; N]
。 - 编写更函数式的代码而不是命令式代码。例如,在这个crate中的顶点着色器基本上是一个接收类型为
Vertex
的对象并返回另一个对象(该对象将被传递到下一个阶段)的函数。 - 在
rustc
中捕捉语义错误。例如,将一个bool
赋值给你的着色器代码中的f32
将触发一个rustc
错误。 - 让一些代码变得无法编写。例如,你将无法在 顶点着色器 中使用仅在 片段着色器 上下文中有效的表达式,因为这不可能通过它们的定义来实现。
- 扩展和向著名的着色语言添加更多项目。例如,GLSL 没有提供
π
常量。这种情况得到了解决,所以你再也不必自己编写π
的十进制数了。 - 因为你编写的是Rust,你可以从所有语言类型糖果、可组合性、可扩展性和正确性中受益。
- 在功能门背后提供了一个实验性的单调体验。这允许你使用 do-notation crate 编写着色器,为你移除大量样板代码,使 作用域 和 着色器作用域 对你来说隐藏起来,让你感觉像是在编写魔法着色器代码。
为什么你不会喜欢这个
截至现在,这个crate仍然非常实验性。以下是一个你可能不喜欢这个crate的列表
- 当前的冗长性是不可接受的。大多数你需要使用的lambda都需要你对其参数进行注释,尽管这些参数显然是可以猜测的。这种情况应该尽快解决,但人们必须知道当前的情况意味着大量类型注解。
- 有些人会争论说编写GLSL 要快得多,也简单得多,他们是对的。然而,你首先需要学习GLSL;你将无法针对 SPIR-V;你没有静态类型问题的解决方案;等等。
- 如果你的着色器代码在运行时编译/链接失败,调试它可能会具有挑战性,因为所有标识符(除了少数例外)都是为你生成的。这将使理解生成的代码变得更困难。
- 一些概念,尤其是控制流语句,看起来有点奇怪。例如,使用这个crate的GLSL中的
for
循环以一种更加复杂的方式编写。生成的代码相同,但通过这个crate,代码更加冗长。
依赖项
约220KB