Extension Examples
NoopExt
The smallest possible extension, useful as a smoke test that the wiring works:
#![allow(unused)]
fn main() {
use wgsl_rs::WgslExtension;
use wgsl_rs::ir;
pub struct NoopExt;
impl WgslExtension for NoopExt {
fn modify_ir(_module: &mut ir::Module) {}
}
}
#![allow(unused)]
fn main() {
#[wgsl(extensions = [my_crate::NoopExt])]
mod shader {
// ...
}
}
SlabItemExt
A derive-driven code generator. It finds structs annotated with #[derive(SlabItem)] and injects slab_read and slab_write helper functions for each.
#![allow(unused)]
fn main() {
use wgsl_rs::WgslExtension;
use wgsl_rs::ir::{Item, Module};
pub struct SlabItemExt;
impl WgslExtension for SlabItemExt {
fn modify_ir(module: &mut Module) {
let slab_structs: Vec<String> = module
.items
.iter()
.filter_map(|item| match item {
Item::Struct(s) => {
let derives_slab = s.attrs.iter().any(|a| {
a.path == "derive" && a.args.iter().any(|arg| arg == "SlabItem")
});
derives_slab.then(|| s.name.clone())
}
_ => None,
})
.collect();
for name in slab_structs {
module.items.push(build_slab_read(&name));
module.items.push(build_slab_write(&name));
}
}
}
fn build_slab_read(struct_name: &str) -> Item {
// Construct an ir::Item::Fn that reads `struct_name` from a slab buffer
// at a given element index.
todo!()
}
fn build_slab_write(struct_name: &str) -> Item {
// Construct an ir::Item::Fn that writes a `struct_name` value into a slab
// buffer at a given element index.
todo!()
}
}
wgsl-rs-layout
wgsl-rs-layout is the first real-world extension crate. It is a standalone crate that depends on wgsl-rs for its types and implements WgslExtension to compute WGSL memory layout for Rust structs. It demonstrates that the extension mechanism is sufficient to build a non-trivial, redistributable tool on top of wgsl-rs without forking the transpiler.
See the Memory Layout section for full coverage of wgsl-rs-layout.
Statement Macro Lowering: LowerMyMacro
An extension can claim a custom statement macro via MACROS and replace Stmt::Macro nodes with lowered IR. This example from the trybuild test suite defines my_macro!() and an extension that lowers it to let result: u32 = 42;:
#![allow(unused)]
fn main() {
use wgsl_rs::{ir, wgsl, WgslExtension};
pub struct LowerMyMacro;
impl WgslExtension for LowerMyMacro {
const MACROS: &'static [&'static str] = &["my_macro"];
fn modify_ir(module: &mut ir::Module) {
for item in &mut module.items {
if let ir::Item::Fn(f) = item {
lower_in_block(&mut f.block);
}
}
}
}
fn lower_in_block(block: &mut ir::Block) {
for i in 0..block.stmts.len() {
if let ir::Stmt::Macro { name, .. } = &block.stmts[i] {
if name == "my_macro" {
block.stmts[i] = ir::Stmt::Local(ir::Local {
mutable: false,
name: "result".to_string(),
ty: Some(ir::Type::Scalar(ir::ScalarType::U32)),
init: Some(ir::Expr::Lit(ir::Lit::Int {
digits: "42".to_string(),
suffix: "u32".to_string(),
})),
});
}
}
}
}
}
The shader module uses my_macro!() in statement position, and the extension’s modify_ir replaces it before rendering:
#[wgsl(extensions = [super::LowerMyMacro])]
mod ext_macro_shader {
pub fn main() -> u32 {
my_macro!();
42u32
}
}
After modify_ir runs, the Stmt::Macro is replaced by Stmt::Local, and the rendered WGSL contains no trace of my_macro.