-
Notifications
You must be signed in to change notification settings - Fork 1
WESL Extensions
WESL adds features to WGSL:
- Imports allow splitting code in multiple files.
- Packages are collections of general-purpose shader modules published to npm (JavaScript) and crates.io (Rust).
-
Conditional Translation is a mechanism to conditionally
include/exclude chunks of code. Similar to
#ifdef(C/C++) or#[cfg](Rust). - (experimental) Const Injection
- (experimental) Virtual Modules
In WESL, shader code is divided into modules. Modules have addressable names called module paths. Module paths form a tree heirarchy.
File and directory names names in applications map automatically into the
module path hierarchy.
Typically, each module comes from a .wesl or .wgsl file.
The module's path comes from the filesystem path, with :: as a separator.
Applications storing shader code in strings can label module strings with a module path.
npm packages and cargo crates containing shaders also map into the module path heirarchy.
The module path for a package starts with the package name, followed by ::.
The application's own shader modules are addressable with the reserved prefix
package::.
import sdf::primitives::box; // fn `box` in module `primitives` in lib package `sdf`
import package::binds::mySampler; // var `mySampler` from file `./shaders/binds.wesl`
import super::util; // sibling module `./util.wesl`
import package::animals::{ // ok to nest imports with `{ }`
bird,
mammals::{ dog, cat }
};- Import statements bring WGSL declarations and other modules into scope, so that subsequent statements may reference elements from other modules conveniently.
- Import statements have no side effects on their own.
- Import statements may placed in any WESL file, and must be at the top of the file.
- All top-level declarations in a file may be imported:
functions, structs, variables, consts and aliases.
Modules may also be imported, as with
super::utilin the example above.
-
super- move up one level in the module hierarchy. -
package- start at the root level of the application module hierarchy -
constants- shader const declarations injected from host Rust or JavaScript
WESL also allows a module paths to elements in the shader code
wherever identifiers are usable.
If foo() is grammatical, then so is super::util::foo().
Qualified names inline in the shader don't require a corresponding import statement.
import package::math;
fn main() {
package::foo::bar::my_fn(); // import statement not required.
let x = math::constants::PI; // extend from imported module `math`
}Linking begins from an application root module. The assembled shader includes only modules transitively referenced by the root module. (The application developer is responsible for specifying the root module when linking.)
Element names in the application root module are guaranteed not be mangled during linking.
Developers should place shader elements that are visible from host code
into the root module. Host code visible shader elements are:
entry points (@compute, @fragment, etc.), override, and @binding var.
(Future versions of WESL will relax this restriction.)
Global const_assert statements in a module are included only if an
element from that module is transitively referenced from the root module.
Global enable statements in a module are included only if an
element from that module is transitively referenced from the root module.
A corresponding enable statement must be present in the root module.
Function const_assert statements are included only if
that function is transitively referenced from the root module.
See the Imports spec for futher details.
The @if(condition) attribute makes the following element
optionally included in the final shader, depending on the condition.
The condition is determined by of feature flags which are set when you invoke the linker (wesl-js or wesl-rs).
@if(debug_mode && !raytracing_disabled) // attributes on top-level declarations
var<private> debug_count_iterations: i32 = 0;
@if(is_2d)
alias point = vec2f;
@if(!is_2d)
alias point = vec3f;
struct Line {
start_point: point, end_point: point,
@if(colored) color: vec4f, // attributes on struct members
}
fn fibonacci(n: u32) {
@if(use_lookup_table) { // attributes on blocks `{ }`
const lut = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144);
return lut[n];
}
// ...
}The condition is a boolean expression containing:
feature names, && (and), || (or), ! (not), true and false.
You can place attributes on declarations (fn, struct, var, const, etc.),
statements, struct members, and function parameters.
See the Conditional Translation spec for futher details.
The linkers allow host code (Rust or TypeScript/JavaScript)
to define shader constants.
The constant values will typically be available in the module heirarchy with
the constants:: prefix.
See:
- in Rust, set_custom_resolver
- in JavaScript/TypeScript, Runtime Constants
The linkers allow host code (Rust or TypeScript/JavaScript)
to provide a function that generates WESL shader code.
The generated code will be available in the module heirarchy with
a custom prefix provided by the caller.
e.g. MyGenerated::.
- in JavaScript/TypeScript, Virtual Modules
- in Rust, VirtualResolver