Rendered from AUTHORING.md at commit cdb12dc0. The package is imported here by its 0.2.0 name, typeshade.
Authoring shaders with typeshade
After this page you know what one TypeScript source turns into, how to import the
compiler, and which page of this guide answers which question. The samples are written
against the source in this repository, and examples/ holds complete shaders written the
same way.
What you write and what comes out
You write a shader as an ordinary TypeScript file whose first statement is the directive
"use typeshade". The file uses TypeScript’s own syntax: function, const and let,
if, for, class, operators and literals. The types are the GPU’s: f32, u32, vec3,
mat4, array<f32, 16>. A decorator such as @fragment marks the function the GPU calls.
A wrong type or a misspelt field is an error in your editor, at the line you wrote.
Three places carry the structure. A top-level export function with a stage decorator
(@vertex, @fragment or @compute) is an entry point: the function the GPU calls, once
per vertex, per fragment or per compute invocation. Every other function is a helper it
calls. A declare const with a type such as uniform<T> or storage<T> is a resource the
host binds. A class, an interface or a type alias is a struct.
"use typeshade";
// One helper. The return type is the one the body returns.export function ringMask(uv: vec2, radius: f32): f32 { return abs(length(uv) - radius);}compile() reads that source and returns the module and its outputs:
wgslis WGSL, the shading language WebGPU accepts.glslis{ vertex, fragment }, GLSL ES 3.00 source for WebGL2, for a module that has a vertex and a fragment entry point.moduleis the compiled module. The other calls of the package read it.diagnosticslists every error and warning, each with its code, line and column.
The module is the input to the rest of the package:
emitModule(m)andemitGlslStages(m)emit it again, under options you choose.compileModule(m)returns the CPU oracle: the same program evaluated in JavaScript with every value a double, which gives you the mathematically intended answer to compare a GPU run against.reflect(m)returns the metadata a host needs to build the pipeline: bind groups, the std140 and std430 byte layouts a uniform or storage buffer has to match, vertex attributes and entry signatures.
import { compile, compileModule, reflect } from 'typeshade'
const { wgsl, diagnostics, module: m } = compile(source) // source: the file above, as textconst cpu = compileModule(m) // cpu.fns.ringMask([0.3, 0.4], 0.5) === 0const meta = reflect(m) // bind groups, layouts, entry signatureswgsl holds this:
fn ringMask(uv: vec2<f32>, radius: f32) -> f32 { return abs((length(uv) - radius));}The names you wrote survive into the output, the parameter types became WGSL types, and the return type came from the body. The optimizer adds bindings of its own where a subexpression repeats, so an emitted body can carry a name you did not write.
A fixed-length array can be read with a local binding pattern: const [first, , last] = xs.
A shorter pattern is allowed; omitted positions still count toward the source length.
const [a, b]: [u32, u32] = [1, 2] supplies an explicit homogeneous tuple type. Without an
annotation, const [a, b] = [1., 2.] uses the same inference as array(1., 2.).
Nested object/array patterns and getters work, with each receiver evaluated once. The bound
values are local copies, and numeric conversions still require explicit constructors.
Rest, defaults, vector iteration and patterns on runtime-sized arrays remain unsupported.
See docs/use-typeshade-surface.md §14 and §18.
Importing
A shader file imports nothing from the package: the types, the builtins and the decorators are the language’s own. Host code imports the compiler and the calls that read its result from the package root:
import { compile, emitModule, emitGlslStages, reflect, compileModule } from 'typeshade'Other subpaths carry surface you do not need in order to compile and emit, and an import you never write costs nothing in your bundle:
typeshade/runtimeloads a compiled program and runs it on WebGPU or on WebGL2, with no compiler in the bundle.docs/use-typeshade-surface.md§69 has it.typeshade/vitehas the Vite plugin through which an ordinary host file imports a.shade.tsand calls its helper functions, which run on the CPU. See Calling a module’s helpers from host code.typeshade/devhas the development tooling: lint reports, optimizer measurement and source locations in errors.typeshade/emit-prodhas the ship-time text plugins that mangle, minify and obfuscate the emitted source.typeshade/computehas the runner that dispatches a portable compute kernel on whichever backend the host has.typeshade/debugsteps one invocation of a"use typeshade"shader on the CPU oracle: it stops at each statement the author wrote, reports the source span and the frame’s locals, and resolves breakpoints by line. It also carries the launch configuration that describes such a run as data, keyed by what the entry declares rather than by argument position, with a baked JSON Schema for alaunch.jsonand a formatter that renders a value in the shader type its author wrote. It is what an editor’s debug adapter and the Playground’s step panel are both built on. Seedocs/debugging.md.typeshade/core/iris the IR builder, for an engine or a tool that assembles a module in code instead of compiling a source. It has no stability promise before 1.0, and this guide does not use it:docs/ir-builder.mddoes.
This package ships the compiler, and the small runtime a host import runs on. The shaders themselves live in your repository and import the package like any other dependency.
How this guide is ordered
The pages are in learning order, one topic each, and reading them in order the first time
is the shortest path. After that, each page stands on its own. Per-function detail lives
on that function’s reference page, and every rule and refusal of the language is in
docs/use-typeshade-surface.md.
The pages that follow, in order:
- Your first shader: write a file with a vertex and a fragment entry point, and compile it for both targets.
- Values and mutation: make a value, give it a type, and change it.
- Functions and entry points: declare a helper, an entry point, and the module that carries them.
- Control flow: branch, loop, dispatch on an integer and return early.
- Layouts and resources: declare an IO struct, a uniform block, a storage buffer or a texture once, and read the fields back with types.
- Emitting and reflection: emit WGSL and GLSL, and read the pipeline metadata a host binds against.
- The CPU oracle: run the same module in double precision and compare its numbers against a GPU run.
- Diagnostics: read a coded error, and collect every failure in a module into one report.
- Conditional programs: build one specialized program per feature combination.
- Capabilities & extensions: declare the GPU features a module’s emit depends on, and check a booted device against them.
- fp64: get double precision on hardware that has only floats.
- GLSL float precision: emit a GLSL stage at mediump, and see what that changes in the source.
- Production emit: mangle, minify and obfuscate the shipped source, and read a driver log back through the renaming.
- Migrating a GLSL shader: look up the GLSL construct in front of you and see how it is spelled here.
If you have never used this package, start with the next page. If you are here to port a shader you already have, read Migrating a GLSL shader first and follow its links back.