diff --git a/Makefile.emscripten b/Makefile.emscripten
new file mode 100644
index 0000000000..c1e913419c
--- /dev/null
+++ b/Makefile.emscripten
@@ -0,0 +1,159 @@
+TARGET = retroarch.js
+
+OBJ = frontend/frontend_emscripten.o \
+ retroarch.o \
+ file.o \
+ file_path.o \
+ driver.o \
+ conf/config_file.o \
+ settings.o \
+ hash.o \
+ dynamic.o \
+ dynamic_dummy.o \
+ message.o \
+ rewind.o \
+ movie.o \
+ gfx/gfx_common.o \
+ input/input_common.o \
+ input/rwebinput_input.o \
+ core_options.o \
+ patch.o \
+ compat/compat.o \
+ compat/rxml/rxml.o \
+ screenshot.o \
+ cheats.o \
+ audio/utils.o \
+ audio/rwebaudio.o \
+ input/overlay.o \
+ fifo_buffer.o \
+ gfx/scaler/scaler.o \
+ gfx/scaler/pixconv.o \
+ gfx/scaler/scaler_int.o \
+ gfx/scaler/filter.o \
+ gfx/state_tracker.o \
+ gfx/shader_parse.o \
+ gfx/fonts/fonts.o \
+ gfx/fonts/bitmapfont.o \
+ gfx/image.o \
+ audio/resampler.o \
+ audio/sinc.o \
+ audio/null.o \
+ performance.o
+
+HAVE_OPENGL = 1
+HAVE_RGUI = 1
+HAVE_SDL = 0
+HAVE_ZLIB = 1
+HAVE_FBO = 1
+WANT_MINIZ = 1
+MEMORY = 67108864
+LTO = 0
+FAST_DOUBLES = 1
+
+ifneq ($(NATIVE_ZLIB),)
+ WANT_MINIZ = 0
+endif
+
+libretro = libretro_emscripten.bc
+
+LIBS = -lm
+DEFINES = -DHAVE_SCREENSHOTS -DHAVE_NULLAUDIO -DHAVE_BSV_MOVIE -DPACKAGE_VERSION=\"0.9.9.3\"
+LDFLAGS = -L. -s TOTAL_MEMORY=$(MEMORY) --js-library emscripten/library_rwebaudio.js --js-library emscripten/library_rwebinput.js
+
+ifeq ($(PERF_TEST), 1)
+ DEFINES += -DPERF_TEST
+endif
+
+ifeq ($(HAVE_RGUI), 1)
+ DEFINES += -DHAVE_RGUI
+ OBJ += frontend/menu/menu_common.o frontend/menu/rgui.o frontend/menu/history.o
+endif
+
+ifeq ($(HAVE_SDL), 1)
+ OBJ += input/sdl_input.o
+ LIBS += -lSDL
+ DEFINES += -ISDL -DHAVE_SDL
+endif
+
+ifeq ($(HAVE_OPENGL), 1)
+ OBJ += gfx/gl.o gfx/math/matrix.o gfx/fonts/gl_font.o gfx/fonts/gl_raster_font.o gfx/gfx_context.o gfx/context/emscriptenegl_ctx.o gfx/shader_glsl.o gfx/glsym/rglgen.o gfx/glsym/glsym_es2.o
+ DEFINES += -DHAVE_OPENGL -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DHAVE_EGL -DHAVE_OVERLAY -DHAVE_GLSL
+endif
+
+ifeq ($(HAVE_ZLIB), 1)
+ OBJ += gfx/rpng/rpng.o file_extract.o
+ DEFINES += -DHAVE_ZLIB
+ ifeq ($(WANT_MINIZ), 1)
+ OBJ += deps/miniz/miniz.o
+ DEFINES += -DWANT_MINIZ
+ else
+ LIBS += -lz
+ DEFINES += -DHAVE_ZLIB_DEFLATE
+ endif
+endif
+
+ifeq ($(HAVE_FBO), 1)
+ DEFINES += -DHAVE_FBO
+endif
+
+ifneq ($(V), 1)
+ Q := @
+endif
+
+ifeq ($(DEBUG), 1)
+ LDFLAGS += -O0 -g
+ CFLAGS += -O0 -g
+else
+ LDFLAGS += -O2
+ # WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake)
+ ifeq ($(FAST_DOUBLES), 1)
+ LDFLAGS += -s DOUBLE_MODE=0
+ endif
+ ifeq ($(LTO), 1)
+ LDFLAGS += --llvm-lto 3
+ endif
+ CFLAGS += -O2
+endif
+
+CFLAGS += -Wall -Wno-unused-result -Wno-unused-variable -I. -std=gnu99
+
+all: $(TARGET)
+
+$(TARGET): $(OBJ)
+ @$(if $(Q), $(shell echo echo LD $@),)
+ $(Q)$(LD) -o $@ $(OBJ) $(libretro) $(LIBS) $(LDFLAGS)
+
+%.o: %.c
+ @$(if $(Q), $(shell echo echo CC $<),)
+ $(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
+
+%.o: %.cpp
+ @$(if $(Q), $(shell echo echo CXX $<),)
+ $(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $<
+
+clean:
+ rm -f *.o
+ rm -f deps/miniz/*.o
+ rm -f frontend/*.o
+ rm -f frontend/menu/*.o
+ rm -f audio/*.o
+ rm -f audio/xaudio-c/*.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/d3d9/*.o
+ rm -f gfx/context/*.o
+ rm -f gfx/math/*.o
+ rm -f gfx/fonts/*.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 $(TARGET)
+
+.PHONY: all clean
+
diff --git a/audio/rwebaudio.c b/audio/rwebaudio.c
new file mode 100644
index 0000000000..7139a88de4
--- /dev/null
+++ b/audio/rwebaudio.c
@@ -0,0 +1,90 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Michael Lelli
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include "../driver.h"
+#include "../general.h"
+
+#include "../emscripten/RWebAudio.h"
+
+static void ra_free(void *data)
+{
+ RWebAudioFree();
+}
+
+static void *ra_init(const char *device, unsigned rate, unsigned latency)
+{
+ (void)device;
+ (void)rate;
+ void *data = RWebAudioInit(latency);
+ if (data)
+ g_settings.audio.out_rate = RWebAudioSampleRate();
+ return data;
+}
+
+static ssize_t ra_write(void *data, const void *buf, size_t size)
+{
+ (void)data;
+ return RWebAudioWrite(buf, size);
+}
+
+static bool ra_stop(void *data)
+{
+ (void)data;
+ return RWebAudioStop();
+}
+
+static void ra_set_nonblock_state(void *data, bool state)
+{
+ (void)data;
+ RWebAudioSetNonblockState(state);
+}
+
+static bool ra_start(void *data)
+{
+ (void)data;
+ return RWebAudioStart();
+}
+
+static bool ra_use_float(void *data)
+{
+ (void)data;
+ return true;
+}
+
+static size_t ra_write_avail(void *data)
+{
+ (void)data;
+ return RWebAudioWriteAvail();
+}
+
+static size_t ra_buffer_size(void *data)
+{
+ (void)data;
+ return RWebAudioBufferSize();
+}
+
+const audio_driver_t audio_rwebaudio = {
+ ra_init,
+ ra_write,
+ ra_stop,
+ ra_start,
+ ra_set_nonblock_state,
+ ra_free,
+ ra_use_float,
+ "rwebaudio",
+ ra_write_avail,
+ ra_buffer_size,
+};
+
diff --git a/config.def.h b/config.def.h
index 909f386bca..da09d29909 100644
--- a/config.def.h
+++ b/config.def.h
@@ -63,6 +63,7 @@ enum
AUDIO_PS3,
AUDIO_XENON360,
AUDIO_WII,
+ AUDIO_RWEBAUDIO,
AUDIO_NULL,
INPUT_ANDROID,
@@ -77,6 +78,7 @@ enum
INPUT_LINUXRAW,
INPUT_APPLE,
INPUT_QNX,
+ INPUT_RWEBINPUT,
INPUT_NULL
};
@@ -130,6 +132,8 @@ enum
#define AUDIO_DEFAULT_DRIVER AUDIO_SL
#elif defined(HAVE_DSOUND)
#define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND
+#elif defined(EMSCRIPTEN)
+#define AUDIO_DEFAULT_DRIVER AUDIO_RWEBAUDIO
#elif defined(HAVE_SDL)
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
#elif defined(HAVE_XAUDIO)
@@ -152,6 +156,8 @@ enum
#define INPUT_DEFAULT_DRIVER INPUT_ANDROID
#elif defined(_WIN32)
#define INPUT_DEFAULT_DRIVER INPUT_DINPUT
+#elif defined(EMSCRIPTEN)
+#define INPUT_DEFAULT_DRIVER INPUT_RWEBINPUT
#elif defined(HAVE_SDL)
#define INPUT_DEFAULT_DRIVER INPUT_SDL
#elif defined(__CELLOS_LV2__)
diff --git a/driver.c b/driver.c
index 3b041229ea..20273357bd 100644
--- a/driver.c
+++ b/driver.c
@@ -84,6 +84,9 @@ static const audio_driver_t *audio_drivers[] = {
#ifdef GEKKO
&audio_gx,
#endif
+#ifdef EMSCRIPTEN
+ &audio_rwebaudio,
+#endif
#ifdef HAVE_NULLAUDIO
&audio_null,
#endif
@@ -165,6 +168,9 @@ static const input_driver_t *input_drivers[] = {
#ifdef __BLACKBERRY_QNX__
&input_qnx,
#endif
+#ifdef EMSCRIPTEN
+ &input_rwebinput,
+#endif
#ifdef HAVE_NULLINPUT
&input_null,
#endif
diff --git a/driver.h b/driver.h
index a5dead5df2..5a30c74d8c 100644
--- a/driver.h
+++ b/driver.h
@@ -511,6 +511,7 @@ extern const audio_driver_t audio_coreaudio;
extern const audio_driver_t audio_xenon360;
extern const audio_driver_t audio_ps3;
extern const audio_driver_t audio_gx;
+extern const audio_driver_t audio_rwebaudio;
extern const audio_driver_t audio_null;
extern const video_driver_t video_gl;
extern const video_driver_t video_psp1;
@@ -536,6 +537,7 @@ extern const input_driver_t input_xinput;
extern const input_driver_t input_linuxraw;
extern const input_driver_t input_apple;
extern const input_driver_t input_qnx;
+extern const input_driver_t input_rwebinput;
extern const input_driver_t input_null;
#include "driver_funcs.h"
diff --git a/emscripten/RWebAudio.h b/emscripten/RWebAudio.h
new file mode 100644
index 0000000000..6911753923
--- /dev/null
+++ b/emscripten/RWebAudio.h
@@ -0,0 +1,28 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Michael Lelli
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include
+#include
+#include
+
+unsigned RWebAudioSampleRate(void);
+void *RWebAudioInit(unsigned latency);
+ssize_t RWebAudioWrite(const void *buf, size_t size);
+bool RWebAudioStop(void);
+bool RWebAudioStart(void);
+void RWebAudioSetNonblockState(bool state);
+void RWebAudioFree(void);
+size_t RWebAudioWriteAvail(void);
+size_t RWebAudioBufferSize(void);
diff --git a/emscripten/RWebInput.h b/emscripten/RWebInput.h
new file mode 100644
index 0000000000..103cc7a0cd
--- /dev/null
+++ b/emscripten/RWebInput.h
@@ -0,0 +1,29 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Michael Lelli
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include
+
+typedef struct rwebinput_state
+{
+ char keys[32];
+ int mouse_x;
+ int mouse_y;
+ char mouse_l;
+ char mouse_r;
+} rwebinput_state_t;
+
+int RWebInputInit(void);
+rwebinput_state_t *RWebInputPoll(int context);
+void RWebInputDestroy(int context);
diff --git a/emscripten/library_rwebaudio.js b/emscripten/library_rwebaudio.js
new file mode 100644
index 0000000000..4e7594ec45
--- /dev/null
+++ b/emscripten/library_rwebaudio.js
@@ -0,0 +1,166 @@
+//"use strict";
+
+var LibraryRWebAudio = {
+ $RA__deps: ['$Browser', 'usleep'],
+ $RA: {
+ BUFFER_SIZE: 256,
+
+ context: null,
+ buffers: [],
+ numBuffers: 0,
+ bufIndex: 0,
+ bufOffset: 0,
+ startTime: 0,
+ nonblock: false,
+ currentTimeWorkaround: false,
+
+ setStartTime: function() {
+ if (RA.context.currentTime) {
+ RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000;
+ var time1 = RA.context.currentTime;
+ _usleep(50);
+ if (time1 === RA.context.currentTime) {
+ RA.currentTimeWorkaround = true;
+ if (RA.startTime === 0) throw 'startTime is 0';
+ }
+ Module["resumeMainLoop"]();
+ } else window['setTimeout'](RA.setStartTime, 0);
+ },
+
+ getCurrentPerfTime: function() {
+ if (!RA.currentTimeWorkaround) return RA.context.currentTime;
+ else if (RA.startTime) return (window['performance']['now']() - RA.startTime) / 1000;
+ else throw 'getCurrentPerfTime() called before start time set';
+ },
+
+ process: function(queueBuffers) {
+ var currentTime = RA.getCurrentPerfTime();
+ for (var i = 0; i < RA.bufIndex; i++) {
+ if (RA.buffers[i].endTime < currentTime) {
+ var buf = RA.buffers.splice(i, 1);
+ RA.buffers[RA.numBuffers - 1] = buf[0];
+ i--;
+ RA.bufIndex--;
+ }
+ }
+ },
+
+ fillBuffer: function(buf, samples) {
+ var count = 0;
+ var leftBuffer = RA.buffers[RA.bufIndex].getChannelData(0);
+ var 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') }}};
+ RA.bufOffset++;
+ count++;
+ samples--;
+ }
+
+ return count;
+ },
+
+ queueAudio: function() {
+ var index = RA.bufIndex;
+
+ var startTime;
+ if (RA.bufIndex) startTime = RA.buffers[RA.bufIndex - 1].endTime;
+ else startTime = RA.context.currentTime;
+ RA.buffers[index].endTime = startTime + RA.buffers[index].duration;
+
+ var bufferSource = RA.context.createBufferSource();
+ bufferSource.buffer = RA.buffers[index];
+ bufferSource.connect(RA.context.destination);
+ bufferSource.start(startTime);
+
+ RA.bufIndex++;
+ RA.bufOffset = 0;
+ },
+
+ block: function() {
+ do {
+ RA.process();
+ } while (RA.bufIndex === RA.numBuffers - 1);
+ }
+ },
+
+ RWebAudioInit: function(latency) {
+ var ac = window['AudioContext'] || window['webkitAudioContext'];
+
+ if (!ac) return 0;
+
+ RA.context = new ac();
+
+ 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);
+
+ RA.nonblock = false;
+ RA.startTime = 0;
+ // chrome hack to get currentTime running
+ RA.context.createGain();
+ window['setTimeout'](RA.setStartTime, 0);
+ Module["pauseMainLoop"]();
+ return 1;
+ },
+
+ RWebAudioSampleRate: function() {
+ return RA.context.sampleRate;
+ },
+
+ RWebAudioWrite: function (buf, size) {
+ RA.process();
+ var samples = size / 8;
+ var count = 0;
+
+ while (samples) {
+ 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();
+ }
+ }
+
+ return count * 8;
+ },
+
+ RWebAudioStop: function() {
+ RA.bufIndex = 0;
+ RA.bufOffset = 0;
+ return true;
+ },
+
+ RWebAudioStart: function() {
+ return true;
+ },
+
+ RWebAudioSetNonblockState: function(state) {
+ RA.nonblock = state;
+ },
+
+ RWebAudioFree: function() {
+ RA.bufIndex = 0;
+ RA.bufOffset = 0;
+ return;
+ },
+
+ RWebAudioBufferSize: function() {
+ return RA.numBuffers * RA.BUFFER_SIZE + RA.BUFFER_SIZE;
+ },
+
+ RWebAudioWriteAvail: function() {
+ RA.process();
+ return ((RA.numBuffers - RA.bufIndex) * RA.BUFFER_SIZE - RA.bufOffset) * 8;
+ }
+};
+
+autoAddDeps(LibraryRWebAudio, '$RA');
+mergeInto(LibraryManager.library, LibraryRWebAudio);
diff --git a/emscripten/library_rwebinput.js b/emscripten/library_rwebinput.js
new file mode 100644
index 0000000000..7f45a747d8
--- /dev/null
+++ b/emscripten/library_rwebinput.js
@@ -0,0 +1,108 @@
+//"use strict";
+
+var LibraryRWebInput = {
+ $RI__deps: ['$Browser'],
+ $RI: {
+ temp: null,
+ contexts: [],
+
+ eventHandler: function(event) {
+ var i;
+ switch (event.type) {
+ case 'mousemove':
+ var x = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'];
+ var y = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'];
+ for (i = 0; i < RI.contexts.length; i++) {
+ var oldX = {{{ makeGetValue('RI.contexts[i].state', '32', 'i32') }}};
+ var oldY = {{{ makeGetValue('RI.contexts[i].state', '36', 'i32') }}};
+ x += oldX;
+ y += oldY;
+ {{{ makeSetValue('RI.contexts[i].state', '32', 'x', 'i32') }}};
+ {{{ makeSetValue('RI.contexts[i].state', '36', 'y', 'i32') }}};
+ }
+ break;
+ case 'mouseup':
+ case 'mousedown':
+ var value;
+ var offset;
+ if (event.button === 0) offset = 40;
+ else if (event.button === 2) offset = 41;
+ else break;
+ if (event.type === 'mouseup') value = 0;
+ else value = 1;
+ for (i = 0; i < RI.contexts.length; i++) {
+ {{{ makeSetValue('RI.contexts[i].state', 'offset', 'value', 'i8') }}};
+ }
+ break;
+ case 'keyup':
+ case 'keydown':
+ var key = event.keyCode;
+ var offset = key >> 3;
+ var bit = 1 << (key & 7);
+ if (offset >= 32) throw 'key code error! bad code: ' + key;
+ for (i = 0; i < RI.contexts.length; i++) {
+ var value = {{{ makeGetValue('RI.contexts[i].state', 'offset', 'i8') }}};
+ if (event.type === 'keyup') value &= ~bit;
+ else value |= bit;
+ {{{ makeSetValue('RI.contexts[i].state', 'offset', 'value', 'i8') }}};
+ }
+ event.preventDefault();
+ break;
+ case 'blur':
+ case 'visibilitychange':
+ for (i = 0; i < RI.contexts.length; i++) {
+ _memset(RI.contexts[i].state, 0, 42);
+ }
+ break;
+ }
+ }
+ },
+
+ RWebInputInit: function(latency) {
+ if (RI.contexts.length === 0) {
+ document.addEventListener('keyup', RI.eventHandler, false);
+ document.addEventListener('keydown', RI.eventHandler, false);
+ document.addEventListener('mousemove', RI.eventHandler, false);
+ document.addEventListener('mouseup', RI.eventHandler, false);
+ document.addEventListener('mousedown', RI.eventHandler, false);
+ document.addEventListener('blur', RI.eventHandler, false);
+ document.addEventListener('onvisbilitychange', RI.eventHandler, false);
+ }
+ if (RI.temp === null) RI.temp = _malloc(42);
+
+ var s = _malloc(42);
+ _memset(s, 0, 42);
+ RI.contexts.push({
+ state: s
+ });
+ return RI.contexts.length;
+ },
+
+ RWebInputPoll: function(context) {
+ context -= 1;
+ var state = RI.contexts[context].state;
+ _memcpy(RI.temp, state, 42);
+ // reset mouse movements
+ {{{ makeSetValue('RI.contexts[context].state', '32', '0', 'i32') }}};
+ {{{ makeSetValue('RI.contexts[context].state', '36', '0', 'i32') }}};
+ return RI.temp;
+ },
+
+ RWebInputDestroy: function (context) {
+ if (context === RI.contexts.length) {
+ RI.contexts.pop();
+ if (RI.contexts.length === 0) {
+ document.removeEventListener('keyup', RI.eventHandler, false);
+ document.removeEventListener('keydown', RI.eventHandler, false);
+ document.removeEventListener('mousemove', RI.eventHandler, false);
+ document.removeEventListener('mouseup', RI.eventHandler, false);
+ document.removeEventListener('mousedown', RI.eventHandler, false);
+ document.removeEventListener('blur', RI.eventHandler, false);
+ document.removeEventListener('onvisbilitychange', RI.eventHandler, false);
+ }
+ }
+ }
+};
+
+autoAddDeps(LibraryRWebInput, '$RI');
+mergeInto(LibraryManager.library, LibraryRWebInput);
diff --git a/file_ext.h b/file_ext.h
index bdfddeea3a..54bfb79cf5 100644
--- a/file_ext.h
+++ b/file_ext.h
@@ -42,6 +42,8 @@
#define EXT_EXECUTABLES "dol|DOL"
#define SALAMANDER_FILE "boot.dol"
#define DEFAULT_EXE_EXT ".dol"
+#elif defined(EMSCRIPTEN)
+#define EXT_EXECUTABLES ""
#endif
#endif
diff --git a/frontend/frontend_emscripten.c b/frontend/frontend_emscripten.c
new file mode 100644
index 0000000000..1941e49984
--- /dev/null
+++ b/frontend/frontend_emscripten.c
@@ -0,0 +1,147 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2013 - Daniel De Matteis
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include
+#include "../general.h"
+#include "../conf/config_file.h"
+#include "../file.h"
+#include "../emscripten/RWebAudio.h"
+
+#ifdef HAVE_RGUI
+#include "../frontend/menu/rgui.h"
+#endif
+
+#if defined(HAVE_RGUI) || defined(HAVE_RMENU) || defined(HAVE_RMENU_XUI)
+#define HAVE_MENU
+#else
+#undef HAVE_MENU
+#endif
+
+static bool menuloop;
+
+static void endloop(void)
+{
+ g_extern.system.shutdown = false;
+ menu_free();
+
+ if (g_extern.config_save_on_exit && *g_extern.config_path)
+ config_save_file(g_extern.config_path);
+
+ if (g_extern.main_is_init)
+ rarch_main_deinit();
+
+ rarch_deinit_msg_queue();
+
+#ifdef PERF_TEST
+ rarch_perf_log();
+#endif
+
+ rarch_main_clear_state();
+
+ exit(0);
+}
+
+static void mainloop(void)
+{
+ if (g_extern.system.shutdown)
+ {
+ endloop();
+ }
+ else if (menuloop)
+ {
+ if (!menu_iterate())
+ {
+ menuloop = false;
+ driver_set_nonblock_state(driver.nonblock_state);
+
+ if (driver.audio_data && !audio_start_func())
+ {
+ RARCH_ERR("Failed to resume audio driver. Will continue without audio.\n");
+ g_extern.audio_active = false;
+ }
+
+ g_extern.lifecycle_mode_state &= ~(1ULL << MODE_MENU);
+ }
+ }
+ else if (g_extern.lifecycle_mode_state & (1ULL << MODE_LOAD_GAME))
+ {
+ load_menu_game_prepare();
+
+ // If ROM load fails, we exit RetroArch. On console it might make more sense to go back to menu though ...
+ if (load_menu_game())
+ g_extern.lifecycle_mode_state |= (1ULL << MODE_GAME);
+ else
+ {
+#ifdef RARCH_CONSOLE
+ g_extern.lifecycle_mode_state |= (1ULL << MODE_MENU);
+#else
+ return;
+#endif
+ }
+
+ g_extern.lifecycle_mode_state &= ~(1ULL << MODE_LOAD_GAME);
+ }
+ else if (g_extern.lifecycle_mode_state & (1ULL << MODE_GAME))
+ {
+ bool r;
+ if (g_extern.is_paused && !g_extern.is_oneshot)
+ r = rarch_main_idle_iterate();
+ else
+ r = rarch_main_iterate();
+ if (!r)
+ g_extern.lifecycle_mode_state &= ~(1ULL << MODE_GAME);
+ }
+ else if (g_extern.lifecycle_mode_state & (1ULL << MODE_MENU))
+ {
+ g_extern.lifecycle_mode_state |= 1ULL << MODE_MENU_PREINIT;
+ // Menu should always run with vsync on.
+ video_set_nonblock_state_func(false);
+
+ if (driver.audio_data)
+ audio_stop_func();
+
+ menuloop = true;
+ }
+ else
+ {
+ g_extern.system.shutdown = true;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ emscripten_set_canvas_size(800, 600);
+
+ rarch_main_clear_state();
+ rarch_init_msg_queue();
+
+ int init_ret;
+ if ((init_ret = rarch_main_init(argc, argv))) return init_ret;
+
+#ifdef HAVE_MENU
+ menu_init();
+ g_extern.lifecycle_mode_state |= 1ULL << MODE_GAME;
+
+ // If we started a ROM directly from command line,
+ // push it to ROM history.
+ if (!g_extern.libretro_dummy)
+ menu_rom_history_push_current();
+#endif
+
+ emscripten_set_main_loop(mainloop, g_settings.video.vsync ? 0 : INT_MAX, 1);
+
+ return 0;
+}
diff --git a/gfx/context/emscriptenegl_ctx.c b/gfx/context/emscriptenegl_ctx.c
new file mode 100644
index 0000000000..f9b89a42ca
--- /dev/null
+++ b/gfx/context/emscriptenegl_ctx.c
@@ -0,0 +1,275 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Hans-Kristian Arntzen
+ * Copyright (C) 2012 - Michael Lelli
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+// VideoCore context, for Rasperry Pi.
+
+#include "../../driver.h"
+#include "../gfx_context.h"
+#include "../gl_common.h"
+#include "../gfx_common.h"
+
+#ifdef HAVE_CONFIG_H
+#include "../../config.h"
+#endif
+
+#include
+#include
+
+#include
+#include
+#include
+
+static EGLContext g_egl_ctx;
+static EGLSurface g_egl_surf;
+static EGLDisplay g_egl_dpy;
+static EGLConfig g_config;
+
+static bool g_inited;
+
+static unsigned g_fb_width;
+static unsigned g_fb_height;
+
+static void gfx_ctx_swap_interval(unsigned interval)
+{
+ // no way to control vsync in WebGL
+ (void)interval;
+}
+
+static void gfx_ctx_check_window(bool *quit,
+ bool *resize, unsigned *width, unsigned *height, unsigned frame_count)
+{
+ (void)frame_count;
+ int iWidth, iHeight, isFullscreen;
+
+ emscripten_get_canvas_size(&iWidth, &iHeight, &isFullscreen);
+ *width = (unsigned) iWidth;
+ *height = (unsigned) iHeight;
+
+ if (*width != g_fb_width || *height != g_fb_height)
+ *resize = true;
+ else
+ *resize = false;
+
+ g_fb_width = (unsigned) iWidth;
+ g_fb_height = (unsigned) iHeight;
+ *quit = false;
+}
+
+static void gfx_ctx_swap_buffers(void)
+{
+ // no-op in emscripten, no way to force swap/wait for vsync in browsers
+ //eglSwapBuffers(g_egl_dpy, g_egl_surf);
+}
+
+static void gfx_ctx_set_resize(unsigned width, unsigned height)
+{
+ (void)width;
+ (void)height;
+}
+
+static void gfx_ctx_update_window_title(void)
+{
+ char buf[128];
+ if (gfx_get_fps(buf, sizeof(buf), false))
+ RARCH_LOG("%s\n", buf);
+}
+
+static void gfx_ctx_get_video_size(unsigned *width, unsigned *height)
+{
+ *width = g_fb_width;
+ *height = g_fb_height;
+}
+
+static void gfx_ctx_destroy(void);
+
+static bool gfx_ctx_init(void)
+{
+ EGLint width;
+ EGLint height;
+
+ RARCH_LOG("[EMSCRIPTEN/EGL]: Initializing...\n");
+ if (g_inited)
+ {
+ RARCH_LOG("[EMSCRIPTEN/EGL]: Attempted to re-initialize driver.\n");
+ return true;
+ }
+
+ EGLint num_config;
+
+ static const EGLint attribute_list[] =
+ {
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+
+ static const EGLint context_attributes[] =
+ {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+
+ // get an EGL display connection
+ g_egl_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!g_egl_dpy)
+ goto error;
+
+ // initialize the EGL display connection
+ if (!eglInitialize(g_egl_dpy, NULL, NULL))
+ goto error;
+
+ // get an appropriate EGL frame buffer configuration
+ if (!eglChooseConfig(g_egl_dpy, attribute_list, &g_config, 1, &num_config))
+ goto error;
+
+ // create an EGL rendering context
+ g_egl_ctx = eglCreateContext(g_egl_dpy, g_config, EGL_NO_CONTEXT, context_attributes);
+ if (!g_egl_ctx)
+ goto error;
+
+ // create an EGL window surface
+ g_egl_surf = eglCreateWindowSurface(g_egl_dpy, g_config, 0, NULL);
+ if (!g_egl_surf)
+ goto error;
+
+ // connect the context to the surface
+ if (!eglMakeCurrent(g_egl_dpy, g_egl_surf, g_egl_surf, g_egl_ctx))
+ goto error;
+
+ eglQuerySurface(g_egl_dpy, g_egl_surf, EGL_WIDTH, &width);
+ eglQuerySurface(g_egl_dpy, g_egl_surf, EGL_HEIGHT, &height);
+ g_fb_width = width;
+ g_fb_height = height;
+ RARCH_LOG("[EMSCRIPTEN/EGL]: Dimensions: %ux%u\n", width, height);
+
+ return true;
+
+error:
+ gfx_ctx_destroy();
+ return false;
+}
+
+static bool gfx_ctx_set_video_mode(
+ unsigned width, unsigned height,
+ bool fullscreen)
+{
+ if (g_inited)
+ return false;
+
+ g_inited = true;
+ return true;
+}
+
+static bool gfx_ctx_bind_api(enum gfx_ctx_api api, unsigned major, unsigned minor)
+{
+ (void)major;
+ (void)minor;
+ switch (api)
+ {
+ case GFX_CTX_OPENGL_ES_API:
+ return eglBindAPI(EGL_OPENGL_ES_API);
+ default:
+ return false;
+ }
+}
+
+static void gfx_ctx_destroy(void)
+{
+ if (g_egl_dpy)
+ {
+ eglMakeCurrent(g_egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ if (g_egl_ctx)
+ {
+ eglDestroyContext(g_egl_dpy, g_egl_ctx);
+ }
+
+ if (g_egl_surf)
+ {
+ eglDestroySurface(g_egl_dpy, g_egl_surf);
+ }
+
+ eglTerminate(g_egl_dpy);
+ }
+
+ g_egl_ctx = NULL;
+ g_egl_surf = NULL;
+ g_egl_dpy = NULL;
+ g_config = 0;
+ g_inited = false;
+}
+
+static void gfx_ctx_input_driver(const input_driver_t **input, void **input_data)
+{
+ *input = NULL;
+
+ void *rwebinput = input_rwebinput.init();
+
+ if (rwebinput)
+ {
+ *input = &input_rwebinput;
+ *input_data = rwebinput;
+ }
+}
+
+static bool gfx_ctx_has_focus(void)
+{
+ return g_inited;
+}
+
+static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol)
+{
+ return eglGetProcAddress(symbol);
+}
+
+static float gfx_ctx_translate_aspect(unsigned width, unsigned height)
+{
+ return (float)width / height;
+}
+
+static bool gfx_ctx_init_egl_image_buffer(const video_info_t *video)
+{
+ return false;
+}
+
+static bool gfx_ctx_write_egl_image(const void *frame, unsigned width, unsigned height, unsigned pitch, bool rgb32, unsigned index, void **image_handle)
+{
+ return false;
+}
+
+const gfx_ctx_driver_t gfx_ctx_emscripten = {
+ gfx_ctx_init,
+ gfx_ctx_destroy,
+ gfx_ctx_bind_api,
+ gfx_ctx_swap_interval,
+ gfx_ctx_set_video_mode,
+ gfx_ctx_get_video_size,
+ gfx_ctx_translate_aspect,
+ gfx_ctx_update_window_title,
+ gfx_ctx_check_window,
+ gfx_ctx_set_resize,
+ gfx_ctx_has_focus,
+ gfx_ctx_swap_buffers,
+ gfx_ctx_input_driver,
+ gfx_ctx_get_proc_address,
+ gfx_ctx_init_egl_image_buffer,
+ gfx_ctx_write_egl_image,
+ NULL,
+ "emscripten",
+};
diff --git a/gfx/gfx_context.c b/gfx/gfx_context.c
index 4d5c45c30e..67d771d665 100644
--- a/gfx/gfx_context.c
+++ b/gfx/gfx_context.c
@@ -13,6 +13,7 @@
* If not, see .
*/
+#include "../general.h"
#include "gfx_context.h"
#include "../general.h"
#include
@@ -52,6 +53,9 @@ static const gfx_ctx_driver_t *gfx_ctx_drivers[] = {
#if defined(IOS) || defined(OSX) //< Don't use __APPLE__ as it breaks basic SDL builds
&gfx_ctx_apple,
#endif
+#ifdef EMSCRIPTEN
+ &gfx_ctx_emscripten,
+#endif
};
const gfx_ctx_driver_t *gfx_ctx_find_driver(const char *ident)
diff --git a/gfx/gfx_context.h b/gfx/gfx_context.h
index c8614799a5..da5c682792 100644
--- a/gfx/gfx_context.h
+++ b/gfx/gfx_context.h
@@ -108,6 +108,7 @@ extern const gfx_ctx_driver_t gfx_ctx_wgl;
extern const gfx_ctx_driver_t gfx_ctx_videocore;
extern const gfx_ctx_driver_t gfx_ctx_bbqnx;
extern const gfx_ctx_driver_t gfx_ctx_apple;
+extern const gfx_ctx_driver_t gfx_ctx_emscripten;
extern const gfx_ctx_driver_t gfx_ctx_null;
const gfx_ctx_driver_t *gfx_ctx_find_driver(const char *ident); // Finds driver with ident. Does not initialize.
diff --git a/input/input_common.c b/input/input_common.c
index a623f52d80..0e7418bfb8 100644
--- a/input/input_common.c
+++ b/input/input_common.c
@@ -16,6 +16,7 @@
#include "input_common.h"
#include
#include
+#include
#include "../general.h"
#include "../driver.h"
@@ -515,6 +516,96 @@ const struct rarch_key_map rarch_key_map_dinput[] = {
};
#endif
+#ifdef EMSCRIPTEN
+const struct rarch_key_map rarch_key_map_rwebinput[] = {
+ { 37, RETROK_LEFT },
+ { 39, RETROK_RIGHT },
+ { 38, RETROK_UP },
+ { 40, RETROK_DOWN },
+ { 13, RETROK_RETURN },
+ { 9, RETROK_TAB },
+ { 45, RETROK_INSERT },
+ { 46, RETROK_DELETE },
+ { 16, RETROK_RSHIFT },
+ { 16, RETROK_LSHIFT },
+ { 17, RETROK_LCTRL },
+ { 35, RETROK_END },
+ { 36, RETROK_HOME },
+ { 34, RETROK_PAGEDOWN },
+ { 33, RETROK_PAGEUP },
+ { 18, RETROK_LALT },
+ { 32, RETROK_SPACE },
+ { 27, RETROK_ESCAPE },
+ { 8, RETROK_BACKSPACE },
+ { 13, RETROK_KP_ENTER },
+ { 107, RETROK_KP_PLUS },
+ { 109, RETROK_KP_MINUS },
+ { 106, RETROK_KP_MULTIPLY },
+ { 111, RETROK_KP_DIVIDE },
+ { 192, RETROK_BACKQUOTE },
+ { 19, RETROK_PAUSE },
+ { 96, RETROK_KP0 },
+ { 97, RETROK_KP1 },
+ { 98, RETROK_KP2 },
+ { 99, RETROK_KP3 },
+ { 100, RETROK_KP4 },
+ { 101, RETROK_KP5 },
+ { 102, RETROK_KP6 },
+ { 103, RETROK_KP7 },
+ { 104, RETROK_KP8 },
+ { 105, RETROK_KP9 },
+ { 48, RETROK_0 },
+ { 49, RETROK_1 },
+ { 50, RETROK_2 },
+ { 51, RETROK_3 },
+ { 52, RETROK_4 },
+ { 53, RETROK_5 },
+ { 54, RETROK_6 },
+ { 55, RETROK_7 },
+ { 56, RETROK_8 },
+ { 57, RETROK_9 },
+ { 112, RETROK_F1 },
+ { 113, RETROK_F2 },
+ { 114, RETROK_F3 },
+ { 115, RETROK_F4 },
+ { 116, RETROK_F5 },
+ { 117, RETROK_F6 },
+ { 118, RETROK_F7 },
+ { 119, RETROK_F8 },
+ { 120, RETROK_F9 },
+ { 121, RETROK_F10 },
+ { 122, RETROK_F11 },
+ { 123, RETROK_F12 },
+ { 65, RETROK_a },
+ { 66, RETROK_b },
+ { 67, RETROK_c },
+ { 68, RETROK_d },
+ { 69, RETROK_e },
+ { 70, RETROK_f },
+ { 71, RETROK_g },
+ { 72, RETROK_h },
+ { 73, RETROK_i },
+ { 74, RETROK_j },
+ { 75, RETROK_k },
+ { 76, RETROK_l },
+ { 77, RETROK_m },
+ { 78, RETROK_n },
+ { 79, RETROK_o },
+ { 80, RETROK_p },
+ { 81, RETROK_q },
+ { 82, RETROK_r },
+ { 83, RETROK_s },
+ { 84, RETROK_t },
+ { 85, RETROK_u },
+ { 86, RETROK_v },
+ { 87, RETROK_w },
+ { 88, RETROK_x },
+ { 89, RETROK_y },
+ { 90, RETROK_z },
+ { 0, RETROK_UNKNOWN },
+};
+#endif
+
static enum retro_key rarch_keysym_lut[RETROK_LAST];
void input_init_keyboard_lut(const struct rarch_key_map *map)
diff --git a/input/input_common.h b/input/input_common.h
index ffc08d99a3..96d641dbdc 100644
--- a/input/input_common.h
+++ b/input/input_common.h
@@ -106,6 +106,7 @@ struct rarch_key_map
extern const struct rarch_key_map rarch_key_map_x11[];
extern const struct rarch_key_map rarch_key_map_sdl[];
extern const struct rarch_key_map rarch_key_map_dinput[];
+extern const struct rarch_key_map rarch_key_map_rwebinput[];
void input_init_keyboard_lut(const struct rarch_key_map *map);
enum retro_key input_translate_keysym_to_rk(unsigned sym);
diff --git a/input/rwebinput_input.c b/input/rwebinput_input.c
new file mode 100644
index 0000000000..1a65eec7d3
--- /dev/null
+++ b/input/rwebinput_input.c
@@ -0,0 +1,149 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2013 - Michael Lelli
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include "input_common.h"
+
+#include "../driver.h"
+
+#include "../boolean.h"
+#include "../general.h"
+
+#include "../emscripten/RWebInput.h"
+
+static bool uninited = false;
+
+typedef struct rwebinput_input
+{
+ rwebinput_state_t state;
+ int context;
+} rwebinput_input_t;
+
+static void *rwebinput_input_init(void)
+{
+ rwebinput_input_t *rwebinput = (rwebinput_input_t*)calloc(1, sizeof(*rwebinput));
+ if (!rwebinput)
+ return NULL;
+
+ rwebinput->context = RWebInputInit();
+ if (!rwebinput->context)
+ {
+ free(rwebinput);
+ return NULL;
+ }
+
+ input_init_keyboard_lut(rarch_key_map_rwebinput);
+
+ return rwebinput;
+}
+
+static bool rwebinput_key_pressed(rwebinput_input_t *rwebinput, int key)
+{
+ if (key >= RETROK_LAST)
+ return false;
+
+ unsigned sym = input_translate_rk_to_keysym((enum retro_key)key);
+ bool ret = rwebinput->state.keys[sym >> 3] & (1 << (sym & 7));
+ return ret;
+}
+
+static bool rwebinput_is_pressed(rwebinput_input_t *rwebinput, const struct retro_keybind *binds, unsigned id)
+{
+ if (id < RARCH_BIND_LIST_END)
+ {
+ const struct retro_keybind *bind = &binds[id];
+ return bind->valid && rwebinput_key_pressed(rwebinput, binds[id].key);
+ }
+ else
+ return false;
+}
+
+static bool rwebinput_bind_button_pressed(void *data, int key)
+{
+ rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
+ return rwebinput_is_pressed(rwebinput, g_settings.input.binds[0], key);
+}
+
+static int16_t rwebinput_mouse_state(rwebinput_input_t *rwebinput, unsigned id)
+{
+ switch (id)
+ {
+ case RETRO_DEVICE_ID_MOUSE_X:
+ return (int16_t) rwebinput->state.mouse_x;
+ case RETRO_DEVICE_ID_MOUSE_Y:
+ return (int16_t) rwebinput->state.mouse_y;
+ case RETRO_DEVICE_ID_MOUSE_LEFT:
+ return rwebinput->state.mouse_l;
+ case RETRO_DEVICE_ID_MOUSE_RIGHT:
+ return rwebinput->state.mouse_r;
+ default:
+ return 0;
+ }
+}
+
+static int16_t rwebinput_input_state(void *data, const struct retro_keybind **binds, unsigned port, unsigned device, unsigned index, unsigned id)
+{
+ rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
+
+ switch (device)
+ {
+ case RETRO_DEVICE_JOYPAD:
+ return rwebinput_is_pressed(rwebinput, binds[port], id);
+
+ case RETRO_DEVICE_KEYBOARD:
+ return rwebinput_key_pressed(rwebinput, id);
+
+ case RETRO_DEVICE_MOUSE:
+ return rwebinput_mouse_state(rwebinput, id);
+
+ default:
+ return 0;
+ }
+}
+
+static void rwebinput_input_free(void *data)
+{
+ rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
+ uninited = true;
+
+ RWebInputDestroy(rwebinput->context);
+
+ free(data);
+}
+
+static void rwebinput_input_poll(void *data)
+{
+ rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
+
+ rwebinput_state_t *state = RWebInputPoll(rwebinput->context);
+ memcpy(&rwebinput->state, state, sizeof(rwebinput->state));
+}
+
+static void rwebinput_grab_mouse(void *data, bool state)
+{
+ (void)data;
+ (void)state;
+}
+
+const input_driver_t input_rwebinput = {
+ rwebinput_input_init,
+ rwebinput_input_poll,
+ rwebinput_input_state,
+ rwebinput_bind_button_pressed,
+ rwebinput_input_free,
+ NULL,
+ "rwebinput",
+ rwebinput_grab_mouse,
+};
+
diff --git a/libretro-test/Makefile b/libretro-test/Makefile
index 15853aaeeb..1998f05f22 100644
--- a/libretro-test/Makefile
+++ b/libretro-test/Makefile
@@ -1,4 +1,8 @@
+ifneq ($(EMSCRIPTEN),)
+ platform = emscripten
+endif
+
ifeq ($(platform),)
platform = unix
ifeq ($(shell uname -a),)
@@ -32,6 +36,10 @@ else ifeq ($(platform), qnx)
TARGET := $(TARGET_NAME)_libretro_qnx.so
fpic := -fPIC
SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined
+else ifeq ($(platform), emscripten)
+ TARGET := $(TARGET_NAME)_libretro_emscripten.so
+ fpic := -fPIC
+ SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined
else
CC = gcc
TARGET := $(TARGET_NAME)_retro.dll
diff --git a/performance.c b/performance.c
index 7829fe265d..faad97135d 100644
--- a/performance.c
+++ b/performance.c
@@ -58,6 +58,10 @@
#include
#endif
+#ifdef EMSCRIPTEN
+#include
+#endif
+
#ifdef PERF_TEST
#define MAX_COUNTERS 64
static struct rarch_perf_counter *perf_counters[MAX_COUNTERS];
@@ -145,6 +149,8 @@ rarch_time_t rarch_get_time_usec(void)
if (clock_gettime(CLOCK_MONOTONIC, &tv) < 0)
return 0;
return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000;
+#elif defined(EMSCRIPTEN)
+ return emscripten_get_now() * 1000;
#else
#error "Your platform does not have a timer function implemented in rarch_get_time_usec(). Cannot continue."
#endif
diff --git a/settings.c b/settings.c
index c13ad20b91..4cad7763cd 100644
--- a/settings.c
+++ b/settings.c
@@ -67,6 +67,8 @@ const char *config_get_default_audio(void)
return "ps3";
case AUDIO_WII:
return "gx";
+ case AUDIO_RWEBAUDIO:
+ return "rwebaudio";
case AUDIO_NULL:
return "null";
default:
@@ -137,6 +139,8 @@ const char *config_get_default_input(void)
return "apple_input";
case INPUT_QNX:
return "qnx_input";
+ case INPUT_RWEBINPUT:
+ return "rwebinput";
case INPUT_NULL:
return "null";
default: