variantFamily
On this page

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

spec VariantFamilySpec<A>

the axes, the per-point builder, and the key derivation.

Return value

VariantFamily<A>

the built family: every variant with its module, reflection and key, plus the three emit shapes.

Exceptions

Error

Error when 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, keyed
family.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

Edit this page Report a problem