(Emscripten) Modularize the JavaScript and clean up the web build (#15688)

* Increase emscripten stack size and decrease path size to fix emscripten builds broken since de45fc2

* use modularize flags for better-behaved javascript output

* makefile and loader changes

* use specialHTMLTargets to support modular access to canvas

* bind key events to canvas, not document

This way focus means focus and we can have multiple RA instances in
one page.

* Work around an emscripten bug in strict mode

* (Emscripten) Use console.error() for error messages

* increase asyncify stack size

* Fix `-lm` flag-related compile warnings in emscripten

---------

Co-authored-by: Rob Loach <robloach@gmail.com>
This commit is contained in:
Joe Osborn 2023-11-02 13:25:50 -07:00 committed by GitHub
parent 8523eaf5c0
commit 862bebf687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 136 deletions

View File

@ -2,6 +2,7 @@ HAVE_STATIC_DUMMY ?= 0
ifeq ($(TARGET),)
ifeq ($(LIBRETRO),)
TARGET := retroarch.js
LIBRETRO = dummy
else
TARGET := $(LIBRETRO)_libretro.js
endif
@ -48,7 +49,6 @@ HAVE_7ZIP = 1
HAVE_BSV_MOVIE = 1
HAVE_AL = 1
# WARNING -- READ BEFORE ENABLING
# The rwebaudio driver is known to have several audio bugs, such as
# minor crackling, or the entire page freezing/crashing.
@ -78,8 +78,11 @@ OBJDIR := obj-emscripten
#if you compile with SDL2 flag add this Emscripten flag "-s USE_SDL=2" to LDFLAGS:
LIBS := -s USE_ZLIB=1
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s TOTAL_MEMORY=$(MEMORY) -s NO_EXIT_RUNTIME=0 -s FULL_ES2=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']" \
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_load_state', '_cmd_take_screenshot']" \
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s TOTAL_MEMORY=$(MEMORY) -s NO_EXIT_RUNTIME=0 -s FULL_ES2=1 \
-s "EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH', 'ERRNO_CODES']" \
-s ALLOW_MEMORY_GROWTH=1 -s "EXPORTED_FUNCTIONS=['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_load_state', '_cmd_take_screenshot']" \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="$(LIBRETRO)" \
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 \
--js-library emscripten/library_errno_codes.js \
--js-library emscripten/library_rwebcam.js
@ -102,7 +105,10 @@ else
endif
ifeq ($(ASYNC), 1)
LDFLAGS += -s ASYNCIFY=$(ASYNC)
LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192
ifeq ($(DEBUG), 1)
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
endif
endif
ifeq ($(HAVE_SDL2), 1)
@ -127,8 +133,8 @@ ifneq ($(V), 1)
endif
ifeq ($(DEBUG), 1)
LDFLAGS += -O0 -g
CFLAGS += -O0 -g
LDFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
CFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s SAFE_HEAP_LOG=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
else
LDFLAGS += -O3 -s WASM=1
# WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake)
@ -139,7 +145,12 @@ else
CFLAGS += -O3
endif
CFLAGS += -Wall -I. -Ilibretro-common/include -std=gnu99 $(LIBS) #\
# 128 * 1024, double the usual emscripten stack size
LDFLAGS += -s STACK_SIZE=131072
LDFLAGS += --extern-pre-js emscripten/pre.js
CFLAGS += -Wall -I. -Ilibretro-common/include -std=gnu99 #\
# -s EXPORTED_FUNCTIONS="['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_take_screenshot']"
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))

View File

@ -247,8 +247,8 @@ for f in `ls -v *_${platform}.${EXT}`; do
if [ $MAKEFILE_GRIFFIN = "yes" ]; then
make -C ../ -f Makefile.griffin $OPTS platform=${platform} $whole_archive $big_stack -j3 || exit 1
elif [ $PLATFORM = "emscripten" ]; then
echo "BUILD COMMAND: make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 TARGET=${name}_libretro.js"
make -C ../ -f Makefile.emscripten $OPTS PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 TARGET=${name}_libretro.js || exit 1
echo "BUILD COMMAND: make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 LIBRETRO=${name} TARGET=${name}_libretro.js"
make -C ../ -f Makefile.emscripten $OPTS PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 LIBRETRO=${name} TARGET=${name}_libretro.js || exit 1
elif [ $PLATFORM = "unix" ]; then
make -C ../ -f Makefile LINK=g++ $whole_archive $big_stack -j3 || exit 1
elif [ $PLATFORM = "ctr" ]; then

2
emscripten/pre.js Normal file
View File

@ -0,0 +1,2 @@
// To work around a bug in emscripten's polyfills for setImmediate in strict mode
var setImmediate;

View File

@ -160,8 +160,12 @@ int main(int argc, char *argv[])
{
dummyErrnoCodes();
emscripten_set_canvas_element_size("#canvas", 800, 600);
emscripten_set_element_css_size("#canvas", 800.0, 600.0);
EM_ASM({
specialHTMLTargets["!canvas"] = Module.canvas;
});
emscripten_set_canvas_element_size("!canvas", 800, 600);
emscripten_set_element_css_size("!canvas", 800.0, 600.0);
emscripten_set_main_loop(emscripten_mainloop, 0, 0);
rarch_main(argc, argv, NULL);

View File

@ -70,7 +70,7 @@ static void gfx_ctx_emscripten_get_canvas_size(int *width, int *height)
if (!is_fullscreen)
{
r = emscripten_get_canvas_element_size("#canvas", width, height);
r = emscripten_get_canvas_element_size("!canvas", width, height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -105,14 +105,14 @@ static void gfx_ctx_emscripten_check_window(void *data, bool *quit,
if ( (input_width != emscripten->fb_width)
|| (input_height != emscripten->fb_height))
{
r = emscripten_set_canvas_element_size("#canvas",
r = emscripten_set_canvas_element_size("!canvas",
input_width, input_height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
RARCH_ERR("[EMSCRIPTEN/EGL]: error resizing canvas: %d\n", r);
/* fix Module.requestFullscreen messing with the canvas size */
r = emscripten_set_element_css_size("#canvas",
r = emscripten_set_element_css_size("!canvas",
(double)input_width, (double)input_height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
@ -194,7 +194,7 @@ static void *gfx_ctx_emscripten_init(void *video_driver)
* be grabbed? */
if ( (emscripten->initial_width == 0)
|| (emscripten->initial_height == 0))
emscripten_get_canvas_element_size("#canvas",
emscripten_get_canvas_element_size("!canvas",
&emscripten->initial_width,
&emscripten->initial_height);

View File

@ -291,7 +291,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
rwebinput_generate_lut();
r = emscripten_set_keydown_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, rwebinput, false,
"!canvas", rwebinput, false,
rwebinput_keyboard_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -300,7 +300,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
}
r = emscripten_set_keyup_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, rwebinput, false,
"!canvas", rwebinput, false,
rwebinput_keyboard_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -309,7 +309,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
}
r = emscripten_set_keypress_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, rwebinput, false,
"!canvas", rwebinput, false,
rwebinput_keyboard_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -317,7 +317,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
"[EMSCRIPTEN/INPUT] failed to create keypress callback: %d\n", r);
}
r = emscripten_set_mousedown_callback("#canvas", rwebinput, false,
r = emscripten_set_mousedown_callback("!canvas", rwebinput, false,
rwebinput_mouse_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -325,7 +325,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
"[EMSCRIPTEN/INPUT] failed to create mousedown callback: %d\n", r);
}
r = emscripten_set_mouseup_callback("#canvas", rwebinput, false,
r = emscripten_set_mouseup_callback("!canvas", rwebinput, false,
rwebinput_mouse_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -333,7 +333,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
"[EMSCRIPTEN/INPUT] failed to create mouseup callback: %d\n", r);
}
r = emscripten_set_mousemove_callback("#canvas", rwebinput, false,
r = emscripten_set_mousemove_callback("!canvas", rwebinput, false,
rwebinput_mouse_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -342,7 +342,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
}
r = emscripten_set_wheel_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, rwebinput, false,
"!canvas", rwebinput, false,
rwebinput_wheel_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
@ -651,7 +651,7 @@ static void rwebinput_input_poll(void *data)
static void rwebinput_grab_mouse(void *data, bool state)
{
if (state)
emscripten_request_pointerlock("#canvas", EM_TRUE);
emscripten_request_pointerlock("!canvas", EM_TRUE);
else
emscripten_exit_pointerlock();
}

View File

@ -90,7 +90,7 @@ static INLINE bool bits_any_different(uint32_t *a, uint32_t *b, uint32_t count)
}
#ifndef PATH_MAX_LENGTH
#if defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(__PSL1GHT__) || defined(__PS3__)
#if defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(__PSL1GHT__) || defined(__PS3__) || defined(HAVE_EMSCRIPTEN)
#define PATH_MAX_LENGTH 512
#else
#define PATH_MAX_LENGTH 4096

View File

@ -6,6 +6,89 @@
var BrowserFS = BrowserFS;
var afs;
var initializationCount = 0;
var setImmediate;
var Module = {
noInitialRun: true,
arguments: ["-v", "--menu"],
encoder: new TextEncoder(),
message_queue:[],
message_out:[],
message_accum:"",
retroArchSend: function(msg) {
let bytes = this.encoder.encode(msg+"\n");
this.message_queue.push([bytes,0]);
},
retroArchRecv: function() {
let out = this.message_out.shift();
if(out == null && this.message_accum != "") {
out = this.message_accum;
this.message_accum = "";
}
return out;
},
preRun: [
function(module) {
function stdin() {
// Return ASCII code of character, or null if no input
while(module.message_queue.length > 0){
var msg = module.message_queue[0][0];
var index = module.message_queue[0][1];
if(index >= msg.length) {
module.message_queue.shift();
} else {
module.message_queue[0][1] = index+1;
// assumption: msg is a uint8array
return msg[index];
}
}
return null;
}
function stdout(c) {
if(c == null) {
// flush
if(module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
let s = String.fromCharCode(c);
if(s == "\n") {
if(module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
module.message_accum = module.message_accum+s;
}
}
}
module.FS.init(stdin, stdout);
}
],
postRun: [],
onRuntimeInitialized: function()
{
appInitialized();
},
print: function(text)
{
console.log(text);
},
printErr: function(text)
{
console.error(text);
},
canvas: document.getElementById("canvas"),
totalDependencies: 0,
monitorRunDependencies: function(left)
{
this.totalDependencies = Math.max(this.totalDependencies, left);
}
};
function cleanupStorage()
{
@ -119,8 +202,8 @@ function setupFileSystem(backend)
mfs.mount('/home/web_user/retroarch/bundle', xfs1);
mfs.mount('/home/web_user/retroarch/userdata/content/downloads', xfs2);
BrowserFS.initialize(mfs);
var BFS = new BrowserFS.EmscriptenFS();
FS.mount(BFS, {root: '/home'}, '/home');
var BFS = new BrowserFS.EmscriptenFS(Module.FS, Module.PATH, Module.ERRNO_CODES);
Module.FS.mount(BFS, {root: '/home'}, '/home');
console.log("WEBPLAYER: " + backend + " filesystem initialization successful");
}
@ -150,11 +233,12 @@ function startRetroArch()
document.getElementById("btnMenu").disabled = false;
document.getElementById("btnFullscreen").disabled = false;
Module["canvas"] = document.getElementById("canvas");
Module["canvas"].addEventListener("click", () => Module["canvas"].focus());
Module['callMain'](Module['arguments']);
Module['resumeMainLoop']();
document.getElementById('canvas').focus();
Module['canvas'].focus();
}
function selectFiles(files)
{
$('#btnAdd').addClass('disabled');
@ -184,66 +268,13 @@ function selectFiles(files)
function uploadData(data,name)
{
var dataView = new Uint8Array(data);
FS.createDataFile('/', name, dataView, true, false);
Module.FS.createDataFile('/', name, dataView, true, false);
var data = FS.readFile(name,{ encoding: 'binary' });
FS.writeFile('/home/web_user/retroarch/userdata/content/' + name, data ,{ encoding: 'binary' });
FS.unlink(name);
var data = Module.FS.readFile(name,{ encoding: 'binary' });
Module.FS.writeFile('/home/web_user/retroarch/userdata/content/' + name, data ,{ encoding: 'binary' });
Module.FS.unlink(name);
}
var encoder = new TextEncoder();
var message_queue = [];
function retroArchSend(msg) {
var bytes = encoder.encode(msg+"\n");
message_queue.push([bytes,0]);
}
var Module =
{
noInitialRun: true,
arguments: ["-v", "--menu"],
preRun: [
function() {
function stdin() {
// Return ASCII code of character, or null if no input
while(message_queue.length > 0){
var msg = message_queue[0][0];
var index = message_queue[0][1];
if(index >= msg.length) {
message_queue.shift();
} else {
message_queue[0][1] = index+1;
// assumption: msg is a uint8array
return msg[index];
}
}
return null;
}
FS.init(stdin);
}
],
postRun: [],
onRuntimeInitialized: function()
{
appInitialized();
},
print: function(text)
{
console.log(text);
},
printErr: function(text)
{
console.log(text);
},
canvas: document.getElementById('canvas'),
totalDependencies: 0,
monitorRunDependencies: function(left)
{
this.totalDependencies = Math.max(this.totalDependencies, left);
}
};
function switchCore(corename) {
localStorage.setItem("core", corename);
}
@ -311,36 +342,39 @@ $(function() {
var coreChoice = $(this).data('core');
switchCore(coreChoice);
});
// Find which core to load.
var core = localStorage.getItem("core", core);
if (!core) {
core = 'gambatte';
}
loadCore(core);
});
function loadCore(core) {
// Make the core the selected core in the UI.
var coreTitle = $('#core-selector a[data-core="' + core + '"]').addClass('active').text();
$('#dropdownMenu1').text(coreTitle);
// Load the Core's related JavaScript.
$.getScript(core + '_libretro.js', function ()
{
import("./"+core+"_libretro.js").then(script => {
script.default(Module).then(mod => {
Module = mod;
$('#icnRun').removeClass('fa-spinner').removeClass('fa-spin');
$('#icnRun').addClass('fa-play');
$('#lblDrop').removeClass('active');
$('#lblLocal').addClass('active');
idbfsInit();
});
});
}).catch(err => { console.error("Couldn't instantiate module",err); throw err; });
}).catch(err => { console.error("Couldn't load script",err); throw err; });
}
function keyPress(k)
{
kp(k, "keydown");
setTimeout(function(){kp(k, "keyup")}, 50);
}
kp = function(k, event) {
function kp(k, event) {
var oEvent = new KeyboardEvent(event, { code: k });
document.dispatchEvent(oEvent);
document.getElementById('canvas').focus();
}
kp(k, "keydown");
setTimeout(function(){kp(k, "keyup")}, 50);
}