specialiseMaterial function

MaterialProgram specialiseMaterial(
  1. MaterialProgram program,
  2. MaterialVariant variant
)

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,
  );
}