Introduction
This is the report of the initial effort to add Web Assembly (WASM) support to the Amethyst game engine.
Feel free to refer to this to learn from our experience.
Report accurate as of 2020-05-06.
Initial State
Before beginning any implementation work, some scout work was done to collate existing information from pre-existing attempts. The summary from each attempt is listed below.
Jojolepro – amethyst:wasm
Attempt at getting `amethyst` to compile with `wasm-bindgen`.
- Top down approach – try to get
amethyst
to compile for wasm with minimal features, then incrementally enable crates. - Partial update of winit to 0.21.
- Places crates / code behind feature toggles.
Relevant commits:
-
-
removes
backtrace
fromamethyst_error
-
places the following crates under a feature flag in
Cargo.toml
:- amethyst_controls
- amethyst_input
- amethyst_ui
- amethyst_utils
- amethyst_window
- winit
- failure
-
-
2c9a78f2
: adds#[cfg(feature = "renderer")]
in mainamethyst
crate. -
-
Bumps
winit
to0.21
and updates features. -
Puts the following behind feature flags:
amethyst_audio
amethyst_ui
-
Removes
#[cfg(feature = "renderer")]
fromsrc/app.rs:30
-
Semtexv – amethyst:rendy-all
(based on #2040)
Updates `amethyst` to use `winit 0.21` and `rendy 0.5`.
Summary:
- Updates winit to 0.21, including
Event
and screen logical / physical size changes. - Updates rendy to 0.5.1.
- Updates most (all?) examples to properly run with
winit
's new event loop mechanism.
Interesting diff:
/// # Examples
///
/// ~~~no_run
/// let event_loop = EventLoop::new();
/// let mut game = Application::new(assets_dir, Example::new(), game_data)?;
/// game.initialize();
/// event_loop.run(move |event, _, control_flow| {
/// #[cfg(feature = "profiler")]
/// profile_scope!("run_event_loop");
/// log::trace!("main loop run");
/// game.run_winit_loop(event, control_flow)
/// })
/// ~~~
Omni-viral – amethyst:gl
(PR #1877)
The first attempt that compiled and ran.
-
Updates winit to
0.20.0-alpha2
. -
Disables the following crates:
amethyst_audio
amethyst_network
Also for
amethyst_gltf
:mikktspace
-
Removes usage of
rayon::ThreadPool
. -
Changes shader compilation script, and recompiles all shaders.
Jojolepro – web_worker
Allows rayon::ThreadPool
to be used by using a custom constructor.
Jaynus – rendy:jaynus-fixes
- Bumps
gfx-hal
to latest git master as of March 11, 2020 - Compiles
rendy/rendy
examples to WASM usingwasm-bindgen
Plan
Because a game engine covers many complex domains -- event loop, audio, graphics, etcetera -- we want to make sure there is a "good" starting state, and be able to develop and test each domain independently. In addition, we want to encourage contribution from the community where possible.
Jump Start
The plan to create a stable equilibrium used is:
- Get
amethyst
to compile for thewasm32-unknown-unknown
target. - Set up an end-to-end project to test usage of
amethyst
. - Set up automated build to ensure all contributions maintain that base quality check.
- Make it easier for potential contributors to get into the "build, test, contribute" loop.
Equilibrium
There are two workflows for ongoing development:
-
Discovery
- Run end-to-end project to discover issues.
- Open a GitHub issue for each issue, providing any stack traces or screenshots, and the expected behaviour for the resolution of the issue.
- Provide some initial investigation to where the root cause of the problem lies, to make it easier to begin working on a fix.
-
Implementation
- Contributor finds an issue they wish to do.
- Locally, the contributor implements a fix, and tests it against end-to-end project.
- The change is submitted for review, and amended if necessary.
- After passing review and the automated build, the contribution is merged.
Implementation
This section covers the WASM support implementation in chronological (time) order.
ℹ️ Note: There is no design process as we are simply adding WASM support to existing functionality, or gating features as unsupported by WASM.
-
Week 1: Contribution Baseline – Preparation of repositories and guidelines.
-
Week 2: End-to-End Discovery – Event loop execution and first successful run of Pong.
-
Week 3: Stability and UI – WASM memory fix and UI rendering correction.
-
Week 4: Audio and Rendering – Audio playback and rendering fixes on Windows.
-
Week 5: Tidying – Input loading and automated testing with GL.
-
Week 6: Fin Ack – Web sockets, logging, and UI rendering correction.
Week 1: Contribution Baseline
Summary
Date: 2020-03-16 to 2020-03-21
- Amethyst compiles to a WASM library.
- Repository forks and branches are created.
- Contribution guide is written.
- Basic CI check is set up.
Repository | Commit Range |
---|---|
pong_wasm | 9b403f69^..9f6240fe |
amethyst | 8044b2a5^..0af12f84 |
rendy | 27e5cdc1^..757c4aa9 |
glutin (fork) | f29d87a3 |
gfx-rs (fork) | 3e6db5f0 |
winit (fork) | 26e4374a^..04225a39 |
builder (CI agent) | 13730efd^..2bb67aae |
End Result
$ wasm-pack build -- --features "wasm gl"
# ..
[INFO]: :-) Done in 37.87s
[INFO]: :-) Your wasm pkg is ready to publish at ./pkg.
Implementation
Based off the #2040
branch, which has the changes to use the new winit
event loop introduced in winit 0.20.0
.
-
Get amethyst to compile with
winit 0.22.0
for better WASM support. (winit#1478) -
Attempt to compile amethyst with
wasm-pack
.Instructions from https://rustwasm.github.io/docs/book/ were followed to package the library.
When building with:
wasm-pack build --target no-modules -- --features "wasm gl"
-
If it fails due to usage of a
-sys
library, feature gate the dependency and the code that uses it. -
If it fails and requires a code change:
- Fork the repository.
- Create a
wasm
branch. - Amend the code.
- Point
amethyst
at the forked repository. - Make a pull request back to the original repository.
Repeat until
wasm-pack
succeeds. -
-
Create end-to-end application, and make sure it compiles:
pong_wasm
. -
Write contribution guidelines. (amethyst#2171)
Make it easy:
- Links to all forks and branches.
- Cut and paste commands.
- Setup and development instructions.
-
Create CI job to build amethyst as a WASM library. (amethyst#2175)
Note: CI agent needs
wasm32-unknown-unknown
target, andrust-src
component. -
Publicise the WASM effort on the community forum and chat server.
Week 2: End-to-End Discovery
Summary
Date: 2020-03-22 to 2020-03-28
-
Limitations discovered around:
winit
event loop and WASM event loop requirements.- Web worker threading requirements.
- Audio loading requirements.
- Texture loading requirements.
-
Assets load from HTTP source using XHRs.
Repository | Commit Range |
---|---|
pong_wasm | 3bcf94de^..de355df6 |
amethyst | 65a1e27a^..1d491d18 |
rendy | e1e03fee^..4de9ca2a |
winit (fork) | 8595aec7^..9827b34a |
gfx-rs (fork) | a9a4419d^..672f551a |
web_worker | 892abf29^..2b78b6ca |
End Result
Implementation
-
Clean up
web_worker
repository.- Move
jojolepro/web_worker
toamethyst/web_worker
- Allow constructing thread pool without JavaScript (workers still need
worker.js
to run).
- Move
-
Get dispatcher to execute serially –
"no-parallel"
. (amethyst#2177, amethyst#2189, amethyst#2191) -
Assets load from server via
XmlHttpRequest
s. (amethyst#2180)- Loading texture assets into GL backend. (amethyst#2174)
-
Get GL to render correctly. (amethyst#2198)
-
Update
pong_wasm
to run. (pong_wasm#3bcf94d)
Week 3: Stability and UI
Summary
Date: 2020-03-29 to 2020-04-04
- UI pass works
- WASM app doesn't crash 90% of the time from double mutable borrow in winit.
End Result
Implementation
-
Winit stability fixes.
- Fix double borrow mut. (amethyst/winit#4fbf95b, winit#1512)
- Add
requestAnimationFrame
support towinit
. (amethyst/winit#3d5274b, winit#1493, winit#1519)
-
Take in HTML
<canvas>
element from user. (amethyst#2202, rendy#48915cb, pong_wasm#1)- Update to
gfx-hal 0.5
. - Don't invert coordinate system. (rendy#5d10084)
- Update to
-
Fixed UI pass by using consistent shader variable names. (amethyst#2205, amethyst#2207)
Week 4: Audio and Rendering
Summary
Date: 2020-04-05 to 2020-04-11
- GL depth buffer fix -- rendering is corrected on Windows.
- Audio plays in WASM, albeit delayed.
End Result
Implementation
-
Split audio logic from other systems. (amethyst#2215, amethyst#2216, pong_wasm#2)
This allows the rest of Pong to run even if audio is disabled.
-
Initialization stability on Windows:
Make drag and drop
winit
feature optional. (amethyst/winit#a2eea3e, winit#1524) -
Fixed
gfx
issues.- Clear GL depth buffer. (gfx#35c45ac, gfx#3202, gfx#3205)
- Load
Rgb8Srgb
texture format. (gfx#c4b75d3, gfx#3222, gfx#3223)
-
Get audio to play on WASM.
- Use ishitatsuyuki/cpal#dpeckett-webaudio-poc, which is based off wasm-bindgen POC by
dpeckett
. (amethyst/cpal#ee1ee1a, cpal#372) - Send audio through
AudioBuffer
in JavaScript. (amethyst#2195, amethyst#2219) - Use
sed
hack to allow web workers to be instantiated without anAudioContext
. (pong_wasm#3)
- Use ishitatsuyuki/cpal#dpeckett-webaudio-poc, which is based off wasm-bindgen POC by
Week 5: Tidying
Summary
Date: 2020-04-12 to 2020-04-18
-
Input configuration loads from server.
-
amethyst_test
updated to work with winit 0.22.0 event loop.Tests that require a graphical backend can run on CI without dedicated graphics cards by using the software GL rendering backend -- tests can be run through XVFB.
End Result
Implementation
-
Load input configuration from server using JavaScript. (amethyst#2214, pong_wasm#4)
This is simply a
fetch
invocation. For better UX, we should:- Initialize the fetch from within the application
- Return control to the browser so that it is responsive, and indicate to the user that a resource is being fetched, such as by rendering a spinner.
- Resume application initialization after the fetch has returned.
-
Remove requirement to specify
shred
in crate[patch]
section. (amethyst#2238) -
Update
amethyst_test
framework to work with newwinit
.- Expose API function for bundles needing the
EventLoop
to be built. (amethyst#2240) - Allow multiple
winit
event loops to run in sub threads. (amethyst#2241, amethyst#2245, autexousious#222, autexousious#223)
- Expose API function for bundles needing the
Week 6: Fin Ack
Summary
Date: 2020-04-19 to 2020-04-25
- UI Coordinates / Screen Dimensions correction
- Configurable Web Logger
- Net Server and Client
End Result
Implementation
-
Allow web logger to be configured. (amethyst#2249, amethyst#2250, console_log#6)
This was necessary as the rate of log messages was about 1000 messages per second. This meant:
- Filtering for relevant log messages was extremely difficult.
- The browser would lag and be unusable to work with.
-
Set canvas width/height only if unset. (amethyst#2247, amethyst/gfx#8537dfb, gfx#3224, gfx#3225)
Quirk in this bug fix is, when setting the
"width"
attribute on the<canvas>
element, the next read ofwidth()
still returns the old value. However, the updated value is returned after interacting with the canvas' WebGL2 graphics context. -
Provide
WebSocket
transport layer implementation foramethyst_network
. (amethyst#2251, amethyst#2253, autexousious#209, autexousious#221)- Use
tungstenite
for native targets. - Use
web-sys::WebSocket
forwasm32-unknown-unknown
target.
- Use
Issue Summary
Item | Crate(s) | WASM Rush links | Upstream Status |
---|---|---|---|
Event s must be Clone | winit , amethyst | #2211, #2040 | ❌ #1478 |
rendy to use gfx-hal 0.5 | rendy , amethyst_rendy | rendy:wasm, #2198 | ❌ #275, #277 |
WASM: parallel dispatch | amethyst , web_worker | #2177, #2189 | ❌ #2191 |
Load assets asynchronously | amethyst_assets | #2180, #2182 | ❌ #2228 |
Load textures as thread local | amethyst_rendy | #2174 | ✔️ #2198 |
winit double borrow mut | winit | 4fbf95b | ✔️ #1512 |
winit::requestAnimationFrame | winit | 3d5274b | ✔️ #1493, #1519 |
Use user provided <canvas> | rendy , amethyst_rendy | #2202, 48915cb , #1 | ❓ |
Use same shader variable names | amethyst_ui | #2205, #2207 | ✔️ |
winit drag-n-drop optional | winit | a2eea3e | ❌ #1524 |
Clear GL depth buffer | gfx-hal | 35c45ac | ✔️ #3202, #3205 |
Load Rgb8Srgb texture format | gfx-hal | c4b75d3 | ✔️ #3222, #3223 |
Proper AudioSocket support | cpal | ee1ee1a , #2195, #2219, #3 | ❌ #372, #2222 |
Load configuration from server | amethyst | #2214, #4 | ❌ |
Integration test support | amethyst_test | #2241, #222, #223 | ✔️ #2240, #2245 |
Configurable web logger | console_log , amethyst | #2249, #2250 | ✔️ #6 |
Overwritten canvas dimensions | gfx-hal | #2247, 8537dfb | ✔️ #3224, #3225 |
WebSocket transport layer | amethyst_network | #2251, #209, #221 | ✔️ #2253 |
Future Work
Future work is largely tracked by the issues and PRs marked ❌ on the issue summary page, and can also be seen in the amethyst
repository's WASM project and on the issue tracker under the "WASM Support" tag.
Notable items are:
These are necessary for applications to "just work" across major browsers and OSes, and work performantly.