diff --git a/Makefile.emscripten b/Makefile.emscripten index fa5d5b88e7..af1c122f7a 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -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)) diff --git a/dist-scripts/dist-cores.sh b/dist-scripts/dist-cores.sh index 13f603c9b0..5c4c675e4c 100755 --- a/dist-scripts/dist-cores.sh +++ b/dist-scripts/dist-cores.sh @@ -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 diff --git a/emscripten/pre.js b/emscripten/pre.js new file mode 100644 index 0000000000..ae4d12534f --- /dev/null +++ b/emscripten/pre.js @@ -0,0 +1,2 @@ +// To work around a bug in emscripten's polyfills for setImmediate in strict mode +var setImmediate; diff --git a/frontend/drivers/platform_emscripten.c b/frontend/drivers/platform_emscripten.c index c842a49854..3856bdf08b 100644 --- a/frontend/drivers/platform_emscripten.c +++ b/frontend/drivers/platform_emscripten.c @@ -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); diff --git a/gfx/drivers_context/emscriptenegl_ctx.c b/gfx/drivers_context/emscriptenegl_ctx.c index 4480513085..2082aef98e 100644 --- a/gfx/drivers_context/emscriptenegl_ctx.c +++ b/gfx/drivers_context/emscriptenegl_ctx.c @@ -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); diff --git a/input/drivers/rwebinput_input.c b/input/drivers/rwebinput_input.c index 729793d62f..0cc23fc747 100644 --- a/input/drivers/rwebinput_input.c +++ b/input/drivers/rwebinput_input.c @@ -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(); } diff --git a/libretro-common/include/retro_miscellaneous.h b/libretro-common/include/retro_miscellaneous.h index 953ab18600..a42ed176b6 100644 --- a/libretro-common/include/retro_miscellaneous.h +++ b/libretro-common/include/retro_miscellaneous.h @@ -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 diff --git a/pkg/emscripten/libretro/libretro.js b/pkg/emscripten/libretro/libretro.js index 48a3e233c8..00eeaab0c1 100644 --- a/pkg/emscripten/libretro/libretro.js +++ b/pkg/emscripten/libretro/libretro.js @@ -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); } @@ -258,7 +289,7 @@ function switchStorage(backend) { // When the browser has loaded everything. $(function() { - // Enable all available ToolTips. + // Enable all available ToolTips. $('.tooltip-enable').tooltip({ placement: 'right' }); @@ -273,74 +304,77 @@ $(function() { /** * Attempt to disable some default browser keys. */ - var keys = { - 9: "tab", - 13: "enter", - 16: "shift", - 18: "alt", - 27: "esc", - 33: "rePag", - 34: "avPag", - 35: "end", - 36: "home", - 37: "left", - 38: "up", - 39: "right", - 40: "down", - 112: "F1", - 113: "F2", - 114: "F3", - 115: "F4", - 116: "F5", - 117: "F6", - 118: "F7", - 119: "F8", - 120: "F9", - 121: "F10", - 122: "F11", - 123: "F12" - }; - window.addEventListener('keydown', function (e) { - if (keys[e.which]) { - e.preventDefault(); - } - }); + var keys = { + 9: "tab", + 13: "enter", + 16: "shift", + 18: "alt", + 27: "esc", + 33: "rePag", + 34: "avPag", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 112: "F1", + 113: "F2", + 114: "F3", + 115: "F4", + 116: "F5", + 117: "F6", + 118: "F7", + 119: "F8", + 120: "F9", + 121: "F10", + 122: "F11", + 123: "F12" + }; + window.addEventListener('keydown', function (e) { + if (keys[e.which]) { + e.preventDefault(); + } + }); // Switch the core when selecting one. $('#core-selector a').click(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 () - { - $('#icnRun').removeClass('fa-spinner').removeClass('fa-spin'); - $('#icnRun').addClass('fa-play'); - $('#lblDrop').removeClass('active'); - $('#lblLocal').addClass('active'); - idbfsInit(); - }); - }); + 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) { + 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); } - -kp = function(k, event) { - var oEvent = new KeyboardEvent(event, { code: k }); - - document.dispatchEvent(oEvent); - document.getElementById('canvas').focus(); -}