summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2021-05-21 11:14:34 +0200
committerMorten Johan Sørvig <morten.sorvig@qt.io>2021-06-22 11:11:20 +0000
commit7ee4468a18f18dd2269fc422220fea4dc77017c7 (patch)
tree776251da15851e28841a3c53262e974c898d84cf
parenta97445274ab94e38f457a0d874ec64aa0da60846 (diff)
wasm: enable MODULARIZE option and set EXPORT_NAME
Make Emscripten generate a global constructor function ("createQtAppInstance()") instead of a global javascript module object. This enables more fine-grained control over module instantiation; previously the module object would be created when the runtime javascript was evaluated, and the number of emscripten module/instances was limited to one per page. Set EXPORT_NAME to “createQtAppInstance” which avoids collisions with other non-Qt Emscripten modules on the same page. A further improvement would be to include the app name in EXPORT_NAME, but this is not done at this time. Update the code in qtloader.js to call the constructor function instead of working on a global module object. The qtloader.js API is functional before the wasm and Emscripten modules have been instantiated; store properties and forward to the Emscripten module when it's created. Change-Id: I12c49a5b9a4a932bbc46fcc5e5ecc453fd0fe7f0 Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
-rw-r--r--cmake/QtWasmHelpers.cmake10
-rw-r--r--mkspecs/wasm-emscripten/qmake.conf4
-rw-r--r--src/plugins/platforms/wasm/qtloader.js63
3 files changed, 52 insertions, 25 deletions
diff --git a/cmake/QtWasmHelpers.cmake b/cmake/QtWasmHelpers.cmake
index bc5e455fcd..ecea701042 100644
--- a/cmake/QtWasmHelpers.cmake
+++ b/cmake/QtWasmHelpers.cmake
@@ -7,6 +7,16 @@ function (qt_internal_setup_wasm_target_properties wasmTarget)
"SHELL:-s USE_WEBGL2=1"
"--bind"
"SHELL:-s FETCH=1")
+
+ # Enable MODULARIZE and set EXPORT_NAME, which makes it possible to
+ # create application instances using a global constructor function,
+ # e.g. let app_instance = await createQtAppInstance().
+ # (as opposed to MODULARIZE=0, where Emscripten creates a global app
+ # instance object at Javascript eval time)
+ target_link_options("${wasmTarget}" INTERFACE
+ "SHELL:-s MODULARIZE=1"
+ "SHELL:-s EXPORT_NAME=createQtAppInstance")
+
target_compile_options("${wasmTarget}" INTERFACE --bind)
# Hardcode wasm memory size. Emscripten does not currently support memory growth
diff --git a/mkspecs/wasm-emscripten/qmake.conf b/mkspecs/wasm-emscripten/qmake.conf
index ad66ff3982..cc3bc8064e 100644
--- a/mkspecs/wasm-emscripten/qmake.conf
+++ b/mkspecs/wasm-emscripten/qmake.conf
@@ -41,7 +41,9 @@ EMCC_COMMON_LFLAGS += \
-s ERROR_ON_UNDEFINED_SYMBOLS=1 \
-s EXTRA_EXPORTED_RUNTIME_METHODS=[\"UTF16ToString\",\"stringToUTF16\"] \
--bind \
- -s FETCH=1
+ -s FETCH=1 \
+ -s MODULARIZE=1 \
+ -s EXPORT_NAME=createQtAppInstance
# The -s arguments can also be used with release builds,
# but are here in debug for clarity.
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index b159cd63ab..14a22fc0f1 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -128,10 +128,18 @@
// Sets the logical font dpi for the application.
-var Module = {}
-
function QtLoader(config)
{
+ // The Emscripten module and module configuration object. The module
+ // object is created in completeLoadEmscriptenModule().
+ self.module = undefined;
+ self.moduleConfig = {};
+
+ // Qt properties. These are propagated to the Emscripten module after
+ // it has been created.
+ self.qtCanvasElements = undefined;
+ self.qtFontDpi = 96;
+
function webAssemblySupported() {
return typeof WebAssembly !== "undefined"
}
@@ -341,7 +349,7 @@ function QtLoader(config)
// The wasm binary has been compiled into a module during resource download,
// and is ready to be instantiated. Define the instantiateWasm callback which
// emscripten will call to create the instance.
- Module.instantiateWasm = function(imports, successCallback) {
+ self.moduleConfig.instantiateWasm = function(imports, successCallback) {
WebAssembly.instantiate(wasmModule, imports).then(function(instance) {
successCallback(instance, wasmModule);
}, function(error) {
@@ -351,27 +359,27 @@ function QtLoader(config)
return {};
};
- Module.locateFile = Module.locateFile || function(filename) {
+ self.moduleConfig.locateFile = self.moduleConfig.locateFile || function(filename) {
return config.path + filename;
};
// Attach status callbacks
- Module.setStatus = Module.setStatus || function(text) {
+ self.moduleConfig.setStatus = self.moduleConfig.setStatus || function(text) {
// Currently the only usable status update from this function
// is "Running..."
if (text.startsWith("Running"))
setStatus("Running");
};
- Module.monitorRunDependencies = Module.monitorRunDependencies || function(left) {
+ self.moduleConfig.monitorRunDependencies = self.moduleConfig.monitorRunDependencies || function(left) {
// console.log("monitorRunDependencies " + left)
};
// Attach standard out/err callbacks.
- Module.print = Module.print || function(text) {
+ self.moduleConfig.print = self.moduleConfig.print || function(text) {
if (config.stdoutEnabled)
console.log(text)
};
- Module.printErr = Module.printErr || function(text) {
+ self.moduleConfig.printErr = self.moduleConfig.printErr || function(text) {
// Filter out OpenGL getProcAddress warnings. Qt tries to resolve
// all possible function/extension names at startup which causes
// emscripten to spam the console log with warnings.
@@ -387,12 +395,12 @@ function QtLoader(config)
// Emscripten will typically call printErr with the error text
// as well. Note that emscripten may also throw exceptions from
// async callbacks. These should be handled in window.onerror by user code.
- Module.onAbort = Module.onAbort || function(text) {
+ self.moduleConfig.onAbort = self.moduleConfig.onAbort || function(text) {
publicAPI.crashed = true;
publicAPI.exitText = text;
setStatus("Exited");
};
- Module.quit = Module.quit || function(code, exception) {
+ self.moduleConfig.quit = self.moduleConfig.quit || function(code, exception) {
if (exception.name == "ExitStatus") {
// Clean exit with code
publicAPI.exitText = undefined
@@ -404,17 +412,20 @@ function QtLoader(config)
setStatus("Exited");
};
- // Set environment variables
- Module.preRun = Module.preRun || []
- Module.preRun.push(function() {
+ self.moduleConfig.preRun = self.moduleConfig.preRun || []
+ self.moduleConfig.preRun.push(function(module) {
+ // Set environment variables
for (var [key, value] of Object.entries(config.environment)) {
ENV[key.toUpperCase()] = value;
}
+ // Propagate Qt module properties
+ module.qtCanvasElements = self.qtCanvasElements;
+ module.qtFontDpi = self.qtFontDpi;
});
- Module.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'});
+ self.moduleConfig.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'});
- Module.qtCanvasElements = config.canvasElements;
+ self.qtCanvasElements = config.canvasElements;
config.restart = function() {
@@ -438,9 +449,13 @@ function QtLoader(config)
publicAPI.exitText = undefined;
publicAPI.crashed = false;
- // Finally evaluate the emscripten application script, which will
- // reference the global Module object created above.
- self.eval(emscriptenModuleSource); // ES5 indirect global scope eval
+ // Load the Emscripten application module. This is done by eval()'ing the
+ // javascript runtime generated by Emscripten, and then calling
+ // createQtAppInstance(), which was added to the global scope.
+ eval(emscriptenModuleSource);
+ createQtAppInstance(self.moduleConfig).then(function(module) {
+ self.module = module;
+ });
}
function setErrorContent() {
@@ -544,31 +559,31 @@ function QtLoader(config)
function addCanvasElement(element) {
if (publicAPI.status == "Running")
- Module.qtAddCanvasElement(element);
+ self.module.qtAddCanvasElement(element);
else
console.log("Error: addCanvasElement can only be called in the Running state");
}
function removeCanvasElement(element) {
if (publicAPI.status == "Running")
- Module.qtRemoveCanvasElement(element);
+ self.module.qtRemoveCanvasElement(element);
else
console.log("Error: removeCanvasElement can only be called in the Running state");
}
function resizeCanvasElement(element) {
if (publicAPI.status == "Running")
- Module.qtResizeCanvasElement(element);
+ self.module.qtResizeCanvasElement(element);
}
function setFontDpi(dpi) {
- Module.qtFontDpi = dpi;
+ self.qtFontDpi = dpi;
if (publicAPI.status == "Running")
- Module.qtSetFontDpi(dpi);
+ self.qtSetFontDpi(dpi);
}
function fontDpi() {
- return Module.qtFontDpi;
+ return self.qtFontDpi;
}
setStatus("Created");