As we keep following Rust's release cadence, it's time for another release of Rust-GPU!
Our project aimed at making Rust a first class language and ecosystem for GPU programming.
You can read more about why we at Embark started this project in the original announcement.
For Rust-GPU 0.9.0
, the Rust nightly version has been updated to nightly-2023-05-27
,
so make sure to update your rust-toolchain.toml
file when upgrading to Rust-GPU 0.9.0
.
This Rust nightly is equivalent (in language and library features) to the stable Rust 1.71.0
version, released recently.
As usual, you can find a complete list of changes in the changelog, but keep reading for the highlights.
panic!
at the GPU
panic!
s then (Rust-GPU 0.8.0
and earlier)
As many Rust APIs (or even language features) have some edge cases which they handle by panicking (e.g. bounds-checked indexing), Rust-GPU has had some panic!(...)
support for a long time, but it was both unsound, and unhelpful to users:
- unsound, because in Rust-GPU
0.8.0
(and earlier)panic!(...)
was turned intoloop {}
the SPIR-V standard doesn't seem to allow it, but SPIR-V tools/drivers appear to be making the same mistake LLVM originally did, i.e. assuming that such "unproductive" infinite loops can never happen, and makingloop {}
UB (see rust-lang/rust#28728 - UB in safe Rust due to the C-specific assumptions in LLVM, fixed much later by making those assumptions opt-in as "mustprogress
") - unhelpful, because even if the
loop {}
wasn't optimized out, the best case scenario was a timeout error
but on some platforms/drivers, timeouts only work well for compute, or even require using a compute-only queue (via Vulkan, e.g.wgpu
doesn't allow queue selection yet) to avoid disrupting the rest of the user's system (as compositors and other applications may be slowed down or even hanged into a crash by a timeout on one of the graphical/mixed queues, if they're blocked behind theloop {}
-ing shader)- crucially, neither "which
panic!
", nor "which shader invocation" (caused thepanic!
) could be determined
- crucially, neither "which
panic!
s now (Rust-GPU 0.9.0
)
PRs #1070, #1080, #1036, #1082 replaced the old strategy (and refined the new one), resulting in:
-
by default, a
panic!(...)
is now soundly propagated all the way up to the shader entry-point, as if the Rust code had used-> Result<_, ()>
and?
after every call (and so acts as a silent "early exit" for invocations that trigger it) -
spirv_builder::ShaderPanicStrategy
allows further customizing the behavior, notably:SpirvBuilder::new(/* ... */) .shader_panic_strategy(ShaderPanicStrategy::DebugPrintfThenExit { print_inputs: true, print_backtrace: true, })
enables
debugPrintf
with maximal detail:panic!
message + shader "inputs" + (compile-time) "backtrace"
(seeShaderPanicStrategy
docs for more details, and also how to e.g. enabledebugPrintf
output from Vulkan) -
panic!(...)
messages support (some) formatting, by conversion to an equivalentdebugPrintf
(e.g.assert!(x < 1.0, "{x} is not sub-unitary")
will printx
's value usingdebugPrintf
's%f
specifier)
Here's what it looks like in practice to get panic!(...)
messages from the GPU:
Specialization Constants
SPIR-V allows shaders to declare "specialization constants" (i.e. OpSpecConstant
) which can have their values specified just before using the shader module in e.g. a Vulkan pipeline (WebGPU also has this feature, calling it "pipeline-overridable constants").
While some shader languages can choose to expose this feature as a variation on their equivalent of "compile-time constants" (e.g. layout(constant_id = 123) const T x;
in GLSL), Rust's const
s and const
-generic parameters are required to be known by the Rust compiler, so they're not a plausible path for Rust-GPU to expose this SPIR-V feature.
Instead, we're exposing the feature via #[spirv(spec_constant(...))]
-decorated shader inputs (only u32
for now):
#[spirv(vertex)]
fn main(
// Default is implicitly `0`, if not specified.
#[spirv(spec_constant(id = 1))] no_default: u32,
// IDs don't need to be sequential or obey any order.
#[spirv(spec_constant(id = 9000, default = 123))] default_123: u32,
) {...}
You can read more about this feature in the "Specialization constants" section in the Rust-GPU book (which goes into more detail about the background of this feature, and Rust/Rust-GPU-specific limitations).