This is section is going to walk you through writing a shader in Rust and setting up your shader crate.
Be aware that this project is in a very early phase, please file an issue if there's something not working or unclear.
You can now test out and try building shaders with rust-gpu from the browser!
- SHADERed A shader IDE which has a lite version, which allows you to build and run shaders on the web.
- Shader Playground A playground for building and checking the output of shader code similar to godbolt or play.rust-lang.org.
There are two main ways to setup your shader project locally.
- Using the
spirv-builder
crate. Thespirv-builder
is a crate designed to automate the process of building and linking therust-gpu
to be able to compile SPIR-V shaders into your main Rust crate. - Using
.cargo/config
. Alternatively if you're willing to do the setup yourself you can manually set flags in your cargo configuration to enable you to runcargo build
in your shader crate.
If you're writing a bigger application and you want to integrate SPIR-V shader
crates to display, it's recommended to use spirv-builder
in a build script.
- Copy the
rust-toolchain.toml
file to your project. (You must use the same version of Rust asrust-gpu
. Ultimately, the build will fail with a nice error message when you don't use the exact same version) - Reference
spirv-builder
in your Cargo.toml:All dependent crates are published on crates.io.[build-dependencies] spirv-builder = "0.9"
- Create a
build.rs
in your project root.
Paste the following into build.rs
use spirv_builder::{MetadataPrintout, SpirvBuilder};
fn main() -> Result<(), Box<dyn std::error::Error>> {
SpirvBuilder::new(shader_crate, target)
.print_metadata(MetadataPrintout::Full)
.build()?;
Ok(())
}
Substituting shader_crate
with a relative path to your shader crate. The values available for the target
parameter are available
here. For example, if building for vulkan 1.1, use
"spirv-unknown-vulkan1.1"
.
The SpirvBuilder
struct has numerous configuration options available, see
documentation.
The following will directly include the shader module binary into your application.
const SHADER: &[u8] = include_bytes!(env!("<shader_crate>.spv"));
Note If your shader name contains hyphens, the name of environment variable will be the name with hyphens changed to underscores.
Keep in mind that by default, build-dependencies are built in debug mode. This
means that the rust-gpu compiler (rustc_codegen_spirv
) will be built in debug
mode, and will be incredibly slow. You can solve this by placing this bit of
configuration in your workspace Cargo.toml
:
# Compile build-dependencies in release mode with
# the same settings as regular dependencies.
[profile.release.build-override]
opt-level = 3
codegen-units = 16
[profile.dev.build-override]
opt-level = 3
Keep in mind this will optimize all build script dependencies as release, which may slow down full rebuilds a bit. Please read this issue for more information, there's a few important caveats to know about this.
Note This method will require manually rebuilding
rust-gpu
each time there has been changes to the repository.
If you just want to build a shader crate, and don't need to automatically
compile the SPIR-V binary at build time, you can use .cargo/config.toml
to set the
necessary flags. Before you can do that however you need to do a couple of steps
first to build the compiler backend.
- Clone the
rust-gpu
repository cargo build --release
inrust-gpu
.
Now you should have a librustc_codegen_spirv
dynamic library available in
target/release
. You'll need to keep this somewhere stable that you can
reference from your shader project.
Copy the rust-gpu/rust-toolchain.toml
file to your project. You must use the same
version of Rust as rust-gpu
so that dynamic codegen library can be loaded by rustc
.
Now we need to add our .cargo/config.toml
file that can be used to teach cargo
how to build SPIR-V. Here are a few things we need to mention there.
- Path to a spec of a target you're compiling for (see platform support).
These specs reside in a directory inside the
spirv-builder
crate and an example relative path could look like../rust-gpu/crates/spirv-builder/target-specs/spirv-unknown-spv1.3.json
. - Absolute path to the
rustc_codegen_spirv
dynamic library that we built above. - Some additional options.
[build]
target = "<path_to_target_spec>"
rustflags = [
"-Zcodegen-backend=<absolute_path_to_librustc_codegen_spirv>",
"-Zbinary-dep-depinfo",
"-Csymbol-mangling-version=v0",
"-Zcrate-attr=feature(register_tool)",
"-Zcrate-attr=register_tool(rust_gpu)"
]
[unstable]
build-std=["core"]
build-std-features=["compiler-builtins-mem"]
Now we can build our crate with cargo as normal.
cargo build
Now you should have <project_name>.spv
SPIR-V file in target/debug
that you
can give to a renderer.
Configure your shader crate as a "dylib"
type crate, and add spirv-std
to its dependencies:
[lib]
crate-type = ["dylib"]
[dependencies]
spirv-std = { version = "0.9" }
Make sure your shader code uses the no_std
attribute and makes the spirv
attribute visible in the global scope. Then, you're ready to write your first shader. Here's a very simple fragment shader called main_fs
as an example that outputs the color red:
#![no_std]
use spirv_std::spirv;
use spirv_std::glam::{vec4, Vec4};
#[spirv(fragment)]
pub fn main_fs(output: &mut Vec4) {
*output = vec4(1.0, 0.0, 0.0, 1.0);
}