[HTML5] GDNative support via SIDE_MODULE.

Working, with emscripten > 2.0.9
Yes, the unreleased version. 2.0.9 works, but throws and error due to a
bug in emscripten with the thirdparty ENet library.
The issue is fixed upstream so newer releases will work.
This commit is contained in:
Fabio Alessandrelli 2020-10-24 16:02:09 +02:00
parent 8ebb52fad6
commit 4d1ebaad0f
8 changed files with 109 additions and 33 deletions

View file

@ -333,11 +333,11 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
platform_android.library_extension = "*.so"; platform_android.library_extension = "*.so";
platforms["Android"] = platform_android; platforms["Android"] = platform_android;
// TODO: Javascript platform is not supported yet NativePlatformConfig platform_html5;
// NativePlatformConfig platform_html5; platform_html5.name = "HTML5";
// platform_html5.name = "HTML5"; platform_html5.entries.push_back("web");
// platform_html5.library_extension = "*.wasm"; platform_html5.library_extension = "*.wasm";
// platforms["Javascript"] = platform_html5; platforms["HTML5"] = platform_html5;
NativePlatformConfig platform_ios; NativePlatformConfig platform_ios;
platform_ios.name = "iOS"; platform_ios.name = "iOS";

View file

@ -11,13 +11,8 @@ javascript_files = [
"api/javascript_tools_editor_plugin.cpp", "api/javascript_tools_editor_plugin.cpp",
] ]
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] sys_env = env.Clone()
if env["threads_enabled"]: sys_env.AddJSLibraries(
build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
build = env.add_program(build_targets, javascript_files)
env.AddJSLibraries(
[ [
"js/libs/library_godot_audio.js", "js/libs/library_godot_audio.js",
"js/libs/library_godot_display.js", "js/libs/library_godot_display.js",
@ -28,12 +23,47 @@ env.AddJSLibraries(
) )
if env["tools"]: if env["tools"]:
env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"])
if env["javascript_eval"]: if env["javascript_eval"]:
env.AddJSLibraries(["js/libs/library_godot_eval.js"]) sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"])
for lib in env["JS_LIBS"]: for lib in sys_env["JS_LIBS"]:
env.Append(LINKFLAGS=["--js-library", lib]) sys_env.Append(LINKFLAGS=["--js-library", lib])
env.Depends(build, env["JS_LIBS"])
build = []
if env["gdnative_enabled"]:
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
# Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
sys_env["LIBS"] = []
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
# JS prepended to the module code loading the side library.
sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")])
# Configure it as a main module (dynamic linking support).
sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"])
sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"])
sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"])
sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"])
# Force exporting the standard library (printf, malloc, etc.)
sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi"
# The main emscripten runtime, with exported standard libraries.
sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"])
sys_env.Depends(sys, "js/dynlink.pre.js")
# The side library, containing all Godot code.
wasm_env = env.Clone()
wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"])
wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"])
wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", javascript_files)
build = [sys[0], sys[1], wasm[0]]
else:
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
if env["threads_enabled"]:
build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"])
sys_env.Depends(build[0], sys_env["JS_LIBS"])
engine = [ engine = [
"js/engine/preloader.js", "js/engine/preloader.js",
@ -60,8 +90,11 @@ out_files = [
] ]
html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html"
in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"]
if env["threads_enabled"]: if env["gdnative_enabled"]:
in_files.append(build[2]) in_files.append(build[2]) # Runtime
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
elif env["threads_enabled"]:
in_files.append(build[2]) # Worker
out_files.append(zip_dir.File(binary_name + ".worker.js")) out_files.append(zip_dir.File(binary_name + ".worker.js"))
zip_files = env.InstallAs(out_files, in_files) zip_files = env.InstallAs(out_files, in_files)

View file

@ -23,6 +23,7 @@ def get_opts():
# eval() can be a security concern, so it can be disabled. # eval() can be a security concern, so it can be disabled.
BoolVariable("javascript_eval", "Enable JavaScript eval interface", True), BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False), BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False),
BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False),
BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
] ]
@ -83,10 +84,8 @@ def configure(env):
# LTO # LTO
if env["use_lto"]: if env["use_lto"]:
env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"]) env.Append(CCFLAGS=["-flto=full"])
env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"]) env.Append(LINKFLAGS=["-flto=full"])
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
# Closure compiler # Closure compiler
if env["use_closure_compiler"]: if env["use_closure_compiler"]:
@ -133,6 +132,9 @@ def configure(env):
if env["javascript_eval"]: if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
if env["threads_enabled"] and env["gdnative_enabled"]:
raise Exception("Threads and GDNative support can't be both enabled due to WebAssembly limitations")
# Thread support (via SharedArrayBuffer). # Thread support (via SharedArrayBuffer).
if env["threads_enabled"]: if env["threads_enabled"]:
env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
@ -144,14 +146,15 @@ def configure(env):
else: else:
env.Append(CPPDEFINES=["NO_THREADS"]) env.Append(CPPDEFINES=["NO_THREADS"])
if env["gdnative_enabled"]:
env.Append(CCFLAGS=["-s", "RELOCATABLE=1"])
env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"])
env.extra_suffix = ".gdnative" + env.extra_suffix
# Reduce code size by generating less support code (e.g. skip NodeJS support). # Reduce code size by generating less support code (e.g. skip NodeJS support).
env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
# We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to # Wrap the JavaScript support code around a closure named Godot.
# be linked explicitly.
env.Append(LIBS=["idbfs.js"])
env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"]) env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
# Allow increasing memory buffer size during runtime. This is efficient # Allow increasing memory buffer size during runtime. This is efficient
@ -162,12 +165,14 @@ def configure(env):
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"]) env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
# Do not call main immediately when the support code is ready.
env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"]) env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"])
# Allow use to take control of swapping WebGL buffers. # Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
# callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS. # callMain for manual start.
env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"])
# Add code that allow exiting runtime. # Add code that allow exiting runtime.
env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])

View file

@ -74,7 +74,7 @@ void main_loop_callback() {
} }
} }
int main(int argc, char *argv[]) { extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
// Set locale // Set locale
char locale_ptr[16]; char locale_ptr[16];
godot_js_config_locale_get(locale_ptr, sizeof(locale_ptr)); godot_js_config_locale_get(locale_ptr, sizeof(locale_ptr));

View file

@ -0,0 +1,35 @@
/*************************************************************************/
/* javascript_runtime.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
extern int godot_js_main(int argc, char *argv[]);
int main(int argc, char *argv[]) {
return godot_js_main(argc, argv);
}

View file

@ -0,0 +1 @@
Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'];

View file

@ -58,6 +58,9 @@ const Engine = (function () {
initPromise = new Promise(function (resolve, reject) { initPromise = new Promise(function (resolve, reject) {
config['locateFile'] = Utils.createLocateRewrite(loadPath); config['locateFile'] = Utils.createLocateRewrite(loadPath);
config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
// Emscripten configuration.
config['thisProgram'] = me.executableName;
config['noExitRuntime'] = true;
Godot(config).then(function (module) { Godot(config).then(function (module) {
module['initFS'](me.persistentPaths).then(function (fs_err) { module['initFS'](me.persistentPaths).then(function (fs_err) {
me.rtenv = module; me.rtenv = module;
@ -119,9 +122,6 @@ const Engine = (function () {
locale = navigator.languages ? navigator.languages[0] : navigator.language; locale = navigator.languages ? navigator.languages[0] : navigator.language;
locale = locale.split('.')[0]; locale = locale.split('.')[0];
} }
// Emscripten configuration.
me.rtenv['thisProgram'] = me.executableName;
me.rtenv['noExitRuntime'] = true;
// Godot configuration. // Godot configuration.
me.rtenv['initConfig']({ me.rtenv['initConfig']({
'resizeCanvasOnStart': me.resizeCanvasOnStart, 'resizeCanvasOnStart': me.resizeCanvasOnStart,

View file

@ -8,6 +8,8 @@ const Utils = { // eslint-disable-line no-unused-vars
return `${execName}.audio.worklet.js`; return `${execName}.audio.worklet.js`;
} else if (path.endsWith('.js')) { } else if (path.endsWith('.js')) {
return `${execName}.js`; return `${execName}.js`;
} else if (path.endsWith('.side.wasm')) {
return `${execName}.side.wasm`;
} else if (path.endsWith('.wasm')) { } else if (path.endsWith('.wasm')) {
return `${execName}.wasm`; return `${execName}.wasm`;
} }