diff --git a/Makefile.emscripten b/Makefile.emscripten index 2b6383a3d4..7622aefa4c 100644 --- a/Makefile.emscripten +++ b/Makefile.emscripten @@ -39,7 +39,8 @@ OBJ = frontend/frontend_emscripten.o \ audio/sinc.o \ audio/null.o \ performance.o \ - core_info.o + core_info.o \ + camera/rwebcam.o HAVE_OPENGL = 1 HAVE_RGUI = 1 @@ -58,8 +59,8 @@ endif libretro = libretro_emscripten.bc LIBS = -lm -DEFINES = -DHAVE_SCREENSHOTS -DHAVE_NULLAUDIO -DHAVE_BSV_MOVIE -LDFLAGS = -L. -s TOTAL_MEMORY=$(MEMORY) -s OUTLINING_LIMIT=50000 --js-library emscripten/library_rwebaudio.js --js-library emscripten/library_rwebinput.js --no-heap-copy +DEFINES = -DHAVE_SCREENSHOTS -DHAVE_CAMERA -DHAVE_NULLAUDIO -DHAVE_BSV_MOVIE +LDFLAGS = -L. -s TOTAL_MEMORY=$(MEMORY) -s OUTLINING_LIMIT=50000 --js-library emscripten/library_rwebaudio.js --js-library emscripten/library_rwebinput.js --js-library emscripten/library_rwebcam.js --no-heap-copy ifeq ($(PERF_TEST), 1) DEFINES += -DPERF_TEST diff --git a/camera/rwebcam.c b/camera/rwebcam.c new file mode 100644 index 0000000000..a8a3c2a503 --- /dev/null +++ b/camera/rwebcam.c @@ -0,0 +1,53 @@ +/* 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 "../emscripten/RWebCam.h" + +static void *rwebcam_init(const char *device, uint64_t caps, unsigned width, unsigned height) +{ + (void)device; + return RWebCamInit(caps, width, height); +} + +static void rwebcam_free(void *data) +{ + RWebCamFree(data); +} + +static bool rwebcam_start(void *data) +{ + return RWebCamStart(data); +} + +static void rwebcam_stop(void *data) +{ + RWebCamStop(data); +} + +static bool rwebcam_poll(void *data, retro_camera_frame_raw_framebuffer_t frame_raw_cb, + retro_camera_frame_opengl_texture_t frame_gl_cb) +{ + return RWebCamPoll(data, frame_raw_cb, frame_gl_cb); +} + +const camera_driver_t camera_rwebcam = { + rwebcam_init, + rwebcam_free, + rwebcam_start, + rwebcam_stop, + rwebcam_poll, + "rwebcam", +}; diff --git a/config.def.h b/config.def.h index 4d731e481a..bb889ee8a3 100644 --- a/config.def.h +++ b/config.def.h @@ -82,6 +82,7 @@ enum INPUT_NULL, CAMERA_V4L2, + CAMERA_RWEBCAM, CAMERA_NULL, }; @@ -183,6 +184,8 @@ enum #if defined(HAVE_V4L2) #define CAMERA_DEFAULT_DRIVER CAMERA_V4L2 +#elif defined(EMSCRIPTEN) +#define CAMERA_DEFAULT_DRIVER CAMERA_RWEBCAM #else #define CAMERA_DEFAULT_DRIVER CAMERA_NULL #endif diff --git a/driver.c b/driver.c index ce624bd5a6..2ab1834752 100644 --- a/driver.c +++ b/driver.c @@ -183,6 +183,9 @@ static const input_driver_t *input_drivers[] = { static const camera_driver_t *camera_drivers[] = { #ifdef HAVE_V4L2 &camera_v4l2, +#endif +#ifdef EMSCRIPTEN + &camera_rwebcam, #endif NULL, }; diff --git a/driver.h b/driver.h index 1e497505bd..1b2cda5dfc 100644 --- a/driver.h +++ b/driver.h @@ -612,6 +612,7 @@ extern const input_driver_t input_qnx; extern const input_driver_t input_rwebinput; extern const input_driver_t input_null; extern const camera_driver_t camera_v4l2; +extern const camera_driver_t camera_rwebcam; extern const input_osk_driver_t input_ps3_osk; #include "driver_funcs.h" diff --git a/emscripten/RWebCam.h b/emscripten/RWebCam.h new file mode 100644 index 0000000000..6556a26f56 --- /dev/null +++ b/emscripten/RWebCam.h @@ -0,0 +1,24 @@ +/* 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 "../driver.h" + +void *RWebCamInit(uint64_t caps, unsigned width, unsigned height); +void RWebCamFree(void *data); +bool RWebCamStart(void *data); +void RWebCamStop(void *data); +bool RWebCamPoll(void *data, retro_camera_frame_raw_framebuffer_t frame_raw_cb, retro_camera_frame_opengl_texture_t frame_gl_cb); diff --git a/emscripten/library_rwebcam.js b/emscripten/library_rwebcam.js index 42192f51a5..77216d4381 100644 --- a/emscripten/library_rwebcam.js +++ b/emscripten/library_rwebcam.js @@ -2,19 +2,20 @@ var LibraryRWebCam = { $RWC: { - /* - run modes: - 0: not running - 1: waiting for user to confirm webcam - 2: running - */ - runMode: 0, - videoElement: null + RETRO_CAMERA_BUFFER_OPENGL_TEXTURE: 0, + RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER: 1, + tmp: null, + + contexts: [], + counter: 0, + + ready: function(data) { + return RWC.contexts[data].runMode == 2 && !RWC.contexts[data].videoElement.paused && RWC.contexts[data].videoElement.videoWidth != 0 && RWC.contexts[data].videoElement.videoHeight != 0; + } }, - RWebCamInit: function() { - RWC.runMode = 0; - + RWebCamInit__deps: ['malloc'], + RWebCamInit: function(caps1, caps2, width, height) { if (!navigator) return 0; navigator.getMedia = navigator.getUserMedia || @@ -24,47 +25,93 @@ var LibraryRWebCam = { if (!navigator.getMedia) return 0; - RWC.videoElement = document.createElement("video"); + var c = ++RWC.counter; - RWC.runMode = 1; + RWC.contexts[c] = []; + RWC.contexts[c].videoElement = document.createElement("video"); + if (width !== 0 && height !== 0) { + RWC.contexts[c].videoElement.width = width; + RWC.contexts[c].videoElement.height = height; + } + RWC.contexts[c].runMode = 1; + RWC.contexts[c].glTex = caps1 & (1 << RWC.RETRO_CAMERA_BUFFER_OPENGL_TEXTURE); + RWC.contexts[c].rawFb = caps1 & (1 << RWC.RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER); navigator.getMedia({video: true, audio: false}, function(stream) { - RWC.videoElement.autoplay = true; - RWC.videoElement.src = URL.createObjectURL(stream); - RWC.runMode = 2; + RWC.contexts[c].videoElement.autoplay = true; + RWC.contexts[c].videoElement.src = URL.createObjectURL(stream); + RWC.contexts[c].runMode = 2; }, function (err) { console.log("webcam request failed", err); RWC.runMode = 0; }); - return 1; + // for getting/storing texture id in GL mode + if (!RWC.tmp) RWC.tmp = _malloc(4); + return c; }, - RWebCamTexImage2D__deps: ['RWebCamReady'], - RWebCamTexImage2D: function(width, height) { - if (!_RWebCamReady()) return 0; - - Module.ctx.texImage2D(Module.ctx.TEXTURE_2D, 0, Module.ctx.RGB, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, RWC.videoElement); - - if (width) {{{ makeSetValue('width', '0', 'RWC.videoElement.videoWidth', 'i32') }}}; - if (height) {{{ makeSetValue('height', '0', 'RWC.videoElement.videoHeight', 'i32') }}}; + RWebCamFree: function(data) { + RWC.contexts[data].videoElement.pause(); + URL.revokeObjectURL(RWC.contexts[data].videoElement.src); + RWC.contexts[data].videoElement = null; + RWC.contexts[data] = null; }, - RWebCamTexSubImage2D__deps: ['RWebCamReady'], - RWebCamTexSubImage2D: function(x, y) { - if (!_RWebCamReady()) return 0; + RWebCamStart__deps: ['glGenTextures', 'glBindTexture', 'glGetIntegerv', 'glTexParameteri'], + RWebCamStart: function(data) { + var ret = 0; + if (RWC.contexts[data].glTex) { + _glGenTextures(1, RWC.tmp); + RWC.contexts[data].glTexId = {{{ makeGetValue('RWC.tmp', '0', 'i32') }}}; + if (RWC.contexts[data].glTexId !== 0) { + // save previous texture + _glGetIntegerv(0x8069 /* GL_TEXTURE_BINDING_2D */, RWC.tmp); + var prev = {{{ makeGetValue('RWC.tmp', '0', 'i32') }}}; + _glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, RWC.contexts[data].glTexId); + /* NPOT textures in WebGL must have these filters and clamping settings */ + _glTexParameteri(0x0DE1 /* GL_TEXTURE_2D */, 0x2800 /* GL_TEXTURE_MAG_FILTER */, 0x2601 /* GL_LINEAR */); + _glTexParameteri(0x0DE1 /* GL_TEXTURE_2D */, 0x2801 /* GL_TEXTURE_MIN_FILTER */, 0x2601 /* GL_LINEAR */); + _glTexParameteri(0x0DE1 /* GL_TEXTURE_2D */, 0x2802 /* GL_TEXTURE_WRAP_S */, 0x812F /* GL_CLAMP_TO_EDGE */); + _glTexParameteri(0x0DE1 /* GL_TEXTURE_2D */, 0x2803 /*GL_TEXTURE_WRAP_T */, 0x812F /* GL_CLAMP_TO_EDGE */); + _glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, prev); + RWC.contexts[data].glFirstFrame = true; + ret = 1; + } + } - Module.ctx.texSubImage2D(Module.ctx.TEXTURE_2D, 0, x, y, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, RWC.videoElement); + return ret; }, - RWebCamReady: function() { - return (RWC.runMode == 2 && !RWC.videoElement.paused && RWC.videoElement.videoWidth != 0 && RWC.videoElement.videoHeight != 0) ? 1 : 0; + RWebCamStop__deps: ['glDeleteTextures'], + RWebCamStop: function(data) { + if (RWC.contexts[data].glTexId) { + _glDeleteTextures(1, RWC.contexts[data].glTexId); + } }, - RWebCamFree: function() { - RWC.videoElement.pause(); - RWC.videoElement = null; - RWC.runMode = 0; + RWebCamPoll__deps: ['glBindTexture', 'glGetIntegerv'], + RWebCamPoll: function(data, frame_raw_cb, frame_gl_cb) { + if (!RWC.ready(data)) return 0; + var ret = 0; + + if (RWC.contexts[data].glTexId !== 0 && frame_gl_cb !== 0) { + _glGetIntegerv(0x8069 /* GL_TEXTURE_BINDING_2D */, RWC.tmp); + var prev = {{{ makeGetValue('RWC.tmp', '0', 'i32') }}}; + _glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, RWC.contexts[data].glTexId); + if (RWC.contexts[data].glFirstFrame) { + Module.ctx.texImage2D(Module.ctx.TEXTURE_2D, 0, Module.ctx.RGB, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, RWC.contexts[data].videoElement); + RWC.contexts[data].glFirstFrame = false; + } else { + Module.ctx.texSubImage2D(Module.ctx.TEXTURE_2D, 0, 0, 0, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, RWC.contexts[data].videoElement); + } + _glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, prev); + Runtime.dynCall('viii', frame_gl_cb, [RWC.contexts[data].glTexId, 0x0DE1 /* GL_TEXTURE_2D */, 0]); + + ret = 1; + } + + return ret; } }; diff --git a/frontend/frontend_emscripten.c b/frontend/frontend_emscripten.c index be836075b7..5c2a044180 100644 --- a/frontend/frontend_emscripten.c +++ b/frontend/frontend_emscripten.c @@ -21,8 +21,8 @@ #include "../file.h" #include "../emscripten/RWebAudio.h" -#ifdef HAVE_RGUI -#include "../frontend/menu/rgui.h" +#ifdef HAVE_MENU +#include "../frontend/menu/menu_common.h" #endif static bool menuloop; diff --git a/settings.c b/settings.c index ced3899fe0..0f99876ccc 100644 --- a/settings.c +++ b/settings.c @@ -155,6 +155,8 @@ const char *config_get_default_camera(void) { case CAMERA_V4L2: return "video4linux2"; + case CAMERA_RWEBCAM: + return "rwebcam"; case CAMERA_NULL: return "null"; default: