MCPcopy Index your code
hub / github.com/Swoorup/wgsl-bindgen

github.com/Swoorup/wgsl-bindgen @wgsl_bindgen-v0.22.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release wgsl_bindgen-v0.22.2 ↗ · + Follow
571 symbols 1,352 edges 57 files 69 documented · 12% updated 3mo agowgsl_bindgen-v0.22.2 · 2026-03-31★ 828 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

wgsl-bindgen

Latest Version docs.rs License Rust Version

🚀 Generate typesafe Rust bindings from WGSL shaders for wgpu

wgsl_bindgen transforms your WGSL shader development workflow by automatically generating Rust types, constants, and boilerplate code that perfectly match your shaders. Powered by naga-oil, it integrates seamlessly into your build process to catch shader-related errors at compile time rather than runtime.

🎯 Why wgsl_bindgen?

Before: Manual, error-prone shader bindings

// ❌ Easy to make mistakes - no compile-time verification
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
    entries: &[
        wgpu::BindGroupEntry {
            binding: 0, // Is this the right binding index?
            resource: texture_view.as_binding(), // Is this the right type?
        },
        wgpu::BindGroupEntry {
            binding: 1, // What if you change the shader?
            resource: sampler.as_binding(),
        },
    ],
    // ... more boilerplate
});

After: Typesafe, auto-generated bindings

// ✅ Compile-time safety - generated from your actual shaders
let bind_group = my_shader::WgpuBindGroup0::from_bindings(
    device,
    my_shader::WgpuBindGroup0Entries::new(my_shader::WgpuBindGroup0EntriesParams {
        my_texture: &texture_view,  // Type-checked parameter names
        my_sampler: &sampler,       // Matches your WGSL exactly
    })
);
bind_group.set(&mut render_pass); // Simple, safe usage

✨ Key Benefits

  • 🛡️ Type Safety: Catch shader binding mismatches at compile time
  • 🔄 Automatic Sync: Changes to WGSL automatically update Rust bindings
  • 📝 Reduced Boilerplate: Generate tedious wgpu setup code automatically
  • 🎮 Shader-First Workflow: Design in WGSL, get Rust bindings for free
  • 🔧 Flexible: Works with bytemuck, encase, serde, and custom types
  • Fast: Build-time generation with intelligent caching

📦 Version Matrix

wgsl_bindgen is tightly coupled to wgpu due to the code it generates. Here is the compatibility matrix:

wgsl_bindgen wgpu
0.22.x 29.x
0.21.2 - 0.21.3 26.x
0.19.0 - 0.21.1 25.x
0.16.0 - 0.18.2 24.x
0.15.2 23.x
0.15.0 - 0.15.1 22.x
0.12.0 - 0.14.1 0.20.x
0.5.1 - 0.11.0 0.19.x

Features

General:

  • Generates either new or enum-like short constructors to ease creating the generated types, especially ones that require to be padded when using with bytemuck.
  • More strongly typed bind group and bindings initialization
  • Generate your own binding entries for non-wgpu types. This is a work in progress feature to target other non-wgpu frameworks.

Shader Handling:

  • Supports import syntax and many more features from naga oil flavour.
  • Add shader defines dynamically when using either WgslShaderSourceType::EmbedWithNagaOilComposer or WgslShaderSourceType::ComposerWithRelativePath source output type.

    The WgslShaderSourceType::ComposerWithRelativePath provides full control over file I/O without requiring nightly Rust, making it ideal for integration with custom asset systems and hot reloading.

  • File Visitor Pattern: The visit_shader_files function allows custom processing of all shader files in a dependency tree. This enables advanced use cases like:

    • Hot reloading: Watch for file changes and rebuild shaders automatically
    • Caching: Store processed shader content for faster rebuilds
    • Debugging: Log or analyze shader dependencies and content
    • Custom asset systems: Integrate with existing asset loading pipelines

    ```rust // Example: Hot reloading with file watching use shader_bindings::visit_shader_files;

    visit_shader_files( "shaders", ShaderEntry::MyShader, |path| std::fs::read_to_string(path), |file_path, file_content| { println!("Processing shader: {}", file_path); // Add to file watcher, cache, etc. } )?; ```

  • Shader registry utility to dynamically call create_shader variants depending on the variant. This is useful when trying to keep cache of entry to shader modules. Also remember to add shader defines to accomodate for different permutation of the shader modules.

  • Ability to add additional scan directories for shader imports when defining the workflow.

Type Handling:

  • BYO - Bring Your Own Types for Wgsl matrix, vector types. Bindgen will automatically include assertions to test alignment and sizes for your types at compile time.
  • Override generated struct types either entirely or just particular field of struct from your crate, which is handy for small primitive types. You can also use this to overcome the limitation of uniform buffer type restrictions in wgsl.
  • Rust structs for vertex, storage, and uniform buffers.
  • Either use encase or bytemuck derives, and optionally serde for generated structs.
  • Const validation of WGSL memory layout for provided vector and matrix types and generated structs when using bytemuck
  • Override the alignment for the struct generated. This also affects the size of the struct generated.

🚀 Quick Start

1. Add to your Cargo.toml

[build-dependencies]
wgsl_bindgen = "0.19"

[dependencies]
wgpu = "25"
bytemuck = { version = "1.0", features = ["derive"] }
# Optional: for additional features
# encase = "0.8"
# serde = { version = "1.0", features = ["derive"] }

# Note: When using ComposerWithRelativePath, enable naga-ir feature for optimal performance:
# wgpu = { version = "25", features = ["naga-ir"] }

2. Create your WGSL shader (shaders/my_shader.wgsl)

struct Uniforms {
    transform: mat4x4<f32>,
    time: f32,
}

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) uv: vec2<f32>,
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var my_texture: texture_2d<f32>;
@group(0) @binding(2) var my_sampler: sampler;

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;
    output.clip_position = uniforms.transform * vec4<f32>(input.position, 1.0);
    output.uv = input.uv;
    return output;
}

@fragment  
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    return textureSample(my_texture, my_sampler, input.uv);
}

3. Set up build script (build.rs)

use wgsl_bindgen::{WgslBindgenOptionBuilder, WgslTypeSerializeStrategy, GlamWgslTypeMap};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    WgslBindgenOptionBuilder::default()
        .workspace_root("shaders")
        .add_entry_point("shaders/my_shader.wgsl")
        .serialization_strategy(WgslTypeSerializeStrategy::Bytemuck)
        .type_map(GlamWgslTypeMap) // Use glam for math types
        .output("src/shader_bindings.rs")
        .build()?
        .generate()?;
    Ok(())
}

4. Use the generated bindings

// Include the generated bindings
mod shader_bindings;
use shader_bindings::my_shader;

fn setup_render_pipeline(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> wgpu::RenderPipeline {
    // Create shader module from generated code
    let shader = my_shader::create_shader_module_embed_source(device);

    // Use generated pipeline layout
    let pipeline_layout = my_shader::create_pipeline_layout(device);

    // Use generated vertex entry with proper buffer layout
    let vertex_entry = my_shader::vs_main_entry(wgpu::VertexStepMode::Vertex);

    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        layout: Some(&pipeline_layout),
        vertex: my_shader::vertex_state(&shader, &vertex_entry),
        fragment: Some(my_shader::fragment_state(&shader, &my_shader::fs_main_entry([
            Some(wgpu::ColorTargetState {
                format: surface_format,
                blend: Some(wgpu::BlendState::REPLACE),
                write_mask: wgpu::ColorWrites::ALL,
            })
        ]))),
        // ... other pipeline state
    })
}

fn setup_bind_group(device: &wgpu::Device, texture_view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> my_shader::WgpuBindGroup0 {
    // Create uniform buffer with generated struct
    let uniforms = my_shader::Uniforms::new(
        glam::Mat4::IDENTITY,  // transform
        0.0,                   // time
    );
    let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        contents: bytemuck::cast_slice(&[uniforms]),
        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
    });

    // Create bind group using generated types - fully type-safe!
    my_shader::WgpuBindGroup0::from_bindings(
        device,
        my_shader::WgpuBindGroup0Entries::new(my_shader::WgpuBindGroup0EntriesParams {
            uniforms: wgpu::BufferBinding {
                buffer: &uniform_buffer,
                offset: 0,
                size: None,
            },
            my_texture: texture_view,
            my_sampler: sampler,
        })
    )
}

🎉 That's it! Your shader bindings are now fully type-safe and will automatically update when you modify your WGSL files.

📚 See the example project for a complete working demo with multiple shaders, including advanced features like texture arrays and overlay rendering.

🔧 Advanced Configuration

Serialization Strategies

Choose how your WGSL types are serialized to Rust:

// For zero-copy, compile-time verified layouts (recommended)
.serialization_strategy(WgslTypeSerializeStrategy::Bytemuck)

// For runtime padding/alignment handling
.serialization_strategy(WgslTypeSerializeStrategy::Encase)

Type Mapping

Use your preferred math library:

// glam (recommended for games)
.type_map(GlamWgslTypeMap)

// nalgebra (recommended for scientific computing)  
.type_map(NalgebraWgslTypeMap)

// Use built-in Rust arrays (no external dependencies)
.type_map(RustWgslTypeMap)

Custom Types

Override specific types or structs:

.override_struct_field_type([
    ("MyStruct", "my_field", quote!(MyCustomType))
])
.add_override_struct_mapping(("MyWgslStruct", quote!(my_crate::MyRustStruct)))

Shader Source Options

Control how shaders are embedded:

// Embed shader source directly (recommended for most cases)
.shader_source_type(WgslShaderSourceType::EmbedSource)

// Use file paths for hot-reloading during development
.shader_source_type(WgslShaderSourceType::HardCodedFilePath)

// Use naga-oil composer for advanced import features
.shader_source_type(WgslShaderSourceType::EmbedWithNagaOilComposer)

// Use relative paths with custom file loading (no nightly Rust required)
// Requires wgpu "naga-ir" feature for optimal performance
.shader_source_type(WgslShaderSourceType::ComposerWithRelativePath)

Using Custom File Loading

The ComposerWithRelativePath option allows you to provide your own file loading logic, which is perfect for integrating with custom asset systems.

Performance Note: This mode uses wgpu's naga-ir feature to pass Naga IR modules directly to the GPU instead of converting back to WGSL source. This provides better performance by avoiding the round-trip conversion process. Make sure to enable the feature in your dependencies:

[dependencies]
wgpu = { version = "25", features = ["naga-ir"] }
// In your build.rs
.shader_source_type(WgslShaderSourceType::ComposerWithRelativePath)

// In your application code
let module = main::load_naga_module_from_path(
    "assets/shaders",  // Base directory
    ShaderEntry::Main, // Entry point enum variant
    &mut composer,
    shader_defs,
    |path| std::fs::read_to_string(path), // Your custom file loader
)?;

// Or use your own asset system
let module = main::load_naga_module_from_path(
    "shaders",
    ShaderEntry::Main,
    &mut composer,
    shader_defs,
    |path| asset_manager.load_text_file(path), // Custom asset manager
)?;

Wgsl Import Resolution

wgsl_bindgen uses a specific strategy to resolve the import paths in your WGSL source code. This process is handled by the ModulePathResolver::generate_possible_paths function.

Consider the following directory structure:

/my_project
├── src
│   ├── shaders
│   │   ├── main.wgsl
│   │   ├── utils
│   │   │   ├── math.wgsl
│   ├── main.rs
├── Cargo.toml

And the following import statement in main.wgsl:

import utils::math;

Here's how wgsl_bindgen resolves the import path:

  1. The function first checks if the import module name (utils::math) starts with the module prefix. If a module prefix is set and matches, it removes the prefix and treats the rest of the import module name as a relative path from the entry source directory converting the double semicolor :: to forward slash / from the directory of the current source file (src/shaders).
  2. If the import module name does not start with the module prefix, it treats the entire import module name as a relative path from the directory of the current source file. In this case, it will look for utils/math.wgsl in the same directory as main.wgsl.
  3. The function then retur

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Method 238
Function 218
Class 93
Enum 16
Interface 6

Languages

Rust100%

Modules by API surface

example/src/shader_bindings.rs58 symbols
wgsl_bindgen/src/quote_gen/rust_struct_builder.rs35 symbols
wgsl_bindgen/src/generate/shader_module.rs30 symbols
wgsl_bindgen/src/generate/bind_group/single_bind_group.rs24 symbols
wgsl_bindgen/src/structs.rs23 symbols
wgsl_bindgen/src/quote_gen/rust_module_builder.rs22 symbols
wgsl_bindgen/src/bindgen/options/mod.rs19 symbols
wgsl_bindgen/src/generate/entry.rs18 symbols
wgsl_bindgen/src/generate/bind_group/mod.rs18 symbols
wgsl_bindgen/src/lib.rs17 symbols
wgsl_bindgen/src/wgsl.rs16 symbols
wgsl_bindgen/src/quote_gen/rust_type_info.rs16 symbols

For agents

$ claude mcp add wgsl-bindgen \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page