The pipeline
On this page

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.

StageWhat it is handedWhat it produces
VertexOne 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.
FragmentThe values the vertex stage produced, weighed for this fragment, and the fragment’s own position.A value for each colour attachment the pipeline declares.
ComputeIts 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
}
@vertex
export function vs(@builtin("vertex_index") vid: u32, vin: VsIn): vec4 {
return camera.view * vec4(vin.position, 1)
}
@fragment
export 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

Next in this path: WebGPU and WebGL2, which divides the work between the host application and the compiler.

Edit this page Report a problem