Unverified Commit 7e683c02 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] add tool support for shader hot reload (#107963)

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