diff --git a/packages/flutter/lib/src/material/ink_sparkle.dart b/packages/flutter/lib/src/material/ink_sparkle.dart index 2dd08f36b2..2b02319ed5 100644 --- a/packages/flutter/lib/src/material/ink_sparkle.dart +++ b/packages/flutter/lib/src/material/ink_sparkle.dart @@ -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; diff --git a/packages/flutter/test/material/ink_sparkle_test.dart b/packages/flutter/test/material/ink_sparkle_test.dart index 5d71e9b42f..f55ea2cc23 100644 --- a/packages/flutter/test/material/ink_sparkle_test.dart +++ b/packages/flutter/test/material/ink_sparkle_test.dart @@ -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 _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++) { diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index ee86042f74..aa60e47a8a 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index 5f458bd53e..d719a1c3fd 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -31,6 +31,7 @@ Future copyAssets( Map? 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 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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index afa21709af..656f1943df 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 27a5098535..ae0e8dea69 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -493,6 +493,7 @@ abstract class IosAssetBundle extends Target { environment, assetDirectory, targetPlatform: TargetPlatform.ios, + shaderTarget: ShaderTarget.sksl, ); final DepfileService depfileService = DepfileService( fileSystem: environment.fileSystem, diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart index 322195e1be..f68b425130 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -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 _kLinuxArtifacts = [ @@ -143,7 +144,8 @@ abstract class BundleLinuxAssets extends Target { targetPlatform: targetPlatform, additionalContent: { 'version.json': DevFSStringContent(versionInfo), - } + }, + shaderTarget: ShaderTarget.sksl, ); final DepfileService depfileService = DepfileService( fileSystem: environment.fileSystem, diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart index ac5ffc591e..10a85d392f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/shader_compiler.dart b/packages/flutter_tools/lib/src/build_system/targets/shader_compiler.dart index 2d642de0fb..827e571121 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/shader_compiler.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/shader_compiler.dart @@ -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 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 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 cmd = [ 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; } diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 35b7b4e765..a72213a1a1 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/windows.dart b/packages/flutter_tools/lib/src/build_system/targets/windows.dart index b5f80a331e..f940ebc596 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -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 _kWindowsArtifacts = [ @@ -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, diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index e72f5a9bea..df1dc51613 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -184,6 +184,7 @@ Future writeBundle( doCopy = !await shaderCompiler.compileShader( input: input, outputPath: file.path, + target: ShaderTarget.sksl, // TODO(zanderso): configure impeller target when enabled. ); break; } diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 39d4768576..50d49f6765 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -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 assetPathsToEvict = {}; + final Set shaderPathsToEvict = {}; // A flag to indicate whether we have called `setAssetDirectory` on the target device. bool hasSetAssetDirectory = false; @@ -574,6 +576,7 @@ class DevFS { required List 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 dirtyEntries = {}; + final List> pendingShaderCompiles = >[]; + 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 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); } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index bf41154672..301678b32a 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -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 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 get shaderPathsToEvict => {}; } class ReleaseAssetServer { diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index ccb52c41cf..ac259be640 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -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)}.'); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 38a79c6a8a..5cddcf8876 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -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? observatoryUris; @@ -563,6 +577,7 @@ class FlutterDevice { invalidatedFiles: invalidatedFiles, packageConfig: packageConfig, devFSWriter: devFSWriter, + shaderCompiler: developmentShaderCompiler, dartPluginRegistrant: FlutterProject.current().dartPluginRegistrant, ); } on DevFSException { diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index f849bc02c1..0745e240d1 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -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 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 evictDirtyAssets() async { final List?>> futures = >>[]; for (final FlutterDevice? device in flutterDevices) { - if (device!.devFS!.assetPathsToEvict.isEmpty) { + if (device!.devFS!.assetPathsToEvict.isEmpty && device.devFS!.shaderPathsToEvict.isEmpty) { continue; } final List 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?>(futures); } diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index a704c8c706..2238814dff 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -727,6 +727,19 @@ class FlutterVmService { ); } + Future?> flutterEvictShader(String assetPath, { + required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.ui.window.reinitializeShader', + isolateId: isolateId, + args: { + 'assetKey': assetPath, + }, + ); + } + + /// Exit the application by calling [exit] from `dart:io`. /// /// This method is only supported by certain embedders. This is diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart index 2e4329bf96..299dbc3e17 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart @@ -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( command: [ @@ -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( + command: [ + 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( + command: [ + 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( @@ -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()), ); expect(fileSystem.file(outputPath).existsSync(), false); }); + + testWithoutContext('DevelopmentShaderCompiler can compile for android non-impeller', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + FakeCommand( + command: [ + 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([1, 2, 3, 4]); + } + ), + ]); + fileSystem.file(fragPath).writeAsBytesSync([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(), [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( + command: [ + 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([1, 2, 3, 4]); + } + ), + ]); + fileSystem.file(fragPath).writeAsBytesSync([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(), [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)); + }); } diff --git a/packages/flutter_tools/test/general.shard/cold_test.dart b/packages/flutter_tools/test/general.shard/cold_test.dart index e2b71e00a3..1151ff62a3 100644 --- a/packages/flutter_tools/test/general.shard/cold_test.dart +++ b/packages/flutter_tools/test/general.shard/cold_test.dart @@ -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 recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index edaa74939c..fce8a66e0d 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -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: [], packageConfig: PackageConfig.empty, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.syncedBytes, 5); @@ -262,6 +264,7 @@ void main() { trackWidgetCreation: false, invalidatedFiles: [], packageConfig: PackageConfig.empty, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.success, false); @@ -302,6 +305,7 @@ void main() { trackWidgetCreation: false, invalidatedFiles: [], packageConfig: PackageConfig.empty, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.success, true); @@ -344,6 +348,7 @@ void main() { invalidatedFiles: [], packageConfig: PackageConfig.empty, devFSWriter: localDevFSWriter, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.success, true); @@ -393,6 +398,7 @@ void main() { invalidatedFiles: [], packageConfig: PackageConfig.empty, devFSWriter: writer, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.success, true); @@ -466,6 +472,7 @@ void main() { trackWidgetCreation: false, invalidatedFiles: [], packageConfig: PackageConfig.empty, + shaderCompiler: const FakeShaderCompiler(), ); expect(report.success, true); @@ -551,6 +558,7 @@ void main() { invalidatedFiles: [], packageConfig: PackageConfig.empty, bundle: FakeBundle(), + shaderCompiler: const FakeShaderCompiler(), ); expect(report1.success, true); logger.messages.clear(); @@ -564,6 +572,7 @@ void main() { invalidatedFiles: [], 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: [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? invalidatedFiles) async { + fileSystem.file('lib/foo.dill') + ..createSync(recursive: true) + ..writeAsBytesSync([1, 2, 3, 4, 5]); + return const CompilerOutput('lib/foo.dill', 0, []); + }; + final FakeBundle bundle = FakeBundle() + ..entries['foo.frag'] = DevFSByteContent([1, 2, 3, 4]) + ..entries['not.frag'] = DevFSByteContent([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: [], + packageConfig: PackageConfig.empty, + shaderCompiler: const FakeShaderCompiler(), + bundle: bundle, + ); + + expect(report.success, true); + expect(devFS.shaderPathsToEvict, {'foo.frag'}); + expect(devFS.assetPathsToEvict, {'not.frag'}); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); + }); } class FakeResidentCompiler extends Fake implements ResidentCompiler { @@ -630,10 +699,10 @@ class FakeBundle extends AssetBundle { Map> get deferredComponentsEntries => >{}; @override - Map get entries => {}; + final Map entries = {}; @override - Map get entryKinds => {}; + final Map entryKinds = {}; @override List get inputFiles => []; @@ -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 recompileShader(DevFSContent inputShader) async { + return DevFSByteContent(await inputShader.contentsAsBytes()); + } +} diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index f68fbe0a34..4ffa42a177 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -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 devices = [ - 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 devices = [ - 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 devices = [ - 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 assetPathsToEvict = {}; + @override + Set shaderPathsToEvict= {}; + @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 get isolates => []; } + +class FakeShaderCompiler implements DevelopmentShaderCompiler { + const FakeShaderCompiler(); + + @override + void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { } + + @override + Future recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 8c251b2b3f..1e39119fd9 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -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: { + '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: [ + listViews, + setAssetBundlePath, + evictShader, + ]); + residentRunner = HotRunner( + [ + flutterDevice, + ], + stayResident: false, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + target: 'main.dart', + devtoolsHandler: createNoOpHandler, + ); + + (flutterDevice.devFS as FakeDevFS).shaderPathsToEvict = {'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: [ ]); @@ -2296,7 +2329,7 @@ class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceE class TestFlutterDevice extends FlutterDevice { TestFlutterDevice(Device device, { Stream 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 get observatoryUris => Stream.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 connect({ @@ -2591,6 +2630,9 @@ class FakeDevFS extends Fake implements DevFS { @override Set assetPathsToEvict = {}; + @override + Set shaderPathsToEvict = {}; + UpdateFSReport nextUpdateReport = UpdateFSReport(success: true); @override @@ -2615,6 +2657,7 @@ class FakeDevFS extends Fake implements DevFS { @required List 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 recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index fcbb3b30e2..0707e5e846 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -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 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 get observatoryUris => Stream.value(testUri); + @override + DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler(); + @override FlutterVmService vmService; @@ -1524,3 +1529,15 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { @override Future updateReloadStatus(bool wasReloadSuccessful) async {} } + +class FakeShaderCompiler implements DevelopmentShaderCompiler { + const FakeShaderCompiler(); + + @override + void configureCompiler(TargetPlatform platform, { @required bool enableImpeller }) { } + + @override + Future recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart index bc57766be5..646a24a85f 100644 --- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart @@ -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 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 get errors => _errors.stream; final StreamController _errors = StreamController(); } + +class FakeShaderCompiler implements DevelopmentShaderCompiler { + const FakeShaderCompiler(); + + @override + void configureCompiler(TargetPlatform? platform, { required bool enableImpeller }) { } + + @override + Future recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index 32d080db3b..4c82aa7fa5 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -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 recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +}