Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wgpu Linkage Overview

wgpu linkage is the bridge between a #[wgsl] module and a running wgpu pipeline. Given a shader module, the linkage analyzer produces everything wgpu needs to create buffers, bind groups, and pipelines, with sizes computed per WGSL §14.4.1 (not Rust’s sizeof).

Enabling linkage

Linkage lives in the wgsl-rs-linkage-wgpu crate, separate from wgsl-rs (which stays wgpu-free). The default features give you the latest supported wgpu major, exposed at the crate root:

[dependencies]
wgsl-rs = "0.1"
wgsl-rs-linkage-wgpu = "0.1"

Pinned to an older wgpu major? Disable the default and enable the matching module:

[dependencies]
wgsl-rs-linkage-wgpu = { version = "0.1", default-features = false, features = ["v29"] }
#![allow(unused)]
fn main() {
// Latest: everything at the crate root.
use wgsl_rs_linkage_wgpu::{analyze_wgsl_module, analyze_ir_module, WgpuLinkage};

// Pinned: the same API under the matching module.
use wgsl_rs_linkage_wgpu::v29::analyze_wgsl_module;
}

Both majors may be enabled at once (feature unification); the modules coexist with distinct, incompatible wgpu types, and the crate root re-exports the latest enabled major.

See Cargo Features.

WgpuLinkage

WgpuLinkage is the main type. It owns the concrete IR it was built from and renders the WGSL source via ir::render_module. You get one of two ways:

#![allow(unused)]
fn main() {
use wgsl_rs_linkage_wgpu::{analyze_wgsl_module, analyze_ir_module, WgpuLinkage};

// From a concrete (non-template) Source:
let linkage: WgpuLinkage = analyze_wgsl_module(&my_shader::WGSL_SOURCE)?;

// From an instantiated template's IR (see [Template Modules & Instantiation](../generics/templates.md)):
let concrete_ir: ir::Module = my_shader::instantiate::<f32, u32>();
let linkage: WgpuLinkage = analyze_ir_module(concrete_ir);
}

Concrete IR modules also expose generate_linkage() — an extension trait method (IrModuleExt) exported from the linkage crate’s root — as a shorthand for analyze_ir_module. For a Source, use analyze_wgsl_module:

#![allow(unused)]
fn main() {
use wgsl_rs_linkage_wgpu::*;

// From a concrete IR module (e.g. a template instantiation):
let linkage = my_shader::instantiate::<f32, u32>().generate_linkage();

// From a Source:
let linkage = analyze_wgsl_module(&my_shader::WGSL_SOURCE)?;
}

What WgpuLinkage provides

MethodReturnsDescription
wgsl_source()StringThe rendered WGSL text.
shader_module(device)wgpu::ShaderModuleCompiled shader module (no &Module arg needed).
vertex_entry(name)Option<&EntryPointInfo>Vertex entry point info.
fragment_entry(name)Option<&EntryPointInfo>Fragment entry point info.
compute_entry(name)Option<&ComputeEntryInfo>Compute entry point info (workgroup size).
bind_group(n)Option<&BindGroupInfo>Bind group at @group(n).
bind_groups&BTreeMap<u32, BindGroupInfo>All bind groups.
create_bind_group_named(group, device, resources)Result<wgpu::BindGroup, Error>Creates a bind group by binding names, caching the layout.
pipeline_layout(&mut self, device, label)wgpu::PipelineLayoutPipeline layout, lazily cached.
buffer(name)Option<&BufferDescriptorInfo>Find a buffer by its binding name.

See Bind Groups & Buffers and Pipeline Layouts for detail.

High-level workflow

  1. Write your shader in a #[wgsl] module.
  2. Build the WgpuLinkage (via generate_linkage() or analyze_*).
  3. Create the wgpu::ShaderModule from the linkage.
  4. Create the buffers and bind groups you need.
  5. Create the pipeline layout (cached) and the render/compute pipeline.
#![allow(unused)]
fn main() {
use wgsl_rs_linkage_wgpu::*;

let mut linkage = analyze_wgsl_module(&my_shader::WGSL_SOURCE)?;
let shader = linkage.shader_module(&device);

let frame_bg = linkage.create_bind_group_named(0, &device, &[
    ("FRAME", frame_buffer.as_entire_binding()),
])?;

let layout = linkage.pipeline_layout(&device, Some("main_layout"));
let pipeline = device.create_render_pipeline(wgpu::RenderPipelineDescriptor {
    label: Some("main"),
    layout: Some(&layout),
    vertex: linkage.vertex_entry("vs_main").unwrap().vertex_state(&shader),
    fragment: Some(
        linkage.fragment_entry("fs_main").unwrap().fragment_state(
            &shader,
            &[Some(wgpu::ColorTargetState {
                format: surface_format,
                blend: None,
                write_mask: wgpu::ColorWrites::all(),
            })],
        ),
    ),
    primitive: wgpu::PrimitiveState::default(),
    depth_stencil: None,
    multisample: wgpu::MultisampleState::default(),
    multiview_mask: None,
    cache: None,
});
}

Sizing

Buffer and binding sizes are computed from the IR using WGSL §14.4.1 alignment and size rules, not Rust’s size_of. This means a Rust struct with padding or a different layout from its WGSL equivalent still gets the right wgpu binding size. See Memory Layout for the underlying trait machinery.

Layout caching

WgpuLinkage lazily caches wgpu::BindGroupLayouts (per group index) and the wgpu::PipelineLayout. Methods that create these take &mut self; the returned wgpu types are Arc-backed, so cloning is cheap. You typically keep one WgpuLinkage per shader and call the &mut self methods once at pipeline-construction time.

Migrating from the linkage-wgpu feature

Earlier betas shipped linkage inside wgsl-rs behind a linkage-wgpu cargo feature. It now lives in its own crate so users on different wgpu majors can each get matching types. To migrate:

  1. Remove the linkage-wgpu feature from your wgsl-rs dependency.
  2. Add a dependency on wgsl-rs-linkage-wgpu (default features = the latest wgpu major; enable the vNN feature matching your pinned major otherwise).
  3. Replace wgsl_rs::linkage::wgpu::X with wgsl_rs_linkage_wgpu::X (or wgsl_rs_linkage_wgpu::vNN::X when pinned).

wgsl-rs itself no longer depends on wgpu at all; everything else (#[wgsl], std, dispatch, validation) is unchanged.