Skip to content

Commit f21c5a8

Browse files
authored
feat(flame_3d): Add experimental web support using webgpu backend (#3930)
Adding experimental web support using `webgpu`, it serves as a showcase for the backend API working as intended and as a way to bring web into the mix.
1 parent fda1bc9 commit f21c5a8

13 files changed

Lines changed: 2042 additions & 211 deletions

File tree

.github/.cspell/gamedev_dictionary.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# general development-adjacent terms and expressions
22
AABB # axis aligned bounding box
33
abelian # Abelian Group, also known as commutative group
4+
alignof # alignment of
45
ARGB # alpha red green blue
56
arities # plural of arity
67
backgrounded # moving the app to the background
@@ -58,7 +59,12 @@ tileset # image with a collection of tiles. in games, tiles are small square spr
5859
tilesets # plural of tileset
5960
truecolor # truecolor rendering
6061
tweening # the process of tween
62+
uncapturederror # dumb webgpu javascript name
63+
unorm # unsigned normalized integer
6164
viewports # plural of viewport
6265
WASD # movement keys on a keyboard
6366
WBMP # wireless bitmap image format
67+
webgpu # gpu standard for web browsers
6468
WebP # WebP image format
69+
WGSL # WGSL shader language
70+
wgslbundle # WGSL shader bundle format

packages/flame_3d/README.md

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ Adds 3D support for <a href="https://github.com/flame-engine/flame">Flame</a> us
1717
</p>
1818

1919
---
20+
2021
<!-- markdownlint-enable MD013 -->
2122

2223
<!-- markdownlint-disable-next-line MD002 -->
24+
2325
# flame_3d
2426

25-
This package provides an experimental implementation of 3D support for Flame. The main focus is to
26-
explore the potential capabilities of 3D for Flame while providing a familiar API to existing Flame
27-
developers.
27+
This package provides an experimental implementation of 3D support for Flame.
28+
The main focus is to explore the potential capabilities of 3D for Flame while
29+
providing a familiar API to existing Flame developers.
2830

2931
Supported platforms:
3032

@@ -35,41 +37,48 @@ Supported platforms:
3537
| macOS ||
3638
| Windows ||
3739
| Linux ||
38-
| Web ||
40+
| Web | ⚠️¹ |
41+
42+
⚠️¹ Web support is experimental, see [Web support](#web-support-experimental)
43+
below.
3944

4045

4146
## Prologue
4247

43-
**STOP**, we know you are hyped up and want to start coding some funky 3D stuff but we first have to
44-
set your expectations and clarify some things. So turn down your music, put away the coffee and make
45-
some tea instead because you have to do some reading first!
48+
**STOP**, we know you are hyped up and want to start coding some funky 3D stuff
49+
but we first have to set your expectations and clarify some things. So turn down
50+
your music, put away the coffee and make some tea instead because you have to do
51+
some reading first!
4652

47-
This package provides 3D support for Flame but it depends on the still experimental
48-
[Flutter GPU](https://github.com/flutter/flutter/wiki/Flutter-GPU), which in turn depends on
49-
Impeller.
53+
This package provides 3D support for Flame but it depends on the still
54+
experimental [Flutter GPU](https://github.com/flutter/flutter/wiki/Flutter-GPU),
55+
which in turn depends on Impeller.
5056

5157
Therefore, this package is also experimental; you can check our
5258
[Roadmap](https://github.com/flame-engine/flame/blob/main/packages/flame_3d/ROADMAP.md)
5359
for more details on our plans and what is currently supported.
5460

55-
This package does not guarantee that it will follow correct [semver](https://semver.org/) versioning
56-
rules, nor does it assure that its APIs wont break. Be ready to constantly have to refactor your
57-
code if you are planning on using this package, and potentially to have to contribute with
61+
This package does not guarantee that it will follow correct
62+
[semver](https://semver.org/) versioning rules, nor does it assure that its APIs
63+
wont break. Be ready to constantly have to refactor your code if you are
64+
planning on using this package, and potentially to have to contribute with
5865
improvements and fixes. Please do not use this for production environments.
5966

60-
Documentation and tests might be lacking for quite a while because of the potential constant changes
61-
of the API. Where possible, we will try to provide in-code documentation and code examples to help
62-
developers but our main goal for now is to enable the usage of 3D rendering within a Flame
63-
ecosystem.
67+
Documentation and tests might be lacking for quite a while because of the
68+
potential constant changes of the API. Where possible, we will try to provide
69+
in-code documentation and code examples to help developers but our main goal for
70+
now is to enable the usage of 3D rendering within a Flame ecosystem.
6471

6572

6673
## Prerequisites
6774

68-
In order to use flame_3d, you will need to ensure a few things. Firstly, the only platforms that we
69-
have explicitly tested so far for support were Android, iOS, and macOS.
75+
In order to use flame_3d, you will need to ensure a few things. Firstly, the
76+
only platforms that we have explicitly tested so far for support were Android,
77+
iOS, and macOS.
7078

71-
Then, you need to enable Impeller, if not already enabled by default. For example, for macOS, add
72-
the following to the generated `macos/runner/Info.plist` directory:
79+
Then, you need to enable Impeller, if not already enabled by default. For
80+
example, for macOS, add the following to the generated `macos/runner/Info.plist`
81+
directory:
7382

7483
```xml
7584
<dict>
@@ -87,43 +96,71 @@ Alternatively, you can run Flutter with this flag instead:
8796
flutter run --enable-flutter-gpu
8897
```
8998

90-
Now everything is set up you can start doing some 3D magic! You can check out the
91-
[example](https://github.com/flame-engine/flame/tree/main/packages/flame_3d/example) to see how you
92-
can set up a simple 3D environment using Flame.
99+
Now everything is set up you can start doing some 3D magic! You can check out
100+
the
101+
[example](https://github.com/flame-engine/flame/tree/main/packages/flame_3d/example)
102+
to see how you can set up a simple 3D environment using Flame.
103+
104+
Also check our more advanced examples,
105+
[Collect the Donut](https://github.com/luanpotter/collect_the_donut) and
106+
[Defend the Donut](https://github.com/flame-engine/defend_the_donut).
107+
93108

94-
Also check our more advanced examples, [Collect the Donut](https://github.com/luanpotter/collect_the_donut)
95-
and [Defend the Donut](https://github.com/flame-engine/defend_the_donut).
109+
## Web support (experimental)
110+
111+
Flame 3D also runs on the web, though this is **even more experimental** than
112+
the rest of the package. Flutter GPU does not run in the browser (for now), so
113+
on web `flame_3d` renders through the browser's native
114+
[WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) API
115+
instead, via a separate rendering backend. If you want to run on web add the
116+
following before your `runApp` call in `main.dart`:
117+
118+
```dart
119+
await GpuBackend.initialize();
120+
```
96121

97122

98123
## Building shaders
99124

100-
If you are using the `SpatialMaterial` provided by `flame_3d`, you do not need to worry about shaders.
125+
If you are using the materials provided by `flame_3d`, you do not need to worry
126+
about shaders.
101127

102-
That being said, you can write your own shaders and use them on custom materials.
103-
Currently, Flutter does not do the bundling of shaders for us so this package provides a simple
104-
Dart script. Create your fragment and vertex shader in a `shaders` directory,
105-
make sure the file names are identical. Like so:
128+
That being said, you can write your own shaders and use them on custom
129+
materials. Currently, Flutter does not do the bundling of shaders for us so this
130+
package provides a simple Dart script. Create your fragment and vertex shader in
131+
a `shaders` directory, make sure the file names are identical. Like so:
106132

107133
- `my_custom_shader`.frag
108134
- `my_custom_shader`.vert
109135

110-
You can then run `dart pub run flame_3d:build_shaders` to bundle the shaders. They will
111-
automatically be placed in `assets/shaders`.
136+
You can then run `dart pub run flame_3d:build_shaders` to bundle the shaders.
137+
They will automatically be placed in `assets/shaders`.
138+
139+
For [web support](#web-support-experimental), also pass `--with-web-gpu` to
140+
build the WebGPU shader bundles. That step needs the `naga` CLI on your `PATH`
141+
(`cargo install naga-cli`).
142+
143+
Shaders can also reuse shared GLSL through `#include`. An
144+
`#include <package_name/file.glsl>` resolves against the `shaders/` directory of
145+
any package in your dependency graph, so chunks can be shared across packages.
146+
Including ones that `flame_3d` itself ships, such as
147+
`#include <flame_3d/skinning.glsl>` for vertex skinning.
112148

113149
You can check out the
114-
[default shaders](https://github.com/flame-engine/flame/tree/main/packages/flame_3d/shaders) if you
115-
want to have some examples.
150+
[default shaders](https://github.com/flame-engine/flame/tree/main/packages/flame_3d/shaders)
151+
if you want to have some examples.
116152

117153

118154
## Contributing
119155

120-
Have you found a bug or have a suggestion of how to enhance the 3D APIs? Open an issue and we will
121-
take a look at it as soon as possible.
156+
Have you found a bug or have a suggestion of how to enhance the 3D APIs? Open an
157+
issue and we will take a look at it as soon as possible.
122158

123-
Do you want to contribute with a PR? PRs are always welcome, just make sure to create it from the
124-
correct branch (main) and follow the [checklist](.github/pull_request_template.md) which will
125-
appear when you open the PR.
159+
Do you want to contribute with a PR? PRs are always welcome, just make sure to
160+
create it from the correct branch (main) and follow the
161+
[checklist](.github/pull_request_template.md) which will appear when you open
162+
the PR.
126163

127-
For bigger changes, or if in doubt, make sure to talk about your contribution to the team. Either
128-
via an issue, GitHub discussion, or reach out to the team using the
129-
[Discord server](https://discord.gg/pxrBmy4).
164+
For bigger changes, or if in doubt, make sure to talk about your contribution to
165+
the team. Either via an issue, GitHub discussion, or reach out to the team using
166+
the [Discord server](https://discord.gg/pxrBmy4).
24 Bytes
Binary file not shown.

packages/flame_3d/assets/shaders/spatial_material.wgslbundle

Lines changed: 67 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"vertex": "struct JointMatrices {\n joints: array<mat4x4<f32>, 16>,\n}\n\nstruct VertexInfo {\n model: mat4x4<f32>,\n view: mat4x4<f32>,\n projection: mat4x4<f32>,\n}\n\nstruct VertexOutput {\n @location(0) fragTexCoord: vec2<f32>,\n @location(1) fragColor: vec4<f32>,\n @location(2) fragPosition: vec3<f32>,\n @location(3) fragNormal: vec3<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> vertexPosition_1: vec3<f32>;\nvar<private> vertexTexCoord_1: vec2<f32>;\nvar<private> vertexColor_1: vec4<f32>;\nvar<private> vertexNormal_1: vec3<f32>;\nvar<private> vertexJoints_1: vec4<f32>;\nvar<private> vertexWeights_1: vec4<f32>;\n@group(0) @binding(0) \nvar<uniform> jointMatrices: JointMatrices;\nvar<private> fragTexCoord: vec2<f32>;\nvar<private> fragColor: vec4<f32>;\nvar<private> fragPosition: vec3<f32>;\nvar<private> fragNormal: vec3<f32>;\n@group(0) @binding(1) \nvar<uniform> vertex_info: VertexInfo;\nvar<private> gl_Position: vec4<f32>;\n\nfn computeSkinMatrix() -> mat4x4<f32> {\n let _e9 = vertexWeights_1;\n let _e13 = vertexWeights_1;\n let _e18 = vertexWeights_1;\n let _e23 = vertexWeights_1;\n if ((((_e9.x == 0f) && (_e13.y == 0f)) && (_e18.z == 0f)) && (_e23.w == 0f)) {\n {\n return mat4x4<f32>(vec4<f32>(1f, 0f, 0f, 0f), vec4<f32>(0f, 1f, 0f, 0f), vec4<f32>(0f, 0f, 1f, 0f), vec4<f32>(0f, 0f, 0f, 1f));\n }\n }\n let _e35 = vertexWeights_1;\n let _e37 = vertexJoints_1;\n let _e42 = jointMatrices.joints[i32(_e37.x)];\n let _e44 = vertexWeights_1;\n let _e46 = vertexJoints_1;\n let _e51 = jointMatrices.joints[i32(_e46.y)];\n let _e54 = vertexWeights_1;\n let _e56 = vertexJoints_1;\n let _e61 = jointMatrices.joints[i32(_e56.z)];\n let _e64 = vertexWeights_1;\n let _e66 = vertexJoints_1;\n let _e71 = jointMatrices.joints[i32(_e66.w)];\n return ((((_e35.x * _e42) + (_e44.y * _e51)) + (_e54.z * _e61)) + (_e64.w * _e71));\n}\n\nfn main_1() {\n var skinMatrix: mat4x4<f32>;\n var position: vec3<f32>;\n var normal: vec3<f32>;\n var modelViewProjection: mat4x4<f32>;\n\n let _e20 = computeSkinMatrix();\n skinMatrix = _e20;\n let _e22 = skinMatrix;\n let _e23 = vertexPosition_1;\n position = (_e22 * vec4<f32>(_e23.x, _e23.y, _e23.z, 1f)).xyz;\n let _e32 = skinMatrix;\n let _e33 = vertexNormal_1;\n normal = normalize((_e32 * vec4<f32>(_e33.x, _e33.y, _e33.z, 0f)).xyz);\n let _e43 = vertex_info;\n let _e45 = vertex_info;\n let _e48 = vertex_info;\n modelViewProjection = ((_e43.projection * _e45.view) * _e48.model);\n let _e53 = modelViewProjection;\n let _e54 = position;\n gl_Position = (_e53 * vec4<f32>(_e54.x, _e54.y, _e54.z, 1f));\n let _e61 = vertexTexCoord_1;\n fragTexCoord = _e61;\n let _e62 = vertexColor_1;\n fragColor = _e62;\n let _e63 = vertex_info;\n let _e65 = position;\n fragPosition = vec3<f32>((_e63.model * vec4<f32>(_e65.x, _e65.y, _e65.z, 1f)).xyz);\n let _e74 = vertex_info;\n let _e77 = transpose(_naga_inverse_4x4_f32(_e74.model));\n let _e87 = normal;\n fragNormal = (mat3x3<f32>(_e77[0].xyz, _e77[1].xyz, _e77[2].xyz) * _e87);\n let _e90 = gl_Position;\n let _e92 = gl_Position;\n gl_Position.z = ((_e90.z + _e92.w) * 0.5f);\n return;\n}\n\n@vertex \nfn main(@location(0) vertexPosition: vec3<f32>, @location(1) vertexTexCoord: vec2<f32>, @location(2) vertexColor: vec4<f32>, @location(3) vertexNormal: vec3<f32>, @location(4) vertexJoints: vec4<f32>, @location(5) vertexWeights: vec4<f32>) -> VertexOutput {\n vertexPosition_1 = vertexPosition;\n vertexTexCoord_1 = vertexTexCoord;\n vertexColor_1 = vertexColor;\n vertexNormal_1 = vertexNormal;\n vertexJoints_1 = vertexJoints;\n vertexWeights_1 = vertexWeights;\n main_1();\n let _e43 = fragTexCoord;\n let _e45 = fragColor;\n let _e47 = fragPosition;\n let _e49 = fragNormal;\n let _e51 = gl_Position;\n return VertexOutput(_e43, _e45, _e47, _e49, _e51);\n}\n\nfn _naga_inverse_4x4_f32(m: mat4x4<f32>) -> mat4x4<f32> {\n let sub_factor00: f32 = m[2][2] * m[3][3] - m[3][2] * m[2][3];\n let sub_factor01: f32 = m[2][1] * m[3][3] - m[3][1] * m[2][3];\n let sub_factor02: f32 = m[2][1] * m[3][2] - m[3][1] * m[2][2];\n let sub_factor03: f32 = m[2][0] * m[3][3] - m[3][0] * m[2][3];\n let sub_factor04: f32 = m[2][0] * m[3][2] - m[3][0] * m[2][2];\n let sub_factor05: f32 = m[2][0] * m[3][1] - m[3][0] * m[2][1];\n let sub_factor06: f32 = m[1][2] * m[3][3] - m[3][2] * m[1][3];\n let sub_factor07: f32 = m[1][1] * m[3][3] - m[3][1] * m[1][3];\n let sub_factor08: f32 = m[1][1] * m[3][2] - m[3][1] * m[1][2];\n let sub_factor09: f32 = m[1][0] * m[3][3] - m[3][0] * m[1][3];\n let sub_factor10: f32 = m[1][0] * m[3][2] - m[3][0] * m[1][2];\n let sub_factor11: f32 = m[1][1] * m[3][3] - m[3][1] * m[1][3];\n let sub_factor12: f32 = m[1][0] * m[3][1] - m[3][0] * m[1][1];\n let sub_factor13: f32 = m[1][2] * m[2][3] - m[2][2] * m[1][3];\n let sub_factor14: f32 = m[1][1] * m[2][3] - m[2][1] * m[1][3];\n let sub_factor15: f32 = m[1][1] * m[2][2] - m[2][1] * m[1][2];\n let sub_factor16: f32 = m[1][0] * m[2][3] - m[2][0] * m[1][3];\n let sub_factor17: f32 = m[1][0] * m[2][2] - m[2][0] * m[1][2];\n let sub_factor18: f32 = m[1][0] * m[2][1] - m[2][0] * m[1][1];\n\n var adj: mat4x4<f32>;\n adj[0][0] = (m[1][1] * sub_factor00 - m[1][2] * sub_factor01 + m[1][3] * sub_factor02);\n adj[1][0] = - (m[1][0] * sub_factor00 - m[1][2] * sub_factor03 + m[1][3] * sub_factor04);\n adj[2][0] = (m[1][0] * sub_factor01 - m[1][1] * sub_factor03 + m[1][3] * sub_factor05);\n adj[3][0] = - (m[1][0] * sub_factor02 - m[1][1] * sub_factor04 + m[1][2] * sub_factor05);\n adj[0][1] = - (m[0][1] * sub_factor00 - m[0][2] * sub_factor01 + m[0][3] * sub_factor02);\n adj[1][1] = (m[0][0] * sub_factor00 - m[0][2] * sub_factor03 + m[0][3] * sub_factor04);\n adj[2][1] = - (m[0][0] * sub_factor01 - m[0][1] * sub_factor03 + m[0][3] * sub_factor05);\n adj[3][1] = (m[0][0] * sub_factor02 - m[0][1] * sub_factor04 + m[0][2] * sub_factor05);\n adj[0][2] = (m[0][1] * sub_factor06 - m[0][2] * sub_factor07 + m[0][3] * sub_factor08);\n adj[1][2] = - (m[0][0] * sub_factor06 - m[0][2] * sub_factor09 + m[0][3] * sub_factor10);\n adj[2][2] = (m[0][0] * sub_factor11 - m[0][1] * sub_factor09 + m[0][3] * sub_factor12);\n adj[3][2] = - (m[0][0] * sub_factor08 - m[0][1] * sub_factor10 + m[0][2] * sub_factor12);\n adj[0][3] = - (m[0][1] * sub_factor13 - m[0][2] * sub_factor14 + m[0][3] * sub_factor15);\n adj[1][3] = (m[0][0] * sub_factor13 - m[0][2] * sub_factor16 + m[0][3] * sub_factor17);\n adj[2][3] = - (m[0][0] * sub_factor14 - m[0][1] * sub_factor16 + m[0][3] * sub_factor18);\n adj[3][3] = (m[0][0] * sub_factor15 - m[0][1] * sub_factor17 + m[0][2] * sub_factor18);\n\n let det = (m[0][0] * adj[0][0] + m[0][1] * adj[1][0] + m[0][2] * adj[2][0] + m[0][3] * adj[3][0]);\n\n return adj * (1 / det);\n}\n",
3+
"fragment": "struct Material {\n albedoColor: vec4<f32>,\n}\n\nstruct FragmentOutput {\n @location(0) outColor: vec4<f32>,\n}\n\nvar<private> fragTexCoord_1: vec2<f32>;\nvar<private> fragColor_1: vec4<f32>;\nvar<private> fragPosition_1: vec3<f32>;\nvar<private> fragNormal_1: vec3<f32>;\nvar<private> outColor: vec4<f32>;\n@group(1) @binding(0) \nvar albedoTexture: texture_2d<f32>;\n@group(1) @binding(1) \nvar albedoTextureSampler: sampler;\n@group(1) @binding(2) \nvar<uniform> material: Material;\n\nfn main_1() {\n var texColor: vec4<f32>;\n\n let _e10 = fragTexCoord_1;\n let _e11 = textureSample(albedoTexture, albedoTextureSampler, _e10);\n texColor = _e11;\n let _e13 = texColor;\n let _e14 = material;\n outColor = (_e13 * _e14.albedoColor);\n return;\n}\n\n@fragment \nfn main(@location(0) fragTexCoord: vec2<f32>, @location(1) fragColor: vec4<f32>, @location(2) fragPosition: vec3<f32>, @location(3) fragNormal: vec3<f32>) -> FragmentOutput {\n fragTexCoord_1 = fragTexCoord;\n fragColor_1 = fragColor;\n fragPosition_1 = fragPosition;\n fragNormal_1 = fragNormal;\n main_1();\n let _e26 = outColor;\n return FragmentOutput(_e26);\n}\n",
4+
"slots": {
5+
"JointMatrices": {
6+
"group": 0,
7+
"binding": 0,
8+
"sizeInBytes": 1024,
9+
"memberOffsets": {
10+
"joints": 0
11+
}
12+
},
13+
"VertexInfo": {
14+
"group": 0,
15+
"binding": 1,
16+
"sizeInBytes": 192,
17+
"memberOffsets": {
18+
"model": 0,
19+
"view": 64,
20+
"projection": 128
21+
}
22+
},
23+
"Material": {
24+
"group": 1,
25+
"binding": 2,
26+
"sizeInBytes": 16,
27+
"memberOffsets": {
28+
"albedoColor": 0
29+
}
30+
},
31+
"albedoTexture": {
32+
"group": 1,
33+
"binding": 0,
34+
"samplerBinding": 1
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)