Browse Source

[Web] Small fixes and enhancements.

- "Definitive" fix for ENOENT randomly disappearing from emscripten.
- Proper shutdown when setup fails.
- Re-enable WebGL explicit buffer swap.
- Re-enable optional per-pixel transparency.
- Add type cast to make closure compiler happy.
- Remove emscripten Safari WebGL workaround.
- Improve AudioWorklet cleanup.
Fabio Alessandrelli 2 years ago
parent
commit
27f22b29f8

+ 4 - 0
platform/web/detect.py

@@ -227,3 +227,7 @@ def configure(env):
 
 
     # 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"])
+
+    # This workaround creates a closure that prevents the garbage collector from freeing the WebGL context.
+    # We also only use WebGL2, and changing context version is not widely supported anyway.
+    env.Append(LINKFLAGS=["-s", "GL_WORKAROUND_SAFARI_GETCONTEXT_BUG=0"])

+ 2 - 2
platform/web/display_server_web.cpp

@@ -764,10 +764,10 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
 	if (wants_webgl2 && !webgl2_init_failed) {
 	if (wants_webgl2 && !webgl2_init_failed) {
 		EmscriptenWebGLContextAttributes attributes;
 		EmscriptenWebGLContextAttributes attributes;
 		emscripten_webgl_init_context_attributes(&attributes);
 		emscripten_webgl_init_context_attributes(&attributes);
-		//attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
-		attributes.alpha = true;
+		attributes.alpha = OS::get_singleton()->is_layered_allowed();
 		attributes.antialias = false;
 		attributes.antialias = false;
 		attributes.majorVersion = 2;
 		attributes.majorVersion = 2;
+		attributes.explicitSwapControl = true;
 
 
 		webgl_ctx = emscripten_webgl_create_context(canvas_id, &attributes);
 		webgl_ctx = emscripten_webgl_create_context(canvas_id, &attributes);
 		if (emscripten_webgl_make_context_current(webgl_ctx) != EMSCRIPTEN_RESULT_SUCCESS) {
 		if (emscripten_webgl_make_context_current(webgl_ctx) != EMSCRIPTEN_RESULT_SUCCESS) {

+ 2 - 1
platform/web/js/engine/config.js

@@ -317,7 +317,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
 		if (!(this.canvas instanceof HTMLCanvasElement)) {
 		if (!(this.canvas instanceof HTMLCanvasElement)) {
 			const nodes = document.getElementsByTagName('canvas');
 			const nodes = document.getElementsByTagName('canvas');
 			if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
 			if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
-				this.canvas = nodes[0];
+				const first = nodes[0];
+				this.canvas = /** @type {!HTMLCanvasElement} */ (first);
 			}
 			}
 			if (!this.canvas) {
 			if (!this.canvas) {
 				throw new Error('No canvas found in page');
 				throw new Error('No canvas found in page');

+ 2 - 0
platform/web/js/libs/audio.worklet.js

@@ -133,6 +133,8 @@ class GodotProcessor extends AudioWorkletProcessor {
 			this.running = false;
 			this.running = false;
 			this.output = null;
 			this.output = null;
 			this.input = null;
 			this.input = null;
+			this.lock = null;
+			this.notifier = null;
 		} else if (p_cmd === 'start_nothreads') {
 		} else if (p_cmd === 'start_nothreads') {
 			this.output = new RingBuffer(p_data[0], p_data[0].length, false);
 			this.output = new RingBuffer(p_data[0], p_data[0].length, false);
 		} else if (p_cmd === 'chunk') {
 		} else if (p_cmd === 'chunk') {

+ 7 - 2
platform/web/js/libs/library_godot_audio.js

@@ -339,16 +339,21 @@ const GodotAudioWorklet = {
 				if (GodotAudioWorklet.promise === null) {
 				if (GodotAudioWorklet.promise === null) {
 					return;
 					return;
 				}
 				}
-				GodotAudioWorklet.promise.then(function () {
+				const p = GodotAudioWorklet.promise;
+				p.then(function () {
 					GodotAudioWorklet.worklet.port.postMessage({
 					GodotAudioWorklet.worklet.port.postMessage({
 						'cmd': 'stop',
 						'cmd': 'stop',
 						'data': null,
 						'data': null,
 					});
 					});
 					GodotAudioWorklet.worklet.disconnect();
 					GodotAudioWorklet.worklet.disconnect();
+					GodotAudioWorklet.worklet.port.onmessage = null;
 					GodotAudioWorklet.worklet = null;
 					GodotAudioWorklet.worklet = null;
 					GodotAudioWorklet.promise = null;
 					GodotAudioWorklet.promise = null;
 					resolve();
 					resolve();
-				}).catch(function (err) { /* aborted? */ });
+				}).catch(function (err) {
+					// Aborted?
+					GodotRuntime.error(err);
+				});
 			});
 			});
 		},
 		},
 	},
 	},

+ 9 - 5
platform/web/js/libs/library_godot_os.js

@@ -106,12 +106,14 @@ autoAddDeps(GodotConfig, '$GodotConfig');
 mergeInto(LibraryManager.library, GodotConfig);
 mergeInto(LibraryManager.library, GodotConfig);
 
 
 const GodotFS = {
 const GodotFS = {
-	$GodotFS__deps: ['$ERRNO_CODES', '$FS', '$IDBFS', '$GodotRuntime'],
+	$GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'],
 	$GodotFS__postset: [
 	$GodotFS__postset: [
 		'Module["initFS"] = GodotFS.init;',
 		'Module["initFS"] = GodotFS.init;',
 		'Module["copyToFS"] = GodotFS.copy_to_fs;',
 		'Module["copyToFS"] = GodotFS.copy_to_fs;',
 	].join(''),
 	].join(''),
 	$GodotFS: {
 	$GodotFS: {
+		// ERRNO_CODES works every odd version of emscripten, but this will break too eventually.
+		ENOENT: 44,
 		_idbfs: false,
 		_idbfs: false,
 		_syncing: false,
 		_syncing: false,
 		_mount_points: [],
 		_mount_points: [],
@@ -138,8 +140,9 @@ const GodotFS = {
 				try {
 				try {
 					FS.stat(dir);
 					FS.stat(dir);
 				} catch (e) {
 				} catch (e) {
-					if (e.errno !== ERRNO_CODES.ENOENT) {
-						throw e;
+					if (e.errno !== GodotFS.ENOENT) {
+						// Let mkdirTree throw in case, we cannot trust the above check.
+						GodotRuntime.error(e);
 					}
 					}
 					FS.mkdirTree(dir);
 					FS.mkdirTree(dir);
 				}
 				}
@@ -208,8 +211,9 @@ const GodotFS = {
 			try {
 			try {
 				FS.stat(dir);
 				FS.stat(dir);
 			} catch (e) {
 			} catch (e) {
-				if (e.errno !== ERRNO_CODES.ENOENT) {
-					throw e;
+				if (e.errno !== GodotFS.ENOENT) {
+					// Let mkdirTree throw in case, we cannot trust the above check.
+					GodotRuntime.error(e);
 				}
 				}
 				FS.mkdirTree(dir);
 				FS.mkdirTree(dir);
 			}
 			}

+ 25 - 1
platform/web/web_main.cpp

@@ -55,6 +55,18 @@ void cleanup_after_sync() {
 	emscripten_set_main_loop(exit_callback, -1, false);
 	emscripten_set_main_loop(exit_callback, -1, false);
 }
 }
 
 
+void early_cleanup() {
+	emscripten_cancel_main_loop(); // After this, we can exit!
+	int exit_code = OS_Web::get_singleton()->get_exit_code();
+	memdelete(os);
+	os = nullptr;
+	emscripten_force_exit(exit_code); // No matter that we call cancel_main_loop, regular "exit" will not work, forcing.
+}
+
+void early_cleanup_sync() {
+	emscripten_set_main_loop(early_cleanup, -1, false);
+}
+
 void main_loop_callback() {
 void main_loop_callback() {
 	uint64_t current_ticks = os->get_ticks_usec();
 	uint64_t current_ticks = os->get_ticks_usec();
 
 
@@ -87,7 +99,19 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
 	// We must override main when testing is enabled
 	// We must override main when testing is enabled
 	TEST_MAIN_OVERRIDE
 	TEST_MAIN_OVERRIDE
 
 
-	Main::setup(argv[0], argc - 1, &argv[1]);
+	Error err = Main::setup(argv[0], argc - 1, &argv[1]);
+
+	// Proper shutdown in case of setup failure.
+	if (err != OK) {
+		int exit_code = (int)err;
+		if (err == ERR_HELP) {
+			exit_code = 0; // Called with --help.
+		}
+		os->set_exit_code(exit_code);
+		// Will only exit after sync.
+		godot_js_os_finish_async(early_cleanup_sync);
+		return exit_code;
+	}
 
 
 	// Ease up compatibility.
 	// Ease up compatibility.
 	ResourceLoader::set_abort_on_missing_resources(false);
 	ResourceLoader::set_abort_on_missing_resources(false);