Browse Source

[HTML5] GDNative support via SIDE_MODULE.

Working with emscripten >= 2.0.10
Fabio Alessandrelli 4 years ago
parent
commit
ca34b5e57a

+ 5 - 5
modules/gdnative/gdnative_library_editor_plugin.cpp

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

+ 47 - 14
platform/javascript/SCsub

@@ -12,13 +12,8 @@ javascript_files = [
     "api/javascript_tools_editor_plugin.cpp",
 ]
 
-build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
-if env["threads_enabled"]:
-    build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
-
-build = env.add_program(build_targets, javascript_files)
-
-env.AddJSLibraries(
+sys_env = env.Clone()
+sys_env.AddJSLibraries(
     [
         "js/libs/library_godot_audio.js",
         "js/libs/library_godot_display.js",
@@ -29,12 +24,47 @@ env.AddJSLibraries(
 )
 
 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"]:
-    env.AddJSLibraries(["js/libs/library_godot_eval.js"])
-for lib in env["JS_LIBS"]:
-    env.Append(LINKFLAGS=["--js-library", lib])
-env.Depends(build, env["JS_LIBS"])
+    sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"])
+for lib in sys_env["JS_LIBS"]:
+    sys_env.Append(LINKFLAGS=["--js-library", lib])
+
+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 = [
     "js/engine/preloader.js",
@@ -61,8 +91,11 @@ out_files = [
 ]
 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"]
-if env["threads_enabled"]:
-    in_files.append(build[2])
+if env["gdnative_enabled"]:
+    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"))
 
 zip_files = env.InstallAs(out_files, in_files)

+ 15 - 10
platform/javascript/detect.py

@@ -23,6 +23,7 @@ def get_opts():
         # eval() can be a security concern, so it can be disabled.
         BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
         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),
     ]
 
@@ -85,10 +86,8 @@ def configure(env):
 
     # LTO
     if env["use_lto"]:
-        env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"])
-        env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"])
-        env.Append(CCFLAGS=["-flto"])
-        env.Append(LINKFLAGS=["-flto"])
+        env.Append(CCFLAGS=["-flto=full"])
+        env.Append(LINKFLAGS=["-flto=full"])
 
     # Closure compiler
     if env["use_closure_compiler"]:
@@ -135,6 +134,9 @@ def configure(env):
     if env["javascript_eval"]:
         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).
     if env["threads_enabled"]:
         env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
@@ -146,14 +148,15 @@ def configure(env):
     else:
         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).
     env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
 
-    # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to
-    # be linked explicitly.
-    env.Append(LIBS=["idbfs.js"])
-
-    env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
+    # Wrap the JavaScript support code around a closure named Godot.
     env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
 
     # Allow increasing memory buffer size during runtime. This is efficient
@@ -164,12 +167,14 @@ def configure(env):
     # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 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"])
 
     # Allow use to take control of swapping WebGL buffers.
     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']"])
+
     # Add code that allow exiting runtime.
     env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])

+ 1 - 1
platform/javascript/javascript_main.cpp

@@ -75,7 +75,7 @@ void main_loop_callback() {
 }
 
 /// When calling main, it is assumed FS is setup and synced.
-int main(int argc, char *argv[]) {
+extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
 	os = new OS_JavaScript();
 
 	// We must override main when testing is enabled

+ 35 - 0
platform/javascript/javascript_runtime.cpp

@@ -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);
+}

+ 1 - 0
platform/javascript/js/dynlink.pre.js

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

+ 3 - 3
platform/javascript/js/engine/engine.js

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

+ 2 - 0
platform/javascript/js/engine/utils.js

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