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 is gated behind the linkage-wgpu cargo feature:
[dependencies]
wgsl-rs = { version = "0.1", features = ["linkage-wgpu"] }
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::SOURCE)?; // From an instantiated template's IR (see [Template Modules & Instantiation](../generics/templates.md)): let concrete_ir: ir::Module = template.instantiate::<f32, u32>()?; let linkage: WgpuLinkage = analyze_ir_module(concrete_ir); }
Source and ir::Module both expose generate_linkage() — an extension
trait method (IrModuleExt) re-exported via wgsl_rs::*:
#![allow(unused)] fn main() { use wgsl_rs::*; let linkage = my_shader::SOURCE.generate_linkage()?; }
What WgpuLinkage provides
| Method | Returns | Description |
|---|---|---|
wgsl_source() | String | The rendered WGSL text. |
shader_module(device) | wgpu::ShaderModule | Compiled shader module (no &Module arg needed). |
entry_point(name) | Option<&EntryPointInfo> | Vertex/fragment/compute entry point info. |
bind_group(n) | Option<&BindGroupInfo> | Bind group at @group(n). |
bind_groups() | &HashMap<u32, BindGroupInfo> | All bind groups. |
create_bind_group_named(group, device, resources) | wgpu::BindGroup | Creates a bind group, caching the layout. |
pipeline_layout(&mut self, device, label) | wgpu::PipelineLayout | Pipeline 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
- Write your shader in a
#[wgsl]module. - Build the
WgpuLinkage(viagenerate_linkage()oranalyze_*). - Create the
wgpu::ShaderModulefrom the linkage. - Create the buffers and bind groups you need.
- Create the pipeline layout (cached) and the render/compute pipeline.
#![allow(unused)] fn main() { use wgsl_rs::*; let mut linkage = my_shader::SOURCE.generate_linkage()?; 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(&mut self, &device, Some("main_layout")); let pipeline = device.create_render_pipeline(wgpu::RenderPipelineDescriptor { label: Some("main"), layout: Some(&layout), vertex: linkage.entry_point("vs").unwrap().vertex_state(&device, &[], &[]), fragment: Some(wgpu::FragmentState { module: &shader, entry_point: linkage.entry_point("fs").unwrap().name(), targets: &[Some(wgpu::TextureFormat::Bgra8Unorm.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: 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.