43 个版本 (10 个重大更新)
新 0.15.1 | 2024 年 8 月 20 日 |
---|---|
0.14.1 | 2024 年 7 月 27 日 |
0.10.0 | 2024 年 3 月 12 日 |
#33 在 图形 API 中
每月 822 次下载
320KB
8K SLoC
wgsl-bindgen
一个从 WGSL 着色器生成类型安全 Rust 绑定的实验性库,用于 wgpu。
wgsl_bindgen,由 naga-oil 驱动,是一个集成到您的 Rust 构建过程中的工具。它解析 WGSL 着色器并生成相应的 Rust 模块。这些模块包含与您的着色器匹配的类型定义和样板代码,通过在编译时捕捉不匹配来减少运行时错误的风险。
此工具简化了着色器导向的工作流程。当您修改您的 WGSL 着色器时,更改将自动反映在 Rust 代码中。这种即时反馈有助于早期捕捉错误,使得与着色器一起工作变得更加容易。
功能
通用
- 生成新的或类似于枚举的短构造函数,以便轻松创建生成的类型,特别是当与 bytemuck 一起使用时需要填充的类型。
- 更强的类型绑定组和绑定初始化
- 为非 wgpu 类型生成自己的绑定条目。这是一个正在开发中的功能,旨在针对其他非 wgpu 框架。
着色器处理
-
支持导入语法和 naga-oil 风格的许多其他功能。
-
当使用
WgslShaderSourceType::UseComposerEmbed
或WgslShaderSourceType::UseComposerWithPath
输出类型时,动态添加着色器定义。可以使用
WgslShaderSourceType::UseComposerWithPath
进行热重载。 -
着色器注册实用工具,根据变体动态调用
create_shader
变体。这在尝试保留着色器模块的缓存时很有用。同时,请记得添加着色器定义以适应不同着色器模块的排列组合。 -
在定义工作流程时,可以添加额外的扫描目录以用于着色器导入。
类型处理
- BYO - Bring Your Own Types for Wgsl 矩阵和向量类型。Bindgen 会自动在编译时包含断言以测试您类型的对齐和大小。
- 可以完全或部分覆盖来自您仓库的结构体类型生成的类型,这对于小型原始类型很有用。您还可以使用此功能来克服 wgsl 中统一缓冲区类型限制。
- 用于顶点、存储和统一缓冲区的 Rust 结构体。
- 可以使用 encase 或 bytemuck derive,以及可选的 serde 对生成的结构体。
- 使用 bytemuck 时,对提供的向量、矩阵类型和生成的结构体进行常量验证 WGSL 内存布局。
- 覆盖结构体生成的对齐。这也影响了结构体生成的大小。
用法
当为类似 bytemuck、serde 或 encase 的仓库启用 derive 时,这些依赖项也应添加到 Cargo.toml
中,并使用适当的 derive 功能。请参阅提供的 示例项目 以了解基本用法。
[dependencies]
bytemuck = "..."
include_file_path = "..."
[build-dependencies]
wgsl_bindgen = "..."
然后,在您的 build.rs 中
use wgsl_bindgen::WgslBindgenOptionBuilder;
fn main() {
let bindgen = WgslBindgenOptionBuilder::default()
.workspace_root("shaders")
.add_entry_point("shaders/pbr.wgsl")
.add_entry_point("shaders/pfx.wgsl")
.output("src/shader.rs")
.build()
.unwrap();
bindgen.generate().unwrap();
}
这将生成位于 src/pbr.wgsl
、src/pfx.wgsl
的 WGSL 着色器的 Rust 绑定,并将它们写入 src/shader.rs
。请参阅示例仓库了解如何使用生成的代码。使用 cargo run
运行示例。
Wgsl 导入解析
wgsl_bindgen 使用特定的策略来解决您的 WGSL 源代码中的导入路径。此过程由 ModulePathResolver::generate_possible_paths 函数处理。
考虑以下目录结构
/my_project
├── src
│ ├── shaders
│ │ ├── main.wgsl
│ │ ├── utils
│ │ │ ├── math.wgsl
│ ├── main.rs
├── Cargo.toml
以及 main.wgsl 中的以下导入语句
import utils::math;
以下是 wgsl_bindgen 解决导入路径的方式
- 函数首先检查导入模块名称 (
utils::math
) 是否以模块前缀开头。如果设置了模块前缀并且匹配,它将移除前缀并将导入模块名称的其余部分视为从入口源目录的相对路径,并将目录中的双分号::
转换为正斜杠/
。 - 如果导入模块名称不以模块前缀开头,它将整个导入模块名称视为从当前源文件目录的相对路径。在这种情况下,它将在与
main.wgsl
相同的目录中查找utils/math.wgsl
。 - 然后,该函数返回一组可能的导入路径。导入语句实际引用的文件是这组中第一个存在的文件。在这种情况下,它将成功找到并导入
src/shaders/utils/math.wgsl
。 - 如果没有,它将尝试的第二条可能的路径将是
src/shaders/utils.wgsl
,如果存在,将math
视为utils.wgsl
中的一个项目。
这种策略允许 wgsl_bindgen
处理各种导入语句格式和目录结构,为组织您的 WGSL 源文件提供灵活性。
内存布局
WGSL 结构的内存布局要求与 Rust 结构或标准布局算法(如 repr(C)
或 repr(packed)
)不同。匹配预期的布局以在 CPU 和 GPU 之间共享数据可能是繁琐且易出错的。wgsl_bindgen 提供了添加 encase 的 derives 的选项来处理运行时的填充和对齐,或者 bytemuck 来强制在编译时填充和对齐。
当使用 bytemuck derive 时,wgsl_bindgen 将使用 naga 的布局计算来添加 const 断言,以确保所有主机共享类型(统一和存储缓冲区的结构)的所有字段都符合 WGSL 期望的正确偏移量、大小和对齐方式。
绑定组
wgpu 使用组织到绑定组中的资源绑定来定义全局着色器资源,如纹理和缓冲区。着色器可以具有许多资源绑定,组织成最多 4 个绑定组。wgsl_bindgen 将以更类型安全的方式生成初始化和设置这些绑定组的类型和函数。在 WGSL 着色器中添加、删除或更改绑定组通常会导致编译错误,而不是在编译代码时更新创建或使用这些绑定组的代码时的运行时错误。
虽然可以使用 set_bind_groups
函数一次性设置所有绑定组,但建议根据更新频率将绑定组织到绑定组中。绑定组 0 将最少更改,如每帧资源,而绑定组 3 将最频繁更改,如每绘制资源。可以使用它们的 set(render_pass)
方法单独设置绑定组。这可以为具有许多绘制调用的场景提供轻微的性能提升。有关详细信息,请参阅 描述符表频率(DX12) 和 描述符集频率(Vulkan)。
以这种方式组织绑定组还可以帮助在应用程序代码中更好地组织渲染资源,而不是在每个对象中冗余地存储所有资源。可能只需存储一次 BindGroup0
,而 WgpuBindGroup3
可能需要存储场景中的每个网格。请注意,绑定组存储对其底层资源绑定的引用,因此如果只有统一或存储缓冲区的内容更改,则不需要重新创建绑定组。如果可能的话,避免在渲染过程中创建新的绑定组以获得最佳性能。
限制
- 可能需要禁用对不支持类型或功能的着色器运行此函数。如果任何新的或现有的 WGSL 语法不受支持,请提出问题。目标是仅生成使用 wgpu 与 WGSL 着色器一起使用所需的大部分繁琐且易出错的样板代码。
- 目前支持大多数但不是所有 WGSL 类型。
- 在 WGSL 中使用浮点类型(如
vec2<f32>
)作为顶点属性时,假定它们使用浮点输入而不是归一化属性(如 unorm 或 snorm 整数)。 - 所有纹理都假定可过滤,所有采样器都假定启用过滤。这可能导致兼容性问题。通常可以通过请求仅本机功能的TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES来解决。
- 在某些情况下,例如避免冗余的绑定组绑定或调整资源着色器阶段可见性,可以实现比生成的代码略好的性能。这应该通过在适当的地方使用一些手写的代码来解决。
与wgsl_to_wgpu分叉的差异。
- 支持WGSL导入语法以及来自naga油味的许多其他功能。
- 您只能选择用于序列化的bytemuck或encase。
- Bytemuck模式支持在Rust中作为泛型const数组使用Runtime-Sized-Array。
- Bytemuck模式正确地为mat3x3、vec3添加了填充,而原始版本会在编译断言中失败。(分叉主要出于使用bytemuck并在所有情况下确保其工作的原因,而不是拒绝某些类型。)
- 用户可以使用
quote
库提供自己的wgsl类型映射。 - 预计API表面将出现小的破坏性变化。
发布Crates
提供的示例项目将生成的绑定输出到src/
目录,用于文档目的。这种方法也适用于应用程序。发布的Crates应遵循Cargo Book中关于构建脚本的建议。
use miette::{IntoDiagnostic, Result};
use wgsl_bindgen::{WgslTypeSerializeStrategy, WgslBindgenOptionBuilder, GlamWgslTypeMap};
// src/build.rs
fn main() -> Result<()> {
WgslBindgenOptionBuilder::default()
.workspace_root("src/shader")
.add_entry_point("src/shader/testbed.wgsl")
.add_entry_point("src/shader/triangle.wgsl")
.serialization_strategy(WgslTypeSerializeStrategy::Bytemuck)
.type_map(GlamWgslTypeMap)
.derive_serde(false)
.output("src/shader.rs")
.build()?
.generate()
.into_diagnostic()
}
生成的代码需要包含在一个正常的源文件中。这包括根据需要添加任何嵌套模块。
// src/lib.rs
mod shader;
依赖项
~11–21MB
~318K SLoC