Synchronization
Demonstrates synchronization builtin functions for compute shaders: workgroup_barrier, storage_barrier, and workgroup_uniform_load. These coordinate memory visibility and execution ordering across invocations within a workgroup and must only be called from compute entry points in uniform control flow.
Rust Source
#[wgsl]
pub mod synchronization_example {
//! Demonstrates synchronization builtin functions for compute shaders.
//!
//! These functions coordinate memory visibility and execution ordering
//! across invocations within a workgroup. They must only be called from
//! compute shader entry points in uniform control flow.
use wgsl_rs::std::*;
workgroup!(SCRATCH: [u32; 64]);
storage!(group(0), binding(0), INPUT: [u32; 64]);
storage!(group(0), binding(1), read_write, OUTPUT: [u32; 64]);
#[compute]
#[workgroup_size(64)]
pub fn main(#[builtin(local_invocation_index)] local_idx: u32) {
// Copy input data into workgroup-shared memory.
get_mut!(SCRATCH)[local_idx as usize] = get!(INPUT)[local_idx as usize];
// Ensure all workgroup memory writes are visible to every invocation.
workgroup_barrier();
// Read from a neighbor's slot (with wrap-around) to demonstrate
// that the barrier made all writes visible.
let neighbor_idx: u32 = (local_idx + 1u32) % 64u32;
let neighbor_val: u32 = get!(SCRATCH)[neighbor_idx as usize];
// Ensure all storage writes from the workgroup are complete
// before writing results.
storage_barrier();
get_mut!(OUTPUT)[local_idx as usize] = neighbor_val;
}
#[compute]
#[workgroup_size(64)]
pub fn uniform_load_example(#[builtin(local_invocation_index)] local_idx: u32) {
// Each invocation writes its index into shared memory.
get_mut!(SCRATCH)[local_idx as usize] = local_idx;
// Ensure all writes are visible before uniform load.
workgroup_barrier();
// Uniformly load the first element across the entire workgroup.
// All invocations receive the same value.
let first: [u32; 64] = workgroup_uniform_load(&SCRATCH);
get_mut!(OUTPUT)[local_idx as usize] = first[0];
}
}
Generated WGSL
var<workgroup> SCRATCH: array<u32, 64>;
@group(0) @binding(0) var<storage, read> INPUT: array<u32, 64>;
@group(0) @binding(1) var<storage, read_write> OUTPUT: array<u32, 64>;
@compute @workgroup_size(64) fn main(@builtin(local_invocation_index) local_idx: u32) {
SCRATCH[u32(local_idx)] = INPUT[u32(local_idx)];
workgroupBarrier();
let neighbor_idx: u32 = (local_idx + 1u) % 64u;
let neighbor_val: u32 = SCRATCH[u32(neighbor_idx)];
storageBarrier();
OUTPUT[u32(local_idx)] = neighbor_val;
}
@compute @workgroup_size(64) fn uniform_load_example(@builtin(local_invocation_index) local_idx: u32) {
SCRATCH[u32(local_idx)] = local_idx;
workgroupBarrier();
let first: array<u32, 64> = workgroupUniformLoad(&SCRATCH);
OUTPUT[u32(local_idx)] = first[0];
}
Notes
workgroup_barrier()maps toworkgroupBarrier;storage_barrier()maps tostorageBarrier.workgroup_uniform_load(&SCRATCH)maps toworkgroupUniformLoad(&SCRATCH).- These functions must only be called from compute entry points in uniform control flow.