Skip to content

rust-gpu v0.9

Latest
Compare
Choose a tag to compare
@eddyb eddyb released this 25 Jul 10:03
· 25 commits to main since this release

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 into loop {}
    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 making loop {} 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 the loop {}-ing shader)
    • crucially, neither "which panic!", nor "which shader invocation" (caused the panic!) could be determined

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"
    (see ShaderPanicStrategy docs for more details, and also how to e.g. enable debugPrintf output from Vulkan)

  • panic!(...) messages support (some) formatting, by conversion to an equivalent debugPrintf
    (e.g. assert!(x < 1.0, "{x} is not sub-unitary") will print x's value using debugPrintf's %f specifier)

Here's what it looks like in practice to get panic!(...) messages from the GPU:

Adding an assert! just for demonstration purposes:

--- a/examples/shaders/sky-shader/src/lib.rs
+++ b/examples/shaders/sky-shader/src/lib.rs
@@ -65,2 +65,7 @@ fn sun_intensity(zenith_angle_cos: f32) -> f32 {
     let cutoff_angle = PI / 1.95; // Earth shadow hack
+    const MIN_ZAC: f32 = 0.075;
+    assert!(
+        zenith_angle_cos > MIN_ZAC,
+        "expected {zenith_angle_cos} > {MIN_ZAC}, oops!"
+    );
     SUN_INTENSITY_FACTOR

Enabling debugPrintf output without code changes/debug mode:

VK_LOADER_LAYERS_ENABLE='VK_LAYER_KHRONOS_validation' \
VK_LAYER_ENABLES='VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT' \
DEBUG_PRINTF_TO_STDOUT=1 \
  cargo run -p example-runner-wgpu --release -- --force-spirv-passthru

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 consts 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).