Skip to content

Commit 2be34f8

Browse files
authored
refactor(flame_3d): Shader generation now supports #include (#3924)
Adding `#include` support to our shader generator. With support for N packages.
1 parent f2fb407 commit 2be34f8

7 files changed

Lines changed: 109 additions & 40 deletions

File tree

-16 Bytes
Binary file not shown.
21.6 KB
Binary file not shown.

packages/flame_3d/bin/build_shaders.dart

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:convert';
22
import 'dart:io';
3+
import 'dart:isolate';
34

45
/// Bundle a shader ('name'.frag & 'name'.vert) into a single shader bundle and
56
/// store it in the assets directory.
@@ -8,23 +9,68 @@ import 'dart:io';
89
/// Flutter might support auto-bundling themselves but until then we have to
910
/// do it manually.
1011
///
11-
/// Note: this script should be run from the root of the package:
12-
/// packages/flame_3d
12+
/// Run from the package root whose shaders are being built. When invoked
13+
/// from a consumer package via `dart run flame_3d:build_shaders`, the
14+
/// consumer's own `shaders/` is bundled. Every Dart dependency that ships a
15+
/// top-level `shaders/` directory is added to impellerc's include path under
16+
/// its package name, so shaders can `#include <pkg_name/foo.glsl>` against
17+
/// any of them. `<flutter/...>` resolves to the engine builtins.
1318
void main(List<String> arguments) async {
1419
final root = Directory.current;
1520
final assets = Directory.fromUri(root.uri.resolve('assets/shaders'));
1621
final shaders = Directory.fromUri(root.uri.resolve('shaders'));
22+
final packageShaderDirs = await _resolvePackageShaderDirs();
1723

18-
await compute(assets, shaders);
24+
await compute(assets, shaders, packageShaderDirs);
1925
if (arguments.contains('watch')) {
2026
stdout.writeln('Running in watch mode');
2127
shaders.watch(recursive: true).listen((event) {
22-
compute(assets, shaders);
28+
compute(assets, shaders, packageShaderDirs);
2329
});
2430
}
2531
}
2632

27-
Future<void> compute(Directory assets, Directory shaders) async {
33+
/// Returns every Dart dependency's top-level `shaders/` directory, so an
34+
/// `#include <pkg_name/foo.glsl>` can resolve to
35+
/// `<pkg-root>/shaders/pkg_name/foo.glsl`.
36+
Future<List<Directory>> _resolvePackageShaderDirs() async {
37+
final configUri = await Isolate.packageConfig;
38+
if (configUri == null) {
39+
throw Exception(
40+
'Unable to locate package_config.json. Run `dart pub get` first.',
41+
);
42+
}
43+
44+
final configFile = File.fromUri(configUri);
45+
final config =
46+
jsonDecode(configFile.readAsStringSync()) as Map<String, dynamic>;
47+
final packages = (config['packages'] as List).cast<Map<String, dynamic>>();
48+
final result = <Directory>[];
49+
for (final package in packages) {
50+
final name = package['name'] as String;
51+
if (name == 'flutter') {
52+
// `flutter` ships no shader chunks; its includes come from the engine.
53+
continue;
54+
}
55+
56+
final rootUriRaw = package['rootUri'] as String;
57+
final rootUri = configUri.resolve(
58+
rootUriRaw.endsWith('/') ? rootUriRaw : '$rootUriRaw/',
59+
);
60+
61+
final shaderDir = Directory.fromUri(rootUri.resolve('shaders/'));
62+
if (shaderDir.existsSync()) {
63+
result.add(shaderDir);
64+
}
65+
}
66+
return result;
67+
}
68+
69+
Future<void> compute(
70+
Directory assets,
71+
Directory shaders,
72+
List<Directory> packageShaderDirs,
73+
) async {
2874
// Delete all the bundled shaders so we can replace them with new ones.
2975
if (assets.existsSync()) {
3076
assets.deleteSync(recursive: true);
@@ -44,6 +90,9 @@ Future<void> compute(Directory assets, Directory shaders) async {
4490
.map((f) => f.path.split(Platform.pathSeparator).last.split('.').first)
4591
.toSet();
4692

93+
final impellerC = await findImpellerC();
94+
final engineShaderLib = impellerC.resolve('./shader_lib/').toFilePath();
95+
4796
for (final name in uniqueShaders) {
4897
final bundle = {
4998
'TextureFragment': {
@@ -57,14 +106,17 @@ Future<void> compute(Directory assets, Directory shaders) async {
57106
};
58107

59108
stdout.writeln('Computing shader "$name"');
60-
final impellerC = await findImpellerC();
61109
final result = await Process.run(impellerC.toFilePath(), [
62110
'--sl=${assets.path}${Platform.pathSeparator}$name.shaderbundle',
63111
'--shader-bundle=${jsonEncode(bundle)}',
112+
'--include=${shaders.path}',
113+
for (final dir in packageShaderDirs) '--include=${dir.path}',
114+
'--include=$engineShaderLib',
64115
]);
65116

66117
if (result.exitCode != 0) {
67-
return stderr.writeln(result.stderr);
118+
stderr.writeln('Failed to compile shader "$name":\n${result.stderr}');
119+
exitCode = 1;
68120
}
69121
}
70122
}
@@ -126,7 +178,7 @@ Future<Uri> findImpellerC() async {
126178
// ignore: do_not_use_environment
127179
const impellercEnvVar = String.fromEnvironment('IMPELLERC');
128180
if (impellercEnvVar != '') {
129-
if (!doesFileExist(impellercEnvVar)) {
181+
if (!File(impellercEnvVar).existsSync()) {
130182
throw Exception(
131183
'IMPELLERC environment variable is set, '
132184
"but it doesn't point to a valid file!",
@@ -147,7 +199,7 @@ Future<Uri> findImpellerC() async {
147199
final tried = <Uri>[];
148200
for (final variant in _impellercLocations) {
149201
final impellercPath = engineArtifactsDir.resolve(variant);
150-
if (doesFileExist(impellercPath.toFilePath())) {
202+
if (File(impellercPath.toFilePath()).existsSync()) {
151203
found = impellercPath;
152204
break;
153205
}
@@ -161,7 +213,3 @@ Future<Uri> findImpellerC() async {
161213

162214
return found;
163215
}
164-
165-
bool doesFileExist(String path) {
166-
return File(path).existsSync();
167-
}

packages/flame_3d/lib/src/resources/material/unlit_material.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class UnlitMaterial extends Material {
2424
super(
2525
vertexShader: VertexShader.fromAsset(
2626
'packages/flame_3d/assets/shaders/unlit_material.shaderbundle',
27-
slots: ['VertexInfo'],
27+
slots: ['VertexInfo', 'JointMatrices'],
2828
),
2929
fragmentShader: FragmentShader.fromAsset(
3030
'packages/flame_3d/assets/shaders/unlit_material.shaderbundle',
@@ -45,8 +45,21 @@ class UnlitMaterial extends Material {
4545
..setMatrix4('VertexInfo.view', context.view)
4646
..setMatrix4('VertexInfo.projection', context.projection);
4747

48+
final jointTransforms = context.jointsInfo.jointTransforms;
49+
if (jointTransforms.length > _maxJoints) {
50+
throw Exception(
51+
'At most $_maxJoints joints per surface are supported;'
52+
' found ${jointTransforms.length}',
53+
);
54+
}
55+
for (final (index, transform) in jointTransforms.indexed) {
56+
vertexShader.setMatrix4('JointMatrices.joints[$index]', transform);
57+
}
58+
4859
fragmentShader
4960
..setTexture('albedoTexture', albedoTexture)
5061
..setColor('Material.albedoColor', albedoColor);
5162
}
63+
64+
static const _maxJoints = 16;
5265
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef FLAME_SKINNING_GLSL_
2+
#define FLAME_SKINNING_GLSL_
3+
4+
in vec4 vertexJoints;
5+
in vec4 vertexWeights;
6+
7+
uniform JointMatrices {
8+
mat4 joints[16];
9+
} jointMatrices;
10+
11+
mat4 computeSkinMatrix() {
12+
if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) {
13+
return mat4(1.0);
14+
}
15+
16+
return vertexWeights.x * jointMatrices.joints[int(vertexJoints.x)] +
17+
vertexWeights.y * jointMatrices.joints[int(vertexJoints.y)] +
18+
vertexWeights.z * jointMatrices.joints[int(vertexJoints.z)] +
19+
vertexWeights.w * jointMatrices.joints[int(vertexJoints.w)];
20+
}
21+
22+
#endif

packages/flame_3d/shaders/spatial_material.vert

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ in vec3 vertexPosition;
44
in vec2 vertexTexCoord;
55
in vec4 vertexColor;
66
in vec3 vertexNormal;
7-
in vec4 vertexJoints;
8-
in vec4 vertexWeights;
7+
8+
#include <flame_3d/skinning.glsl>
99

1010
out vec2 fragTexCoord;
1111
out vec4 fragColor;
@@ -18,21 +18,6 @@ uniform VertexInfo {
1818
mat4 projection;
1919
} vertex_info;
2020

21-
uniform JointMatrices {
22-
mat4 joints[16];
23-
} jointMatrices;
24-
25-
mat4 computeSkinMatrix() {
26-
if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) {
27-
return mat4(1.0);
28-
}
29-
30-
return vertexWeights.x * jointMatrices.joints[int(vertexJoints.x)] +
31-
vertexWeights.y * jointMatrices.joints[int(vertexJoints.y)] +
32-
vertexWeights.z * jointMatrices.joints[int(vertexJoints.z)] +
33-
vertexWeights.w * jointMatrices.joints[int(vertexJoints.w)];
34-
}
35-
3621
void main() {
3722
mat4 skinMatrix = computeSkinMatrix();
3823
vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz;

packages/flame_3d/shaders/unlit_material.vert

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ in vec3 vertexPosition;
44
in vec2 vertexTexCoord;
55
in vec4 vertexColor;
66
in vec3 vertexNormal;
7-
in vec4 vertexJoints;
8-
in vec4 vertexWeights;
7+
8+
#include <flame_3d/skinning.glsl>
99

1010
out vec2 fragTexCoord;
1111
out vec4 fragColor;
@@ -19,14 +19,15 @@ uniform VertexInfo {
1919
} vertex_info;
2020

2121
void main() {
22-
mat4 mvp = vertex_info.projection * vertex_info.view * vertex_info.model;
23-
gl_Position = mvp * vec4(vertexPosition, 1.0);
22+
mat4 skinMatrix = computeSkinMatrix();
23+
vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz;
24+
vec3 normal = normalize((skinMatrix * vec4(vertexNormal, 0.0)).xyz);
25+
26+
mat4 modelViewProjection = vertex_info.projection * vertex_info.view * vertex_info.model;
27+
gl_Position = modelViewProjection * vec4(position, 1.0);
2428

2529
fragTexCoord = vertexTexCoord;
2630
fragColor = vertexColor;
27-
28-
// Pass through all vertex attributes so the compiler doesn't strip them,
29-
// which would break the vertex buffer layout.
30-
fragPosition = vertexPosition + vertexJoints.xyz * vertexWeights.x;
31-
fragNormal = vertexNormal;
31+
fragPosition = vec3(vertex_info.model * vec4(position, 1.0));
32+
fragNormal = mat3(transpose(inverse(vertex_info.model))) * normal;
3233
}

0 commit comments

Comments
 (0)