-
-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebAssembly support #438
Comments
Hello, thanks for opening this issue! Hopefully other contributors can see this and feel motivated to help 😄 (EDIT: results in this comment are outdated; see #438 (comment)) I'd like to share here some of the progress we have so far. In particular, I tried to summarize our current progress in a gist, after attempting to compile a Gdext project to WASM from scratch: https://gist.github.com/PgBiel/ffa695a479ef4466cb24755db983950b The most important section in the link above is the
I've been playing with this last error but haven't found much so far. Curiously, with the right settings (larger stack size etc.), I managed to receive an In the Discord thread, there were other theories such as problems with function pointer tables being too large, but also a theory that function pointers in WASM aren't "legit" function pointers due to JavaScript interop in WASM, so using them may require adaptation somehow. Overall, we need some more investigation on this matter before determining what we can do in gdext to solve this. (It's possible that there are upstream problems as well, from Godot and/or from emscripten, but we just don't know yet if that's the case.) Let's hope other interested contributors - and/or WASM experts 👀 - drop by and give their opinions as well 😉 |
As of writing, this is what I know needs to be done. There's still some gdext work to be done, it seems like, but if anyone gets involved this should hopefully serve as a recap. Compile gdextension web templateYou'll need to manually compile the template. Godot doesn't ship with a web template that has gdextension support. It's important to note this template will be SPECIFIC to the version of emscripten that it was compiled with. This probably means you'll need to install emsdk along with a few other tools. Follow the official docs for a guide on what to do, and where to place the template zip. You do not need to recompile the entire editor. ONLY the template. If compiling from git, don't forget to make use of tags prior to compiling anything so it matches the editor version you are using. Setup Rust configYou need to be able to use unstable features. So, a nightly build is probably the best choice. rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-emscripten At the root directory of your project, you'll need to make a The big thing is needing to compile your extension with SHARED_MEMORY. This requires a few flags and rebuilding std (this is because the std included from rustup was not compiled with it enabled). [unstable]
build-std = ["std"]
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2 -sEXPORT_ALL=1",
"-Zlink-native-libraries=no",
"-C", "link-args=-pthread",
"-C", "target-feature=+bulk-memory",
"-C", "target-feature=+atomics",
"-C", "target-feature=+mutable-globals",
"-C", "link-args=-sSHARED_MEMORY=1",
] You'll then have to either compile with [build]
target = ["wasm32-unknown-emscripten"] to the Don't forget to add a web section to your extension's
Setup a web serverYou need to have HTTPS and enable some cross-origin headers. Either use certbot or a self-signed cert.
inside the DebuggingUse chrome and install this extension. It'll basically allow the devtools (F12) to act like a worse LLDB. Breakpoints, memory inspector, etc. Random notes
|
Newer info:
|
Thanks to @zecozephyr for figuring out how to work around some of the rust+emscripten limitations, we currently have gdext on wasm working with the dodge-the-creeps example. We're hoping to get more people to test with their projects to jump in and confirm/deny this. This is coming with a big DISCLAIMER that this patch is probably buggy and not production ready. On top of that, we literally found two bugs in emscripten in the process of this, so... yeah. Bugs! KNOWN CAVEATSGodot 4.1.3+ or 4.2-dev is necessary. Steps to test wasm patch
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
rustup target add wasm32-unknown-emscripten --toolchain nightly
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 3.1.39
./emsdk activate 3.1.39
source ./emsdk.sh (or ./emsdk.bat on windows)
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2",
"-C", "link-args=-sUSE_PTHREADS=1",
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Zlink-native-libraries=no"
]
godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = ["experimental-wasm", "lazy-function-tables"] } NOTE: May need to enable the "lazy-function-tables" feature for a successful runtime and a SIGNIFICANTLY shorter compile time.
cargo +nightly build -Zbuild-std --target wasm32-unknown-emscripten
DiscussionEither post here or post in the discord thread where most of this has been taking place. |
Preliminary support has now been merged into master by #493. Instructions remain largely unchanged except that one now needs to enable the feature |
Since I know that there's quite a few people looking to run gdext on wasm, but are afraid to use it due to instability here's a few words: If you haven't made anything yet within godot and are able to switch engines the bevy game engine is pretty good if you want to use rust and have web-assembly/full multiplatform support. Bevy is in much earlier stages than godot, but so is gdext and it's understandable that each of them have their own challenges. Bevy having no editor could be preferrable as I've seen people ditch the editor for pure-code scene creation in some cases. As someone who had experience now with both - gdext and bevy are good enough to make work with few days of research and hands-on fixes, as a bonus - bevy has webgpu support. |
@DeprecatedLuke please don't hijack the issue tracker only to advertise other projects. People are aware that there are many choices in the Rust ecosystem. Godot itself works absolutely fine with web (there are tons of game jam entries for it, and I have used it myself). The main issue specific to GDExtension is that it's not yet fully supported in non-Chromium based browsers; something that will likely improve. If you want to discuss the issue further, please bring it up on Discord, here is not the right place. |
I wonder if godotengine/godot-cpp#1489 has relevance for us (panics might use the same mechanism as exceptions) 🤔 |
Perhaps, but this will depend on Rust and LLVM support for wasm exceptions, which seems to be fairly early days still. However, as seen here rust-lang/rust#118168 , since last year both the language and stdlib got support for building with |
A new problem seems to be appearing when exporting gdext to Wasm on Godot 4.3 (commit 26d1577f3985363faab48a65e9a0d9eed0e26d86), even with Emscripten 3.1.62, which supposedly brings fixes for dynamic linking (something we depend on). Here are my observations so far:
I haven't been able to proceed from here. I'm not really sure of the semantics behind Details, Artifacts, ReproducersVersions of things
Emscripten 3.1.62 Full logs from my latest attempt: My modified Note that lib.rs code
mod player;
use godot::prelude::*;
struct MyExtension;
// #[gdextension]
// unsafe impl ExtensionLibrary for MyExtension {}
unsafe impl ExtensionLibrary for MyExtension {}
// #[cfg(target_os = "emscripten")]
#[inline(always)]
fn emscripten_preregistration() {
let script = b"var pkgName = \'hello_gdext\';\n console.log(\"[DEBUG] Reached point A.\");\n var libName = pkgName.replaceAll(\'-\', \'_\') + \'.wasm\';\n console.log(\"[DEBUG] Reached point B.\");\n var dso = LDSO.loadedLibsByName[libName];\n // This property was renamed as of emscripten 3.1.34\n var dso_exports = \"module\" in dso ? dso[\"module\"] : dso[\"exports\"];\n var registrants = [];\n console.log(\"[DEBUG] Reached point C.\");\n for (sym in dso_exports) {\n console.log(`[DEBUG] Let\'s check this symbol \'${sym}\'...`);\n if (sym.startsWith(\"dynCall_\") || sym.startsWith(\"invoke_\")) {\n console.log(\"[DEBUG] It is special...\");\n if (!(sym in Module)) {\n console.log(`Patching Module with ${sym}`);\n Module[sym] = dso_exports[sym];\n }\n } else if (sym.startsWith(\"rust_gdext_registrant_\")) {\n registrants.push(sym);\n console.log(\"[DEBUG] Pushed one.\");\n }\n }\n for (sym of registrants) {\n console.log(`Running registrant ${sym}`);\n dso_exports[sym]();\n }\n console.log(\"Added\", registrants.length, \"plugins to registry!\");\n \0";
extern "C" {
fn emscripten_run_script(script: *const std::ffi::c_char);
}
unsafe {
emscripten_run_script(script as *const _ as *const std::ffi::c_char);
}
}
#[no_mangle]
unsafe extern "C" fn gdext_rust_init(
interface_or_get_proc_address: ::godot::sys::InitCompat,
library: ::godot::sys::GDExtensionClassLibraryPtr,
init: *mut ::godot::sys::GDExtensionInitialization,
) -> ::godot::sys::GDExtensionBool {
#[cfg(target_os = "emscripten")]
{
let script = b"var pkgName = \'hello_gdext\';\n console.log(\"[DEBUG] Reached point A.\");\n var libName = pkgName.replaceAll(\'-\', \'_\') + \'.wasm\';\n console.log(\"[DEBUG] Reached point B.\");\n var dso = LDSO.loadedLibsByName[libName];\n // This property was renamed as of emscripten 3.1.34\n var dso_exports = \"module\" in dso ? dso[\"module\"] : dso[\"exports\"];\n var registrants = [];\n console.log(\"[DEBUG] Reached point C.\");\n for (sym in dso_exports) {\n console.log(`[DEBUG] Let\'s check this symbol \'${sym}\'...`);\n if (sym.startsWith(\"dynCall_\") || sym.startsWith(\"invoke_\")) {\n console.log(\"[DEBUG] It is special...\");\n if (!(sym in Module)) {\n console.log(`Patching Module with ${sym}`);\n Module[sym] = dso_exports[sym];\n }\n } else if (sym.startsWith(\"rust_gdext_registrant_\")) {\n registrants.push(sym);\n console.log(\"[DEBUG] Pushed one.\");\n }\n }\n for (sym of registrants) {\n console.log(`Running registrant ${sym}`);\n dso_exports[sym]();\n }\n console.log(\"Added\", registrants.length, \"plugins to registry!\");\n \0";
extern "C" {
fn emscripten_run_script(script: *const std::ffi::c_char);
}
unsafe {
emscripten_run_script(script as *const _ as *const std::ffi::c_char);
}
}
// emscripten_preregistration();
::godot::init::__gdext_load_library::<MyExtension>(interface_or_get_proc_address, library, init)
}
fn __static_type_check() {
let _unused: ::godot::sys::GDExtensionInitializationFunction = Some(gdext_rust_init);
}
#[no_mangle]
#[doc(hidden)]
#[cfg(target_os = "linux")]
pub unsafe extern "C" fn __cxa_thread_atexit_impl(
func: *mut ::std::ffi::c_void,
obj: *mut ::std::ffi::c_void,
dso_symbol: *mut ::std::ffi::c_void,
) {
::godot::sys::linux_reload_workaround::thread_atexit(func, obj, dso_symbol);
} Here's the script above in a more readable form: var pkgName = {env!("CARGO_PKG_NAME")};
console.log("[DEBUG] Reached point A.");
var libName = pkgName.replaceAll('-', '_') + '.wasm';
console.log("[DEBUG] Reached point B.");
var dso = LDSO.loadedLibsByName[libName];
// This property was renamed as of emscripten 3.1.34
var dso_exports = "module" in dso ? dso["module"] : dso["exports"];
var registrants = [];
console.log("[DEBUG] Reached point C.");
for (sym in dso_exports) {
console.log(`[DEBUG] Let's check this symbol '${sym}'...`);
if (sym.startsWith("dynCall_") || sym.startsWith("invoke_")) {
console.log("[DEBUG] It is special...");
if (!(sym in Module)) {
console.log(`Patching Module with ${sym}`);
Module[sym] = dso_exports[sym];
}
} else if (sym.startsWith("rust_gdext_registrant_")) {
registrants.push(sym);
console.log("[DEBUG] Pushed one.");
}
}
for (sym of registrants) {
console.log(`Running registrant ${sym}`);
dso_exports[sym]();
}
console.log("Added", registrants.length, "plugins to registry!"); .cargo/config.toml (added some flags to enable debugging): [target.wasm32-unknown-emscripten]
rustflags = [
"-C",
"link-args=-sSIDE_MODULE=2",
# "-C",
# "link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Clink-args=-sEXPORT_ALL=1",
# Trying out stuff
"-Clink-arg=-O0",
"-Clink-arg=-g",
# "-Clink-arg=-sASSERTIONS=2",
"-Clink-arg=-sDEMANGLE_SUPPORT=1",
# "-Clink-arg=-sEMULATE_FUNCTION_POINTER_CASTS",
# ---
"-Zlink-native-libraries=no",
] Test project (adapted from https://github.com/PgBiel/hello-gdext-wasm, but updated for compatibility with gdext 0.1): Compiled web export templates (with and without threads): godot.web.template_debug.wasm32.dlink.zip |
Update: Fix for Godot 4.3-beta2+ found!TL;DR: Change your [target.wasm32-unknown-emscripten]
rustflags = [
"-C",
"link-args=-sSIDE_MODULE=2",
"-C",
"link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Cllvm-args=-enable-emscripten-cxx-exceptions=0",
"-Zlink-native-libraries=no",
] That is, add This will work with the official Godot web export templates for 4.3-beta2 - recompiling Godot is not needed (at least, for this sample project I'm smoke-testing with). If the flag above doesn't fix it (it should), try adding the rustflags below as well (please ping me if you happen to need to use those flags): [target.wasm32-unknown-emscripten]
rustflags = [
# ... other flags ...
"-Clink-arg=-fwasm-exceptions",
"-C",
"link-args=-sSUPPORT_LONGJMP=wasm",
"-Cllvm-args=-enable-emscripten-cxx-exceptions=0",
"-Cllvm-args=-wasm-enable-sjlj",
"-C",
"link-args=-sDISABLE_EXCEPTION_CATCHING=1",
] Fun factAs a result, gdext is also running on Firefox (didn't work there before)! 🎉 What happened?Basically, since godotengine/godot#93143 (which was merged in time for Godot 4.3-beta2), Godot's web export templates are compiled with I tried to add that linker flag directly, but that didn't fix it. After some googling, I got here rust-lang/rust#112195 (comment) . Adding all of these flags as well fixed it! Eventually, after some testing, the only flag necessary turned out to be How did I get here?After doing some research regarding the Which suggested that invoke functions are only exported when I then remembered seeing Bromeon's comment above #438 (comment) , about Godot switching to With the testing above, this turned out to be the case! (With a few adjustments...) |
Regarding single-threaded wasm buildsGodot 4.3's web export template can be compiled with (Also, based on godotengine/godot-cpp#1451 , we should eventually warn gdext users that you should have separate threaded - with However, while the fix in the above comment works to remove the
Full logs
Seems like we could work around this somehow by avoiding calling this function, but perhaps there is some other flag missing which would make it work. Either way, non-threaded builds do not work at the moment because of this. |
One issue found by @Ughuuu (posting for awareness, but also as a reminder so we can make a fix later): the current workaround for emscripten support assumes that the wasm binary is named |
Note also some changes in flags like godotengine/godot-cpp#1566. We currently elaborate in the book how to compile WASM, but I wonder if there's a better way than copy-pasting a |
In principle it seems inevitable that we'll be playing a game of "cat and mouse" here, having to manually keep up with any new flags introduced by Godot, though it'd be nice if:
|
Getting a bizarre issue that I can't find a solution for. I've tried every single flag combination in this thread, but I keep getting "resolved is not a function". I've tested with both Firefox and Chromium on ArchLinux. emcc: It seems like something called Here's the relevant wasm code(?)
Here's my .cargo/config.toml[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2",
"-C", "link-args=-sASSERTIONS=2",
"-C", "link-arg=-fwasm-exceptions",
"-C", "link-args=-sSUPPORT_LONGJMP=wasm",
"-C", "llvm-args=-wasm-enable-sjlj",
# "-C", "link-args=-pthread", # Seems to cause issues with both chromium and firefox
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
"-C", "link-args=-sEXPORT_ALL=1",
"-C", "link-arg=-O0",
"-C", "link-arg=-g",
"-C", "link-arg=-sDEMANGLE_SUPPORT=1",
"-C", "llvm-args=-enable-emscripten-cxx-exceptions=0",
"-C", "link-args=-sDISABLE_EXCEPTION_CATCHING=1",
"-Zlink-native-libraries=no"
] |
Try disabling However I also got some different problems, most notably
which I couldn't resolve. There's some discussion around it on Discord. |
Figured out a solution for my problem, documenting it here for anyone else who encounters a similar problem. Following the book for setting up a project for web export, the After going through the 5 stages of grief over the course of 5 days, I decided to try everything I could think of, skipping past all of the attempts that didn't work, here's what did work:
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2",
"-C", "link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Z", "link-native-libraries=no",
"-Z", "link-native-libraries=no",
"-C", "link-arg=-fwasm-exceptions",
"-C", "link-args=-sSUPPORT_LONGJMP=wasm",
"-C", "llvm-args=-enable-emscripten-cxx-exceptions=0",
"-C", "llvm-args=-wasm-enable-sjlj",
"-C", "link-args=-sDISABLE_EXCEPTION_CATCHING=1",
] Now I can load my project in Firefox and Chromium. I don't know what specifically ended up fixing my problems, but everything works for me now. |
Thanks a lot! What is really not great at the moment is that the For example, gdext has two features: I'm not sure what's the best way to keep library options in sync with |
Maybe a hacky solution could be to have a macro or function that is called from a user created For now though the docs should be updated to indicate the |
thank you! I was having a whole raft of issue up to this point. Checking the "Thread Support" checkbox in Godot and using your cargo.toml make everything work all of a sudden! |
Thanks a lot, i was having the same issue
and your solution worked for me , using emcc 3.1.39 and Godot 4.3.stable |
Just to clarify, you don't have to enable Thread support if you remove the |
I used @Rune580 's
That worked for me, except my sound effects and background music did not work. I don't know which of the remaining flags might've been required to get sound to work. |
@scroggo 's base project works with just a few modifications. I was able to run it on Firefox 133 and Chromium 131. A more complex project might reveal more problems, but this setup worked first try with sound on my end: scroggo/squash-the-creeps#1 You must click the game viewport once for sound to start playing. Removing
|
Thank you for the investigation @milesdesiran! @scroggo can you confirm that the sound works when you click the viewport, as suggested above, regardless of emcc flags? |
I'm confused. I already had sound working, but that had required scroggo/squash-the-creeps@6264445. That commit adds
to @milesdesiran's commit adds the very similar line
in addition to the Do those two lines result in a meaningful difference? Regardless, I think what we really want to test is:
I tried @milesdesiran's commit in the editor1 with I also tried the approach described in this comment, and it worked with I'm also not sure why I don't need a custom template, but maybe that's a separate discussion 🤷 Footnotes
|
You are absolutely correct that I added a duplicate flag - I'm sorry, I wasn't paying close enough attention, haha. I see the Examining the diff now, I don't think I really did anything, haha If @scroggo wants to add the book documentation like in godot-rust/book#56 , that would be helpful - that PR has similar changes, but has been left unattended by the creator for a few months. |
This issue serves as a knowledge base for approching WASM builds. Ideally it should have more consolidated pieces of information. Please edit your responses to update anything outdated.
For free-form discussion, check out the Discord thread.
The text was updated successfully, but these errors were encountered: