emscripten: modularize js/wasm, split from html shell

This commit is contained in:
Andrei Alexeyev 2024-10-23 18:04:47 +02:00
parent 164a9fbd05
commit 2a17e82f9d
No known key found for this signature in database
GPG key ID: 72D26128040B9690
5 changed files with 135 additions and 141 deletions

View file

@ -1,5 +1,6 @@
em_preamble = files('preamble.js')
em_postamble = files('postamble.js')
em_shell = files('shell.html')
if host_machine.system() == 'emscripten'
@ -12,4 +13,10 @@ if host_machine.system() == 'emscripten'
install_dir : bindir,
install_tag : 'runtime',
)
install_data(
em_shell,
rename : 'taisei.html',
install_dir : bindir,
install_tag : 'runtime',
)
endif

View file

@ -1,118 +1,26 @@
function E(id) { return document.getElementById(id); }
Module['totalDependencies'] = 0;
Module['monitorRunDependencies'] = function(left) {
Module['totalDependencies'] = Math.max(Module['totalDependencies'], left);
Module['setStatus'](
left ? 'Preparing… (' + (Module['totalDependencies']-left) + '/' + Module['totalDependencies'] + ')'
: 'All downloads complete.');
};
var statusElement = E('status');
var progressElement = E('progress');
var spinnerElement = E('spinner');
var canvasElement = E('canvas');
var canvasContainerElement = E('canvasContainer');
var logToggleElement = E('logToggle');
var logToggleContainerElement = E('logToggleContainer');
var logContainerElement = E('logContainer');
var logOutputElement = E('output');
var dlMessage = statusElement.innerText;
logToggleElement.checked = false;
window['toggleLog'] = function toggleLog() {
logContainerElement.hidden = !logToggleElement.checked;
logOutputElement.scrollTop = logOutputElement.scrollHeight;
}
var glContext = canvasElement.getContext('webgl2', {
'alpha' : false,
'antialias' : false,
'depth' : false,
'powerPreference' : 'high-performance',
'premultipliedAlpha' : true,
'preserveDrawingBuffer' : false,
'stencil' : false,
});
if(!glContext) {
throw "Could not create a WebGL 2 context";
}
// glContext = WebGLDebugUtils.makeDebugContext(glContext);
Module['initFilesystem'] = function() {
FS.mkdir('/persistent');
FS.mount(IDBFS, {}, '/persistent');
};
// WebGL extensions have to be explicitly enabled to make the functionality available.
// Note that Emscripten will always report all *supported* extensions in GL_EXTENSIONS,
// regardless of whether they are enabled or not. This is non-conformant from the GLES
// perspective. The easiest way to fix that is to enable all of them here.
var glContext = Module['preinitializedWebGLContext'];
glContext.getSupportedExtensions().forEach(function(ext) {
glContext.getExtension(ext);
});
canvasElement.addEventListener("webglcontextlost", function(e) {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
logOutputElement.value = ''; // clear browser cache
Module = {
'preRun': [function() {
ENV["TAISEI_NOASYNC"] = "1";
ENV["TAISEI_NOUNLOAD"] = "1";
ENV["TAISEI_PREFER_SDL_VIDEODRIVERS"] = "emscripten";
ENV["TAISEI_RENDERER"] = "gles30";
FS.mkdir('/persistent');
FS.mount(IDBFS, {}, '/persistent');
}],
'postRun': [],
'onFirstFrame': function() {
canvasContainerElement.hidden = false;
logToggleContainerElement.style.display = "inline-block";
Module['setStatus']('', true);
},
'print': function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
logOutputElement.value += text + "\n";
logOutputElement.scrollTop = logOutputElement.scrollHeight; // focus on bottom
},
'printErr': function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
'canvas': canvasElement,
'preinitializedWebGLContext': glContext,
'setStatus': function(text, force) {
var ss = Module['setStatus'];
if (!text && !force) return;
if (!ss.last) ss.last = { time: Date.now(), text: '' };
if (text === ss.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - ss.last.time < 30) return; // if this is a progress update, skip it if too soon
ss.last.time = now;
ss.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = !canvasElement.hidden;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerText = text.replace(/^Downloading(?: data)?\.\.\./, dlMessage).replace('...', '…');
console.log("[STATUS] " + statusElement.innerText);
},
'totalDependencies': 0,
'monitorRunDependencies': function(left) {
Module['totalDependencies'] = Math.max(Module['totalDependencies'], left);
Module['setStatus'](left ? 'Preparing… (' + (Module['totalDependencies']-left) + '/' + Module['totalDependencies'] + ')' : 'All downloads complete.');
}
};
window.onerror = function(error) {
Module['setStatus']('Error: ' + error);
};
function SyncFS(is_load, ccptr) {
FS.syncfs(is_load, function(err) {
Module['ccall'](
@ -124,35 +32,8 @@ function SyncFS(is_load, ccptr) {
});
}
(function() {
// Try to enable audio playback as soon as possible.
// It must happen inside an input event handler.
// https://github.com/emscripten-core/emscripten/issues/6511
// https://github.com/emscripten-ports/SDL2/issues/57
function resumeAudio() {
var sdl2 = Module['SDL2'];
if(typeof sdl2 === 'undefined') {
return;
}
if(sdl2.audioContext.state == 'suspended') {
sdl2.audioContext.resume();
}
if(sdl2.audioContext.state == 'running') {
canvasElement.removeEventListener('click', resumeAudio);
document.removeEventListener('keydown', resumeAudio);
}
}
canvasElement.addEventListener('click', resumeAudio);
document.addEventListener('keydown', resumeAudio);
})();
if(typeof dynCall === 'undefined') {
dynCall = window['dynCall'] = Module['dynCall'] = function dynCall(sig, ptr, args) {
dynCall = Module['dynCall'] = function dynCall(sig, ptr, args) {
return wasmTable.get(ptr).apply(this, args);
}
};

View file

@ -5,6 +5,7 @@
<link rel="icon" type="image/x-icon" href="favicon.ico"/>
<link rel="preload" href="taisei.wasm" as="fetch" crossorigin/>
<title>Taisei Project — Web version (Experimental!)</title>
<script id="taisei-script" src="taisei.js" async></script>
<style>
body {
background-image: url('background.webp');
@ -211,6 +212,108 @@
Powered by&nbsp;<a href="https://emscripten.org/">Emscripten</a>
</div>
<!--<script type="text/javascript" src="webgl-debug.js"></script>-->
{{{ SCRIPT }}}
<script type="application/javascript">
function E(id) { return document.getElementById(id); }
var statusElement = E('status');
var progressElement = E('progress');
var spinnerElement = E('spinner');
var canvasElement = E('canvas');
var canvasContainerElement = E('canvasContainer');
var logToggleElement = E('logToggle');
var logToggleContainerElement = E('logToggleContainer');
var logContainerElement = E('logContainer');
var logOutputElement = E('output');
var taiseiScriptElement = E('taisei-script');
var dlMessage = statusElement.innerText;
logToggleElement.checked = false;
var setStatus = (() => {
var ss = {};
return (text, force) => {
if (!text && !force) return;
if (!ss.last) ss.last = { time: Date.now(), text: '' };
if (text === ss.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - ss.last.time < 30) return; // if this is a progress update, skip it if too soon
ss.last.time = now;
ss.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = !canvasElement.hidden;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerText = text.replace(/^Downloading(?: data)?\.\.\./, dlMessage).replace('...', '…');
console.log("[STATUS] " + statusElement.innerText);
};
})();
window.onerror = function(error) {
setStatus('Error: ' + error);
};
window['toggleLog'] = function toggleLog() {
logContainerElement.hidden = !logToggleElement.checked;
logOutputElement.scrollTop = logOutputElement.scrollHeight;
}
var glContext = canvasElement.getContext('webgl2', {
'alpha' : false,
'antialias' : false,
'depth' : false,
'powerPreference' : 'high-performance',
'premultipliedAlpha' : true,
'preserveDrawingBuffer' : false,
'stencil' : false,
});
if(!glContext) {
throw "Could not create a WebGL 2 context";
}
// glContext = WebGLDebugUtils.makeDebugContext(glContext);
canvasElement.addEventListener("webglcontextlost", function(e) {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
logOutputElement.value = ''; // clear browser cache
var taisei;
taiseiScriptElement.addEventListener('load', async() => {
taisei = await createTaisei({
'canvas': canvasElement,
'preinitializedWebGLContext': glContext,
'onFirstFrame': function() {
canvasContainerElement.hidden = false;
logToggleContainerElement.style.display = "inline-block";
setStatus('', true);
},
'print': function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
logOutputElement.value += text + "\n";
logOutputElement.scrollTop = logOutputElement.scrollHeight; // focus on bottom
},
'printErr': function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
'setStatus': setStatus,
});
taisei.initFilesystem();
taisei.callMain();
});
</script>
</body>
</html>

View file

@ -285,7 +285,7 @@ if host_machine.system() == 'emscripten'
em_debug = is_debug_build
em_link_outputs = []
em_link_output_suffixes = ['html', 'wasm', 'js'] # first element is significant
em_link_output_suffixes = ['js', 'wasm'] # first element is significant
em_data_dir = config.get_unquoted('TAISEI_BUILDCONF_DATA_PATH')
em_common_args = []
em_link_args = [
@ -299,10 +299,11 @@ if host_machine.system() == 'emscripten'
'-s', 'DYNAMIC_EXECUTION=0',
'-s', 'ENVIRONMENT=web',
'-s', 'EXIT_RUNTIME=0',
'-s', 'EXPORT_NAME=createTaisei',
'-s', 'EXPORTED_FUNCTIONS=["_main", "_vfs_sync_callback"]',
'-s', 'EXPORTED_RUNTIME_METHODS=["ccall"]',
'-s', 'FETCH',
'-s', 'EXPORTED_RUNTIME_METHODS=["ccall","callMain"]',
'-s', 'FETCH_SUPPORT_INDEXEDDB=0',
'-s', 'FETCH',
'-s', 'FILESYSTEM=1',
'-s', 'FORCE_FILESYSTEM=1',
'-s', 'GL_ENABLE_GET_PROC_ADDRESS',
@ -312,12 +313,12 @@ if host_machine.system() == 'emscripten'
'-s', 'GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=0',
'-s', 'IGNORE_MISSING_MAIN=0',
'-s', 'INITIAL_MEMORY=268435456',
'-s', 'INVOKE_RUN=0',
'-s', 'LLD_REPORT_UNDEFINED',
'-s', 'MAX_WEBGL_VERSION=2',
'-s', 'MIN_WEBGL_VERSION=2',
'-s', 'MODULARIZE=0',
'-s', 'MODULARIZE=1',
'-s', 'STACK_SIZE=1MB',
'-s', 'STRICT_JS=1',
'-s', 'SUPPORT_BIG_ENDIAN=1',
'-s', 'WASM=1',
'-lGL',
@ -386,12 +387,12 @@ if host_machine.system() == 'emscripten'
link_whole : libtaisei,
)
taisei_html = custom_target(em_link_outputs[0],
taisei_js = custom_target(em_link_outputs[0],
command : [
meson.get_compiler('cpp').cmd_array(),
taisei,
'--pre-js', em_preamble,
'--shell-file', em_shell,
'--post-js', em_postamble,
get_option('c_args'),
get_option('c_link_args'),
em_common_args,
@ -406,7 +407,7 @@ if host_machine.system() == 'emscripten'
console : true,
)
bindist_deps += taisei_html
bindist_deps += taisei_js
elif host_machine.system() == 'nx'
taisei_elf_name = '@0@.elf'.format(taisei_basename)
taisei_elf = executable(taisei_elf_name, taisei_src, taisei_main_src, version_deps,

View file

@ -13,6 +13,8 @@
#include "rwops/rwops_dummy.h"
#include <dirent.h>
#include <emscripten/em_js.h>
#include <emscripten/emscripten.h>
#include <emscripten/fetch.h>
#include <sys/stat.h>
#include <unistd.h>