diff --git a/.gitignore b/.gitignore index 60c69dd615..71db6addba 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,10 @@ obj-unix/ /pkg/msvc/msvc-2010/Release Cg/*.pdb retroarch.cfg Makefile.local + +# Emscripten artifacts + +retroarch.js +retroarch.js.mem +*.bc +*.wasm diff --git a/Makefile.emscripten b/Makefile.emscripten index 0334d2edf3..4ac23c1bf6 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -21,13 +21,15 @@ HAVE_SDL = 0 HAVE_SDL2 = 0 HAVE_ZLIB = 1 WANT_ZLIB = 1 +HAVE_SHADERPIPELINE = 1 HAVE_STATIC_VIDEO_FILTERS = 1 HAVE_STATIC_AUDIO_FILTERS = 1 MEMORY = 536870912 -# XXX: setting this to 1/2 currently crashes Firefox nightly PRECISE_F32 = 2 +OBJDIR := obj-emscripten + ifneq ($(NATIVE_ZLIB),) WANT_ZLIB = 0 endif @@ -65,7 +67,7 @@ ifeq ($(DEBUG), 1) LDFLAGS += -O0 -g CFLAGS += -O0 -g else - LDFLAGS += -O2 + LDFLAGS += -O2 -s WASM=1 # WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake) LDFLAGS += -s PRECISE_F32=$(PRECISE_F32) ifeq ($(LTO), 1) @@ -74,48 +76,29 @@ else CFLAGS += -O2 endif -CFLAGS += -DHAVE_RPNG -Wall -Wno-unused-result -Wno-unused-variable -I. -Ilibretro-common/include -std=gnu99 -s USE_ZLIB=1 \ +CFLAGS += -DHAVE_RPNG -Wall -I. -Ilibretro-common/include -std=gnu99 -s USE_ZLIB=1 \ -s EXPORTED_FUNCTIONS="['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_take_screenshot']" +RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ)) + all: $(TARGET) -$(TARGET): $(OBJ) +$(TARGET): $(RARCH_OBJ) @$(if $(Q), $(shell echo echo LD $@),) - $(Q)$(LD) -o $@ $(OBJ) $(libretro) $(LIBS) $(LDFLAGS) + $(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro) $(LIBS) $(LDFLAGS) -%.o: %.c +$(OBJDIR)/%.o: %.c + @mkdir -p $(dir $@) @$(if $(Q), $(shell echo echo CC $<),) $(Q)$(CC) $(CFLAGS) $(DEFINES) $(EOPTS) -c -o $@ $< -%.o: %.cpp +$(OBJDIR)/%.o: %.cpp + @mkdir -p $(dir $@) @$(if $(Q), $(shell echo echo CXX $<),) $(Q)$(CXX) $(CXXFLAGS) $(DEFINES) $(EOPTS) -c -o $@ $< clean: - rm -f *.o - rm -f deps/libz/*.o - rm -f frontend/*.o - rm -f menu/*.o - rm -f menu/disp/*.o - rm -f audio/*.o - rm -f compat/*.o - rm -f compat/rxml/*.o - rm -f conf/*.o - rm -f gfx/scaler/*.o - rm -f gfx/*.o - rm -f gfx/d3d/*.o - rm -f gfx/drivers_context/*.o - rm -f gfx/math/*.o - rm -f gfx/drivers_font/*.o - rm -f gfx/drivers_font_renderer/*.o - rm -f gfx/py_state/*.o - rm -f gfx/rpng/*.o - rm -f gfx/glsym/*.o - rm -f record/*.o - rm -f input/*.o - rm -f tools/*.o - rm -f libretro-common/*/*.o + rm -rf $(OBJDIR) rm -f $(TARGET) - rm -f *.d .PHONY: all clean diff --git a/configuration.c b/configuration.c index 9e7eacc246..6a70465a0c 100644 --- a/configuration.c +++ b/configuration.c @@ -224,6 +224,7 @@ enum joypad_driver_enum JOYPAD_DOS, JOYPAD_HID, JOYPAD_QNX, + JOYPAD_RWEBPAD, JOYPAD_NULL }; @@ -454,6 +455,8 @@ static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_DOS; static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_HID; #elif defined(__QNX__) static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_QNX; +#elif defined(EMSCRIPTEN) +static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_RWEBPAD; #else static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_NULL; #endif @@ -783,9 +786,9 @@ const char *config_get_default_input(void) case INPUT_COCOA: return "cocoa"; case INPUT_QNX: - return "qnx_input"; + return "qnx_input"; case INPUT_RWEBINPUT: - return "rwebinput"; + return "rwebinput"; case INPUT_DOS: return "dos"; case INPUT_NULL: @@ -846,6 +849,8 @@ const char *config_get_default_joypad(void) return "hid"; case JOYPAD_QNX: return "qnx"; + case JOYPAD_RWEBPAD: + return "rwebpad"; case JOYPAD_DOS: return "dos"; case JOYPAD_NULL: diff --git a/emscripten/library_rwebaudio.js b/emscripten/library_rwebaudio.js index fd85d607a0..03b70a8340 100644 --- a/emscripten/library_rwebaudio.js +++ b/emscripten/library_rwebaudio.js @@ -23,13 +23,14 @@ var LibraryRWebAudio = { getCurrentPerfTime: function() { if (RA.startTime) return (window['performance']['now']() - RA.startTime) / 1000; - else throw 'getCurrentPerfTime() called before start time set'; + else return 0; }, process: function(queueBuffers) { var currentTime = RA.getCurrentPerfTime(); for (var i = 0; i < RA.bufIndex; i++) { - if (RA.buffers[i].endTime < currentTime) { + if (RA.buffers[i].endTime !== 0 && RA.buffers[i].endTime < currentTime) { + RA.buffers[i].endTime = 0; var buf = RA.buffers.splice(i, 1); RA.buffers[RA.numBuffers - 1] = buf[0]; i--; @@ -40,8 +41,8 @@ var LibraryRWebAudio = { fillBuffer: function(buf, samples) { var count = 0; - var leftBuffer = RA.buffers[RA.bufIndex].getChannelData(0); - var rightBuffer = RA.buffers[RA.bufIndex].getChannelData(1); + const leftBuffer = RA.buffers[RA.bufIndex].getChannelData(0); + const rightBuffer = RA.buffers[RA.bufIndex].getChannelData(1); while (samples && RA.bufOffset !== RA.BUFFER_SIZE) { leftBuffer[RA.bufOffset] = {{{ makeGetValue('buf', 'count * 8', 'float') }}}; rightBuffer[RA.bufOffset] = {{{ makeGetValue('buf', 'count * 8 + 4', 'float') }}}; @@ -73,7 +74,7 @@ var LibraryRWebAudio = { block: function() { do { RA.process(); - } while (RA.bufIndex === RA.numBuffers - 1); + } while (RA.bufIndex === RA.numBuffers); } }, @@ -87,7 +88,10 @@ var LibraryRWebAudio = { RA.numBuffers = ((latency * RA.context.sampleRate) / (1000 * RA.BUFFER_SIZE))|0; if (RA.numBuffers < 2) RA.numBuffers = 2; - for (var i = 0; i < RA.numBuffers; i++) RA.buffers[i] = RA.context.createBuffer(2, RA.BUFFER_SIZE, RA.context.sampleRate); + for (var i = 0; i < RA.numBuffers; i++) { + RA.buffers[i] = RA.context.createBuffer(2, RA.BUFFER_SIZE, RA.context.sampleRate); + RA.buffers[i].endTime = 0 + } RA.nonblock = false; RA.startTime = 0; @@ -97,7 +101,7 @@ var LibraryRWebAudio = { Module["pauseMainLoop"](); return 1; }, - + RWebAudioSampleRate: function() { return RA.context.sampleRate; }, @@ -108,16 +112,17 @@ var LibraryRWebAudio = { var count = 0; while (samples) { + if (RA.bufIndex === RA.numBuffers) { + if (RA.nonblock) break; + else RA.block(); + } + var fill = RA.fillBuffer(buf, samples); samples -= fill; count += fill; buf += fill * 8; if (RA.bufOffset === RA.BUFFER_SIZE) { - if (RA.bufIndex === RA.numBuffers - 1) { - if (RA.nonblock) break; - else RA.block(); - } RA.queueAudio(); } } @@ -142,16 +147,21 @@ var LibraryRWebAudio = { RWebAudioFree: function() { RA.bufIndex = 0; RA.bufOffset = 0; - return; }, RWebAudioBufferSize: function() { - return RA.numBuffers * RA.BUFFER_SIZE + RA.BUFFER_SIZE; + return RA.numBuffers * RA.BUFFER_SIZE * 8; }, RWebAudioWriteAvail: function() { RA.process(); return ((RA.numBuffers - RA.bufIndex) * RA.BUFFER_SIZE - RA.bufOffset) * 8; + }, + + RWebAudioRecalibrateTime: function() { + if (RA.startTime) { + RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000; + } } }; diff --git a/frontend/drivers/platform_emscripten.c b/frontend/drivers/platform_emscripten.c index 838f3e365e..4671638609 100644 --- a/frontend/drivers/platform_emscripten.c +++ b/frontend/drivers/platform_emscripten.c @@ -39,14 +39,42 @@ #include "../../defaults.h" #include "../../content.h" #include "../../retroarch.h" +#include "../../verbosity.h" #include "../../command.h" #include "../../tasks/tasks_internal.h" #include "../../file_path_special.h" +void RWebAudioRecalibrateTime(void); + +static unsigned emscripten_fullscreen_reinit; + +static EM_BOOL emscripten_fullscreenchange_cb(int event_type, + const EmscriptenFullscreenChangeEvent *fullscreen_change_event, + void *user_data) +{ + (void)event_type; + (void)fullscreen_change_event; + (void)user_data; + + emscripten_fullscreen_reinit = 5; + + return EM_TRUE; +} + static void emscripten_mainloop(void) { unsigned sleep_ms = 0; - int ret = runloop_iterate(&sleep_ms); + int ret; + + RWebAudioRecalibrateTime(); + + if (emscripten_fullscreen_reinit != 0) + { + if (--emscripten_fullscreen_reinit == 0) + command_event(CMD_EVENT_REINIT, NULL); + } + + ret = runloop_iterate(&sleep_ms); if (ret == 1 && sleep_ms > 0) retro_sleep(sleep_ms); @@ -162,19 +190,24 @@ static void frontend_emscripten_get_env(int *argc, char *argv[], if (!string_is_empty(dir_path)) path_mkdir(dir_path); } - - snprintf(g_defaults.settings.menu, sizeof(g_defaults.settings.menu), "rgui"); } int main(int argc, char *argv[]) { - settings_t *settings = config_get_ptr(); + EMSCRIPTEN_RESULT r; 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); - emscripten_set_main_loop(emscripten_mainloop, - settings->bools.video_vsync ? 0 : INT_MAX, 1); + + r = emscripten_set_fullscreenchange_callback("#document", NULL, false, + emscripten_fullscreenchange_cb); + if (r != EMSCRIPTEN_RESULT_SUCCESS) + { + RARCH_ERR( + "[EMSCRIPTEN/CTX] failed to create fullscreen callback: %d\n", r); + } return 0; } diff --git a/gfx/drivers_context/emscriptenegl_ctx.c b/gfx/drivers_context/emscriptenegl_ctx.c index e72434b5cb..b3d7f3ae43 100644 --- a/gfx/drivers_context/emscriptenegl_ctx.c +++ b/gfx/drivers_context/emscriptenegl_ctx.c @@ -51,8 +51,11 @@ static int emscripten_initial_height; static void gfx_ctx_emscripten_swap_interval(void *data, unsigned interval) { (void)data; - /* no way to control VSync in WebGL. */ - (void)interval; + + if (interval == 0) + emscripten_set_main_loop_timing(EM_TIMING_SETIMMEDIATE, 0); + else + emscripten_set_main_loop_timing(EM_TIMING_RAF, (int)interval); } static void gfx_ctx_emscripten_get_canvas_size(int *width, int *height) @@ -138,8 +141,13 @@ static void gfx_ctx_emscripten_check_window(void *data, bool *quit, static void gfx_ctx_emscripten_swap_buffers(void *data, void *data2) { - (void)data; - /* no-op in emscripten, no way to force swap/wait for VSync in browsers */ + emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data; + + /* doesn't really do anything in WebGL, but it might if we use WebGL workers + * in the future */ +#ifdef HAVE_EGL + egl_swap_buffers(&emscripten->egl); +#endif } static void gfx_ctx_emscripten_get_video_size(void *data, diff --git a/input/drivers/rwebinput_input.c b/input/drivers/rwebinput_input.c index 8cdb425b5a..3108215bd1 100644 --- a/input/drivers/rwebinput_input.c +++ b/input/drivers/rwebinput_input.c @@ -567,7 +567,6 @@ static void rwebinput_input_free(void *data) static void rwebinput_process_keyboard_events(rwebinput_input_t *rwebinput, rwebinput_keyboard_event_t *event) { - uint32_t crc; uint32_t keycode; unsigned translated_keycode; uint32_t character = 0; diff --git a/pkg/emscripten/libretro/index.html b/pkg/emscripten/libretro/index.html index 8fbba05723..407a84e4fa 100644 --- a/pkg/emscripten/libretro/index.html +++ b/pkg/emscripten/libretro/index.html @@ -86,7 +86,7 @@ Cleanup - - - - RetroArch Logo - - - -
-
-
-
-
- -
-
-
+
+
+ +
+ + RetroArch Logo
diff --git a/pkg/emscripten/libretro/libretro.css b/pkg/emscripten/libretro/libretro.css index eb848542c9..15a1f2bc2a 100644 --- a/pkg/emscripten/libretro/libretro.css +++ b/pkg/emscripten/libretro/libretro.css @@ -112,3 +112,8 @@ textarea { #icnShowMenu { color: #565656 !important; } + +/* fix weird white bar in chrome when fullscreen */ +:-webkit-full-screen { + height: 100%; +} diff --git a/pkg/emscripten/libretro/libretro.js b/pkg/emscripten/libretro/libretro.js index b79fdf691f..2dca489768 100644 --- a/pkg/emscripten/libretro/libretro.js +++ b/pkg/emscripten/libretro/libretro.js @@ -184,24 +184,13 @@ var Module = arguments: ["-v", "--menu"], preRun: [], postRun: [], - print: (function() + print: function(text) { - var element = document.getElementById('output'); - element.value = ''; // clear browser cache - return function(text) - { - text = Array.prototype.slice.call(arguments).join(' '); - element.value += text + "\n"; - element.scrollTop = 99999; // focus on bottom - }; - })(), - + console.log(text); + }, printErr: function(text) { - var text = Array.prototype.slice.call(arguments).join(' '); - var element = document.getElementById('output'); - element.value += text + "\n"; - element.scrollTop = 99999; // focus on bottom + console.log(text); }, canvas: document.getElementById('canvas'), totalDependencies: 0, @@ -306,31 +295,7 @@ function keyPress(k) } kp = function(k, event) { - var oEvent = document.createEvent('KeyboardEvent'); - - // Chromium Hack - Object.defineProperty(oEvent, 'keyCode', { - get : function() { - return this.keyCodeVal; - } - }); - Object.defineProperty(oEvent, 'which', { - get : function() { - return this.keyCodeVal; - } - }); - - if (oEvent.initKeyboardEvent) { - oEvent.initKeyboardEvent(event, true, true, document.defaultView, false, false, false, false, k, k); - } else { - oEvent.initKeyEvent(event, true, true, document.defaultView, false, false, false, false, k, 0); - } - - oEvent.keyCodeVal = k; - - if (oEvent.keyCode !== k) { - alert("keyCode mismatch " + oEvent.keyCode + "(" + oEvent.which + ")"); - } + var oEvent = new KeyboardEvent(event, { code: k }); document.dispatchEvent(oEvent); document.getElementById('canvas').focus();