fn
On this page

fn()

Function in Authoring

Author a function. One call covers a plain helper and a @vertex, @fragment or @compute entry point.

import { fn } from 'typeshade'

The signature, the description and the examples come from the compiler's own source at commit 26de7be8.

Syntax

function fn<P extends FnParamSpec, R extends string>(
params: P,
body: FnBodyValue<P, R>,
opts?: FnOpts,
): FnHandle<P, R>;
function fn<P extends FnParamSpec, R extends string>(
name: string,
params: P,
body: FnBodyValue<P, R>,
opts?: FnOpts,
): FnHandle<P, R>;
function fn<P extends FnParamSpec>(
params: P,
body: FnBodyVoid<P>,
opts?: FnOpts,
): FnHandle<P, 'void'>;
function fn<P extends FnParamSpec>(
name: string,
params: P,
body: FnBodyVoid<P>,
opts?: FnOpts,
): FnHandle<P, 'void'>;
function fn<P extends FnParamSpec, T extends ShaderType>(
params: P,
ret: T,
body: FnBody<P, KeyOf<T>>,
opts?: FnOpts,
): FnHandle<P, KeyOf<T>>;
function fn<P extends FnParamSpec, T extends ShaderType>(
name: string,
params: P,
ret: T,
body: FnBody<P, KeyOf<T>>,
opts?: FnOpts,
): FnHandle<P, KeyOf<T>>;

Parameters

params P extends FnParamSpec

the parameter record, in declaration order, as described above.

body FnBodyValue<P, R>

the function body, receiving the typed params and the builder.

optsoptional FnOpts

the stage, the workgroup size, the return attribute, and the lint deviations listed above.

name

the emitted function name. Omit it to let a funcs key record name the function at module assembly.

ret

the return type to pin. Omit it when the body’s own return carries the type, or when the body returns nothing at all.

Return value

FnHandle<P, R>

a callable handle that is also the function declaration.

Exceptions

SD0110

SD0110 when portable is declared without stage: 'compute'.

SD0113

SD0113 when a body authored without a return type returns a value through an ambient Return.

SD0111

Anything outside that shape fails validation on both writers with SD0111 and a per-violation remedy.

Description

The returned FnHandle is both the callable and the declaration: call it directly, list it in module({ funcs }), or read its .decl.

The leading name is optional. An anonymous handle carries a placeholder name (_fn0, _fn1, and so on) that a funcs key record renames when module assembles, so no generated name reaches the emitted source. Keep an explicit name when something refers to the function by its string name and no record renames it, such as an externFn declaration whose body this function provides.

params is a record whose keys are the parameter names, in declaration order. A value is one of three things. A plain ShaderType declares an ordinary parameter. A builtin or location spec declares a stage-attributed entry-point parameter, emitting @builtin(...) or @location(...) on it; these are the same two helpers an ioStruct field uses, so an entry parameter and a struct field are authored alike. A struct handle (structDecl or ioStruct) hands the body that struct’s typed field proxy, so the body reads p.vo.uv with no VsOut.of(p.vo) step. Params are read-only, so p.uv.assign(...) is a tsc error; declare a Var where the body has to mutate.

A parameter may be named in, or any other word GLSL reserves. The IR carries the name and the GLSL backend renames it at emit. JavaScript cannot bind that name in a destructuring pattern, so give it a local name there: ({ in: inp }) => inp.uv.

The return-type token ret is optional when the body returns a value TypeScript can see, (p) => expr or a struct field proxy, since the handle takes that value’s key. It is optional again for a function that returns nothing, the shape of a compute entry: such a body may drop voidT and the handle lands on 'void'.

Pass the token when the body sends its value out through an ambient Return inside a nested closure. TypeScript reads such a body as returning nothing — the same shape as the genuinely void one — so the two are separated once the body has run: a body that took the void form and returned a value is rejected with SD0113 naming the token to write, rather than handed a 'void' key its call sites would believe.

The body is (p, b) => …: the typed param nodes first, the Builder second. Most bodies need only p and the ambient statement surface (Let, Var, If, Loop, Return) plus a final native return, which is type-checked against the return type. Reach for b when a statement is authored outside an active scope, and for b.raw(...).

opts carries six fields:

  • stage: 'vertex' | 'fragment' | 'compute' makes the function a pipeline entry point. Omit it for an ordinary helper.
  • workgroupSize sizes a compute entry, emitting @compute @workgroup_size(N). It defaults to 64. [8, 8] or [4, 4, 4] gives a two- or three-dimensional workgroup, emitting @workgroup_size(8, 8).
  • retAttr attaches an attribute to a bare, non-struct stage return, giving -> @location(0) vec4<f32>. A typed location(0, T) spec is accepted and its .attr is used. A bare non-struct fragment return defaults to @location(0). A struct return carries its attributes in the struct instead.
  • allowEarlyReturn records a deliberate deviation from the single-exit lint rule, for a body whose early Return skips work, such as a guard in front of a bounded loop.
  • portable declares a compute entry a portable kernel; see below.
  • lintDisable lists rule ids whose diagnostics are dropped for this function, the general form of allowEarlyReturn. Use either with a comment saying why.

A portable kernel emits on both backends: natively as @compute on WGSL, and on GLSL ES 3.00 through a compute-to-fragment translation, which WebGL2 dispatches as a fullscreen draw into an R32UI target. In exchange the kernel stays inside the gather-only tier: a one-dimensional workgroup, a global_invocation_id read only as .x, exactly one read_write storage binding of array<u32> written exactly once at the invocation index, a first uniform binding of vec4<u32> whose .x is the invocation count and .y the output-grid width, and no raw statements anywhere the entry can reach. Anything outside that shape fails validation on both writers with SD0111 and a per-violation remedy.

Examples

Example

import { fn, location, vec4, length, vec2fT } from 'typeshade'
const dist = fn('dist', { p: vec2fT, q: vec2fT }, ({ p, q }) => length(p.sub(q)))
const fs = fn('fs_main', { uv: location(0, vec2fT) }, ({ uv }) => vec4(uv, 0, 1), {
stage: 'fragment',
})

In the guide

See also

Source

src/core/ir/builder.ts, line 848, at commit 26de7be8

Edit this page Report a problem