The pipeline
A shader never runs on its own. It sits at one of the fixed points of a GPU pipeline, and that point decides what the entry point is handed and what it has to give back. This page describes the points TypeShade emits for, and leaves the syntax to the language guide.
Stages
Each row is one stage of the pipeline the host set up.
| Stage | What it is handed | What it produces |
|---|---|---|
| Vertex | One vertex of the draw: its index in the draw, and the fields the host laid out in the vertex buffers. | A clip-space position, and the values the fragment stage will read. |
| Fragment | The values the vertex stage produced, weighed for this fragment, and the fragment’s own position. | A value for each colour attachment the pipeline declares. |
| Compute | Its own place in the dispatch grid, and the resources the host bound. | Nothing given back; a compute entry writes through the resources it holds. |
Entry points
The signature of an entry point is that stage interface written out. A @builtin(...) parameter is a value the stage hands the invocation, such as the vertex index or the fragment position. A struct parameter is per-vertex input in a vertex stage and a weighed value in a fragment stage. The return type is what the stage gives back to the pipeline, so a vertex entry returns a position and a fragment entry returns colour. Shader stages has the decorators and the spellings; this page stays with the meaning.
class VsIn { @location(0) position: vec3 @location(1) uv: vec2}
@vertexexport function vs(@builtin("vertex_index") vid: u32, vin: VsIn): vec4 { return camera.view * vec4(vin.position, 1)}
@fragmentexport function fs(@builtin("position") pid: vec4): vec4 { return vec4(pid.x, 0, 0, 1)}Reading a signature therefore tells you which stage the function belongs to, what the pipeline has to supply, and what the pipeline receives.
Interpolation
Between the vertex stage and the fragment stage the rasterizer works out which fragments a primitive covers. For each of them it weighs the values the vertices produced by how near the fragment lies to each vertex, and hands the fragment stage the result. A vertex entry writes a value per vertex and a fragment entry reads a value per fragment, so the two are different values with the same name.
class VsOut { @builtin("position") pos: vec4 @location(0) uv: vec2 @location(1) id: u32 @location(2) @interpolate("flat") tint: vec3}A float @location field is weighed this way unless @interpolate asks for another mode. An integer cannot be weighed, so the compiler gives id the flat qualifier on both targets without being asked, and the fragment reads the value one vertex of the primitive produced. @interpolate("flat") asks the same for tint. Types states the field decorators.
Compute
A compute stage has no rasterizer in front of it and no attachment behind it. The host dispatches a grid of work items, the entry point reads its own coordinates in that grid from a builtin parameter, and everything it produces goes through a storage resource. Resources states how a writable resource is declared.
declare const pixels: storage<array<f32>, "read_write">
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { pixels[gid.x] = camera.pos.x}Further reading
- WebGPU specification defines the render and compute pipelines these stages belong to.
- MDN GPURenderPipeline and MDN GPUComputePassEncoder show the host code that drives them.
Next in this path: WebGPU and WebGL2, which divides the work between the host application and the compiler.