-
-
Notifications
You must be signed in to change notification settings - Fork 185
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. |
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: