variantFamily()
Function in Variants
Build a family of shader variants from a typed axis matrix.
import { variantFamily } from 'typeshade'
The signature, the description and the examples come from the compiler's own source at commit 26de7be8.
Syntax
function variantFamily<A extends Record<string, readonly unknown[]>>( spec: VariantFamilySpec<A>,): VariantFamily<A>Parameters
specVariantFamilySpec<A>the axes, the per-point builder, and the key derivation.
Return value
the built family: every variant with its module, reflection and key, plus the three emit shapes.
Exceptions
ErrorErrorwhen an axis declares no values, or when two points derive the same key, which means the key does not name every axis the builder read.
Description
Give it the axes a host can select, a builder for one point in the space, and a key derivation, and it builds every point once.
Each axis is a name mapped to the list of values the host chooses among, so the family is
the product of the axes and every point is type-checked: the builder receives one value per
axis, and a typo in an axis name is a tsc error. The builder is ordinary TypeScript, so the
variation is a plain if and the losing arm is never built, which means its bindings are
never declared and never reach reflect.
variants holds one entry per point: the axis values, the built ModuleDecl, its
reflect result, and the derived key. keys lists those keys and get(key) looks a
variant up. The key is the part that outlives everything else, because a specialized
program is a different program and every axis has to appear in every key that names it: a
pipeline cache keyed without an axis serves one variant’s program to another variant’s
draw, which compiles, links, renders and is wrong. Deriving the key from the axis values is
what makes omitting one impossible, and two points deriving the same key throws here.
emit(target) returns one preprocessor-free source per key. That is the WGSL path, and it
is what a pipeline cache should prefer on either target.
emitGuarded(defines, opts) is the GLSL-only alternative, for a host that owns the define
and decides at draw time. It generates one source with an #if ladder over the arms, one
arm per variant, from the same typed matrix, so the ladder can be checked against that
matrix. Every arm is byte-identical to the standalone variant of the same key,
which is what keeps the guarded and unguarded paths from being two programs.
emitGuardedFragment(defines, opts) returns the same ladder as a header-less fragment: the
source, and the preamble, the declares and the requires as data. It exists because the
ladder usually goes inside an include, and an include cannot carry a second #version.
Joining the preamble to the source reproduces emitGuarded byte for byte.
Examples
Example
import { variantFamily } from 'typeshade'
const family = variantFamily({ axes: { shadows: [false, true], blend: ['add', 'mix'] }, build: ({ shadows, blend }) => buildModule(shadows, blend), key: ({ shadows, blend }) => `${shadows ? 's' : 'n'}:${blend}`,})
family.emit('wgsl') // four sources, keyedfamily.emitGuarded({ shadows: 'SHADOWS', blend: { add: 'BLEND_ADD', mix: 'BLEND_MIX' } })In the guide
See also
Source
src/core/variant-family.ts, line 266, at commit 26de7be8