Wgslender: My WGSL Toolchain

hugodan1 pts0 comments

wgslender: My WGSL Toolchain

-->

Skip to main content

In this post I am introducing wgslender, the WGSL toolchain that I have been using locally and that has been growing along side some demoscene work.

I have been on this for about 8 months now. It has grown out of the pains of doing the "tangatos" 8K demo for Revision, as well as the more recent "impulsos" full demo.

I will try to angle this on the idea and motivation side, wgslender comes from having spent countless hours writing shaders and then running the minifier in the terminal to check what causes size increases while rethinking compression approaches and what to reuse in the demos and also in the tooling. In a world where every function is weighted in byte costs, reusability is king.

The method of production is itself part of what makes each thing what it is

wgslender was built with heavy LLM assistance, while the demos are handmade, because after all the massage is still the medium.

Multics

Wgslender packs a lot of stuff, it can do:

minification

validation (type-inference, and compilation like)

reflection

linter

binary shader string generation

While running in:

LSP

CLI tool

Browser (wasm/js)

Your code (as a lib)

Yes, thats it. A toolchain that does all that, and that can run everywhere. Everywhere as in, you do not need WebGPU at all to get compiler errors or get insights. It can run equally well in the CLI or in the browse or in your code, and has an LSP for in-editor insights and navigation.

Why everything at once?

I've found that in my workflows these things tend to live together or quite close to each other, and a lot of customizations and nuances that arise from usage could benefit all of these at once.

In wgslender there is one parser that all of them walk with the same identical lexer->parser->analysis flow. Also I want to perform combined operations, things like minifyAndReflect that share not only the same parser but also the analysis, ensuring that we get a stable report by construction not by rederiving it after the mangling.

Sharing code paths is a powerful design for this particular toolchain grounded in trees traversal, it allows for interchangeable operations and a really powerful LSP that can reparse incrementally without having to go through the whole parsing loop again.

The LSP

The language server is not glued with spit on top of the tool but part of its design from the start. It operates on the same in-memory AST/CST as every other tool, the parser keeps a lossless syntax tree next to the AST so the LSP doesn't reparse the shader from scratch as you type (it reparses just the region where the edit landed in and splices the new subtree into the existing trees). It fallsback to a full reparse when edits that don't fit this fast track shape, using the same parser. What comes out is the same module every other tool consumes and with the same pipeline passes run over it: the squiggles are the validator diagnostics, the byte costs are the minifier estimator, computed on the tree that was just spliced.

It is a simple solution that can give me the minification overview while the shader is being written, saving me from having to rerun the CLI tool whenever i'm in doubt on how much a block of code would cost (in bytes, space).

It shares the same correctness and validation ground as the other tools, the output is consistent with what the validator compiler does, the types being shown are the same as the typechecker, etc... you get the idea.

Oh yeah, and it runs in the browser, avoiding me from having to context switch to the CLI to do the minification and reflection and analysis while working in a WebGPU context.

The best part

Use the CLI and run wgslender validate (or the LSP, or the lib in your code, all the same):

Compilation errors and validation in the CLI without having to acquire a WebGPU device and create a ShaderModule. This my friends is a life-saver for me!

All offline, without a navigator.gpu or a browser forcing us into weird async round-trips at specific points of execution.

This means that you get to see those let x: i32 = 1.0; errors at the speed of typing.

typeerr.wgsl:2:9: error: cannot initialize 'x' with type 'abstract-float' (expected 'i32') [E0200]

Not only that but also does the WGSL uniformity analysis. Calling workgroupBarrier(); from within an if condition that varies at each invocation? error: barrier function must only be called from uniform control flow [E0701].

It dramatically reduces the kind of stuff you would otherwise discover through WebGPU shader compilation or pipeline diagnostics.

Go ahead, you don't even need to install it, fire up your terminal and npx wgslender validate shader.wgsl.

The second best part

When coding WGSL I tend to keep an open tab at the WGSL builtins page, why? click on it and find out.

It's a lot for my head, or as we say in Portugal, its a lot of sand for my truck, dozens of builtins, most with several overloads over generic T/vecN...

wgslender wgsl from toolchain having while

Related Articles