Everything below was tested with Godot 4.1.1, gdext at commit master@639aeb495f8f43fe68a326f6abe70a8ce4694ce3, Rust v1.72.0 (when using the stable channel), Rust v1.74.0-nightly (5ae769f06 2023-09-26, when using the nightly channel) and emsdk v3.1.28, in a Debian 12 podman container.
-
Setup: So far, to reach the furthest progress I could find in gdext WebAssembly export, it seems you need at least to:
- Use the Emscripten flags
--no-entry -sSIDE_MODULE=2 -sUSE_PTHREADS=1
(last one appears to be superseded by -pthread on recent versions);- To apply those flags, one can add to the env var RUSTFLAGS
-C link-arg=emscripten-flag-here
for each emscripten flag. - You can also avoid having to use RUSTFLAGS by adding the flags to
project/rust/.cargo/config.toml
in the format
[target.wasm32-unknown-emscripten] rustflags = [ "-Clink-arg=--no-entry", "-Clink-arg=-sSIDE_MODULE=2", "-Clink-arg=-sUSE_PTHREADS=1" ]
- NOTE: To enable some debug symbols and checks, I had to add the Emscripten flags
-O1 -g -sASSERTIONS=2 -sSAFE_HEAP=1 -sSTACK_OVERFLOW_CHECK=2 -sDEMANGLE_SUPPORT=1
- UPDATE (2023-10-11): The flags
-sSTACK_SIZE=2000MB -sINITIAL_MEMORY=3999MB
, while also excluding-sASSERTIONS=2 -sSTACK_OVERFLOW_CHECK=2
, may also be required to prevent certain stack overflow errors.- Additionally, using emscripten 3.1.28 is currently required if building Godot from source.
- To apply those flags, one can add to the env var RUSTFLAGS
- Use the rustc flag
target-feature=+atomics,+bulk-memory,+mutable-globals
(required for emscripten to allow enabling shared memory)- Can be added in RUSTFLAGS as
-C target-feature=+atomics,+bulk-memory,+mutable-globals
- Or as a line in 'rustflags' in the project's local .cargo/config.toml file as shown above
- Due to this, it appears that rebuilding the Rust standard library with these rustc features is necessary. For this, one will have to use nightly cargo and append the following to
~/.cargo/config.toml
:
rustflags = [ "-C", "target-feature=+atomics,+bulk-memory,+mutable-globals", ] [unstable] build-std = ["panic_abort", "std"]
- Can be added in RUSTFLAGS as
- Build with
cargo +nightly build --target wasm32-unknown-emscripten
(plus RUSTFLAGS set or whichever other modifiers are used).
- Make sure to add emscripten target support with
rustup target add wasm32-unknown-emscripten
- NOTE: Don't forget to build normally with
cargo build
as well (if you runcargo clean
) to make sure you can load the project in the Godot editor and perform the Web export. - UPDATE (2023-10-11): If you're using an emscripten version older than 3.1.40 and you get an error indicating some library can't be found, e.g.
-lc-mt-debug
, you may have to apply the changes toemcc.py
from this emscripten 3.1.40 commit into your local/opt/emsdk/upstream/emscripten/emcc.py
file: https://github.com/emscripten-core/emscripten/commit/b76fdf445f0a8dfdebfd0f67749bfbc20665d397 - Additionally, you may have to patch gdext as follows, to ensure a particularly problematic function doesn't run on WASM:
diff --git a/godot-ffi/src/compat/compat_4_1.rs b/godot-ffi/src/compat/compat_4_1.rs index e258e58..679a31a 100644 --- a/godot-ffi/src/compat/compat_4_1.rs +++ b/godot-ffi/src/compat/compat_4_1.rs @@ -49,6 +49,10 @@ impl BindingCompat for sys::GDExtensionInterfaceGetProcAddress { // first fields have values version_major=4 and version_minor=0. This might be deep in UB territory, but the alternative is // to not be able to detect Godot 4.0.x at all, and run into UB anyway. + if cfg!(target_arch = "wasm32") { + return; + } + let get_proc_address = self.expect("get_proc_address unexpectedly null"); let data_ptr = get_proc_address as *const LegacyLayout; // crowbar it via `as` cast
- Add the following line to ExtensionName.gdextension:
web.debug.wasm32 = "res://path/to/rust/target/wasm32-unknown-emscripten/debug/crate_name.wasm"
- Enable "Enable support for GDExtensions" in the Web Export dialog in the Godot editor.
- UPDATE (2023-10-11): Compile a web template from source with Godot v4.2-dev6 (commit
57a6813bb8bc2417ddef1058d422a91f0c9f753c
) and select the resultingbin/godot.web.template_debug.wasm32.dlink.zip
file on "Debug" under "Export template" when exporting to the Web.- To do this, first ensure you have the correct tools to compile Godot from source (see https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_web.html#doc-compiling-for-web).
- Then,
git clone https://github.com/godotengine/godot
,cd godot
andgit checkout --detach 57a6813bb8bc2417ddef1058d422a91f0c9f753c
to go to that commit. - Finally, run
scons platform=web production=yes dlink_enabled=yes debug_symbols=yes target=template_debug
to build the web export template. - It will generate the aforementioned zip which you may select while exporting to the web in your editor.
- Use the Emscripten flags
-
Results: Godot exports successfully; however, this seems to be producing different errors on game startup (when loading the page), depending on the project setup.
- In the Dodge the Creeps example from the gdext repo (commit master@639aeb495f8f43fe68a326f6abe70a8ce4694ce3), I'd get
index.js:51071 Aborted(stack overflow (Attempt to set SP to 0x568490, with stack limits [0x684d0 - 0x9daf60]))
(without enabling debug it's just "memory access out of bounds") - In a simple Hello Gdext example, created manually by following the gdext docs, I'd get
WebAssembly.instantiate(): Compiling function #700:"godot_ffi::gen::table_scene_classes::ClassScene..." failed: local count too large @+416567
(or, without debug flags, justwasm validation error: at offset 416568: too many locals
) - which seems to have been the latest progress reported in the thread regarding Webassembly in the gdext Discord server. - Regarding this difference, I found out that adding
default-features = false
to thegdext
dependency on Cargo.toml would result in the Stack overflow error, while enabling it back would result in the 'local count' error. So this seems to be the cause in the difference between the two projects (the Dodge the Creeps project disables default-features). - NOTE: On Firefox, I'd often get the (unhelpful, without any form of stack trace) error
WebAssembly module validated with warning: failed to allocate executable memory for module
instead, but this seemed to be a one-time thing (after a computer restart, this doesn't seem to be happening anymore, and the errors match Chromium's as the two listed above - although that's weird as I had plenty of memory available (over 40 GB) in my computer).
UPDATE 1 (2023-09-30): The Stack overflow error also appears with default features enabled when compiling with --release
(cargo +nightly build --release --target wasm32-unknown-emscripten
) and changing the necessary file paths in .gdextension
. That is, --release
seems to make the 'local count too large' error disappear. (See also yewstack/yew#478)
UPDATE 2 (2023-10-01): The error changes to "index out of bounds" on Firefox when using -O0
with -sSTACK_SIZE=50MB
and 4 GB of initial memory, in some internal emscripten WASM function named "cull_zombies" (image here).
UPDATE 3 (2023-10-11): Now using Godot 4.2-dev6 (57a6813bb8bc2417ddef1058d422a91f0c9f753c) compiled from source (see new instructions above), with Emscripten 3.1.28 (the only version which works at that commit). As a result, got simply a stack overflow at first, which was solved by adding the flags -sSTACK_SIZE=2000MB -sINITIAL_MEMORY=3999MB -sALLOW_MEMORY_GROWTH=1
and removing assertions and stack overflow checks. Then, hit the error native code called abort()
- gdext was panicking at ensure_static_runtime_compatibility
under godot-ffi/src/compat/compat_4_1.rs
(apparently due to UB). Had thus to apply a patch to return early at that function (see https://github.com/PgBiel/gdext/commit/77932bd0996be9531c754d581379ed46ebf1fd68). With that, I got the error Assertion failed: bad function pointer type - dynCall function not found for sig 'jji'
. Adding -sEMULATE_FUNCTION_POINTER_CASTS=1
changed the error to Cannot convert 491636 to BigInt
. Using -sWASM_BIGINT=1
did not change the error.
UPDATE 4 (2023-10-14): Attempting the same procedure from Update 3, however using Godot 4.2-beta1 (b1371806ad3907c009458ea939bd4b810f9deb21) compiled from source with Emscripten 3.1.39 (the latest version which works at that commit), and disabling debug assertions and stack overflow checks, the game seems to simply freeze instead of throwing the bad function pointer type
error. However, re-enabling stack overflow checks does still result in a stack overflow error.