Skip to content

Commit 700fe3d

Browse files
authored
[Impeller Scene] Add SceneC asset importing (#118157)
1 parent 583a812 commit 700fe3d

14 files changed

+330
-31
lines changed

packages/flutter_tools/lib/src/artifacts.dart

+7
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ enum HostArtifact {
107107

108108
// The Impeller shader compiler.
109109
impellerc,
110+
// The Impeller Scene 3D model importer.
111+
scenec,
110112
// Impeller's tessellation library.
111113
libtessellator,
112114
}
@@ -252,6 +254,8 @@ String _hostArtifactToFileName(HostArtifact artifact, Platform platform) {
252254
return 'dart_sdk.js.map';
253255
case HostArtifact.impellerc:
254256
return 'impellerc$exe';
257+
case HostArtifact.scenec:
258+
return 'scenec$exe';
255259
case HostArtifact.libtessellator:
256260
return 'libtessellator$dll';
257261
}
@@ -432,6 +436,7 @@ class CachedArtifacts implements Artifacts {
432436
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
433437
return _cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName);
434438
case HostArtifact.impellerc:
439+
case HostArtifact.scenec:
435440
case HostArtifact.libtessellator:
436441
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
437442
final String engineDir = _getEngineArtifactsPath(_currentHostPlatform(_platform, _operatingSystemUtils))!;
@@ -866,6 +871,7 @@ class CachedLocalEngineArtifacts implements Artifacts {
866871
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
867872
return _cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName);
868873
case HostArtifact.impellerc:
874+
case HostArtifact.scenec:
869875
case HostArtifact.libtessellator:
870876
final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
871877
final File file = _fileSystem.file(_fileSystem.path.join(_hostEngineOutPath, artifactFileName));
@@ -1151,6 +1157,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
11511157
case HostArtifact.iproxy:
11521158
case HostArtifact.skyEnginePath:
11531159
case HostArtifact.impellerc:
1160+
case HostArtifact.scenec:
11541161
case HostArtifact.libtessellator:
11551162
return _parent.getHostArtifact(artifact);
11561163
}

packages/flutter_tools/lib/src/asset.dart

+15
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ enum AssetKind {
7070
regular,
7171
font,
7272
shader,
73+
model,
7374
}
7475

7576
abstract class AssetBundle {
@@ -772,6 +773,20 @@ class ManifestAssetBundle implements AssetBundle {
772773
}
773774
}
774775

776+
for (final Uri modelUri in flutterManifest.models) {
777+
_parseAssetFromFile(
778+
packageConfig,
779+
flutterManifest,
780+
assetBase,
781+
cache,
782+
result,
783+
modelUri,
784+
packageName: packageName,
785+
attributedPackage: attributedPackage,
786+
assetKind: AssetKind.model,
787+
);
788+
}
789+
775790
// Add assets referenced in the fonts section of the manifest.
776791
for (final Font font in flutterManifest.fonts) {
777792
for (final FontAsset fontAsset in font.fontAssets) {

packages/flutter_tools/lib/src/build_system/targets/assets.dart

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../build_system.dart';
1414
import '../depfile.dart';
1515
import 'common.dart';
1616
import 'icon_tree_shaker.dart';
17+
import 'scene_importer.dart';
1718
import 'shader_compiler.dart';
1819

1920
/// A helper function to copy an asset bundle into an [environment]'s output
@@ -84,6 +85,12 @@ Future<Depfile> copyAssets(
8485
fileSystem: environment.fileSystem,
8586
artifacts: environment.artifacts,
8687
);
88+
final SceneImporter sceneImporter = SceneImporter(
89+
processManager: environment.processManager,
90+
logger: environment.logger,
91+
fileSystem: environment.fileSystem,
92+
artifacts: environment.artifacts,
93+
);
8794

8895
final Map<String, DevFSContent> assetEntries = <String, DevFSContent>{
8996
...assetBundle.entries,
@@ -131,6 +138,12 @@ Future<Depfile> copyAssets(
131138
json: targetPlatform == TargetPlatform.web_javascript,
132139
);
133140
break;
141+
case AssetKind.model:
142+
doCopy = !await sceneImporter.importScene(
143+
input: content.file as File,
144+
outputPath: file.path,
145+
);
146+
break;
134147
}
135148
if (doCopy) {
136149
await (content.file as File).copy(file.path);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:math' as math;
6+
import 'dart:typed_data';
7+
8+
import 'package:meta/meta.dart';
9+
import 'package:pool/pool.dart';
10+
import 'package:process/process.dart';
11+
12+
import '../../artifacts.dart';
13+
import '../../base/error_handling_io.dart';
14+
import '../../base/file_system.dart';
15+
import '../../base/io.dart';
16+
import '../../base/logger.dart';
17+
import '../../convert.dart';
18+
import '../../devfs.dart';
19+
import '../build_system.dart';
20+
21+
/// A wrapper around [SceneImporter] to support hot reload of 3D models.
22+
class DevelopmentSceneImporter {
23+
DevelopmentSceneImporter({
24+
required SceneImporter sceneImporter,
25+
required FileSystem fileSystem,
26+
@visibleForTesting math.Random? random,
27+
}) : _sceneImporter = sceneImporter,
28+
_fileSystem = fileSystem,
29+
_random = random ?? math.Random();
30+
31+
final SceneImporter _sceneImporter;
32+
final FileSystem _fileSystem;
33+
final Pool _compilationPool = Pool(4);
34+
final math.Random _random;
35+
36+
/// Recompile the input ipscene and return a devfs content that should be
37+
/// synced to the attached device in its place.
38+
Future<DevFSContent?> reimportScene(DevFSContent inputScene) async {
39+
final File output = _fileSystem.systemTempDirectory.childFile('${_random.nextDouble()}.temp');
40+
late File inputFile;
41+
bool cleanupInput = false;
42+
Uint8List result;
43+
PoolResource? resource;
44+
try {
45+
resource = await _compilationPool.request();
46+
if (inputScene is DevFSFileContent) {
47+
inputFile = inputScene.file as File;
48+
} else {
49+
inputFile = _fileSystem.systemTempDirectory.childFile('${_random.nextDouble()}.temp');
50+
inputFile.writeAsBytesSync(await inputScene.contentsAsBytes());
51+
cleanupInput = true;
52+
}
53+
final bool success = await _sceneImporter.importScene(
54+
input: inputFile,
55+
outputPath: output.path,
56+
fatal: false,
57+
);
58+
if (!success) {
59+
return null;
60+
}
61+
result = output.readAsBytesSync();
62+
} finally {
63+
resource?.release();
64+
ErrorHandlingFileSystem.deleteIfExists(output);
65+
if (cleanupInput) {
66+
ErrorHandlingFileSystem.deleteIfExists(inputFile);
67+
}
68+
}
69+
return DevFSByteContent(result);
70+
}
71+
}
72+
73+
/// A class the wraps the functionality of the Impeller Scene importer scenec.
74+
class SceneImporter {
75+
SceneImporter({
76+
required ProcessManager processManager,
77+
required Logger logger,
78+
required FileSystem fileSystem,
79+
required Artifacts artifacts,
80+
}) : _processManager = processManager,
81+
_logger = logger,
82+
_fs = fileSystem,
83+
_artifacts = artifacts;
84+
85+
final ProcessManager _processManager;
86+
final Logger _logger;
87+
final FileSystem _fs;
88+
final Artifacts _artifacts;
89+
90+
/// The [Source] inputs that targets using this should depend on.
91+
///
92+
/// See [Target.inputs].
93+
static const List<Source> inputs = <Source>[
94+
Source.pattern(
95+
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/scene_importer.dart'),
96+
Source.hostArtifact(HostArtifact.scenec),
97+
];
98+
99+
/// Calls scenec, which transforms the [input] 3D model into an imported
100+
/// ipscene at [outputPath].
101+
///
102+
/// All parameters are required.
103+
///
104+
/// If the scene importer subprocess fails, it will print the stdout and
105+
/// stderr to the log and throw a [SceneImporterException]. Otherwise, it
106+
/// will return true.
107+
Future<bool> importScene({
108+
required File input,
109+
required String outputPath,
110+
bool fatal = true,
111+
}) async {
112+
final File scenec = _fs.file(
113+
_artifacts.getHostArtifact(HostArtifact.scenec),
114+
);
115+
if (!scenec.existsSync()) {
116+
throw SceneImporterException._(
117+
'The scenec utility is missing at "${scenec.path}". '
118+
'Run "flutter doctor".',
119+
);
120+
}
121+
122+
final List<String> cmd = <String>[
123+
scenec.path,
124+
'--input=${input.path}',
125+
'--output=$outputPath',
126+
];
127+
_logger.printTrace('scenec command: $cmd');
128+
final Process scenecProcess = await _processManager.start(cmd);
129+
final int code = await scenecProcess.exitCode;
130+
if (code != 0) {
131+
final String stdout = await utf8.decodeStream(scenecProcess.stdout);
132+
final String stderr = await utf8.decodeStream(scenecProcess.stderr);
133+
_logger.printTrace(stdout);
134+
_logger.printError(stderr);
135+
if (fatal) {
136+
throw SceneImporterException._(
137+
'Scene import of "${input.path}" to "$outputPath" '
138+
'failed with exit code $code.\n'
139+
'scenec stdout:\n$stdout\n'
140+
'scenec stderr:\n$stderr',
141+
);
142+
}
143+
return false;
144+
}
145+
return true;
146+
}
147+
}
148+
149+
class SceneImporterException implements Exception {
150+
SceneImporterException._(this.message);
151+
152+
final String message;
153+
154+
@override
155+
String toString() => 'SceneImporterException: $message\n\n';
156+
}

packages/flutter_tools/lib/src/bundle_builder.dart

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'build_info.dart';
1313
import 'build_system/build_system.dart';
1414
import 'build_system/depfile.dart';
1515
import 'build_system/targets/common.dart';
16+
import 'build_system/targets/scene_importer.dart';
1617
import 'build_system/targets/shader_compiler.dart';
1718
import 'bundle.dart';
1819
import 'cache.dart';
@@ -158,6 +159,13 @@ Future<void> writeBundle(
158159
artifacts: globals.artifacts!,
159160
);
160161

162+
final SceneImporter sceneImporter = SceneImporter(
163+
processManager: globals.processManager,
164+
logger: globals.logger,
165+
fileSystem: globals.fs,
166+
artifacts: globals.artifacts!,
167+
);
168+
161169
// Limit number of open files to avoid running out of file descriptors.
162170
final Pool pool = Pool(64);
163171
await Future.wait<void>(
@@ -189,6 +197,12 @@ Future<void> writeBundle(
189197
json: targetPlatform == TargetPlatform.web_javascript,
190198
);
191199
break;
200+
case AssetKind.model:
201+
doCopy = !await sceneImporter.importScene(
202+
input: input,
203+
outputPath: file.path,
204+
);
205+
break;
192206
}
193207
if (doCopy) {
194208
input.copySync(file.path);

0 commit comments

Comments
 (0)