1 of 47

WESL for the WGSL Team

community extended WGSL

2 of 47

Overview

WESL intro - features, status, next steps

Conditions and imports - WESL today, WGSL tomorrow?

Possible WGSL tweaks? - const_assert, attributes into grammar

Future features of interest:

  • reflection, generics, namespaces
  • polyfills: swizzles, binding structs, etc.
  • library modularity: overrides, bindings, entry points, uniforms, conditions

Helping WESL - advice, partner intros, spread the word

2

3 of 47

WESL M1 Highlights

3

4 of 47

WESL Demo - WGSL with import and @if

4

mandelbrot.wesl

@fragment

fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {

let mandelbrotEscaped = package::mandelbrot::mandelbrot(...);

let color = mandelbrotEscaped;

return vec4(vec3f(color), 1.0);

}

main.wesl

import package::fullscreen_quad::{fullscreen_quad, QuadVertex};

@if(DEBUG)

let color = sinRand(vec2f(mandelbrotEscaped));

@if(!DEBUG)

import random_wgsl::sinRand;

5 of 47

WESL M1 - Key Features

  1. Modules
  2. Conditions
  3. Libraries

(+ some experimental features, like const injection, code gen virtual modules)

5

WESL is a compatible superset language for WGSL

6 of 47

Adding WESL is easy

use wesl::Wesl;

let wgsl_str = Wesl::new("src/shaders")

.compile("main.wesl")

.unwrap()

.to_string();

import { link } from "wesl";

import appWesl from "./shaders/app.wesl?link";

const linked = await link(appWesl);

linked.createShaderModule(device, {} );

6

Rust

JavaScript / TypeScript

7 of 47

WESL is Ready to Try

7

8 of 47

Can Pioneer Shader Ergonomics in WESL

Build new shader language features in user space

Successful features could graduate to WGSL

8

Idea

WESL extension

WESL

stable

WGSL

Custom Project

9 of 47

Imports

import package::lighting::VertOut;

import bevy_pbr::{

forward_io::VertexOutput,

pbr_types::{PbrInput, pbr_input_new},

pbr_bindings

};

fn main() {

ui_lib::quad(vec2f(0.0, 1.0), 1.0);

import random_wgsl::sinRand;

9

10 of 47

Import System Details

Users expect side effect free imports..

  • But host visible global wgsl elements are side effects

Examples (w/ WESL approach for local modules.. Libraries tbd)

const_assert (include module level const_asserts if element imported)

enable (must agree with root module enable, else error)

override, var (include iff transitively referenced from root)

entry points (prefer root only for now, tbd)

Mapping of import paths to files is efficient (no file probes required)

10

11 of 47

Module System Future Features

Visibility control

  • currently public by default (for WGSL compat), expect to add private

Re-exports

  • Export functions and modules under new names
  • useful for large projects like Bevy, to maintain API stability independent of filesystem

Namespace integration - e.g. each module would get a namespace...

11

12 of 47

A Module is just Shader Text with a Name

12

import { link } from "wesl";

const utils = `const foo = 7;`;

const main = `

import package::utils::foo;

fn main() { let a = foo; }

`;

const weslSrc = { main, utils };

const linked = await link({ weslSrc });

13 of 47

Shader Libraries on NPM and Cargo

Packages are simple: WESL/WGSL text with metadata. (tools available)

Try making a simple shader library and share it with the world!

13

14 of 47

Conditions

conditional compilation

syntax safe

build time and/or runtime conditions (cf. the shader permutations problem)

@if(legacy_implementation || (is_web_version && xyz_not_supported))

let result = legacy_impl();

14

15 of 47

Any Questions on WESL M1?

15

16 of 47

Roadmap Sketch - Driven by Community

16

Time

Language Server

Linkers (rust, js)

Syntax Highlighter

WESL

Tools

Doc

WGSL Lib Portability

Rich Libraries

Doc Generator

Type Checking

Polyfills

Generics

Interfaces

App Directed Linking

Inheritance

Namespaces

Advanced Libraries

Bundler Plugins

Simple Libraries

Conditionals

Modules

User Documentation

Shared Test Suite

Specification

~

17 of 47

WESL M1 - Starting Engagement Now

Polish our M1 release (docs, error reporting, etc.)

Engage with first users (bevy, lygia, …)

Suggestions on whom to engage?

Any upcoming demo projects that we could help WESLify?

17

18 of 47

Minor WGSL Tweaks

(minor ideas to make things a little better for WESL)

18

19 of 47

Attributes in More Places

@if is allowed in lots of places, many more places than WGSL attributes

WESL has to modify the grammar pervasively

Maintenance issue for WESL

  • Track WGSL changes over time
  • Mark WESL additions in our grammars

Perhaps WGSL grammar to allow attributes in more places?

  • (we can file an issue to discuss details if so)

19

20 of 47

const_assert in Unreachable Functions?

20

const random = 4; // from fair dice roll

@if(MOBILE)

const NUM_LIGHTS = 4;

fn unused() {

const_assert(NUM_LIGHTS > 4);

}

util

import super::util::random;

fn main() -> u32 { return random; }

main

unused assert breaks expectations

can WGSL evolve?

should WESL diverge?

21 of 47

Source maps

We’re using private source maps for remapping naga/tint error messages.

  • Trickery via injected WebGPU error scopes and injected browser src maps. ht stefnotch / greggman

21

22 of 47

22

23 of 47

23

24 of 47

Source Maps (cont)

We’d like to pass our source maps to WebGPU eventually..

  • So downstream tools can see WESL src
  • e.g. any shader debuggers coming that display WGSL?

Uncertain priority. Hackery sufficient for naga/tint errors, hopefully.

24

25 of 47

Future WESL Features

25

26 of 47

Reflection

Users continue to ask for reflection

Partner prototypes for reflection are underway using WESL as library

  • currently host lang specific (TypeScript)
  • different approaches to host integration in each

What would make sense for reflection in core WebGPU?

26

27 of 47

TypeGPU Reflection

27

struct BoidState {

position: vec3f,

velocity: vec3f,

}

struct Fish {

kind: u32,

state: BoidState,

}

type-safe data buffers

automatic JS value serialization

optimised close to vanilla performance

// Generated code

export const BoidState = d.struct({

position: d.vec3f,

velocity: d.vec3f,

});

export const Fish = d.struct({

kind: d.u32,

state: BoidState,

});

Generates

import {

BoidState,

Fish

} from './main.wesl?typegpu';

28 of 47

Namespaces

Not enabling for WESL implementations, we need ident binding anyway

Namespaces within files are bonus, but file modularity is the main thing.

We think namespaces can integrate with module system

  • System namespace #4308 would be helpful (corner case)
  • Should we write a proposal?

Uncertain priority.

28

29 of 47

Polyfills

What polyfills are most valuable to WGSL team and WebGPU users?

#4957 binding structs (prototyped)

swizzles

function overrides

generalized aliases

if-expressions/ternary

29

30 of 47

Proposed for WGSL: Binding Structs

import package::uniforms::Uniforms;

struct MyBindings {

@group(0) @binding(0)

particles: ptr<storage, array<u32>, read_write>,

@group(0) @binding(1)

uniforms: ptr<uniform, Uniforms>,

}

@workgroup_size(1)

@compute fn main(b: MyBindings) {

b.particles[0] = b.uniforms.foo;

}

@group(0) @binding(0)

var<storage, read_write> particles : array<u32>;

@group(0) @binding(1)

var<uniform> uniforms : Uniforms;

@workgroup_size(1)

@compute fn main() {

particles[0] = uniforms.foo;

}

struct Uniforms { foo: u32 }

30

import { layouts } from "./app.wesl?bindingLayout";

device.createBindGroupLayout({entries: layouts.myBindings});

WGSL

Proposed WGSL

Reflection

31 of 47

Conditions Alone Get Complicated – Bevy Example

@if(!MAY_DISCARD)

fn prepass_alpha_discard(in: VertexOutput) { }

@if(MAY_DISCARD)

fn prepass_alpha_discard(in: VertexOutput) {

@if(BINDLESS)

let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;

@if(VERTEX_UVS)

{

let uv_transform = pbr_material.uv_transform;

uv = (uv_transform * vec3(uv, 1.0)).xy;

31

32 of 47

What @else should we explore?

Remove known dead conditionals

if(constant TRUE) { ... } else { drop at compile time }

  • Removes imports from dead condition too

Generics

Something else?

32

33 of 47

Generics

Advanced users and library author feature.

Requires type-inference (already in wesl-rs)

Several old WESL proposals, no consensus yet. (e.g. generics on namespaces?)

What should we look at?

33

34 of 47

Portability for Libraries

WESL M1 supports fn libraries on npm and cargo (yea!)

We need modularity for ‘host code visible’ WGSL/WESL constructs in libraries

  • namespacing/gating for override and entry point names
  • libraries add to shared uniforms
  • binding numbers. namify? use.gpu style data getter abstraction?
  • @if conditions

34

35 of 47

WGSL Portability: Bindings for libraries?

@group(0) @binding(0) var<uniform> light: DirectionalLight;

35

Which group?

Which binding?

1/12 of max uniform buffers?

Future work for richer portable libraries. WESL for Libraries

36 of 47

Future Tooling

36

37 of 47

Upcoming Tooling

Language server: wgsl_analyzer is understaffed but adding support for WESL (we will contribute)

Syntax Highlighter - possibly based on new WESL tree sitter grammar?

Doc Generator - possibly, user/contributor interest

Online Playground - import libraries, possibly wesl library outputs

37

38 of 47

Improvements to WESL Internals

Type Inference

  • needed for future features like generics (or better binding struct polyfill)
  • implemented in wesl-rs already

Error Reporting

  • Users see WESL errors first
    • so errors need to be high quality.
  • Errors from WGSL still central (we just map locations to WESL src)

Any tips for us?

38

39 of 47

Conclusion

39

40 of 47

Support Common Tooling for WebGPU!

Connect to Users help spread the word, help introduce to partner projects.

Try WESL in your demo app, make a simple library, we’ll help.

Review our M1 features - possibly future WGSL features?

Keep in Touch lots we can do together!

40

41 of 47

Extra Slides

41

42 of 47

wesl-js Runtime Linking

42

Parse

Bind

Emit

wgsl

wgsl & wesl

linking

pkg

import { link } from "wesl";

import app from "./app.wesl?link";

const linked = link(app);

linked.createShaderModule(device, {});

build time

runtime

43 of 47

wesl-js Build-time Linking

43

Parse

Bind

Emit

wgsl

wgsl & wesl

linking

pkg

import code from "./app.wesl?static";

weslDevice.createShaderModule({code});

build time

44 of 47

wesl-js Runtime Linking (Future)

44

Parse

Bind

Emit

wgsl

wgsl & wesl

pkg

import { blink } from “wesl”;

import app from “./app.wesl?blink”;

const linked = blink(app);

linked.createShaderModule(device, {});

build time

runtime

45 of 47

WESL runs in user space

45

Browser

WGSL

HLSL

MSL

SPIR-V

Driver

WESL

Tools &

Libraries

GPU

46 of 47

Name mangling

Falls into categories

  • HashMap: Keep track of all emitted names, and deal with duplicates & illegal names. _1, _2, _3 is an example
  • Encoding: Does not need to be human readable. base64 is an example
  • Escaping: Pick an escape character, and use that. underscore count is an example

Currently WESL implements escaping and hashmap.

https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/NameMangling.md#underscore-count-mangling

46

47 of 47

overrides in libs

What should their public name be?

Heck, what should happen when one does

import mylib::someOverride;

import mylib::someOverride as otherName;

import mylib::someOverride as yetAnotherName;

Ideally all 3 names would be accessible, right? A good host wrapper could make all 3 names accessible and do the rewiring in the background.

47