Unverified Commit 700fe3d2 authored by Brandon DeRosier's avatar Brandon DeRosier Committed by GitHub

[Impeller Scene] Add SceneC asset importing (#118157)

parent 583a8122
......@@ -107,6 +107,8 @@ enum HostArtifact {
// The Impeller shader compiler.
impellerc,
// The Impeller Scene 3D model importer.
scenec,
// Impeller's tessellation library.
libtessellator,
}
......@@ -252,6 +254,8 @@ String _hostArtifactToFileName(HostArtifact artifact, Platform platform) {
return 'dart_sdk.js.map';
case HostArtifact.impellerc:
return 'impellerc$exe';
case HostArtifact.scenec:
return 'scenec$exe';
case HostArtifact.libtessellator:
return 'libtessellator$dll';
}
......@@ -432,6 +436,7 @@ class CachedArtifacts implements Artifacts {
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
return _cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName);
case HostArtifact.impellerc:
case HostArtifact.scenec:
case HostArtifact.libtessellator:
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
final String engineDir = _getEngineArtifactsPath(_currentHostPlatform(_platform, _operatingSystemUtils))!;
......@@ -866,6 +871,7 @@ class CachedLocalEngineArtifacts implements Artifacts {
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
return _cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName);
case HostArtifact.impellerc:
case HostArtifact.scenec:
case HostArtifact.libtessellator:
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
final File file = _fileSystem.file(_fileSystem.path.join(_hostEngineOutPath, artifactFileName));
......@@ -1151,6 +1157,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
case HostArtifact.iproxy:
case HostArtifact.skyEnginePath:
case HostArtifact.impellerc:
case HostArtifact.scenec:
case HostArtifact.libtessellator:
return _parent.getHostArtifact(artifact);
}
......
......@@ -70,6 +70,7 @@ enum AssetKind {
regular,
font,
shader,
model,
}
abstract class AssetBundle {
......@@ -772,6 +773,20 @@ class ManifestAssetBundle implements AssetBundle {
}
}
for (final Uri modelUri in flutterManifest.models) {
_parseAssetFromFile(
packageConfig,
flutterManifest,
assetBase,
cache,
result,
modelUri,
packageName: packageName,
attributedPackage: attributedPackage,
assetKind: AssetKind.model,
);
}
// Add assets referenced in the fonts section of the manifest.
for (final Font font in flutterManifest.fonts) {
for (final FontAsset fontAsset in font.fontAssets) {
......
......@@ -14,6 +14,7 @@ import '../build_system.dart';
import '../depfile.dart';
import 'common.dart';
import 'icon_tree_shaker.dart';
import 'scene_importer.dart';
import 'shader_compiler.dart';
/// A helper function to copy an asset bundle into an [environment]'s output
......@@ -84,6 +85,12 @@ Future<Depfile> copyAssets(
fileSystem: environment.fileSystem,
artifacts: environment.artifacts,
);
final SceneImporter sceneImporter = SceneImporter(
processManager: environment.processManager,
logger: environment.logger,
fileSystem: environment.fileSystem,
artifacts: environment.artifacts,
);
final Map<String, DevFSContent> assetEntries = <String, DevFSContent>{
...assetBundle.entries,
......@@ -131,6 +138,12 @@ Future<Depfile> copyAssets(
json: targetPlatform == TargetPlatform.web_javascript,
);
break;
case AssetKind.model:
doCopy = !await sceneImporter.importScene(
input: content.file as File,
outputPath: file.path,
);
break;
}
if (doCopy) {
await (content.file as File).copy(file.path);
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:pool/pool.dart';
import 'package:process/process.dart';
import '../../artifacts.dart';
import '../../base/error_handling_io.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/logger.dart';
import '../../convert.dart';
import '../../devfs.dart';
import '../build_system.dart';
/// A wrapper around [SceneImporter] to support hot reload of 3D models.
class DevelopmentSceneImporter {
DevelopmentSceneImporter({
required SceneImporter sceneImporter,
required FileSystem fileSystem,
@visibleForTesting math.Random? random,
}) : _sceneImporter = sceneImporter,
_fileSystem = fileSystem,
_random = random ?? math.Random();
final SceneImporter _sceneImporter;
final FileSystem _fileSystem;
final Pool _compilationPool = Pool(4);
final math.Random _random;
/// Recompile the input ipscene and return a devfs content that should be
/// synced to the attached device in its place.
Future<DevFSContent?> reimportScene(DevFSContent inputScene) async {
final File output = _fileSystem.systemTempDirectory.childFile('${_random.nextDouble()}.temp');
late File inputFile;
bool cleanupInput = false;
Uint8List result;
PoolResource? resource;
try {
resource = await _compilationPool.request();
if (inputScene is DevFSFileContent) {
inputFile = inputScene.file as File;
} else {
inputFile = _fileSystem.systemTempDirectory.childFile('${_random.nextDouble()}.temp');
inputFile.writeAsBytesSync(await inputScene.contentsAsBytes());
cleanupInput = true;
}
final bool success = await _sceneImporter.importScene(
input: inputFile,
outputPath: output.path,
fatal: false,
);
if (!success) {
return null;
}
result = output.readAsBytesSync();
} finally {
resource?.release();
ErrorHandlingFileSystem.deleteIfExists(output);
if (cleanupInput) {
ErrorHandlingFileSystem.deleteIfExists(inputFile);
}
}
return DevFSByteContent(result);
}
}
/// A class the wraps the functionality of the Impeller Scene importer scenec.
class SceneImporter {
SceneImporter({
required ProcessManager processManager,
required Logger logger,
required FileSystem fileSystem,
required Artifacts artifacts,
}) : _processManager = processManager,
_logger = logger,
_fs = fileSystem,
_artifacts = artifacts;
final ProcessManager _processManager;
final Logger _logger;
final FileSystem _fs;
final Artifacts _artifacts;
/// The [Source] inputs that targets using this should depend on.
///
/// See [Target.inputs].
static const List<Source> inputs = <Source>[
Source.pattern(
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/scene_importer.dart'),
Source.hostArtifact(HostArtifact.scenec),
];
/// Calls scenec, which transforms the [input] 3D model into an imported
/// ipscene at [outputPath].
///
/// All parameters are required.
///
/// If the scene importer subprocess fails, it will print the stdout and
/// stderr to the log and throw a [SceneImporterException]. Otherwise, it
/// will return true.
Future<bool> importScene({
required File input,
required String outputPath,
bool fatal = true,
}) async {
final File scenec = _fs.file(
_artifacts.getHostArtifact(HostArtifact.scenec),
);
if (!scenec.existsSync()) {
throw SceneImporterException._(
'The scenec utility is missing at "${scenec.path}". '
'Run "flutter doctor".',
);
}
final List<String> cmd = <String>[
scenec.path,
'--input=${input.path}',
'--output=$outputPath',
];
_logger.printTrace('scenec command: $cmd');
final Process scenecProcess = await _processManager.start(cmd);
final int code = await scenecProcess.exitCode;
if (code != 0) {
final String stdout = await utf8.decodeStream(scenecProcess.stdout);
final String stderr = await utf8.decodeStream(scenecProcess.stderr);
_logger.printTrace(stdout);
_logger.printError(stderr);
if (fatal) {
throw SceneImporterException._(
'Scene import of "${input.path}" to "$outputPath" '
'failed with exit code $code.\n'
'scenec stdout:\n$stdout\n'
'scenec stderr:\n$stderr',
);
}
return false;
}
return true;
}
}
class SceneImporterException implements Exception {
SceneImporterException._(this.message);
final String message;
@override
String toString() => 'SceneImporterException: $message\n\n';
}
......@@ -13,6 +13,7 @@ import 'build_info.dart';
import 'build_system/build_system.dart';
import 'build_system/depfile.dart';
import 'build_system/targets/common.dart';
import 'build_system/targets/scene_importer.dart';
import 'build_system/targets/shader_compiler.dart';
import 'bundle.dart';
import 'cache.dart';
......@@ -158,6 +159,13 @@ Future<void> writeBundle(
artifacts: globals.artifacts!,
);
final SceneImporter sceneImporter = SceneImporter(
processManager: globals.processManager,
logger: globals.logger,
fileSystem: globals.fs,
artifacts: globals.artifacts!,
);
// Limit number of open files to avoid running out of file descriptors.
final Pool pool = Pool(64);
await Future.wait<void>(
......@@ -189,6 +197,12 @@ Future<void> writeBundle(
json: targetPlatform == TargetPlatform.web_javascript,
);
break;
case AssetKind.model:
doCopy = !await sceneImporter.importScene(
input: input,
outputPath: file.path,
);
break;
}
if (doCopy) {
input.copySync(file.path);
......
......@@ -15,6 +15,7 @@ import 'base/logger.dart';
import 'base/net.dart';
import 'base/os.dart';
import 'build_info.dart';
import 'build_system/targets/scene_importer.dart';
import 'build_system/targets/shader_compiler.dart';
import 'compile.dart';
import 'convert.dart' show base64, utf8;
......@@ -483,6 +484,7 @@ class DevFS {
final Directory? rootDirectory;
final Set<String> assetPathsToEvict = <String>{};
final Set<String> shaderPathsToEvict = <String>{};
final Set<String> scenePathsToEvict = <String>{};
// A flag to indicate whether we have called `setAssetDirectory` on the target device.
bool hasSetAssetDirectory = false;
......@@ -582,6 +584,7 @@ class DevFS {
required PackageConfig packageConfig,
required String dillOutputPath,
required DevelopmentShaderCompiler shaderCompiler,
DevelopmentSceneImporter? sceneImporter,
DevFSWriter? devFSWriter,
String? target,
AssetBundle? bundle,
......@@ -600,8 +603,8 @@ class DevFS {
// Update modified files
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
final List<Future<void>> pendingShaderCompiles = <Future<void>>[];
bool shaderCompilationFailed = false;
final List<Future<void>> pendingAssetBuilds = <Future<void>>[];
bool assetBuildFailed = false;
int syncedBytes = 0;
if (fullRestart) {
generator.reset();
......@@ -656,26 +659,48 @@ class DevFS {
didUpdateFontManifest = true;
}
if (bundle.entryKinds[archivePath] == AssetKind.shader) {
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content);
pendingShaderCompiles.add(pending);
pending.then((DevFSContent? content) {
if (content == null) {
shaderCompilationFailed = true;
return;
switch (bundle.entryKinds[archivePath]) {
case AssetKind.shader:
final Future<DevFSContent?> pending = shaderCompiler.recompileShader(content);
pendingAssetBuilds.add(pending);
pending.then((DevFSContent? content) {
if (content == null) {
assetBuildFailed = true;
return;
}
dirtyEntries[deviceUri] = content;
syncedBytes += content.size;
if (archivePath != null && !bundleFirstUpload) {
shaderPathsToEvict.add(archivePath);
}
});
break;
case AssetKind.model:
if (sceneImporter == null) {
break;
}
final Future<DevFSContent?> pending = sceneImporter.reimportScene(content);
pendingAssetBuilds.add(pending);
pending.then((DevFSContent? content) {
if (content == null) {
assetBuildFailed = true;
return;
}
dirtyEntries[deviceUri] = content;
syncedBytes += content.size;
if (archivePath != null && !bundleFirstUpload) {
scenePathsToEvict.add(archivePath);
}
});
break;
case AssetKind.regular:
case AssetKind.font:
case null:
dirtyEntries[deviceUri] = content;
syncedBytes += content.size;
if (archivePath != null && !bundleFirstUpload) {
shaderPathsToEvict.add(archivePath);
assetPathsToEvict.add(archivePath);
}
});
} else {
dirtyEntries[deviceUri] = content;
syncedBytes += content.size;
if (archivePath != null && !bundleFirstUpload) {
assetPathsToEvict.add(archivePath);
}
}
});
......@@ -707,8 +732,8 @@ class DevFS {
_logger.printTrace('Updating files.');
final Stopwatch transferTimer = _stopwatchFactory.createStopwatch('transfer')..start();
await Future.wait(pendingShaderCompiles);
if (shaderCompilationFailed) {
await Future.wait(pendingAssetBuilds);
if (assetBuildFailed) {
return UpdateFSReport();
}
......
......@@ -13,6 +13,9 @@ import 'base/user_messages.dart';
import 'base/utils.dart';
import 'plugins.dart';
/// Whether or not Impeller Scene 3D model import is enabled.
const bool kIs3dSceneSupported = true;
const Set<String> _kValidPluginPlatforms = <String>{
'android', 'ios', 'web', 'windows', 'linux', 'macos',
};
......@@ -370,28 +373,28 @@ class FlutterManifest {
return fonts;
}
late final List<Uri> shaders = _extractAssetUris('shaders', 'Shader');
late final List<Uri> models = kIs3dSceneSupported ? _extractAssetUris('models', 'Model') : <Uri>[];
late final List<Uri> shaders = _extractShaders();
List<Uri> _extractShaders() {
if (!_flutterDescriptor.containsKey('shaders')) {
List<Uri> _extractAssetUris(String key, String singularName) {
if (!_flutterDescriptor.containsKey(key)) {
return <Uri>[];
}
final List<Object?>? shaders = _flutterDescriptor['shaders'] as List<Object?>?;
if (shaders == null) {
final List<Object?>? items = _flutterDescriptor[key] as List<Object?>?;
if (items == null) {
return const <Uri>[];
}
final List<Uri> results = <Uri>[];
for (final Object? shader in shaders) {
if (shader is! String || shader == null || shader == '') {
_logger.printError('Shader manifest contains a null or empty uri.');
for (final Object? item in items) {
if (item is! String || item == null || item == '') {
_logger.printError('$singularName manifest contains a null or empty uri.');
continue;
}
try {
results.add(Uri(pathSegments: shader.split('/')));
results.add(Uri(pathSegments: item.split('/')));
} on FormatException {
_logger.printError('Shader manifest contains invalid uri: $shader.');
_logger.printError('$singularName manifest contains invalid uri: $item.');
}
}
return results;
......@@ -545,6 +548,17 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
);
}
break;
case 'models':
if (yamlValue is! YamlList) {
errors.add('Expected "$yamlKey" to be a list, but got $yamlValue (${yamlValue.runtimeType}).');
} else if (yamlValue.isEmpty) {
break;
} else if (yamlValue[0] is! String) {
errors.add(
'Expected "$yamlKey" to be a list of strings, but the first element is $yamlValue (${yamlValue.runtimeType}).',
);
}
break;
case 'fonts':
if (yamlValue is! YamlList) {
errors.add('Expected "$yamlKey" to be a list, but got $yamlValue (${yamlValue.runtimeType}).');
......
......@@ -27,6 +27,7 @@ import '../base/logger.dart';
import '../base/net.dart';
import '../base/platform.dart';
import '../build_info.dart';
import '../build_system/targets/scene_importer.dart';
import '../build_system/targets/shader_compiler.dart';
import '../build_system/targets/web.dart';
import '../bundle_builder.dart';
......@@ -816,6 +817,7 @@ class WebDevFS implements DevFS {
required PackageConfig packageConfig,
required String dillOutputPath,
required DevelopmentShaderCompiler shaderCompiler,
DevelopmentSceneImporter? sceneImporter,
DevFSWriter? devFSWriter,
String? target,
AssetBundle? bundle,
......@@ -968,6 +970,9 @@ class WebDevFS implements DevFS {
@override
Set<String> get shaderPathsToEvict => <String>{};
@override
Set<String> get scenePathsToEvict => <String>{};
}
class ReleaseAssetServer {
......
......@@ -26,6 +26,7 @@ import 'build_info.dart';
import 'build_system/build_system.dart';
import 'build_system/targets/dart_plugin_registrant.dart';
import 'build_system/targets/localizations.dart';
import 'build_system/targets/scene_importer.dart';
import 'build_system/targets/shader_compiler.dart';
import 'bundle.dart';
import 'cache.dart';
......@@ -51,6 +52,7 @@ class FlutterDevice {
ResidentCompiler? generator,
this.userIdentifier,
required this.developmentShaderCompiler,
this.developmentSceneImporter,
}) : assert(buildInfo.trackWidgetCreation != null),
generator = generator ?? ResidentCompiler(
globals.artifacts!.getArtifactPath(
......@@ -99,6 +101,16 @@ class FlutterDevice {
fileSystem: globals.fs,
);
final DevelopmentSceneImporter sceneImporter = DevelopmentSceneImporter(
sceneImporter: SceneImporter(
artifacts: globals.artifacts!,
logger: globals.logger,
processManager: globals.processManager,
fileSystem: globals.fs,
),
fileSystem: globals.fs,
);
// For both web and non-web platforms we initialize dill to/from
// a shared location for faster bootstrapping. If the compiler fails
// due to a kernel target or version mismatch, no error is reported
......@@ -200,6 +212,7 @@ class FlutterDevice {
buildInfo: buildInfo,
userIdentifier: userIdentifier,
developmentShaderCompiler: shaderCompiler,
developmentSceneImporter: sceneImporter,
);
}
......@@ -209,6 +222,7 @@ class FlutterDevice {
final BuildInfo buildInfo;
final String? userIdentifier;
final DevelopmentShaderCompiler developmentShaderCompiler;
final DevelopmentSceneImporter? developmentSceneImporter;
DevFSWriter? devFSWriter;
Stream<Uri?>? observatoryUris;
......@@ -584,6 +598,7 @@ class FlutterDevice {
packageConfig: packageConfig,
devFSWriter: devFSWriter,
shaderCompiler: developmentShaderCompiler,
sceneImporter: developmentSceneImporter,
dartPluginRegistrant: FlutterProject.current().dartPluginRegistrant,
);
} on DevFSException {
......
......@@ -507,6 +507,7 @@ class HotRunner extends ResidentRunner {
}
devFS.assetPathsToEvict.clear();
devFS.shaderPathsToEvict.clear();
devFS.scenePathsToEvict.clear();
}
}
......@@ -1044,7 +1045,9 @@ class HotRunner extends ResidentRunner {
Future<void> evictDirtyAssets() async {
final List<Future<void>> futures = <Future<void>>[];
for (final FlutterDevice? device in flutterDevices) {
if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) {
if (device!.devFS!.assetPathsToEvict.isEmpty &&
device.devFS!.shaderPathsToEvict.isEmpty &&
device.devFS!.scenePathsToEvict.isEmpty) {
continue;
}
final List<FlutterView> views = await device.vmService!.getFlutterViews();
......@@ -1096,8 +1099,18 @@ class HotRunner extends ResidentRunner {
)
);
}
for (final String assetPath in device.devFS!.scenePathsToEvict) {
futures.add(
device.vmService!
.flutterEvictScene(
assetPath,
isolateId: views.first.uiIsolate!.id!,
)
);
}
device.devFS!.assetPathsToEvict.clear();
device.devFS!.shaderPathsToEvict.clear();
device.devFS!.scenePathsToEvict.clear();
}
await Future.wait<void>(futures);
}
......
......@@ -739,6 +739,18 @@ class FlutterVmService {
);
}
Future<Map<String, Object?>?> flutterEvictScene(String assetPath, {
required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.ui.window.reinitializeScene',
isolateId: isolateId,
args: <String, Object?>{
'assetKey': assetPath,
},
);
}
/// Exit the application by calling [exit] from `dart:io`.
///
......
......@@ -571,6 +571,9 @@ class FakeDevFs extends Fake implements DevFS {
@override
Set<String> shaderPathsToEvict= <String>{};
@override
Set<String> scenePathsToEvict= <String>{};
@override
Uri? baseUri;
}
......
......@@ -18,6 +18,7 @@ import 'package:flutter_tools/src/base/io.dart' as io;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/scene_importer.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
......@@ -2694,6 +2695,9 @@ class FakeDevFS extends Fake implements DevFS {
@override
Set<String> shaderPathsToEvict = <String>{};
@override
Set<String> scenePathsToEvict = <String>{};
@override
bool didUpdateFontManifest = false;
......@@ -2722,6 +2726,7 @@ class FakeDevFS extends Fake implements DevFS {
required PackageConfig packageConfig,
required String dillOutputPath,
required DevelopmentShaderCompiler shaderCompiler,
DevelopmentSceneImporter? sceneImporter,
DevFSWriter? devFSWriter,
String? target,
AssetBundle? bundle,
......
......@@ -16,6 +16,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/scene_importer.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
......@@ -1401,6 +1402,7 @@ class FakeWebDevFS extends Fake implements WebDevFS {
required PackageConfig packageConfig,
required String dillOutputPath,
required DevelopmentShaderCompiler shaderCompiler,
DevelopmentSceneImporter? sceneImporter,
DevFSWriter? devFSWriter,
String? target,
AssetBundle? bundle,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment