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>
This commit is contained in:
Morten Sørvig 2021-05-21 11:14:34 +02:00 committed by Morten Johan Sørvig
parent a97445274a
commit 7ee4468a18
3 changed files with 52 additions and 25 deletions

View File

@ -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

View File

@ -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.

View File

@ -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");