buildTargetShaderBundleJson function

Future<void> buildTargetShaderBundleJson({
  1. required BuildInput buildInput,
  2. required BuildOutputBuilder buildOutput,
  3. required String manifestFileName,
  4. List<Uri> includeDirectories = const [],
  5. TargetShaderBundleAssetMode assetMode = TargetShaderBundleAssetMode.generatedTree,
  6. String? dataAssetName,
  7. int? glesLanguageVersion,
  8. bool copyToGeneratedTree = true,
  9. bool pruneGeneratedTree = true,
  10. String? owner,
  11. String? stamp,
  12. String? fileVariant,
})

Builds a shader bundle and removes backends the target cannot use.

TODO(shader-bundle-cache): the compiler runs on every hook rerun, since only stamp callers skip it. Stamping the manifest and its sources here would let an unrelated asset edit skip the compile.

In TargetShaderBundleAssetMode.generatedTree the trimmed bundle is copied into the app's generated tree, recorded under the manifest name it was built from. copyToGeneratedTree turns that off for a caller that publishes the bundle itself (buildMaterials), and owner names the package the bundle belongs to when the app's hook builds a dependency's shaders.

pruneGeneratedTree drops a tree copy the data-asset registration replaces. flutter_scene's own hook turns that off: one build runs it several times with different asset types, so its tree copy is the fallback for the runs that have no data assets, not a leftover.

Implementation

Future<void> buildTargetShaderBundleJson({
  required BuildInput buildInput,
  required BuildOutputBuilder buildOutput,
  required String manifestFileName,
  List<Uri> includeDirectories = const [],
  TargetShaderBundleAssetMode assetMode =
      TargetShaderBundleAssetMode.generatedTree,
  String? dataAssetName,
  int? glesLanguageVersion,
  bool copyToGeneratedTree = true,
  bool pruneGeneratedTree = true,
  String? owner,
  String? stamp,
  String? fileVariant,
}) async {
  // ignore: deprecated_member_use_from_same_package
  if (assetMode == TargetShaderBundleAssetMode.legacyOnly) {
    throwRemovedAssetMode(
      'TargetShaderBundleAssetMode.legacyOnly',
      'TargetShaderBundleAssetMode.generatedTree',
    );
  }
  // ignore: deprecated_member_use_from_same_package
  if (assetMode == TargetShaderBundleAssetMode.dataAssetsIfAvailable) {
    throwRemovedAssetMode(
      'TargetShaderBundleAssetMode.dataAssetsIfAvailable',
      'TargetShaderBundleAssetMode.generatedTree',
    );
  }
  final emitDataAssets =
      buildInput.config.buildDataAssets &&
      assetMode == TargetShaderBundleAssetMode.dataAssetsRequired;
  final result = await buildShaderBundleJson(
    buildInput: buildInput,
    buildOutput: buildOutput,
    manifestFileName: manifestFileName,
    includeDirectories: includeDirectories,
    assetMode: switch (assetMode) {
      TargetShaderBundleAssetMode.generatedTree =>
        ShaderBundleAssetMode.legacyOnly,
      TargetShaderBundleAssetMode.dataAssetsRequired =>
        ShaderBundleAssetMode.dataAssetsRequired,
      // Rejected above, before anything is compiled.
      _ => ShaderBundleAssetMode.legacyOnly,
    },
    dataAssetName: dataAssetName,
    glesLanguageVersion: glesLanguageVersion,
  );
  final output = File.fromUri(result.outputFile);
  final bytes = trimShaderBundle(
    output.readAsBytesSync(),
    shaderBundleBackendsForBuild(buildInput),
  );
  output.writeAsBytesSync(bytes);

  if (!copyToGeneratedTree) return;

  final bundleFileName = result.outputFile.pathSegments.last;
  final id = bundleFileName.endsWith('.shaderbundle')
      ? bundleFileName.substring(
          0,
          bundleFileName.length - '.shaderbundle'.length,
        )
      : bundleFileName;
  if (emitDataAssets) {
    if (!pruneGeneratedTree) return;
    // A tree left by an earlier build would ship the same bundle twice.
    GeneratedAssetTree.openExisting(
        buildInput.packageRoot,
        buildInput.packageName,
      )
      ?..dropOwned(
        GeneratedAssetFamily.shaderBundle,
        owner: owner ?? buildInput.packageName,
      )
      ..save();
    return;
  }

  final tree = GeneratedAssetTree.open(
    buildInput.packageRoot,
    buildInput.packageName,
  )..requireAssetEntry();
  final copyUri = tree.fileUri(
    GeneratedAssetFamily.shaderBundle,
    nameId: id,
    extension: '.shaderbundle',
    variant: fileVariant,
  );
  writeGeneratedBytes(copyUri, bytes);
  tree
    ..recordFile(
      family: GeneratedAssetFamily.shaderBundle,
      id: id,
      uri: copyUri,
      stamp: stamp ?? fnv1aHex(bytes),
      owner: owner,
    )
    ..save();
}