specialiseMaterial function
program with variant's parameter values folded in, ready to emit or to
evaluate.
Folding is what makes a variant an entry point rather than a setting.
Every MaterialParamRef becomes the number the variant gives it, and a
subtree that is then entirely constant collapses — so pow(x, rimPower)
with rimPower = 2.0 reaches the GPU as pow(x, 2.0) and reaches the
software backend as the same multiplication, from one decision.
Throws ArgumentError for a value of the wrong width or a name the program
does not declare: a variant that set rimPowr would otherwise compile to
the default and look like the parameter not working.
Implementation
MaterialProgram specialiseMaterial(
MaterialProgram program,
MaterialVariant variant,
) {
for (final MapEntry(:key, :value) in variant.values.entries) {
final parameter = program.parameter(key);
if (parameter == null) {
throw ArgumentError(
'Variant "${variant.name}" sets "$key", which '
'"${program.name}" does not declare.',
);
}
if (value.length != parameter.type.components) {
throw ArgumentError(
'Variant "${variant.name}" gives "$key" ${value.length} components, '
'and it is a ${parameter.type}.',
);
}
}
return MaterialProgram(
name: variant.name,
parameters: program.parameters,
textures: program.textures,
body: <MaterialStatement>[
for (final statement in program.body)
switch (statement) {
MaterialLet(:final name, :final value) => MaterialLet(
name,
_fold(value, variant),
),
MaterialReturn(:final value) => MaterialReturn(_fold(value, variant)),
},
],
inputsUsed: program.inputsUsed,
);
}