Unverified Commit 577919db authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Don't hardcode ink sparkle spir-v (#102674)

parent d1bbd822
......@@ -472,6 +472,7 @@ Future<void> verifyNoMissingLicense(String workingDirectory, { bool checkMinimum
failed += await _verifyNoMissingLicenseForExtension(workingDirectory, 'ps1', overrideMinimumMatches ?? 1, _generateLicense('# '));
failed += await _verifyNoMissingLicenseForExtension(workingDirectory, 'html', overrideMinimumMatches ?? 1, '<!-- ${_generateLicense('')} -->', trailingBlank: false, header: r'<!DOCTYPE HTML>\n');
failed += await _verifyNoMissingLicenseForExtension(workingDirectory, 'xml', overrideMinimumMatches ?? 1, '<!-- ${_generateLicense('')} -->', header: r'(<\?xml version="1.0" encoding="utf-8"\?>\n)?');
failed += await _verifyNoMissingLicenseForExtension(workingDirectory, 'frag', overrideMinimumMatches ?? 1, _generateLicense('// '), header: r'#version 320 es(\n)+');
if (failed > 0) {
exitWithError(<String>['License check failed.']);
}
......
#version 320 es
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
precision highp float;
// TODO(antrob): Put these in a more logical order (e.g. separate consts vs varying, etc)
layout(location = 0) uniform vec4 u_color;
layout(location = 1) uniform float u_alpha;
layout(location = 2) uniform vec4 u_sparkle_color;
layout(location = 3) uniform float u_sparkle_alpha;
layout(location = 4) uniform float u_blur;
layout(location = 5) uniform vec2 u_center;
layout(location = 6) uniform float u_radius_scale;
layout(location = 7) uniform float u_max_radius;
layout(location = 8) uniform vec2 u_resolution_scale;
layout(location = 9) uniform vec2 u_noise_scale;
layout(location = 10) uniform float u_noise_phase;
layout(location = 11) uniform vec2 u_circle1;
layout(location = 12) uniform vec2 u_circle2;
layout(location = 13) uniform vec2 u_circle3;
layout(location = 14) uniform vec2 u_rotation1;
layout(location = 15) uniform vec2 u_rotation2;
layout(location = 16) uniform vec2 u_rotation3;
layout(location = 0) out vec4 fragColor;
const float PI = 3.1415926535897932384626;
const float PI_ROTATE_RIGHT = PI * 0.0078125;
const float PI_ROTATE_LEFT = PI * -0.0078125;
const float ONE_THIRD = 1./3.;
const vec2 TURBULENCE_SCALE = vec2(0.8);
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
float triangle_noise(highp vec2 n) {
n = fract(n * vec2(5.3987, 5.4421));
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
float xy = n.x * n.y;
return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
}
float threshold(float v, float l, float h) {
return step(l, v) * (1.0 - step(h, v));
}
mat2 rotate2d(vec2 rad){
return mat2(rad.x, -rad.y, rad.y, rad.x);
}
float soft_circle(vec2 uv, vec2 xy, float radius, float blur) {
float blur_half = blur * 0.5;
float d = distance(uv, xy);
return 1.0 - smoothstep(1.0 - blur_half, 1.0 + blur_half, d / radius);
}
float soft_ring(vec2 uv, vec2 xy, float radius, float thickness, float blur) {
float circle_outer = soft_circle(uv, xy, radius + thickness, blur);
float circle_inner = soft_circle(uv, xy, max(radius - thickness, 0.0), blur);
return saturate(circle_outer - circle_inner);
}
float circle_grid(vec2 resolution, vec2 p, vec2 xy, vec2 rotation, float cell_diameter) {
p = rotate2d(rotation) * (xy - p) + xy;
p = mod(p, cell_diameter) / resolution;
float cell_uv = cell_diameter / resolution.y * 0.5;
float r = 0.65 * cell_uv;
return soft_circle(p, vec2(cell_uv), r, r * 50.0);
}
float sparkle(vec2 uv, float t) {
float n = triangle_noise(uv);
float s = threshold(n, 0.0, 0.05);
s += threshold(n + sin(PI * (t + 0.35)), 0.1, 0.15);
s += threshold(n + sin(PI * (t + 0.7)), 0.2, 0.25);
s += threshold(n + sin(PI * (t + 1.05)), 0.3, 0.35);
return saturate(s) * 0.55;
}
float turbulence(vec2 uv) {
vec2 uv_scale = uv * TURBULENCE_SCALE;
float g1 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle1, u_rotation1, 0.17);
float g2 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle2, u_rotation2, 0.2);
float g3 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle3, u_rotation3, 0.275);
float v = (g1 * g1 + g2 - g3) * 0.5;
return saturate(0.45 + 0.8 * v);
}
void main() {
vec2 p = gl_FragCoord.xy;
vec2 uv = p * u_resolution_scale;
vec2 density_uv = uv - mod(p, u_noise_scale);
float radius = u_max_radius * u_radius_scale;
float turbulence = turbulence(uv);
float ring = soft_ring(p, u_center, radius, 0.05 * u_max_radius, u_blur);
float sparkle = sparkle(density_uv, u_noise_phase) * ring * turbulence * u_sparkle_alpha;
float wave_alpha = soft_circle(p, u_center, radius, u_blur) * u_alpha * u_color.a;
vec4 wave_color = vec4(u_color.rgb * wave_alpha, wave_alpha);
vec4 sparkle_color = vec4(u_sparkle_color.rgb * u_sparkle_color.a, u_sparkle_color.a);
fragColor = mix(wave_color, sparkle_color, sparkle);
}
......@@ -43,6 +43,10 @@ const List<Map<String, Object>> kMaterialFonts = <Map<String, Object>>[
},
];
const List<String> kMaterialShaders = <String>[
'shaders/ink_sparkle.frag',
];
/// Injected factory class for spawning [AssetBundle] instances.
abstract class AssetBundleFactory {
/// The singleton instance, pulled from the [AppContext].
......@@ -404,11 +408,15 @@ class ManifestAssetBundle implements AssetBundle {
}
final List<_Asset> materialAssets = <_Asset>[
if (flutterManifest.usesMaterialDesign)
..._getMaterialAssets(),
..._getMaterialFonts(),
// Include the shaders unconditionally. They are small, and whether
// they're used is determined only by the app source code and not by
// the Flutter manifest.
..._getMaterialShaders(),
];
for (final _Asset asset in materialAssets) {
final File assetFile = asset.lookupAssetFile(_fileSystem);
assert(assetFile.existsSync());
assert(assetFile.existsSync(), 'Missing ${assetFile.path}');
entries[asset.entryUri.path] ??= DevFSFileContent(assetFile);
}
......@@ -490,7 +498,7 @@ class ManifestAssetBundle implements AssetBundle {
}
}
List<_Asset> _getMaterialAssets() {
List<_Asset> _getMaterialFonts() {
final List<_Asset> result = <_Asset>[];
for (final Map<String, Object> family in kMaterialFonts) {
final Object? fonts = family['fonts'];
......@@ -504,7 +512,10 @@ class ManifestAssetBundle implements AssetBundle {
}
final Uri entryUri = _fileSystem.path.toUri(asset);
result.add(_Asset(
baseDir: _fileSystem.path.join(Cache.flutterRoot!, 'bin', 'cache', 'artifacts', 'material_fonts'),
baseDir: _fileSystem.path.join(
Cache.flutterRoot!,
'bin', 'cache', 'artifacts', 'material_fonts',
),
relativeUri: Uri(path: entryUri.pathSegments.last),
entryUri: entryUri,
package: null,
......@@ -515,6 +526,32 @@ class ManifestAssetBundle implements AssetBundle {
return result;
}
List<_Asset> _getMaterialShaders() {
final String shaderPath = _fileSystem.path.join(
Cache.flutterRoot!,
'packages', 'flutter', 'lib', 'src', 'material', 'shaders',
);
// This file will exist in a real invocation unless the git checkout is
// corrupted somehow, but unit tests generally don't create this file
// in their mock file systems. Leaving it out in those cases is harmless.
if (!_fileSystem.directory(shaderPath).existsSync()) {
return <_Asset>[];
}
final List<_Asset> result = <_Asset>[];
for (final String shader in kMaterialShaders) {
final Uri entryUri = _fileSystem.path.toUri(shader);
result.add(_Asset(
baseDir: shaderPath,
relativeUri: Uri(path: entryUri.pathSegments.last),
entryUri: entryUri,
package: null,
));
}
return result;
}
List<Map<String, Object?>> _parseFonts(
FlutterManifest manifest,
PackageConfig packageConfig, {
......
......@@ -14,6 +14,7 @@ import '../build_system.dart';
import '../depfile.dart';
import 'common.dart';
import 'icon_tree_shaker.dart';
import 'shader_compiler.dart';
/// A helper function to copy an asset bundle into an [environment]'s output
/// directory.
......@@ -24,7 +25,9 @@ import 'icon_tree_shaker.dart';
/// included in the final bundle, but not the AssetManifest.json file.
///
/// Returns a [Depfile] containing all assets used in the build.
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
Future<Depfile> copyAssets(
Environment environment,
Directory outputDirectory, {
Map<String, DevFSContent>? additionalContent,
required TargetPlatform targetPlatform,
BuildMode? buildMode,
......@@ -72,6 +75,12 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
fileSystem: environment.fileSystem,
artifacts: environment.artifacts,
);
final ShaderCompiler shaderCompiler = ShaderCompiler(
processManager: environment.processManager,
logger: environment.logger,
fileSystem: environment.fileSystem,
artifacts: environment.artifacts,
);
final Map<String, DevFSContent> assetEntries = <String, DevFSContent>{
...assetBundle.entries,
......@@ -100,6 +109,9 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
input: content.file as File,
outputPath: file.path,
relativePath: entry.key,
) && !await shaderCompiler.compileShader(
input: content.file as File,
outputPath: file.path,
)) {
await (content.file as File).copy(file.path);
}
......@@ -258,6 +270,7 @@ class CopyAssets extends Target {
List<Source> get inputs => const <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
...IconTreeShaker.inputs,
...ShaderCompiler.inputs,
];
@override
......
......@@ -18,6 +18,7 @@ import 'assets.dart';
import 'dart_plugin_registrant.dart';
import 'icon_tree_shaker.dart';
import 'localizations.dart';
import 'shader_compiler.dart';
/// Copies the pre-built flutter bundle.
// This is a one-off rule for implementing build bundle in terms of assemble.
......@@ -33,6 +34,7 @@ class CopyFlutterBundle extends Target {
Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
Source.pattern('{BUILD_DIR}/app.dill'),
...IconTreeShaker.inputs,
...ShaderCompiler.inputs,
];
@override
......
......@@ -19,6 +19,7 @@ import '../exceptions.dart';
import 'assets.dart';
import 'common.dart';
import 'icon_tree_shaker.dart';
import 'shader_compiler.dart';
/// Supports compiling a dart kernel file to an assembly file.
///
......@@ -439,6 +440,7 @@ abstract class IosAssetBundle extends Target {
Source.pattern('{BUILD_DIR}/App.framework/App'),
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
...IconTreeShaker.inputs,
...ShaderCompiler.inputs,
];
@override
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../../artifacts.dart';
import '../../base/common.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/logger.dart';
import '../../convert.dart';
import '../build_system.dart';
/// A class the wraps the functionality of the Impeller shader compiler
/// impellerc.
class ShaderCompiler {
ShaderCompiler({
required ProcessManager processManager,
required Logger logger,
required FileSystem fileSystem,
required Artifacts artifacts,
}) : _processManager = processManager,
_logger = logger,
_fs = fileSystem,
_artifacts = artifacts;
final ProcessManager _processManager;
final Logger _logger;
final FileSystem _fs;
final Artifacts _artifacts;
/// The [Source] inputs that targets using this should depend on.
///
/// See [Target.inputs].
static const List<Source> inputs = <Source>[
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/shader_compiler.dart'),
Source.hostArtifact(HostArtifact.impellerc),
];
/// Calls impellerc, which transforms the [input] glsl shader into a
/// platform specific shader at [outputPath].
///
/// All parameters are required.
///
/// If the input file is not a fragment shader, this returns false.
/// If the shader compiler subprocess fails, it will [throwToolExit].
/// Otherwise, it will return true.
Future<bool> compileShader({
required File input,
required String outputPath,
}) async {
if (!input.path.endsWith('.frag')) {
return false;
}
final File impellerc = _fs.file(
_artifacts.getHostArtifact(HostArtifact.impellerc),
);
if (!impellerc.existsSync()) {
throw ShaderCompilerException._(
'The impellerc utility is missing at "${impellerc.path}". '
'Run "flutter doctor".',
);
}
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
'--flutter-spirv',
'--spirv=$outputPath',
'--input=${input.path}',
];
final Process impellercProcess = await _processManager.start(cmd);
final int code = await impellercProcess.exitCode;
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.',
);
}
return true;
}
}
class ShaderCompilerException implements Exception {
ShaderCompilerException._(this.message);
final String message;
@override
String toString() => 'ShaderCompilerException: $message\n\n';
}
......@@ -13,6 +13,7 @@ import 'build_info.dart';
import 'build_system/build_system.dart';
import 'build_system/depfile.dart';
import 'build_system/targets/common.dart';
import 'build_system/targets/shader_compiler.dart';
import 'bundle.dart';
import 'cache.dart';
import 'devfs.dart';
......@@ -148,6 +149,13 @@ Future<void> writeBundle(
}
bundleDir.createSync(recursive: true);
final ShaderCompiler shaderCompiler = ShaderCompiler(
processManager: globals.processManager,
logger: globals.logger,
fileSystem: globals.fs,
artifacts: globals.artifacts!,
);
// Limit number of open files to avoid running out of file descriptors.
final Pool pool = Pool(64);
await Future.wait<void>(
......@@ -161,7 +169,15 @@ Future<void> writeBundle(
// and the native APIs will look for files this way.
final File file = globals.fs.file(globals.fs.path.join(bundleDir.path, entry.key));
file.parent.createSync(recursive: true);
await file.writeAsBytes(await entry.value.contentsAsBytes());
final DevFSContent devFSContent = entry.value;
if (devFSContent is DevFSFileContent) {
final File input = devFSContent.file as File;
if (!await shaderCompiler.compileShader(input: input, outputPath: file.path)) {
input.copySync(file.path);
}
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
......
......@@ -8,6 +8,7 @@ import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
......@@ -387,6 +388,63 @@ flutter:
ProcessManager: () => FakeProcessManager.any(),
});
group('Shaders: ', () {
MemoryFileSystem fileSystem;
Artifacts artifacts;
String impellerc;
Directory output;
String shaderPath;
String outputPath;
setUp(() {
artifacts = Artifacts.test();
fileSystem = MemoryFileSystem.test();
impellerc = artifacts.getHostArtifact(HostArtifact.impellerc).path;
fileSystem.file(impellerc).createSync(recursive: true);
output = fileSystem.directory('asset_output')..createSync(recursive: true);
shaderPath = fileSystem.path.join('assets', 'shader.frag');
outputPath = fileSystem.path.join(output.path, 'assets', 'shader.frag');
fileSystem.file(shaderPath).createSync(recursive: true);
});
testUsingContext('Including a shader triggers the shader compiler', () async {
fileSystem.file('.packages').createSync();
fileSystem.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/shader.frag
''');
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
expect(await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages'), 0);
await writeBundle(output, bundle.entries, loggerOverride: testLogger);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
impellerc,
'--flutter-spirv',
'--spirv=$outputPath',
'--input=/$shaderPath',
],
onRun: () {
fileSystem.file(outputPath).createSync(recursive: true);
},
),
]),
});
});
testUsingContext('Does not insert dummy file into additionalDependencies '
'when wildcards are used by dependencies', () async {
globals.fs.file('.packages').writeAsStringSync(r'''
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
import '../../../src/common.dart';
import '../../../src/fake_process_manager.dart';
const String shaderPath = '/shaders/my_shader.frag';
const String notShaderPath = '/shaders/not_a_shader.file';
const String outputPath = '/output/shaders/my_shader.spv';
void main() {
late BufferLogger logger;
late MemoryFileSystem fileSystem;
late Artifacts artifacts;
late String impellerc;
setUp(() {
artifacts = Artifacts.test();
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
impellerc = artifacts.getHostArtifact(HostArtifact.impellerc).path;
fileSystem.file(impellerc).createSync(recursive: true);
fileSystem.file(shaderPath).createSync(recursive: true);
fileSystem.file(notShaderPath).createSync(recursive: true);
});
testWithoutContext('compileShader returns false for non-shader files', () async {
final ShaderCompiler shaderCompiler = ShaderCompiler(
processManager: FakeProcessManager.empty(),
logger: logger,
fileSystem: fileSystem,
artifacts: artifacts,
);
expect(
await shaderCompiler.compileShader(
input: fileSystem.file(notShaderPath),
outputPath: outputPath,
),
false,
);
});
testWithoutContext('compileShader returns true for shader files', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
impellerc,
'--flutter-spirv',
'--spirv=$outputPath',
'--input=$shaderPath',
],
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(shaderPath),
outputPath: outputPath,
),
true,
);
expect(fileSystem.file(outputPath).existsSync(), true);
});
}
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