Skip to content

Instantly share code, notes, and snippets.

@Glavin001
Created April 6, 2026 03:18
Show Gist options
  • Select an option

  • Save Glavin001/6699f0ea0acff35984a28c88916d497f to your computer and use it in GitHub Desktop.

Select an option

Save Glavin001/6699f0ea0acff35984a28c88916d497f to your computer and use it in GitHub Desktop.
Rust GPU Cheat Sheet

Rust GPU Cheat Sheet

Scope: current community-owned Rust GPU mainline (rust-gpu), compiled to SPIR-V.
Audience: people writing real shaders and build pipelines, not just toy examples.
Goal: one clear reference for the parts you actually need: setup, crate structure, build flow, attributes, data layout, resources, images, advanced stages, debugging, testing, and the gotchas that matter on the current community version.

Read this first

Rust GPU is still a moving target. Treat it like a pinned toolchain snapshot, not a casually upgradable library stack.

  • Keep all Rust-GPU crates on the exact same version/revision.
  • Use the exact nightly expected by the current Rust-GPU mainline.
  • Prefer the current main branch / pinned git revision when following “latest community version” guidance.
  • When docs and source disagree, trust the current repo source, examples, and tests over older book text.

1) What Rust GPU is

Rust GPU is a Rust compiler backend plus standard-library-like support crates for writing GPU shaders in Rust and compiling them to SPIR-V.

Core pieces:

  • rustc_codegen_spirv — the SPIR-V codegen backend.
  • spirv-std — shader-side APIs, attributes, intrinsics, images, ray tracing types, etc.
  • spirv-builder — the recommended way to build shader crates from a host crate / build.rs.

Mental model:

  • Your host app is ordinary Rust.
  • Your shader crate is no_std-style Rust compiled for a SPIR-V target.
  • A shared crate can hold math helpers and ABI-stable structs used by both CPU and GPU.

2) Current snapshot / practical versioning rules

For the current community version, the important facts are:

  • The project is community-owned under the Rust-GPU organization.
  • The current workspace version is 0.9.0.
  • The workspace edition is Rust 2024.
  • The repo pins nightly-2026-04-02 with rust-src, rustc-dev, and llvm-tools.
  • The project is technically usable but not yet production-ready.
  • The project explicitly warns that it does not offer backward-compatibility guarantees and that the supported path is building from the latest main.

Use one of these two strategies — and do not mix them loosely

A. “Latest community version” / most accurate

  • Pin the Rust-GPU repo by git revision.
  • Copy the repo’s rust-toolchain.toml.
  • Keep your shader-side Rust-GPU crates on that same revision.

B. Release-based

  • Use the exact same published version for all Rust-GPU crates.
  • Do not mix spirv-builder from one release with spirv-std from another.

Treat Rust GPU as a toolchain family

Do not think of it as “just another crate dependency”. The compiler backend, shader stdlib, and builder must line up.


3) The recommended project layout

A good workspace layout looks like this:

my-project/
├─ Cargo.toml
├─ rust-toolchain.toml
├─ build.rs                  # host-side shader build
├─ crates/
│  ├─ app/                   # host app
│  ├─ shaders/my_shader/     # shader crate
│  └─ shared/                # CPU/GPU shared code and ABI types

Recommended split

Host crate

  • owns the windowing / renderer / pipelines / descriptors / bind groups
  • invokes spirv-builder in build.rs
  • loads .spv produced by the builder

Shader crate

  • contains entry points and shader-only resource bindings
  • uses #![cfg_attr(target_arch = "spirv", no_std)]
  • imports spirv_std::spirv

Shared crate

  • contains:
    • #[repr(C)] structs shared with the host
    • POD-safe constant blocks / push constants / per-dispatch structs
    • pure math / utility functions that should run on CPU and GPU
  • should avoid host-only APIs

4) Golden-path setup

4.1 Copy Rust GPU’s toolchain file

Start by copying the Rust-GPU repo’s rust-toolchain.toml into your workspace.
This is the single most important setup rule.

Why:

  • rustc_codegen_spirv is tightly coupled to Rust internals.
  • A “nearby” nightly is often not good enough.
  • If the version mismatches, builds can fail or behave incorrectly.

4.2 Use spirv-builder unless you have a strong reason not to

The recommended path for real applications is a host-side build.rs using spirv-builder.

Minimal recommended build.rs

use spirv_builder::{SpirvBuilder, SpirvMetadata};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut builder =
        SpirvBuilder::new("crates/shaders/my_shader", "spirv-unknown-vulkan1.1")
            .deny_warnings(true)
            .spirv_metadata(SpirvMetadata::NameVariables)
            .preserve_bindings(true);

    // Recommended build-script behavior.
    builder.build_script.defaults = true;

    // Emit an env var like `my_shader.spv` that points to the built module.
    builder.build_script.env_shader_spv_path = Some(true);

    // Optional: avoid incremental target-dir clashes if you have multiple builders.
    // builder.target_dir_path = Some("my-shader-builder".into());

    builder.build()?;
    Ok(())
}

Why this is the best default

  • spirv-builder handles the nested SPIR-V build correctly.
  • build_script.defaults = true turns on the useful build-script defaults.
  • env_shader_spv_path = true makes the .spv easy to include from the host.
  • SpirvMetadata::NameVariables is usually the best reflection/debugging compromise.
  • preserve_bindings(true) helps when tooling or reflection depends on bindings staying visible.

Host-side inclusion

const SHADER: &[u8] = include_bytes!(env!("my_shader.spv"));

For wgpu, you may instead use its SPIR-V inclusion helpers.


4.3 Build build-dependencies optimized

This is a major practical best practice.

rustc_codegen_spirv is a build dependency. If your build dependencies are left unoptimized in dev mode, shader builds can become painfully slow.

Put this in your workspace Cargo.toml:

[profile.release.build-override]
opt-level = 3
codegen-units = 16

[profile.dev.build-override]
opt-level = 3

If you mirror Rust-GPU’s own workspace more closely, you can also use stronger release build-override settings. The key idea is the same: optimize build dependencies.


4.4 Use a unique target dir when you have multiple shader builds

If you build:

  • multiple shader crates,
  • multiple target variants,
  • host + Android/browser variants,
  • or several builders in one workspace,

give each builder a unique target_dir_path to prevent incremental artifact clashes.

builder.target_dir_path = Some("my-shader-target".into());

4.5 Leave multimodule off unless you need it

By default, Rust GPU prefers bundling entry points into a single SPIR-V module.

Use:

.multimodule(true)

only when downstream tooling insists on one module per entry point or behaves badly with multi-entry modules.

Important gotcha

multimodule = true and build_script.env_shader_spv_path = true do not work together.


4.6 Only use manual .cargo/config.toml setup for special cases

You can build shader crates manually via .cargo/config.toml, but this is the lower-level path.

Use it only when you specifically need:

  • direct manual control,
  • standalone shader-crate builds,
  • or custom integration not centered on a host build.rs.

That route typically requires:

  • building rustc_codegen_spirv,
  • pointing Cargo at the dynamic backend,
  • registering the rust_gpu tool via -Zcrate-attr,
  • and using build-std=["core"].

For most applications, spirv-builder is cleaner and safer.


5) Canonical shader crate template

A strong default shader crate starts like this:

#![cfg_attr(target_arch = "spirv", no_std)]
#![deny(warnings)]

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);
}

Why this template is good

  • cfg_attr(target_arch = "spirv", no_std) lets the same crate or shared code remain usable on CPU builds when practical.
  • #![deny(warnings)] is helpful because shader-build warnings can otherwise be easy to miss.
  • spirv_std::spirv is the standard attribute entry point.
  • spirv_std::glam keeps math types aligned with what Rust GPU expects.

Preferred imports

Use:

use spirv_std::spirv;
use spirv_std::glam;

or:

use spirv_std::glam::{Vec2, Vec3, Vec4, UVec3};

This avoids version drift and keeps shader-side math consistent.


6) Shared CPU/GPU code: the right way

One of Rust GPU’s biggest advantages is sharing Rust code between host-side logic and shader logic.

Best practices

  • Keep pure math, color transforms, helper functions, and common constants in a shared crate.
  • Use #[cfg(target_arch = "spirv")] only for the parts that truly need shader-only APIs.
  • Prefer spirv_std::glam types for vectors/matrices in shared code.
  • Keep shared types ABI-stable with #[repr(C)].
  • Derive bytemuck::Pod and bytemuck::Zeroable for host-uploaded structs when appropriate.

Example shared push-constant / uniform struct

#![cfg_attr(target_arch = "spirv", no_std)]

use bytemuck::{Pod, Zeroable};

#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct ShaderConstants {
    pub width: u32,
    pub height: u32,
    pub time: f32,
    pub exposure: f32,
}

ABI rules that matter

For host/GPU shared structs:

  • use #[repr(C)]
  • use only fields whose layout you understand precisely
  • do not assume Rust’s default layout is stable
  • be careful with booleans and exotic enums in externally visible ABI
  • prefer plain scalars, vectors, arrays, and explicit structs

7) Entry points and attributes

Rust GPU uses #[spirv(...)] attributes for SPIR-V-specific behavior.

There are four big buckets you use most:

  1. execution model / entry point
  2. builtins
  3. resource binding / storage class
  4. interpolation / layout / specialization

7.1 Entry-point execution models

Common ones:

#[spirv(vertex)]
#[spirv(fragment)]
#[spirv(compute(threads(64)))]

The current codebase supports these execution model names (snake_case of SPIR-V names):

  • vertex
  • tessellation_control
  • tessellation_evaluation
  • geometry
  • fragment
  • compute
  • task_nv
  • mesh_nv
  • task_ext
  • mesh_ext
  • ray_generation
  • intersection
  • any_hit
  • closest_hit
  • miss
  • callable

Rule

Use the SPIR-V execution model name in snake_case.


7.2 Compute shaders: thread dimensions are mandatory

Compute entry points must specify their local workgroup dimensions:

#[spirv(compute(threads(64)))]
pub fn main_cs(
    #[spirv(global_invocation_id)] id: glam::UVec3,
    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] data: &mut [u32],
) {
    let i = id.x as usize;
    data[i] *= 2;
}

Rules

  • threads(x) means (x, 1, 1)
  • threads(x, y) means (x, y, 1)
  • you can specify 1 to 3 dimensions
  • the x dimension is required

7.3 Override the exported entry-point name

By default, the Rust function name becomes the SPIR-V entry-point name.
Override it like this:

#[spirv(vertex(entry_point_name = "vs_main"))]
pub fn vertex_main() { /* ... */ }

Use this when your engine, host API, or cross-language pipeline expects a specific entry-point name.


7.4 Builtins

Builtins are special inputs/outputs with fixed GPU meaning.

Examples:

#[spirv(vertex)]
pub fn vs(
    #[spirv(vertex_index)] vertex_index: i32,
    #[spirv(position, invariant)] out_pos: &mut glam::Vec4,
) {
    // ...
}
#[spirv(fragment)]
pub fn fs(
    #[spirv(frag_coord)] frag_coord: &glam::Vec4,
    out_color: &mut glam::Vec4,
) {
    // ...
}
#[spirv(compute(threads(64)))]
pub fn cs(
    #[spirv(global_invocation_id)] gid: glam::UVec3,
    #[spirv(local_invocation_id)] lid: glam::UVec3,
    #[spirv(workgroup_id)] wid: glam::UVec3,
) {
    // ...
}

Frequently useful builtins

Graphics

  • position
  • vertex_id
  • vertex_index
  • instance_id
  • instance_index
  • frag_coord
  • frag_depth
  • front_facing
  • primitive_id
  • layer
  • viewport_index
  • sample_id
  • sample_mask

Compute

  • num_workgroups
  • workgroup_id
  • local_invocation_id
  • global_invocation_id
  • local_invocation_index

Subgroup

  • subgroup_size
  • num_subgroups
  • subgroup_id
  • subgroup_local_invocation_id

Ray tracing

  • launch_id
  • launch_size
  • world_ray_origin
  • world_ray_direction
  • object_ray_origin
  • object_ray_direction
  • ray_tmin
  • ray_tmax
  • object_to_world
  • world_to_object
  • hit_kind

Rule

For builtins, use the SPIR-V BuiltIn name in snake_case.


7.5 Resource binding: descriptor_set and binding

Resources visible to the host API need explicit binding metadata.

#[spirv(fragment)]
pub fn main(
    #[spirv(uniform, descriptor_set = 0, binding = 0)] camera: &Camera,
    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] vertices: &[Vertex],
) {
    // ...
}

Important current-version note

Older book text says descriptor_set = 0 is reserved.
That is stale for the current community version.

Current repo examples, macros, and compile tests actively use:

descriptor_set = 0

So in the current mainline, set 0 is usable and is the normal default in examples.


7.6 Storage-class attributes you will actually use

These are the important entry-point parameter storage-class-style attributes:

  • uniform_constant
  • uniform
  • storage_buffer
  • push_constant
  • workgroup
  • image
  • physical_storage_buffer

Ray tracing / callable / task-payload related:

  • callable_data
  • incoming_callable_data
  • ray_payload
  • incoming_ray_payload
  • hit_attribute
  • shader_record_buffer
  • task_payload_workgroup_ext

Typical patterns

Uniform / constant data

#[spirv(uniform, descriptor_set = 0, binding = 0)] camera: &Camera

Read-only storage buffer

#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] input: &[u32]

Writable storage buffer

#[spirv(storage_buffer, descriptor_set = 0, binding = 2)] output: &mut [u32]

Push constants

#[spirv(push_constant)] constants: &ShaderConstants

Workgroup shared memory

#[spirv(workgroup)] scratch: &mut [u32; 256]

7.7 flat

flat means “do not interpolate”.

#[spirv(fragment)]
pub fn main(
    #[spirv(flat)] material_id: u32,
    out_color: &mut glam::Vec4,
) {
    // ...
}

Best-practice rule

Use #[spirv(flat)] for fragment inputs that are discrete / non-interpolated, especially integers and IDs.

Important gotcha

Current compile tests show that integer fragment inputs are expected to be flat.
If an input should not be interpolated, say so explicitly.


7.8 invariant

Use invariant on outputs when you need invariant behavior analogous to GLSL invariant.

#[spirv(vertex)]
pub fn main(
    #[spirv(position, invariant)] out_pos: &mut glam::Vec4,
) {
    // ...
}

Use it only where you actually need invariance guarantees.


7.9 workgroup

This marks shared memory visible to invocations in the same workgroup.

#[spirv(compute(threads(256)))]
pub fn reduce(
    #[spirv(local_invocation_id)] lid: glam::UVec3,
    #[spirv(workgroup)] scratch: &mut [u32; 256],
) {
    let local = lid.x as usize;
    scratch[local] = local as u32;
}

Best practices

  • pair shared-memory use with the right barriers/synchronization
  • keep workgroup arrays fixed-size and simple
  • do not write before the data is logically initialized
  • assume race bugs until proven otherwise

7.10 Specialization constants

Use specialization constants for values you want to override at pipeline creation time.

#[spirv(fragment)]
pub fn main(
    #[spirv(spec_constant(id = 1, default = 64))] block_size: u32,
    out: &mut glam::Vec4,
) {
    let x = block_size as f32;
    *out = glam::vec4(x, 0.0, 0.0, 1.0);
}

Current limitations

  • direct support is for u32
  • larger values can be assembled from multiple u32s
  • these are runtime specialization values, not Rust const generics

Best practice

Use specialization constants for:

  • pipeline tuning
  • algorithm variants
  • fixed-at-pipeline-creation thresholds

Do not mentally model them as compile-time Rust type parameters.


8) Data types, ABI, and layout

8.1 Prefer simple, explicit layouts

Good shader-visible types:

  • u32, i32, f32
  • fixed arrays
  • glam vectors / matrices that Rust GPU supports
  • #[repr(C)] structs made of the above

Avoid vague / hosty / layout-ambiguous public interfaces.


8.2 Prefer spirv_std::glam

Use the re-exported glam from spirv_std where practical:

use spirv_std::glam::{Vec2, Vec3, Vec4, UVec3, Mat4};

Why:

  • less version drift
  • shader-side compatibility is already curated around it

8.3 Slices in buffers are useful and idiomatic

Storage buffers frequently use slices:

#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] input: &[u32]
#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut [u32]

This is one of the nicest ergonomic wins in Rust GPU.

Best practices

  • use slices when the host-side buffer length is dynamic
  • use fixed arrays when the size is truly fixed and ABI-known
  • keep element types POD-like and layout-simple

8.4 TypedBuffer<T> for explicit descriptor indexing

TypedBuffer<T> is an explicit typed buffer handle for descriptor indexing use cases.

Patterns:

  • &[u32] — implicit one buffer
  • &TypedBuffer<[u32]> — explicit one buffer
  • &RuntimeArray<TypedBuffer<[u32]>> — many indexed buffers

Use this when you need descriptor-indexing style access instead of a single directly-bound slice.


8.5 RuntimeArray<T> is advanced and unsafe to index

RuntimeArray<T> models SPIR-V runtime arrays, but it does not know its length at the Rust type level.

Treat it as an advanced low-level feature:

  • host-side bounds discipline matters
  • indexing APIs are unsafe for a reason
  • keep layouts and indexing contracts very clear

8.6 ByteAddressableBuffer is powerful but sharp

ByteAddressableBuffer gives you untyped word-addressed loads/stores.

Use it when you really need HLSL-style arbitrary byte/word access.

Rules

  • everything must be aligned to 4-byte multiples
  • byte_index must be a multiple of 4
  • loads/stores are effectively transmute-like and can bypass normal safety expectations

Best practice

Prefer typed buffers and ordinary structured data first.
Use ByteAddressableBuffer only for:

  • packed formats
  • interoperability with external layouts
  • explicit low-level buffer protocols

9) Images and samplers

Rust GPU supports a large set of SPIR-V image types.

You can write the verbose generic image type directly, but you almost always want one of these:

  • the Image! macro
  • the common type aliases in spirv_std::image

9.1 Common image aliases

Examples:

  • Image2d
  • Image2dU
  • Image2dI
  • StorageImage2d
  • StorageImage2dU
  • StorageImage2dI
  • Cubemap

Typical sampled 2D texture:

use spirv_std::image::Image2d;
use spirv_std::Sampler;

9.2 Image! macro syntax

The macro form is the most expressive:

spirv_std::Image!(2D, type=f32, sampled)
spirv_std::Image!(2D, type=u32, sampled)
spirv_std::Image!(2D, format=rgba32f, sampled=false)
spirv_std::Image!(cube, type=f32, sampled)

General shape:

Image!(
    <dimensionality>,
    <type=... | format=...>,
    [sampled],
    [multisampled],
    [arrayed],
    [depth],
)

Supported dimensionalities include:

  • 1D
  • 2D
  • 3D
  • rect
  • cube
  • subpass

Best practice

Use the type aliases for common cases.
Use Image! when you need a more exact image form.


9.3 Sampled images vs storage images

Sampled image:

#[spirv(descriptor_set = 0, binding = 0)] image: &spirv_std::image::Image2d,
#[spirv(descriptor_set = 0, binding = 1)] sampler: &spirv_std::Sampler,

Storage image:

#[spirv(descriptor_set = 0, binding = 0)] image: &spirv_std::image::StorageImage2d,

Rule of thumb

  • use sampled images for texture sampling
  • use storage images for image load/store style access

9.4 Common image methods

The image API includes methods such as:

  • fetch
  • fetch_with_lod
  • gather
  • sample
  • sample_bias
  • sample_by_lod
  • sample_by_gradient
  • depth-reference sampling variants
  • projected-coordinate sampling variants
  • lower-level *_with(...) forms

Best practice

Start with the highest-level method that matches your use case.
Drop to the more explicit forms only when you actually need the extra operands.


9.5 Input attachments / subpass data

The current compiler enforces that SubpassData image types use #[spirv(input_attachment_index = ...)].

If you work with input attachments:

  • use the correct subpass image type
  • provide the input_attachment_index
  • keep the host-side render pass / subpass contract aligned

10) spirv_std modules you should know

10.1 spirv_std::arch

This is the low-level instruction-adjacent API surface.

Use it for:

  • barriers
  • subgroup operations
  • derivatives
  • atomics
  • helper invocation / demote
  • clocks
  • mesh shading intrinsics
  • ray tracing instruction wrappers
  • miscellaneous SPIR-V ops not covered by higher-level helpers

Best-practice rule

Use spirv_std::arch when:

  • there is no higher-level safe wrapper,
  • or you intentionally need instruction-level control.

Do not start there for everything.


10.2 spirv_std::memory

This exposes memory-model types like:

  • Scope

    • CrossDevice
    • Device
    • Workgroup
    • Subgroup
    • Invocation
    • QueueFamily
  • Semantics

    • ACQUIRE
    • RELEASE
    • ACQUIRE_RELEASE
    • UNIFORM_MEMORY
    • WORKGROUP_MEMORY
    • IMAGE_MEMORY
    • MAKE_AVAILABLE
    • MAKE_VISIBLE
    • VOLATILE
    • and others

Use these when you need explicit memory ordering semantics in advanced synchronization code.


10.3 spirv_std::ray_tracing

This is the shader-side ray tracing API surface.

Key types/features include:

  • AccelerationStructure
  • RayFlags
  • RayQuery
  • ray_query!
  • trace_ray
  • query/commit/confirm helpers
  • callable / payload-related storage-class interactions

10.4 debug_printf! / debug_printfln!

Rust GPU re-exports debug_printf helpers for runtime debugging, and the builder also supports panic strategies that use debug printf.

Use these only when debugging; leave them out of production shader paths.


11) Synchronization, subgroup, and workgroup patterns

The current codebase includes subgroup and barrier support in spirv_std::arch.

Examples of useful primitives:

  • workgroup memory barriers
  • barriers with group synchronization
  • subgroup operations
  • atomics

A very common pattern is:

  1. write workgroup-local shared memory
  2. synchronize
  3. read/reduce/reuse

Example style:

unsafe {
    spirv_std::arch::workgroup_memory_barrier_with_group_sync();
}

Best practices

  • keep barrier placement obvious
  • prefer one clearly-correct synchronization strategy over a “clever” one
  • isolate subgroup/workgroup algorithms into small tested helpers
  • assume host-visible corruption can come from a race before blaming layout

12) Advanced stage support

12.1 Mesh and task shaders

The current community version has mesh/task shader support in the source tree and compile tests.

Execution models:

  • task_ext
  • mesh_ext
  • also task_nv / mesh_nv

Mesh shader example shape

use spirv_std::arch::set_mesh_outputs_ext;
use spirv_std::glam::{UVec3, Vec4};
use spirv_std::spirv;

#[spirv(mesh_ext(
    threads(1),
    output_vertices = 3,
    output_primitives_ext = 1,
    output_triangles_ext
))]
pub fn main(
    #[spirv(position)] positions: &mut [Vec4; 3],
    #[spirv(primitive_triangle_indices_ext)] indices: &mut [UVec3; 1],
) {
    unsafe { set_mesh_outputs_ext(3, 1); }

    positions[0] = Vec4::new(-0.5,  0.5, 0.0, 1.0);
    positions[1] = Vec4::new( 0.5,  0.5, 0.0, 1.0);
    positions[2] = Vec4::new( 0.0, -0.5, 0.0, 1.0);
    indices[0] = UVec3::new(0, 1, 2);
}

Task shader example shape

use spirv_std::arch::emit_mesh_tasks_ext;
use spirv_std::spirv;

#[spirv(task_ext(threads(1)))]
pub fn main() {
    unsafe { emit_mesh_tasks_ext(1, 1, 1); }
}

Mesh/task best practices

  • enable the required capability / extension for the target
  • keep thread counts explicit
  • obey the rules around uniform control flow
  • for mesh shaders, call set_mesh_outputs_ext correctly
  • for task shaders, emit_mesh_tasks_ext* must be used correctly and at the right control-flow point

12.2 Ray tracing

The current codebase includes ray-tracing execution models, payload storage classes, acceleration structures, and ray queries.

Example shape

use spirv_std::spirv;
use spirv_std::ray_tracing::{AccelerationStructure, RayFlags};

#[spirv(ray_generation)]
pub fn main(
    #[spirv(descriptor_set = 0, binding = 0)] accel: &AccelerationStructure,
    #[spirv(ray_payload)] payload: &mut glam::Vec3,
) {
    unsafe {
        accel.trace_ray(
            RayFlags::NONE,
            0, 0, 0, 0,
            glam::vec3(0.0, 0.0, 0.0),
            0.001,
            glam::vec3(0.0, 0.0, -1.0),
            10_000.0,
            payload,
        );
    }
}

Related execution models

  • ray_generation
  • intersection
  • any_hit
  • closest_hit
  • miss
  • callable

Related storage classes

  • ray_payload
  • incoming_ray_payload
  • callable_data
  • incoming_callable_data
  • hit_attribute
  • shader_record_buffer

Best practices

  • gate ray-tracing code behind the right target features/capabilities
  • keep payload structs simple and explicit
  • isolate ray-query logic in helpers
  • match your host pipeline / SBT layout exactly

12.3 Inline SPIR-V assembly

Rust GPU supports inline SPIR-V assembly via nightly asm!.

Use it only when:

  • no higher-level API exists,
  • you need a precise instruction sequence,
  • or you are prototyping backend gaps.

Key syntax facts

  • literal non-ID instructions are written directly
  • SPIR-V IDs use %name
  • result IDs use %result = ...
  • it accepts integers, floats, SIMD vectors, pointers, and function pointers as inputs

Best practice

Use inline asm as a last resort.
When a spirv_std::arch helper exists, prefer that.


13) Build/configuration knobs worth knowing

SpirvBuilder exposes several useful controls.

13.1 Useful builder methods

Metadata

.spirv_metadata(SpirvMetadata::None)
.spirv_metadata(SpirvMetadata::NameVariables)
.spirv_metadata(SpirvMetadata::Full)

Recommendation

  • None for smallest binaries
  • NameVariables for most real projects
  • Full only when debugging hard problems

Warnings

.deny_warnings(true)

Recommended for disciplined shader crates.

Multi-module split

.multimodule(true)

Only when downstream tooling truly needs one module per entry point.

Capabilities and extensions

.capability(...)
.extension("SPV_EXT_mesh_shader")

The builder docs note that you can check these in code with:

#[cfg(target_feature = "RayTracingKHR")]
#[cfg(target_feature = "ext:SPV_EXT_mesh_shader")]

Shader panic handling

.shader_panic_strategy(...)

See the next section.

Layout / validator toggles

  • relax_struct_store
  • relax_logical_pointer
  • relax_block_layout
  • uniform_buffer_standard_layout
  • scalar_block_layout
  • skip_block_layout

Only change these when you understand the API/layout consequences.

Reflection-related

.preserve_bindings(true)

Useful when reflection or downstream tools need bindings preserved even if unused.

Extra codegen args

.extra_arg(...)

Use sparingly. Many codegen args are internal/unstable.

Shader crate feature control

  • shader_crate_default_features(bool)
  • shader_crate_features(...)

Useful for keeping shader dependency surfaces minimal and intentional.

Convenience commands

  • .check()
  • .clippy()
  • .build()

These are useful for CI/linting workflows around shader crates.


13.2 Panic strategy

Current builder panic strategies include:

  • SilentExit (default)
  • DebugPrintfThenExit { ... }

SilentExit

Default, low-noise, and the right production default.

DebugPrintfThenExit

Great for debugging, but requires runtime support:

  • Vulkan validation/debug-printf setup
  • or wgpu SPIR-V passthrough setup

For wgpu, the builder docs note you need:

  • wgpu::Features::SPIRV_SHADER_PASSTHROUGH
  • create_shader_module_passthrough

Best practice

  • use SilentExit in normal builds
  • temporarily switch to debug-printf-based panic handling when chasing shader crashes

13.3 Tracing and logging env vars

Useful environment variables:

  • RUSTGPU_LOG
  • RUSTGPU_LOG_FORMAT
  • RUSTGPU_LOG_COLOR
  • RUSTC_LOG (for rustc-side internals)

Use these when debugging build/backend behavior.


13.4 Codegen args env var

Rust GPU exposes:

  • RUSTGPU_CODEGEN_ARGS

This is useful for experimentation and diagnostics, but many codegen arguments are internal and unstable. Prefer the structured builder API where possible.


14) Testing and validation

The current docs mention these workflows:

  • cargo test
  • cargo compiletest
  • cargo difftest

To update expected outputs for compile tests:

  • cargo compiletest --bless

Practical testing strategy for application authors

Use three layers:

1. CPU/shared-code tests

  • test shared math and pure functions on CPU
  • this is fast and catches a lot

2. Shader compile tests

  • build shaders in CI
  • fail on warnings
  • lock toolchain and target

3. Runtime tests / sample runners

  • render a known image or run a compute shader and validate output

Best practice

Push as much logic as possible into CPU-testable shared code, and keep stage/resource glue thin.


15) Targets and platform families

Rust GPU’s official platform-support page is older and high-level, but it still documents the target families available in the project.

SPIR-V targets

  • spirv-unknown-spv1.0
  • spirv-unknown-spv1.1
  • spirv-unknown-spv1.2
  • spirv-unknown-spv1.3
  • spirv-unknown-spv1.4
  • spirv-unknown-spv1.5
  • spirv-unknown-spv1.6

Vulkan targets

  • spirv-unknown-vulkan1.0
  • spirv-unknown-vulkan1.1
  • spirv-unknown-vulkan1.1spv1.4
  • spirv-unknown-vulkan1.2
  • spirv-unknown-vulkan1.3
  • spirv-unknown-vulkan1.4

WebGPU target

  • spirv-unknown-webgpu0

OpenGL targets

  • spirv-unknown-opengl4.0
  • spirv-unknown-opengl4.1
  • spirv-unknown-opengl4.2
  • spirv-unknown-opengl4.3
  • spirv-unknown-opengl4.5

OpenCL targets

  • spirv-unknown-opencl1.2
  • spirv-unknown-opencl1.2embedded
  • spirv-unknown-opencl2.0
  • spirv-unknown-opencl2.0embedded
  • spirv-unknown-opencl2.1
  • spirv-unknown-opencl2.1embedded
  • spirv-unknown-opencl2.2
  • spirv-unknown-opencl2.2embedded

Practical recommendation

For most current real-world use:

  • prefer Vulkan targets directly,
  • or use Rust GPU with a wgpu host where that fits your portability goals.

16) Ecosystem: what fits with Rust GPU

The official ecosystem and blog pages make it clear that Rust GPU sits inside a broader Rust-on-GPU ecosystem.

For a host-side graphics/compute stack today, the most relevant pairings are:

  • wgpu — broad portable host API, especially good when portability matters
  • ash — lower-level Vulkan host API
  • Naga — relevant in the broader shader translation ecosystem
  • CubeCL, krnl, cudarc — adjacent Rust GPU compute ecosystem projects, not Rust GPU itself

Best practice

Choose your host API and shader compiler path separately in your head:

  • Rust GPU = how your Rust shader code becomes SPIR-V
  • wgpu / ash = how your host app creates pipelines and binds resources

17) Current-version best practices (the short list)

Do this

  • Pin the exact toolchain expected by Rust GPU.
  • Keep all Rust-GPU crate versions/revisions matched exactly.
  • Use spirv-builder in build.rs for normal app integration.
  • Turn on optimized build-dependencies.
  • Put CPU/GPU shared math and ABI structs in a shared crate.
  • Use #![cfg_attr(target_arch = "spirv", no_std)].
  • Prefer spirv_std::glam.
  • Use #[repr(C)] + bytemuck::{Pod, Zeroable} for host-visible data structs.
  • Prefer simple buffer/image interfaces and explicit bindings.
  • Use SpirvMetadata::NameVariables as a good default.
  • Use preserve_bindings(true) if reflection/tooling needs it.
  • Use flat for discrete/non-interpolated fragment inputs.
  • Keep synchronization obvious and conservative.
  • Use spirv_std::arch only when higher-level APIs are insufficient.
  • Test shared logic on CPU and keep stage glue thin.

Avoid this

  • Do not mix arbitrary Rust GPU versions.
  • Do not upgrade the nightly casually.
  • Do not trust older docs over current source/examples/tests when they conflict.
  • Do not start with multimodule unless a tool forces you there.
  • Do not lean on unstable extra_arg/codegen hacks first.
  • Do not expose layout-ambiguous Rust types across the CPU/GPU ABI.
  • Do not reach for ByteAddressableBuffer unless you actually need it.
  • Do not assume “shader constants” behave like Rust const generics.
  • Do not forget that debug-printf-based panic reporting needs runtime support.

18) Common gotchas

“It built yesterday, not today”

Likely causes:

  • nightly mismatch
  • updated Rust GPU revision
  • mixed crate versions
  • stale incremental build outputs

“My shader build is absurdly slow”

Usually:

  • build dependencies are being compiled in debug mode
  • rustc_codegen_spirv is being rebuilt too often
  • target dirs are colliding between builders

“Bindings look wrong”

Check:

  • descriptor set / binding annotations
  • preserved vs optimized-away bindings
  • host-side layout agreement
  • reflection expectations

“Why is descriptor set 0 failing in the docs but used in examples?”

Because the current docs contain a stale statement. Current source/examples/tests use set 0 normally.

“My fragment integer input errors out”

Add #[spirv(flat)].

“My wgpu debug printf isn’t showing up”

You likely need SPIR-V passthrough plus the right validation/debug-printf configuration.

“Can I just use normal Rust?”

Use normal Rust style, yes. Use normal host APIs / std / OS services inside shader code, no.
Write shaders as no_std Rust with spirv-std and explicit GPU interfaces.


19) Compact reference tables

19.1 Most-used entry-point forms

Purpose Form
Vertex shader #[spirv(vertex)]
Fragment shader #[spirv(fragment)]
Compute shader #[spirv(compute(threads(x[, y[, z]])))]
Task shader #[spirv(task_ext(threads(...)))]
Mesh shader #[spirv(mesh_ext(...))]
Ray generation #[spirv(ray_generation)]
Closest hit #[spirv(closest_hit)]
Miss #[spirv(miss)]

19.2 Most-used parameter attributes

Attribute Meaning
position clip-space position builtin
frag_coord fragment coordinates builtin
vertex_index / vertex_id vertex builtin input
global_invocation_id compute global ID
local_invocation_id compute local ID
workgroup_id compute workgroup ID
descriptor_set = N descriptor set
binding = M binding number
push_constant push constant block
storage_buffer storage buffer
uniform uniform buffer / uniform data
workgroup shared local memory
flat no interpolation
invariant invariant output
spec_constant(id=..., default=...) specialization constant
input_attachment_index = N subpass/input attachment index

19.3 Best default builder settings

Setting Recommendation
Toolchain exact Rust-GPU-pinned nightly
Integration spirv-builder in build.rs
build_script.defaults true
env_shader_spv_path true for single-module builds
spirv_metadata NameVariables
deny_warnings true
preserve_bindings true if reflection matters
multimodule false unless required
Panic strategy SilentExit normally

20) Source notes and trust model

This cheat sheet is intentionally based on both the official docs and the current community repo source tree.

That matters because the current mainline has some documented behavior that is fresher in source/examples/tests than in older book text. The clearest example is the outdated note about descriptor_set = 0.

Source-of-truth priority used here

  1. current Rust-GPU repo source / examples / tests
  2. official book + official site
  3. official blog + ecosystem pages

21) References

Official project pages:

Current repo / current-source references:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment