[flutter_tools] add tool support for shader hot reload (#107963)
This commit is contained in:
parent
0b27bd307b
commit
7e683c023a
@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
@ -431,7 +430,6 @@ class _InkSparkleFactory extends InteractiveInkFeatureFactory {
|
||||
|
||||
const _InkSparkleFactory.constantTurbulenceSeed() : turbulenceSeed = 1337.0;
|
||||
|
||||
// TODO(clocksmith): Update this once shaders are precompiled.
|
||||
static void compileShaderIfNeccessary() {
|
||||
if (!_initCalled) {
|
||||
FragmentShaderManager.inkSparkle().then((FragmentShaderManager manager) {
|
||||
@ -440,6 +438,7 @@ class _InkSparkleFactory extends InteractiveInkFeatureFactory {
|
||||
_initCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _initCalled = false;
|
||||
static FragmentShaderManager? _shaderManager;
|
||||
|
||||
|
@ -30,7 +30,7 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
|
||||
testWidgets('InkSparkle default splashFactory paints with drawRect when bounded', (WidgetTester tester) async {
|
||||
@ -61,7 +61,7 @@ void main() {
|
||||
// ignore: avoid_dynamic_calls
|
||||
expect((material as dynamic).debugInkFeatures, isEmpty);
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
|
||||
testWidgets('InkSparkle default splashFactory paints with drawPaint when unbounded', (WidgetTester tester) async {
|
||||
@ -84,7 +84,7 @@ void main() {
|
||||
final MaterialInkController material = Material.of(tester.element(buttonFinder))!;
|
||||
expect(material, paintsExactlyCountTimes(#drawPaint, 1));
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
|
||||
/////////////
|
||||
@ -94,19 +94,19 @@ void main() {
|
||||
testWidgets('InkSparkle renders with sparkles when top left of button is tapped', (WidgetTester tester) async {
|
||||
await _runTest(tester, 'top_left', 0.2);
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
|
||||
testWidgets('InkSparkle renders with sparkles when center of button is tapped', (WidgetTester tester) async {
|
||||
await _runTest(tester, 'center', 0.5);
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
|
||||
testWidgets('InkSparkle renders with sparkles when bottom right of button is tapped', (WidgetTester tester) async {
|
||||
await _runTest(tester, 'bottom_right', 0.8);
|
||||
},
|
||||
skip: kIsWeb, // [intended] SPIR-V shaders are not yet supported for web.
|
||||
skip: kIsWeb, // [intended] shaders are not yet supported for web.
|
||||
);
|
||||
}
|
||||
|
||||
@ -132,12 +132,11 @@ Future<void> _runTest(WidgetTester tester, String positionName, double distanceF
|
||||
|
||||
final Finder buttonFinder = find.byKey(buttonKey);
|
||||
final Finder repaintFinder = find.byKey(repaintKey);
|
||||
|
||||
await _warmUpShader(tester, buttonFinder);
|
||||
|
||||
final Offset topLeft = tester.getTopLeft(buttonFinder);
|
||||
final Offset bottomRight = tester.getBottomRight(buttonFinder);
|
||||
|
||||
await _warmUpShader(tester, buttonFinder);
|
||||
|
||||
final Offset target = topLeft + (bottomRight - topLeft) * distanceFromTopLeft;
|
||||
await tester.tapAt(target);
|
||||
for (int i = 0; i <= 5; i++) {
|
||||
|
@ -15,6 +15,7 @@ import '../exceptions.dart';
|
||||
import 'assets.dart';
|
||||
import 'common.dart';
|
||||
import 'icon_tree_shaker.dart';
|
||||
import 'shader_compiler.dart';
|
||||
|
||||
/// Prepares the asset bundle in the format expected by flutter.gradle.
|
||||
///
|
||||
@ -66,6 +67,7 @@ abstract class AndroidAssetBundle extends Target {
|
||||
outputDirectory,
|
||||
targetPlatform: TargetPlatform.android,
|
||||
buildMode: buildMode,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -31,6 +31,7 @@ Future<Depfile> copyAssets(
|
||||
Map<String, DevFSContent>? additionalContent,
|
||||
required TargetPlatform targetPlatform,
|
||||
BuildMode? buildMode,
|
||||
required ShaderTarget shaderTarget,
|
||||
}) async {
|
||||
// Check for an SkSL bundle.
|
||||
final String? shaderBundlePath = environment.defines[kBundleSkSLPath] ?? environment.inputs[kBundleSkSLPath];
|
||||
@ -124,6 +125,7 @@ Future<Depfile> copyAssets(
|
||||
doCopy = !await shaderCompiler.compileShader(
|
||||
input: content.file as File,
|
||||
outputPath: file.path,
|
||||
target: shaderTarget,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -306,6 +308,7 @@ class CopyAssets extends Target {
|
||||
environment,
|
||||
output,
|
||||
targetPlatform: TargetPlatform.android,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -74,6 +74,7 @@ class CopyFlutterBundle extends Target {
|
||||
environment.outputDir,
|
||||
targetPlatform: TargetPlatform.android,
|
||||
buildMode: buildMode,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -493,6 +493,7 @@ abstract class IosAssetBundle extends Target {
|
||||
environment,
|
||||
assetDirectory,
|
||||
targetPlatform: TargetPlatform.ios,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -15,6 +15,7 @@ import 'assets.dart';
|
||||
import 'common.dart';
|
||||
import 'desktop.dart';
|
||||
import 'icon_tree_shaker.dart';
|
||||
import 'shader_compiler.dart';
|
||||
|
||||
/// The only files/subdirectories we care out.
|
||||
const List<String> _kLinuxArtifacts = <String>[
|
||||
@ -143,7 +144,8 @@ abstract class BundleLinuxAssets extends Target {
|
||||
targetPlatform: targetPlatform,
|
||||
additionalContent: <String, DevFSContent>{
|
||||
'version.json': DevFSStringContent(versionInfo),
|
||||
}
|
||||
},
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -15,6 +15,7 @@ import '../exceptions.dart';
|
||||
import 'assets.dart';
|
||||
import 'common.dart';
|
||||
import 'icon_tree_shaker.dart';
|
||||
import 'shader_compiler.dart';
|
||||
|
||||
/// Copy the macOS framework to the correct copy dir by invoking 'rsync'.
|
||||
///
|
||||
@ -390,6 +391,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
environment,
|
||||
assetDirectory,
|
||||
targetPlatform: TargetPlatform.darwin,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -2,6 +2,11 @@
|
||||
// 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';
|
||||
@ -9,9 +14,110 @@ import '../../base/error_handling_io.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/io.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../convert.dart';
|
||||
import '../../devfs.dart';
|
||||
import '../build_system.dart';
|
||||
|
||||
/// The output shader format that should be used by the [ShaderCompiler].
|
||||
enum ShaderTarget {
|
||||
impellerAndroid('--opengl-es'),
|
||||
impelleriOS('--metal-ios'),
|
||||
sksl('--sksl');
|
||||
|
||||
const ShaderTarget(this.target);
|
||||
|
||||
final String target;
|
||||
}
|
||||
|
||||
/// A wrapper around [ShaderCompiler] to support hot reload of shader sources.
|
||||
class DevelopmentShaderCompiler {
|
||||
DevelopmentShaderCompiler({
|
||||
required ShaderCompiler shaderCompiler,
|
||||
required FileSystem fileSystem,
|
||||
@visibleForTesting math.Random? random,
|
||||
}) : _shaderCompiler = shaderCompiler,
|
||||
_fileSystem = fileSystem,
|
||||
_random = random ?? math.Random();
|
||||
|
||||
final ShaderCompiler _shaderCompiler;
|
||||
final FileSystem _fileSystem;
|
||||
final Pool _compilationPool = Pool(4);
|
||||
final math.Random _random;
|
||||
|
||||
late ShaderTarget _shaderTarget;
|
||||
bool _debugConfigured = false;
|
||||
|
||||
/// Configure the output format of the shader compiler for a particular
|
||||
/// flutter device.
|
||||
void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) {
|
||||
switch (platform) {
|
||||
case TargetPlatform.ios:
|
||||
_shaderTarget = enableImpeller ? ShaderTarget.impelleriOS : ShaderTarget.sksl;
|
||||
break;
|
||||
case TargetPlatform.android_arm64:
|
||||
case TargetPlatform.android_x64:
|
||||
case TargetPlatform.android_x86:
|
||||
case TargetPlatform.android_arm:
|
||||
case TargetPlatform.android:
|
||||
_shaderTarget = enableImpeller ? ShaderTarget.impellerAndroid : ShaderTarget.sksl;
|
||||
break;
|
||||
case TargetPlatform.darwin:
|
||||
case TargetPlatform.linux_x64:
|
||||
case TargetPlatform.linux_arm64:
|
||||
case TargetPlatform.windows_x64:
|
||||
case TargetPlatform.fuchsia_arm64:
|
||||
case TargetPlatform.fuchsia_x64:
|
||||
case TargetPlatform.tester:
|
||||
case TargetPlatform.web_javascript:
|
||||
assert(!enableImpeller);
|
||||
_shaderTarget = ShaderTarget.sksl;
|
||||
break;
|
||||
case null:
|
||||
return;
|
||||
}
|
||||
_debugConfigured = true;
|
||||
}
|
||||
|
||||
/// Recompile the input shader and return a devfs content that should be synced
|
||||
/// to the attached device in its place.
|
||||
Future<DevFSContent?> recompileShader(DevFSContent inputShader) async {
|
||||
assert(_debugConfigured);
|
||||
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 (inputShader is DevFSFileContent) {
|
||||
inputFile = inputShader.file as File;
|
||||
} else {
|
||||
inputFile = _fileSystem.systemTempDirectory.childFile('${_random.nextDouble()}.temp');
|
||||
inputFile.writeAsBytesSync(await inputShader.contentsAsBytes());
|
||||
cleanupInput = true;
|
||||
}
|
||||
final bool success = await _shaderCompiler.compileShader(
|
||||
input: inputFile,
|
||||
outputPath: output.path,
|
||||
target: _shaderTarget,
|
||||
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 shader compiler
|
||||
/// impellerc.
|
||||
class ShaderCompiler {
|
||||
@ -49,6 +155,8 @@ class ShaderCompiler {
|
||||
Future<bool> compileShader({
|
||||
required File input,
|
||||
required String outputPath,
|
||||
required ShaderTarget target,
|
||||
bool fatal = true,
|
||||
}) async {
|
||||
final File impellerc = _fs.file(
|
||||
_artifacts.getHostArtifact(HostArtifact.impellerc),
|
||||
@ -62,10 +170,7 @@ class ShaderCompiler {
|
||||
|
||||
final List<String> cmd = <String>[
|
||||
impellerc.path,
|
||||
// TODO(zanderso): When impeller is enabled, the correct flags for the
|
||||
// target backend will need to be passed.
|
||||
// https://github.com/flutter/flutter/issues/102853
|
||||
'--sksl',
|
||||
target.target,
|
||||
'--iplr',
|
||||
'--sl=$outputPath',
|
||||
'--spirv=$outputPath.spirv',
|
||||
@ -78,12 +183,14 @@ class ShaderCompiler {
|
||||
if (code != 0) {
|
||||
_logger.printTrace(await utf8.decodeStream(impellercProcess.stdout));
|
||||
_logger.printError(await utf8.decodeStream(impellercProcess.stderr));
|
||||
throw ShaderCompilerException._(
|
||||
'Shader compilation of "${input.path}" to "$outputPath" '
|
||||
'failed with exit code $code.',
|
||||
);
|
||||
if (fatal) {
|
||||
throw ShaderCompilerException._(
|
||||
'Shader compilation of "${input.path}" to "$outputPath" '
|
||||
'failed with exit code $code.',
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorHandlingFileSystem.deleteIfExists(_fs.file('$outputPath.spirv'));
|
||||
return true;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import '../depfile.dart';
|
||||
import '../exceptions.dart';
|
||||
import 'assets.dart';
|
||||
import 'localizations.dart';
|
||||
import 'shader_compiler.dart';
|
||||
|
||||
/// Whether the application has web plugins.
|
||||
const String kHasWebPlugins = 'HasWebPlugins';
|
||||
@ -306,6 +307,7 @@ class WebReleaseBundle extends Target {
|
||||
environment,
|
||||
environment.outputDir.childDirectory('assets'),
|
||||
targetPlatform: TargetPlatform.web_javascript,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: globals.fs,
|
||||
|
@ -12,6 +12,7 @@ import 'assets.dart';
|
||||
import 'common.dart';
|
||||
import 'desktop.dart';
|
||||
import 'icon_tree_shaker.dart';
|
||||
import 'shader_compiler.dart';
|
||||
|
||||
/// The only files/subdirectories we care about.
|
||||
const List<String> _kWindowsArtifacts = <String>[
|
||||
@ -142,6 +143,7 @@ abstract class BundleWindowsAssets extends Target {
|
||||
environment,
|
||||
outputDirectory,
|
||||
targetPlatform: TargetPlatform.windows_x64,
|
||||
shaderTarget: ShaderTarget.sksl,
|
||||
);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
|
@ -184,6 +184,7 @@ Future<void> writeBundle(
|
||||
doCopy = !await shaderCompiler.compileShader(
|
||||
input: input,
|
||||
outputPath: file.path,
|
||||
target: ShaderTarget.sksl, // TODO(zanderso): configure impeller target when enabled.
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import 'base/logger.dart';
|
||||
import 'base/net.dart';
|
||||
import 'base/os.dart';
|
||||
import 'build_info.dart';
|
||||
import 'build_system/targets/shader_compiler.dart';
|
||||
import 'compile.dart';
|
||||
import 'convert.dart' show base64, utf8;
|
||||
import 'vmservice.dart';
|
||||
@ -479,6 +480,7 @@ class DevFS {
|
||||
final String fsName;
|
||||
final Directory? rootDirectory;
|
||||
final Set<String> assetPathsToEvict = <String>{};
|
||||
final Set<String> shaderPathsToEvict = <String>{};
|
||||
|
||||
// A flag to indicate whether we have called `setAssetDirectory` on the target device.
|
||||
bool hasSetAssetDirectory = false;
|
||||
@ -574,6 +576,7 @@ class DevFS {
|
||||
required List<Uri> invalidatedFiles,
|
||||
required PackageConfig packageConfig,
|
||||
required String dillOutputPath,
|
||||
required DevelopmentShaderCompiler shaderCompiler,
|
||||
DevFSWriter? devFSWriter,
|
||||
String? target,
|
||||
AssetBundle? bundle,
|
||||
@ -591,6 +594,8 @@ class DevFS {
|
||||
|
||||
// Update modified files
|
||||
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
|
||||
final List<Future<void>> pendingShaderCompiles = <Future<void>>[];
|
||||
bool shaderCompilationFailed = false;
|
||||
int syncedBytes = 0;
|
||||
if (fullRestart) {
|
||||
generator.reset();
|
||||
@ -634,14 +639,32 @@ class DevFS {
|
||||
if (!content.isModified || bundleFirstUpload) {
|
||||
return;
|
||||
}
|
||||
// Modified shaders must be recompiled per-target platform.
|
||||
final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, archivePath));
|
||||
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
|
||||
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
|
||||
}
|
||||
dirtyEntries[deviceUri] = content;
|
||||
syncedBytes += content.size;
|
||||
if (archivePath != null && !bundleFirstUpload) {
|
||||
assetPathsToEvict.add(archivePath);
|
||||
|
||||
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;
|
||||
}
|
||||
dirtyEntries[deviceUri] = content;
|
||||
syncedBytes += content.size;
|
||||
if (archivePath != null && !bundleFirstUpload) {
|
||||
shaderPathsToEvict.add(archivePath);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dirtyEntries[deviceUri] = content;
|
||||
syncedBytes += content.size;
|
||||
if (archivePath != null && !bundleFirstUpload) {
|
||||
assetPathsToEvict.add(archivePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -672,6 +695,12 @@ class DevFS {
|
||||
}
|
||||
_logger.printTrace('Updating files.');
|
||||
final Stopwatch transferTimer = _stopwatchFactory.createStopwatch('transfer')..start();
|
||||
|
||||
await Future.wait(pendingShaderCompiles);
|
||||
if (shaderCompilationFailed) {
|
||||
return UpdateFSReport();
|
||||
}
|
||||
|
||||
if (dirtyEntries.isNotEmpty) {
|
||||
await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri!, _httpWriter);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import '../base/logger.dart';
|
||||
import '../base/net.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../build_info.dart';
|
||||
import '../build_system/targets/shader_compiler.dart';
|
||||
import '../build_system/targets/web.dart';
|
||||
import '../bundle_builder.dart';
|
||||
import '../cache.dart';
|
||||
@ -792,6 +793,7 @@ class WebDevFS implements DevFS {
|
||||
required List<Uri> invalidatedFiles,
|
||||
required PackageConfig packageConfig,
|
||||
required String dillOutputPath,
|
||||
required DevelopmentShaderCompiler shaderCompiler,
|
||||
DevFSWriter? devFSWriter,
|
||||
String? target,
|
||||
AssetBundle? bundle,
|
||||
@ -923,6 +925,9 @@ class WebDevFS implements DevFS {
|
||||
void resetLastCompiled() {
|
||||
// Not used for web compilation.
|
||||
}
|
||||
|
||||
@override
|
||||
Set<String> get shaderPathsToEvict => <String>{};
|
||||
}
|
||||
|
||||
class ReleaseAssetServer {
|
||||
|
@ -502,6 +502,7 @@ class ResidentWebRunner extends ResidentRunner {
|
||||
invalidatedFiles: invalidationResult.uris!,
|
||||
packageConfig: invalidationResult.packageConfig!,
|
||||
trackWidgetCreation: debuggingOptions.buildInfo.trackWidgetCreation,
|
||||
shaderCompiler: device!.developmentShaderCompiler,
|
||||
);
|
||||
devFSStatus.stop();
|
||||
_logger!.printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.');
|
||||
|
@ -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/shader_compiler.dart';
|
||||
import 'bundle.dart';
|
||||
import 'cache.dart';
|
||||
import 'compile.dart';
|
||||
@ -49,6 +50,7 @@ class FlutterDevice {
|
||||
this.targetPlatform,
|
||||
ResidentCompiler? generator,
|
||||
this.userIdentifier,
|
||||
required this.developmentShaderCompiler,
|
||||
}) : assert(buildInfo.trackWidgetCreation != null),
|
||||
generator = generator ?? ResidentCompiler(
|
||||
globals.artifacts!.getArtifactPath(
|
||||
@ -87,6 +89,16 @@ class FlutterDevice {
|
||||
if (device.platformType == PlatformType.fuchsia) {
|
||||
targetModel = TargetModel.flutterRunner;
|
||||
}
|
||||
final DevelopmentShaderCompiler shaderCompiler = DevelopmentShaderCompiler(
|
||||
shaderCompiler: ShaderCompiler(
|
||||
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
|
||||
@ -184,6 +196,7 @@ class FlutterDevice {
|
||||
generator: generator,
|
||||
buildInfo: buildInfo,
|
||||
userIdentifier: userIdentifier,
|
||||
developmentShaderCompiler: shaderCompiler,
|
||||
);
|
||||
}
|
||||
|
||||
@ -192,6 +205,7 @@ class FlutterDevice {
|
||||
final ResidentCompiler? generator;
|
||||
final BuildInfo buildInfo;
|
||||
final String? userIdentifier;
|
||||
final DevelopmentShaderCompiler developmentShaderCompiler;
|
||||
|
||||
DevFSWriter? devFSWriter;
|
||||
Stream<Uri?>? observatoryUris;
|
||||
@ -563,6 +577,7 @@ class FlutterDevice {
|
||||
invalidatedFiles: invalidatedFiles,
|
||||
packageConfig: packageConfig,
|
||||
devFSWriter: devFSWriter,
|
||||
shaderCompiler: developmentShaderCompiler,
|
||||
dartPluginRegistrant: FlutterProject.current().dartPluginRegistrant,
|
||||
);
|
||||
} on DevFSException {
|
||||
|
@ -247,6 +247,9 @@ class HotRunner extends ResidentRunner {
|
||||
|
||||
for (final FlutterDevice? device in flutterDevices) {
|
||||
await device!.initLogReader();
|
||||
device
|
||||
.developmentShaderCompiler
|
||||
.configureCompiler(device.targetPlatform, enableImpeller: debuggingOptions.enableImpeller);
|
||||
}
|
||||
try {
|
||||
final List<Uri?> baseUris = await _initDevFS();
|
||||
@ -496,6 +499,7 @@ class HotRunner extends ResidentRunner {
|
||||
void _resetDirtyAssets() {
|
||||
for (final FlutterDevice? device in flutterDevices) {
|
||||
device!.devFS!.assetPathsToEvict.clear();
|
||||
device.devFS!.shaderPathsToEvict.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,7 +1031,7 @@ class HotRunner extends ResidentRunner {
|
||||
Future<void> evictDirtyAssets() async {
|
||||
final List<Future<Map<String, dynamic>?>> futures = <Future<Map<String, dynamic>>>[];
|
||||
for (final FlutterDevice? device in flutterDevices) {
|
||||
if (device!.devFS!.assetPathsToEvict.isEmpty) {
|
||||
if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
final List<FlutterView> views = await device.vmService!.getFlutterViews();
|
||||
@ -1061,7 +1065,17 @@ class HotRunner extends ResidentRunner {
|
||||
)
|
||||
);
|
||||
}
|
||||
for (final String assetPath in device.devFS!.shaderPathsToEvict) {
|
||||
futures.add(
|
||||
device.vmService!
|
||||
.flutterEvictShader(
|
||||
assetPath,
|
||||
isolateId: views.first.uiIsolate!.id!,
|
||||
)
|
||||
);
|
||||
}
|
||||
device.devFS!.assetPathsToEvict.clear();
|
||||
device.devFS!.shaderPathsToEvict.clear();
|
||||
}
|
||||
await Future.wait<Map<String, Object?>?>(futures);
|
||||
}
|
||||
|
@ -727,6 +727,19 @@ class FlutterVmService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>?> flutterEvictShader(String assetPath, {
|
||||
required String isolateId,
|
||||
}) {
|
||||
return invokeFlutterExtensionRpcRaw(
|
||||
'ext.ui.window.reinitializeShader',
|
||||
isolateId: isolateId,
|
||||
args: <String, Object?>{
|
||||
'assetKey': assetPath,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Exit the application by calling [exit] from `dart:io`.
|
||||
///
|
||||
/// This method is only supported by certain embedders. This is
|
||||
|
@ -2,10 +2,15 @@
|
||||
// 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 'package:file/memory.dart';
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
import '../../../src/fake_process_manager.dart';
|
||||
@ -33,7 +38,7 @@ void main() {
|
||||
fileSystem.file(notFragPath).createSync(recursive: true);
|
||||
});
|
||||
|
||||
testWithoutContext('compileShader invokes impellerc for .frag files', () async {
|
||||
testWithoutContext('compileShader invokes impellerc for .frag files and sksl target', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
@ -63,6 +68,7 @@ void main() {
|
||||
await shaderCompiler.compileShader(
|
||||
input: fileSystem.file(fragPath),
|
||||
outputPath: outputPath,
|
||||
target: ShaderTarget.sksl,
|
||||
),
|
||||
true,
|
||||
);
|
||||
@ -70,6 +76,78 @@ void main() {
|
||||
expect(fileSystem.file(outputSpirvPath).existsSync(), false);
|
||||
});
|
||||
|
||||
testWithoutContext('compileShader invokes impellerc for .frag files and metal ios target', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
impellerc,
|
||||
'--metal-ios',
|
||||
'--iplr',
|
||||
'--sl=$outputPath',
|
||||
'--spirv=$outputPath.spirv',
|
||||
'--input=$fragPath',
|
||||
'--input-type=frag',
|
||||
'--include=$fragDir',
|
||||
],
|
||||
onRun: () {
|
||||
fileSystem.file(outputPath).createSync(recursive: true);
|
||||
},
|
||||
),
|
||||
]);
|
||||
final ShaderCompiler shaderCompiler = ShaderCompiler(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
|
||||
expect(
|
||||
await shaderCompiler.compileShader(
|
||||
input: fileSystem.file(fragPath),
|
||||
outputPath: outputPath,
|
||||
target: ShaderTarget.impelleriOS,
|
||||
),
|
||||
true,
|
||||
);
|
||||
expect(fileSystem.file(outputPath).existsSync(), true);
|
||||
});
|
||||
|
||||
testWithoutContext('compileShader invokes impellerc for .frag files and opengl es', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
impellerc,
|
||||
'--opengl-es',
|
||||
'--iplr',
|
||||
'--sl=$outputPath',
|
||||
'--spirv=$outputPath.spirv',
|
||||
'--input=$fragPath',
|
||||
'--input-type=frag',
|
||||
'--include=$fragDir',
|
||||
],
|
||||
onRun: () {
|
||||
fileSystem.file(outputPath).createSync(recursive: true);
|
||||
},
|
||||
),
|
||||
]);
|
||||
final ShaderCompiler shaderCompiler = ShaderCompiler(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
|
||||
expect(
|
||||
await shaderCompiler.compileShader(
|
||||
input: fileSystem.file(fragPath),
|
||||
outputPath: outputPath,
|
||||
target: ShaderTarget.impellerAndroid,
|
||||
),
|
||||
true,
|
||||
);
|
||||
expect(fileSystem.file(outputPath).existsSync(), true);
|
||||
});
|
||||
|
||||
testWithoutContext('compileShader invokes impellerc for non-.frag files', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
@ -100,6 +178,7 @@ void main() {
|
||||
await shaderCompiler.compileShader(
|
||||
input: fileSystem.file(notFragPath),
|
||||
outputPath: outputPath,
|
||||
target: ShaderTarget.sksl,
|
||||
),
|
||||
true,
|
||||
);
|
||||
@ -134,9 +213,98 @@ void main() {
|
||||
() => shaderCompiler.compileShader(
|
||||
input: fileSystem.file(notFragPath),
|
||||
outputPath: outputPath,
|
||||
target: ShaderTarget.sksl,
|
||||
),
|
||||
throwsA(isA<ShaderCompilerException>()),
|
||||
);
|
||||
expect(fileSystem.file(outputPath).existsSync(), false);
|
||||
});
|
||||
|
||||
testWithoutContext('DevelopmentShaderCompiler can compile for android non-impeller', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
impellerc,
|
||||
'--sksl',
|
||||
'--iplr',
|
||||
'--sl=/.tmp_rand0/0.8255140718871702.temp',
|
||||
'--spirv=/.tmp_rand0/0.8255140718871702.temp.spirv',
|
||||
'--input=$fragPath',
|
||||
'--input-type=frag',
|
||||
'--include=$fragDir',
|
||||
],
|
||||
onRun: () {
|
||||
fileSystem.file('/.tmp_rand0/0.8255140718871702.temp.spirv').createSync();
|
||||
fileSystem.file('/.tmp_rand0/0.8255140718871702.temp')
|
||||
..createSync()
|
||||
..writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
}
|
||||
),
|
||||
]);
|
||||
fileSystem.file(fragPath).writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
final ShaderCompiler shaderCompiler = ShaderCompiler(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
final DevelopmentShaderCompiler developmentShaderCompiler = DevelopmentShaderCompiler(
|
||||
shaderCompiler: shaderCompiler,
|
||||
fileSystem: fileSystem,
|
||||
random: math.Random(0),
|
||||
);
|
||||
|
||||
developmentShaderCompiler.configureCompiler(TargetPlatform.android, enableImpeller: false);
|
||||
|
||||
final DevFSContent? content = await developmentShaderCompiler
|
||||
.recompileShader(DevFSFileContent(fileSystem.file(fragPath)));
|
||||
|
||||
expect(await content!.contentsAsBytes(), <int>[1, 2, 3, 4]);
|
||||
expect(fileSystem.file('/.tmp_rand0/0.8255140718871702.temp.spirv'), isNot(exists));
|
||||
expect(fileSystem.file('/.tmp_rand0/0.8255140718871702.temp'), isNot(exists));
|
||||
});
|
||||
|
||||
testWithoutContext('DevelopmentShaderCompiler can compile for android with impeller', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
impellerc,
|
||||
'--opengl-es',
|
||||
'--iplr',
|
||||
'--sl=/.tmp_rand0/0.8255140718871702.temp',
|
||||
'--spirv=/.tmp_rand0/0.8255140718871702.temp.spirv',
|
||||
'--input=$fragPath',
|
||||
'--input-type=frag',
|
||||
'--include=$fragDir',
|
||||
],
|
||||
onRun: () {
|
||||
fileSystem.file('/.tmp_rand0/0.8255140718871702.temp.spirv').createSync();
|
||||
fileSystem.file('/.tmp_rand0/0.8255140718871702.temp')
|
||||
..createSync()
|
||||
..writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
}
|
||||
),
|
||||
]);
|
||||
fileSystem.file(fragPath).writeAsBytesSync(<int>[1, 2, 3, 4]);
|
||||
final ShaderCompiler shaderCompiler = ShaderCompiler(
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
artifacts: artifacts,
|
||||
);
|
||||
final DevelopmentShaderCompiler developmentShaderCompiler = DevelopmentShaderCompiler(
|
||||
shaderCompiler: shaderCompiler,
|
||||
fileSystem: fileSystem,
|
||||
random: math.Random(0),
|
||||
);
|
||||
|
||||
developmentShaderCompiler.configureCompiler(TargetPlatform.android, enableImpeller: true);
|
||||
|
||||
final DevFSContent? content = await developmentShaderCompiler
|
||||
.recompileShader(DevFSFileContent(fileSystem.file(fragPath)));
|
||||
|
||||
expect(await content!.contentsAsBytes(), <int>[1, 2, 3, 4]);
|
||||
expect(fileSystem.file('/.tmp_rand0/0.8255140718871702.temp.spirv'), isNot(exists));
|
||||
expect(fileSystem.file('/.tmp_rand0/0.8255140718871702.temp'), isNot(exists));
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.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/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/resident_runner.dart';
|
||||
import 'package:flutter_tools/src/run_cold.dart';
|
||||
@ -201,7 +203,7 @@ class TestFlutterDevice extends FlutterDevice {
|
||||
required this.exception,
|
||||
required ResidentCompiler generator,
|
||||
}) : assert(exception != null),
|
||||
super(device, buildInfo: BuildInfo.debug, generator: generator);
|
||||
super(device, buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler());
|
||||
|
||||
/// The exception to throw when the connect method is called.
|
||||
final Exception exception;
|
||||
@ -274,3 +276,15 @@ class FakeVmService extends Fake implements VmService {
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import 'package:flutter_tools/src/base/os.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/build_info.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';
|
||||
import 'package:flutter_tools/src/vmservice.dart';
|
||||
@ -223,6 +224,7 @@ void main() {
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.syncedBytes, 5);
|
||||
@ -262,6 +264,7 @@ void main() {
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.success, false);
|
||||
@ -302,6 +305,7 @@ void main() {
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.success, true);
|
||||
@ -344,6 +348,7 @@ void main() {
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
devFSWriter: localDevFSWriter,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.success, true);
|
||||
@ -393,6 +398,7 @@ void main() {
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
devFSWriter: writer,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.success, true);
|
||||
@ -466,6 +472,7 @@ void main() {
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(report.success, true);
|
||||
@ -551,6 +558,7 @@ void main() {
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
bundle: FakeBundle(),
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
expect(report1.success, true);
|
||||
logger.messages.clear();
|
||||
@ -564,6 +572,7 @@ void main() {
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
bundle: FakeBundle(),
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
expect(report2.success, true);
|
||||
|
||||
@ -575,6 +584,66 @@ void main() {
|
||||
expect(compileLibMainIndex, greaterThanOrEqualTo(0));
|
||||
expect(bundleProcessingDoneIndex, greaterThan(compileLibMainIndex));
|
||||
});
|
||||
|
||||
group('Shader compilation', () {
|
||||
late FileSystem fileSystem;
|
||||
late ProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
processManager = FakeProcessManager.any();
|
||||
});
|
||||
|
||||
testUsingContext('DevFS recompiles shaders', () async {
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[createDevFSRequest],
|
||||
httpAddress: Uri.parse('http://localhost'),
|
||||
);
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final DevFS devFS = DevFS(
|
||||
fakeVmServiceHost.vmService,
|
||||
'test',
|
||||
fileSystem.currentDirectory,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
osUtils: FakeOperatingSystemUtils(),
|
||||
httpClient: FakeHttpClient.any(),
|
||||
);
|
||||
|
||||
await devFS.create();
|
||||
|
||||
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
|
||||
..onRecompile = (Uri mainUri, List<Uri>? invalidatedFiles) async {
|
||||
fileSystem.file('lib/foo.dill')
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
|
||||
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
|
||||
};
|
||||
final FakeBundle bundle = FakeBundle()
|
||||
..entries['foo.frag'] = DevFSByteContent(<int>[1, 2, 3, 4])
|
||||
..entries['not.frag'] = DevFSByteContent(<int>[1, 2, 3, 4])
|
||||
..entryKinds['foo.frag'] = AssetKind.shader;
|
||||
|
||||
final UpdateFSReport report = await devFS.update(
|
||||
mainUri: Uri.parse('lib/main.dart'),
|
||||
generator: residentCompiler,
|
||||
dillOutputPath: 'lib/foo.dill',
|
||||
pathToReload: 'lib/foo.txt.dill',
|
||||
trackWidgetCreation: false,
|
||||
invalidatedFiles: <Uri>[],
|
||||
packageConfig: PackageConfig.empty,
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
bundle: bundle,
|
||||
);
|
||||
|
||||
expect(report.success, true);
|
||||
expect(devFS.shaderPathsToEvict, <String>{'foo.frag'});
|
||||
expect(devFS.assetPathsToEvict, <String>{'not.frag'});
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
||||
@ -630,10 +699,10 @@ class FakeBundle extends AssetBundle {
|
||||
Map<String, Map<String, DevFSContent>> get deferredComponentsEntries => <String, Map<String, DevFSContent>>{};
|
||||
|
||||
@override
|
||||
Map<String, DevFSContent> get entries => <String, DevFSContent>{};
|
||||
final Map<String, DevFSContent> entries = <String, DevFSContent>{};
|
||||
|
||||
@override
|
||||
Map<String, AssetKind> get entryKinds => <String, AssetKind>{};
|
||||
final Map<String, AssetKind> entryKinds = <String, AssetKind>{};
|
||||
|
||||
@override
|
||||
List<File> get inputFiles => <File>[];
|
||||
@ -703,3 +772,15 @@ class AnsweringFakeProcess implements io.Process {
|
||||
@override
|
||||
int get pid => 42;
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) async {
|
||||
return DevFSByteContent(await inputShader.contentsAsBytes());
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.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/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
@ -138,7 +139,8 @@ void main() {
|
||||
..writeAsStringSync('\n');
|
||||
final FakeDevice device = FakeDevice();
|
||||
final List<FlutterDevice> devices = <FlutterDevice>[
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = FakeDevFs(),
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler())
|
||||
..devFS = FakeDevFs(),
|
||||
];
|
||||
final OperationResult result = await HotRunner(
|
||||
devices,
|
||||
@ -208,7 +210,7 @@ void main() {
|
||||
..writeAsStringSync('\n');
|
||||
final FakeDevice device = FakeDevice();
|
||||
final List<FlutterDevice> devices = <FlutterDevice>[
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug),
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()),
|
||||
];
|
||||
await HotRunner(
|
||||
devices,
|
||||
@ -230,7 +232,7 @@ void main() {
|
||||
..writeAsStringSync('\n');
|
||||
final FakeDevice device = FakeDevice();
|
||||
final List<FlutterDevice> devices = <FlutterDevice>[
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug),
|
||||
FlutterDevice(device, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()),
|
||||
];
|
||||
await HotRunner(
|
||||
devices,
|
||||
@ -567,6 +569,9 @@ class FakeDevFs extends Fake implements DevFS {
|
||||
@override
|
||||
Set<String> assetPathsToEvict = <String>{};
|
||||
|
||||
@override
|
||||
Set<String> shaderPathsToEvict= <String>{};
|
||||
|
||||
@override
|
||||
Uri baseUri;
|
||||
}
|
||||
@ -661,7 +666,7 @@ class TestFlutterDevice extends FlutterDevice {
|
||||
@required this.exception,
|
||||
@required ResidentCompiler generator,
|
||||
}) : assert(exception != null),
|
||||
super(device, buildInfo: BuildInfo.debug, generator: generator);
|
||||
super(device, buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler());
|
||||
|
||||
/// The exception to throw when the connect method is called.
|
||||
final Exception exception;
|
||||
@ -739,3 +744,15 @@ class FakeVm extends Fake implements vm_service.VM {
|
||||
@override
|
||||
List<vm_service.IsolateRef> get isolates => <vm_service.IsolateRef>[];
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,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/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/convert.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
@ -145,6 +146,14 @@ const FakeVmServiceRequest evict = FakeVmServiceRequest(
|
||||
}
|
||||
);
|
||||
|
||||
const FakeVmServiceRequest evictShader = FakeVmServiceRequest(
|
||||
method: 'ext.ui.window.reinitializeShader',
|
||||
args: <String, Object>{
|
||||
'assetKey': 'foo.frag',
|
||||
'isolateId': '1',
|
||||
}
|
||||
);
|
||||
|
||||
final Uri testUri = Uri.parse('foo://bar');
|
||||
|
||||
void main() {
|
||||
@ -2240,6 +2249,30 @@ flutter:
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
}));
|
||||
|
||||
testUsingContext('HotRunner sets asset directory when first evict shaders', () => testbed.run(() async {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
listViews,
|
||||
setAssetBundlePath,
|
||||
evictShader,
|
||||
]);
|
||||
residentRunner = HotRunner(
|
||||
<FlutterDevice>[
|
||||
flutterDevice,
|
||||
],
|
||||
stayResident: false,
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
target: 'main.dart',
|
||||
devtoolsHandler: createNoOpHandler,
|
||||
);
|
||||
|
||||
(flutterDevice.devFS as FakeDevFS).shaderPathsToEvict = <String>{'foo.frag'};
|
||||
|
||||
expect(flutterDevice.devFS.hasSetAssetDirectory, false);
|
||||
await (residentRunner as HotRunner).evictDirtyAssets();
|
||||
expect(flutterDevice.devFS.hasSetAssetDirectory, true);
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
}));
|
||||
|
||||
testUsingContext('HotRunner does not sets asset directory when no assets to evict', () => testbed.run(() async {
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
|
||||
]);
|
||||
@ -2296,7 +2329,7 @@ class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceE
|
||||
|
||||
class TestFlutterDevice extends FlutterDevice {
|
||||
TestFlutterDevice(Device device, { Stream<Uri> observatoryUris })
|
||||
: super(device, buildInfo: BuildInfo.debug) {
|
||||
: super(device, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler()) {
|
||||
_observatoryUris = observatoryUris;
|
||||
}
|
||||
|
||||
@ -2332,6 +2365,12 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
|
||||
@override
|
||||
ResidentCompiler generator;
|
||||
|
||||
@override
|
||||
DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
TargetPlatform get targetPlatform => TargetPlatform.android;
|
||||
|
||||
@override
|
||||
Stream<Uri> get observatoryUris => Stream<Uri>.value(testUri);
|
||||
|
||||
@ -2420,7 +2459,7 @@ class FakeDelegateFlutterDevice extends FlutterDevice {
|
||||
BuildInfo buildInfo,
|
||||
ResidentCompiler residentCompiler,
|
||||
this.fakeDevFS,
|
||||
) : super(device, buildInfo: buildInfo, generator: residentCompiler);
|
||||
) : super(device, buildInfo: buildInfo, generator: residentCompiler, developmentShaderCompiler: const FakeShaderCompiler());
|
||||
|
||||
@override
|
||||
Future<void> connect({
|
||||
@ -2591,6 +2630,9 @@ class FakeDevFS extends Fake implements DevFS {
|
||||
@override
|
||||
Set<String> assetPathsToEvict = <String>{};
|
||||
|
||||
@override
|
||||
Set<String> shaderPathsToEvict = <String>{};
|
||||
|
||||
UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);
|
||||
|
||||
@override
|
||||
@ -2615,6 +2657,7 @@ class FakeDevFS extends Fake implements DevFS {
|
||||
@required List<Uri> invalidatedFiles,
|
||||
@required PackageConfig packageConfig,
|
||||
@required String dillOutputPath,
|
||||
@required DevelopmentShaderCompiler shaderCompiler,
|
||||
DevFSWriter devFSWriter,
|
||||
String target,
|
||||
AssetBundle bundle,
|
||||
@ -2627,3 +2670,15 @@ class FakeDevFS extends Fake implements DevFS {
|
||||
return nextUpdateReport;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,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/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
@ -1336,6 +1337,7 @@ class FakeWebDevFS extends Fake implements WebDevFS {
|
||||
@required List<Uri> invalidatedFiles,
|
||||
@required PackageConfig packageConfig,
|
||||
@required String dillOutputPath,
|
||||
@required DevelopmentShaderCompiler shaderCompiler,
|
||||
DevFSWriter devFSWriter,
|
||||
String target,
|
||||
AssetBundle bundle,
|
||||
@ -1454,6 +1456,9 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
|
||||
@override
|
||||
Stream<Uri> get observatoryUris => Stream<Uri>.value(testUri);
|
||||
|
||||
@override
|
||||
DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
FlutterVmService vmService;
|
||||
|
||||
@ -1524,3 +1529,15 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
|
||||
@override
|
||||
Future<void> updateReloadStatus(bool wasReloadSuccessful) async {}
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/signals.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/build_info.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';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/resident_devtools_handler.dart';
|
||||
import 'package:flutter_tools/src/resident_runner.dart';
|
||||
@ -1215,7 +1217,12 @@ void main() {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final ProcessInfo processInfo = ProcessInfo.test(fs);
|
||||
final FakeResidentRunner residentRunner = FakeResidentRunner(
|
||||
FlutterDevice(FakeDevice(), buildInfo: BuildInfo.debug, generator: FakeResidentCompiler()),
|
||||
FlutterDevice(
|
||||
FakeDevice(),
|
||||
buildInfo: BuildInfo.debug,
|
||||
generator: FakeResidentCompiler(),
|
||||
developmentShaderCompiler: const FakeShaderCompiler(),
|
||||
),
|
||||
testLogger,
|
||||
fs,
|
||||
);
|
||||
@ -1391,6 +1398,7 @@ TerminalHandler setUpTerminalHandler(List<FakeVmServiceRequest> requests, {
|
||||
FakeDevice()..supportsScreenshot = supportsScreenshot,
|
||||
buildInfo: BuildInfo(buildMode, '', treeShakeIcons: false),
|
||||
generator: FakeResidentCompiler(),
|
||||
developmentShaderCompiler: const FakeShaderCompiler(),
|
||||
targetPlatform: web
|
||||
? TargetPlatform.web_javascript
|
||||
: TargetPlatform.android_arm,
|
||||
@ -1499,3 +1507,15 @@ class _TestSignals implements Signals {
|
||||
Stream<Object> get errors => _errors.stream;
|
||||
final StreamController<Object> _errors = StreamController<Object>();
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,16 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
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/shader_compiler.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/web.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/convert.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/isolated/devfs_web.dart';
|
||||
import 'package:flutter_tools/src/web/compile.dart';
|
||||
import 'package:logging/logging.dart' as logging;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:test/fake.dart';
|
||||
@ -721,6 +724,7 @@ void main() {
|
||||
packageConfig: PackageConfig.empty,
|
||||
pathToReload: '',
|
||||
dillOutputPath: 'out.dill',
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(webDevFS.webAssetServer.getFile('require.js'), isNotNull);
|
||||
@ -832,6 +836,7 @@ void main() {
|
||||
packageConfig: PackageConfig.empty,
|
||||
pathToReload: '',
|
||||
dillOutputPath: '',
|
||||
shaderCompiler: const FakeShaderCompiler(),
|
||||
);
|
||||
|
||||
expect(webDevFS.webAssetServer.getFile('require.js'), isNotNull);
|
||||
@ -1125,3 +1130,15 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
||||
const FakeShaderCompiler();
|
||||
|
||||
@override
|
||||
void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { }
|
||||
|
||||
@override
|
||||
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user