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