Functions: from TypeScript functions to GPU functions
Functions are the basic unit for naming computation and making inputs and outputs explicit. TypeShade keeps the TypeScript function shape, but every function in a "use typeshade" file must describe computation that can be lowered to GPU IR.
1. Anatomy of a function
A function has a name, parameter list, return type and body. TypeShade keeps that familiar structure while giving parameters and return values GPU semantics.
function addBias(value: f32): f32 { return value + 0.5}Here value is the input parameter, f32 is the GPU type of both the input and result, and return produces the value for the caller.
2. Parameters and return types
A parameter is an input to the function and the return type describes the shape of its result. Reassigning a value parameter changes a local copy for that invocation and leaves the caller’s value unchanged. This applies to helpers, methods and stage inputs. Types tell the compiler which values and operations are valid.
function mix(a: vec4, b: vec4, amount: f32): vec4 { return a + (b - a) * amount}| Part | Role |
|---|---|
a, b | GPU input values. |
amount: f32 | A scalar input whose type participates in expression checking. |
: vec4 | The GPU value shape returned to the caller. |
3. Helper functions
A top-level function without a stage decorator is a helper. Helpers let you name repeated calculations and keep shader entries focused on pipeline inputs, resources and outputs.
function saturate(x: f32): f32 { return clamp(x, 0.0, 1.0)}
function remap(x: f32): f32 { return saturate(x * 2.0 - 1.0)}A helper has no stage decorator because it is not a pipeline entry. This keeps reusable math separate from the pipeline interface.
4. Calling a function
A call looks like an ordinary TypeScript call, but its callee and arguments must stay inside the TypeShade GPU type model. Do not treat the shader as a place to call arbitrary JavaScript APIs.
function addBias(value: f32): f32 { return value + 0.5}
function shade(value: f32): f32 { const biased = addBias(value) return clamp(biased, 0.0, 1.0)}The call looks like a normal function call, but both the argument and the result of addBias must be values supported by the TypeShade GPU model.
5. Shader entry functions
A pipeline entry is declared as a top-level export function with a stage decorator. @compute, @vertex and @fragment attach the function to a GPU execution stage.
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { pixels[gid.x] = addBias(camera.pos.x)}export keeps the familiar module surface and exposes the function to the compiler as an entry candidate. The stage decorator adds the GPU-specific stage information.
6. Builtins are parameters
GPU-provided stage inputs are explicit function parameters, not hidden global variables. Reading the signature tells you exactly which external inputs the entry expects.
@compute([64, 1, 1])export function paint( @builtin("global_invocation_id") gid: vec3u) { const i = gid.x pixels[i] = pixels[i] + camera.pos.x}| Form | Meaning |
|---|---|
@builtin("global_invocation_id") | Selects the GPU-provided compute input. |
gid: vec3u | The TypeShade type and local name for that input. |
gid.x | Reads the x component for the current invocation. |
7. Reading a compute entry
@compute([64, 1, 1]) declares the workgroup size. gid is a parameter receiving the global_invocation_id builtin, and gid.x reads the current invocation’s x coordinate.
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { const i = gid.x pixels[i] = pixels[i] + camera.pos.x}When reading this function, first identify the stage and workgroup size, then inspect the parameters for external GPU inputs, and finally follow the body’s calculation.
8. Vertex and fragment entries
Graphics stages use the same function model. The decorator selects the stage, while parameters and the return type describe the pipeline interface.
@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)}The vertex function has two different inputs: vid is a GPU builtin and vin is a user-defined struct. The fragment function makes its builtin input, pid, explicit as well, so the signature documents the stage interface.
9. Functions and scope
Local variables belong to the current function invocation. Parameters and body locals may shadow module values; a closure resolves the nearest declaration. Duplicate declarations in the same scope remain errors. A variable or parameter may use a legal TypeScript name such as `target`; the shader writer escapes names its backend reserves. Resource names, entry names and struct field names retain their interface naming rules.
function exposure(color: vec3, amount: f32): vec3 { const factor = amount + 1.0 return color * factor}factor is a local value scoped to the function. Resources such as camera and pixels belong to the host-facing shader interface, while builtin parameters are inputs supplied by the GPU stage.
10. Where TypeScript functions stop
- Do not call arbitrary JavaScript runtime APIs.
- Do not assume dynamic object creation or general runtime side effects can become shader computation.
- Entry points are top-level exported functions, not class methods.
- Builtins are explicit parameters, not implicit globals.
- Parameters and return types must have valid GPU value semantics.
11. From a small helper to a real entry
The example below keeps reusable math in a helper and lets the entry connect the builtin input and resources.
"use typeshade";
function addBias(value: f32): f32 { return value + 0.5;}
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { const value = camera.pos.x; pixels[gid.x] = addBias(value);}The important distinction is the boundary: addBias is reusable computation, while paint connects the stage, builtin input and resources to an actual GPU invocation.