When I last left off, I had decided to switch away from OpenGL and start learning gfx-hal. Progress has been good so far - I’m more or less back to where I was before, but with slightly more portable code. I’m planning to go into that soon, but since I’m on holiday, I wanted to write something smaller and simpler.
One of the many changes between old gfx and gfx-hal is that you now have to supply your shaders in SPIR-V format. Thankfully, you can still author them in GLSL, and use Khronos’ compiler to convert them to SPIR-V, so in practice this just adds another step to your build process.
But manual steps are lame and not fun! A much better approach is to use a build script to automate this.
First of all, we’ll need some GLSL shaders to convert. I’m using the following folder structure:
source_assets/ shaders/ simple.vert simple.frag assets/ gen/ shaders/ ... our generated shaders will go here src/ build.rs Cargo.toml ...
And fragment shader:
We can also use the
glsl-to-spirv Rust crate, rather than compiling Khronos’
glslangValidator ourselves. To do so, add it as a build dependency to your Cargo.toml file:
Finally, you should probably add the output path to your
If you have a source file named
build.rs in the root of your project, then cargo will invoke it before compiling your crate. This is where we’ll compile our shaders. I’ll include the full build script here, but I’ll also break it down a bit in this post.
To begin with, we’ll get something running:
For reasons I don’t fully understand, you can give instructions about the build script to cargo by printing them to stdout. Without the
println! above, we would be recompiling our shaders every time we compile our code, even if they hadn’t changed.
Next, we loop over all of our GLSL source shaders, ignoring anything that isn’t a file, and determine the type of shader based on its filename extension:
Assuming we can determine a shader type, we can then invoke the shader compiler:
The result of this compilation is a temporary file containing the SPIR-V binary. We then want to copy that data into our desired output location:
If this works you should be able to run any cargo command and see two new files:
simple.frag.spv. You can now read these directly in your application, and they’ll be recompiled whenever you change them.
There are a few things you could do from here:
Improve error handling and reporting. As things stand, making a mistake in your shader code will probably give you an ugly error like this:
error: failed to run custom build command for `superior v0.1.0 (file:///Users/user/superior)` process didn't exit successfully: `/Users/user/superior/target/debug/build/ superior-876c6153f46c1599/build-script-build` (exit code: 1) --- stdout cargo:rerun-if-changed=source_assets/shaders --- stderr Error: StringError("/var/folders/rn/l191w8g17qng7q6w71kx1h600000gp/T/.tmpbA5DPX/ 0.vert\nERROR: /var/folders/rn/l191w8g17qng7q6w71kx1h600000gp/T/.tmpbA5DPX/0.vert:8: \'\' : syntax error, unexpected IDENTIFIER\nERROR: 1 compilation errors. No code generated.\n\n\nERROR: Linking vertex stage: Missing entry point: Each stage requires one entry point\n\nSPIR-V is not generated for failed compile or link\n")
Some manual formatting of that might be required.
While you probably do want to pre-compile your shaders in release mode, it might be useful in development to be able to tweak and recompile shaders at runtime. I’ll likely end up doing this at some point, so there may be a post on that in future.