Emscripten improvements pt. 4
This commit is contained in:
parent
7cffc6bb60
commit
2d3c18e48d
|
@ -1,4 +1,8 @@
|
|||
# Future
|
||||
- EMSCRIPTEN: Support suspend screensaver
|
||||
- EMSCRIPTEN/RWEBCAM: Fix camera driver
|
||||
- EMSCRIPTEN/RWEBINPUT: Add accelerometer/gyroscope support
|
||||
- EMSCRIPTEN/RWEBPAD: Add rumble support
|
||||
|
||||
# 1.21.0
|
||||
- 3DS: Fix unique IDs for newer cores
|
||||
|
|
|
@ -11,7 +11,7 @@ TARGET_BASE := $(subst .js,,$(TARGET))
|
|||
|
||||
OS = Emscripten
|
||||
OBJ :=
|
||||
DEFINES := -DRARCH_INTERNAL -DHAVE_MAIN -DEMSCRIPTEN -DNO_CANVAS_RESIZE
|
||||
DEFINES := -DRARCH_INTERNAL -DHAVE_MAIN -DEMSCRIPTEN
|
||||
DEFINES += -DHAVE_FILTERS_BUILTIN -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORE_INFO
|
||||
|
||||
HAVE_PATCH = 1
|
||||
|
@ -106,6 +106,9 @@ ASYNC ?= 0
|
|||
LTO ?= 0
|
||||
PTHREAD_POOL_SIZE ?= 4
|
||||
|
||||
ASYNCIFY_ADD ?= dynCall_*,emscripten_mainloop
|
||||
ASYNCIFY_REMOVE ?= threaded_worker
|
||||
|
||||
STACK_SIZE ?= 4194304
|
||||
INITIAL_HEAP ?= 134217728
|
||||
|
||||
|
@ -123,7 +126,7 @@ INITIAL_HEAP ?= 134217728
|
|||
|
||||
OBJDIR := obj-emscripten
|
||||
|
||||
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_take_screenshot,\
|
||||
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_toggle_fullscreen,_cmd_take_screenshot,\
|
||||
_cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focus,_cmd_reset,_cmd_toggle_pause,_cmd_pause,_cmd_unpause,\
|
||||
_cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\
|
||||
_cmd_cheat_get_size,_cmd_cheat_apply_cheats,EmscriptenSendCommand,EmscriptenReceiveCommandReply
|
||||
|
@ -168,7 +171,7 @@ LDFLAGS := -L. --no-heap-copy -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(IN
|
|||
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \
|
||||
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
|
||||
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \
|
||||
-s ENVIRONMENT=web,worker \
|
||||
-s ENVIRONMENT=web,worker -s WASM_BIGINT=1 \
|
||||
--extern-pre-js emscripten/pre.js \
|
||||
--js-library emscripten/library_rwebcam.js \
|
||||
--js-library emscripten/library_platform_emscripten.js
|
||||
|
@ -240,7 +243,7 @@ ifeq ($(HAVE_THREADS), 1)
|
|||
endif
|
||||
|
||||
ifeq ($(WASM_WORKERS), 1)
|
||||
LDFLAGS += -s WASM_WORKERS=2
|
||||
LDFLAGS += -s WASM_WORKERS=1
|
||||
endif
|
||||
|
||||
ifeq ($(ASYNC), 1)
|
||||
|
@ -251,7 +254,7 @@ ifeq ($(ASYNC), 1)
|
|||
endif
|
||||
else ifeq ($(MIN_ASYNC), 1)
|
||||
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_MIN_ASYNCIFY
|
||||
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192 -s ASYNCIFY_IGNORE_INDIRECT=1 -s ASYNCIFY_ADD='dynCall_*,emscripten_mainloop' -s ASYNCIFY_REMOVE='threaded_worker'
|
||||
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192 -s ASYNCIFY_IGNORE_INDIRECT=1 -s ASYNCIFY_ADD='$(ASYNCIFY_ADD)' -s ASYNCIFY_REMOVE='$(ASYNCIFY_REMOVE)'
|
||||
ifeq ($(DEBUG), 1)
|
||||
LDFLAGS += -s ASYNCIFY_ADVISE #-s ASYNCIFY_DEBUG=1
|
||||
endif
|
||||
|
@ -297,21 +300,11 @@ all: $(TARGET)
|
|||
$(libretro_new): ;
|
||||
|
||||
mv_libretro:
|
||||
mv -f $(libretro) $(libretro_new) || true
|
||||
$(Q)mv -f $(libretro) $(libretro_new) || true
|
||||
|
||||
# until emscripten adds something like WASM_WORKERS=2 but for audio worklets, DIY.
|
||||
ifeq ($(HAVE_AUDIOWORKLET), 1)
|
||||
$(TARGET): $(RARCH_OBJ) $(libretro_new) mv_libretro
|
||||
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
|
||||
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
|
||||
$(Q)tr -d '\n' < "$(TARGET_BASE).aw.js" | sed -e "s/[\/&]/\\\\&/g" -e "s/'/\\\\\\\\&/g" > _audioworklet.js
|
||||
$(Q)sed -i.bak -e "s/\"$(TARGET_BASE)\.aw\.js\"/URL.createObjectURL(new Blob(['$$(cat _audioworklet.js)'],{type:'text\/javascript'}))/" -- "$@"
|
||||
$(Q)rm -f "$(TARGET_BASE).aw.js" _audioworklet.js "$@".bak
|
||||
else
|
||||
$(TARGET): $(RARCH_OBJ) $(libretro_new) mv_libretro
|
||||
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
|
||||
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
$(OBJDIR)/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
|
@ -382,6 +382,9 @@ bool audioworklet_external_block(void)
|
|||
{
|
||||
audioworklet_data_t *audioworklet = audioworklet_static_data;
|
||||
|
||||
if (!audioworklet)
|
||||
return false;
|
||||
|
||||
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
|
||||
if (!audioworklet->block_requested)
|
||||
return false;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boolean.h>
|
||||
|
@ -23,7 +24,7 @@
|
|||
#include "../../retroarch.h"
|
||||
|
||||
/* forward declarations */
|
||||
void *RWebCamInit(uint64_t caps, unsigned width, unsigned height);
|
||||
void *RWebCamInit(uint64_t caps, unsigned width, unsigned height, bool debug);
|
||||
void RWebCamFree(void *data);
|
||||
bool RWebCamStart(void *data);
|
||||
void RWebCamStop(void *data);
|
||||
|
@ -34,7 +35,7 @@ static void *rwebcam_init(const char *device, uint64_t caps,
|
|||
unsigned width, unsigned height)
|
||||
{
|
||||
(void)device;
|
||||
return RWebCamInit(caps, width, height);
|
||||
return RWebCamInit(caps, width, height, !!getenv("RWEBCAM_DEBUG"));
|
||||
}
|
||||
|
||||
static void rwebcam_free(void *data)
|
||||
|
|
|
@ -1290,11 +1290,7 @@
|
|||
#endif
|
||||
|
||||
/* Pause gameplay when window loses focus. */
|
||||
#if defined(EMSCRIPTEN)
|
||||
#define DEFAULT_PAUSE_NONACTIVE false
|
||||
#else
|
||||
#define DEFAULT_PAUSE_NONACTIVE true
|
||||
#endif
|
||||
|
||||
/* Pause gameplay when controller disconnects. */
|
||||
#define DEFAULT_PAUSE_ON_DISCONNECT false
|
||||
|
|
|
@ -2,23 +2,14 @@
|
|||
|
||||
var LibraryPlatformEmscripten = {
|
||||
$RPE: {
|
||||
powerStateChange: function(e) {
|
||||
_platform_emscripten_update_power_state(true, Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF, e.target.level, e.target.charging);
|
||||
},
|
||||
|
||||
updateMemoryUsage: function() {
|
||||
// unfortunately this will be innacurate in threaded (worker) builds
|
||||
var used = BigInt(performance.memory.usedJSHeapSize || 0);
|
||||
var limit = BigInt(performance.memory.jsHeapSizeLimit || 0);
|
||||
// emscripten currently only supports passing 32 bit ints, so pack it
|
||||
_platform_emscripten_update_memory_usage(Number(used & 0xFFFFFFFFn), Number(used >> 32n), Number(limit & 0xFFFFFFFFn), Number(limit >> 32n));
|
||||
setTimeout(RPE.updateMemoryUsage, 5000);
|
||||
},
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
sentinelPromise: null,
|
||||
command_queue: [],
|
||||
command_reply_queue: []
|
||||
},
|
||||
|
||||
PlatformEmscriptenWatchCanvasSizeAndDpr__deps: ["platform_emscripten_update_canvas_dimensions"],
|
||||
PlatformEmscriptenWatchCanvasSizeAndDpr__deps: ["platform_emscripten_update_canvas_dimensions_cb"],
|
||||
PlatformEmscriptenWatchCanvasSizeAndDpr: function(dpr) {
|
||||
if (RPE.observer) {
|
||||
RPE.observer.unobserve(Module.canvas);
|
||||
|
@ -36,9 +27,11 @@ var LibraryPlatformEmscripten = {
|
|||
width = Math.round(entry.contentRect.width * window.devicePixelRatio);
|
||||
height = Math.round(entry.contentRect.height * window.devicePixelRatio);
|
||||
}
|
||||
RPE.canvasWidth = width;
|
||||
RPE.canvasHeight = height;
|
||||
// doubles are too big to pass as an argument to exported functions
|
||||
{{{ makeSetValue("dpr", "0", "window.devicePixelRatio", "double") }}};
|
||||
_platform_emscripten_update_canvas_dimensions(width, height, dpr);
|
||||
_platform_emscripten_update_canvas_dimensions_cb(width, height, dpr);
|
||||
});
|
||||
RPE.observer.observe(Module.canvas);
|
||||
window.addEventListener("resize", function() {
|
||||
|
@ -47,27 +40,112 @@ var LibraryPlatformEmscripten = {
|
|||
}, false);
|
||||
},
|
||||
|
||||
PlatformEmscriptenWatchWindowVisibility__deps: ["platform_emscripten_update_window_hidden"],
|
||||
PlatformEmscriptenWatchWindowVisibility__deps: ["platform_emscripten_update_window_hidden_cb"],
|
||||
PlatformEmscriptenWatchWindowVisibility: function() {
|
||||
document.addEventListener("visibilitychange", function() {
|
||||
_platform_emscripten_update_window_hidden(document.visibilityState == "hidden");
|
||||
_platform_emscripten_update_window_hidden_cb(document.visibilityState == "hidden");
|
||||
}, false);
|
||||
},
|
||||
|
||||
PlatformEmscriptenPowerStateInit__deps: ["platform_emscripten_update_power_state"],
|
||||
$PlatformEmscriptenPowerStateChange__deps: ["platform_emscripten_update_power_state_cb"],
|
||||
$PlatformEmscriptenPowerStateChange: function(e) {
|
||||
_platform_emscripten_update_power_state_cb(true, Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF, e.target.level, e.target.charging);
|
||||
},
|
||||
|
||||
PlatformEmscriptenPowerStateInit__deps: ["$PlatformEmscriptenPowerStateChange"],
|
||||
PlatformEmscriptenPowerStateInit: function() {
|
||||
if (!navigator.getBattery) return;
|
||||
navigator.getBattery().then(function(battery) {
|
||||
battery.addEventListener("chargingchange", RPE.powerStateChange);
|
||||
battery.addEventListener("levelchange", RPE.powerStateChange);
|
||||
RPE.powerStateChange({target: battery});
|
||||
battery.addEventListener("chargingchange", PlatformEmscriptenPowerStateChange);
|
||||
battery.addEventListener("levelchange", PlatformEmscriptenPowerStateChange);
|
||||
PlatformEmscriptenPowerStateChange({target: battery});
|
||||
});
|
||||
},
|
||||
|
||||
PlatformEmscriptenMemoryUsageInit__deps: ["platform_emscripten_update_memory_usage"],
|
||||
$PlatformEmscriptenUpdateMemoryUsage__deps: ["platform_emscripten_update_memory_usage_cb"],
|
||||
$PlatformEmscriptenUpdateMemoryUsage: function() {
|
||||
// unfortunately this will be inaccurate in threaded (worker) builds
|
||||
_platform_emscripten_update_memory_usage_cb(BigInt(performance.memory.usedJSHeapSize || 0), BigInt(performance.memory.jsHeapSizeLimit || 0));
|
||||
setTimeout(PlatformEmscriptenUpdateMemoryUsage, 5000);
|
||||
},
|
||||
|
||||
PlatformEmscriptenMemoryUsageInit__deps: ["$PlatformEmscriptenUpdateMemoryUsage"],
|
||||
PlatformEmscriptenMemoryUsageInit: function() {
|
||||
if (!performance.memory) return;
|
||||
RPE.updateMemoryUsage();
|
||||
PlatformEmscriptenUpdateMemoryUsage();
|
||||
},
|
||||
|
||||
PlatformEmscriptenWatchFullscreen__deps: ["platform_emscripten_update_fullscreen_state_cb"],
|
||||
PlatformEmscriptenWatchFullscreen: function() {
|
||||
document.addEventListener("fullscreenchange", function() {
|
||||
_platform_emscripten_update_fullscreen_state_cb(!!document.fullscreenElement);
|
||||
}, false);
|
||||
},
|
||||
|
||||
PlatformEmscriptenGLContextEventInit__deps: ["platform_emscripten_gl_context_lost_cb", "platform_emscripten_gl_context_restored_cb"],
|
||||
PlatformEmscriptenGLContextEventInit: function() {
|
||||
Module.canvas.addEventListener("webglcontextlost", function(e) {
|
||||
e.preventDefault();
|
||||
_platform_emscripten_gl_context_lost_cb();
|
||||
});
|
||||
Module.canvas.addEventListener("webglcontextrestored", function() {
|
||||
_platform_emscripten_gl_context_restored_cb();
|
||||
});
|
||||
},
|
||||
|
||||
$PlatformEmscriptenDoSetCanvasSize: async function(width, height) {
|
||||
// window.resizeTo is terrible; it uses window.outerWidth/outerHeight dimensions,
|
||||
// which are on their own scale entirely, which also changes with the zoom level independently from the DPR.
|
||||
// instead try 2 hardcoded sizes first and interpolate to find the correct size.
|
||||
var expAX = 600;
|
||||
var expAY = 450;
|
||||
var expBX = expAX + 100;
|
||||
var expBY = expAY + 100;
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
window.resizeTo(expAX, expAY);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
var oldWidth = RPE.canvasWidth;
|
||||
var oldHeight = RPE.canvasHeight;
|
||||
window.resizeTo(expBX, expBY);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
var projX = (expBX - expAX) * (width - oldWidth) / (RPE.canvasWidth - oldWidth) + expAX;
|
||||
var projY = (expBY - expAY) * (height - oldHeight) / (RPE.canvasHeight - oldHeight) + expAY;
|
||||
window.resizeTo(Math.round(projX), Math.round(projY));
|
||||
},
|
||||
|
||||
PlatformEmscriptenSetCanvasSize__proxy: "sync",
|
||||
PlatformEmscriptenSetCanvasSize__deps: ["$PlatformEmscriptenDoSetCanvasSize"],
|
||||
PlatformEmscriptenSetCanvasSize: function(width, height) {
|
||||
// c-accessible library functions should not be async
|
||||
PlatformEmscriptenDoSetCanvasSize(width, height);
|
||||
},
|
||||
|
||||
$PlatformEmscriptenDoSetWakeLock: async function(state) {
|
||||
if (state && !RPE.sentinelPromise && navigator?.wakeLock?.request) {
|
||||
try {
|
||||
RPE.sentinelPromise = navigator.wakeLock.request("screen");
|
||||
} catch (e) {}
|
||||
} else if (!state && RPE.sentinelPromise) {
|
||||
try {
|
||||
var sentinel = await RPE.sentinelPromise;
|
||||
sentinel.release();
|
||||
} catch (e) {}
|
||||
RPE.sentinelPromise = null;
|
||||
}
|
||||
},
|
||||
|
||||
PlatformEmscriptenSetWakeLock__proxy: "sync",
|
||||
PlatformEmscriptenSetWakeLock__deps: ["$PlatformEmscriptenDoSetWakeLock"],
|
||||
PlatformEmscriptenSetWakeLock: function(state) {
|
||||
PlatformEmscriptenDoSetWakeLock(state);
|
||||
},
|
||||
|
||||
PlatformEmscriptenGetSystemInfo: function() {
|
||||
var userAgent = navigator?.userAgent?.toLowerCase?.();
|
||||
if (!userAgent) return 0;
|
||||
var browser = 1 + ["chrom", "firefox", "safari"].findIndex(i => userAgent.includes(i));
|
||||
var os = 1 + [/windows/, /linux|cros|android/, /iphone|ipad/, /mac os/].findIndex(i => i.test(userAgent));
|
||||
return browser | (os << 16);
|
||||
},
|
||||
|
||||
$EmscriptenSendCommand__deps: ["platform_emscripten_command_raise_flag"],
|
||||
|
@ -82,4 +160,4 @@ var LibraryPlatformEmscripten = {
|
|||
};
|
||||
|
||||
autoAddDeps(LibraryPlatformEmscripten, '$RPE');
|
||||
mergeInto(LibraryManager.library, LibraryPlatformEmscripten);
|
||||
addToLibrary(LibraryPlatformEmscripten);
|
||||
|
|
|
@ -168,4 +168,4 @@ var LibraryRWebAudio = {
|
|||
};
|
||||
|
||||
autoAddDeps(LibraryRWebAudio, '$RA');
|
||||
mergeInto(LibraryManager.library, LibraryRWebAudio);
|
||||
addToLibrary(LibraryRWebAudio);
|
||||
|
|
|
@ -5,133 +5,273 @@ var LibraryRWebCam = {
|
|||
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;
|
||||
}
|
||||
counter: 0
|
||||
},
|
||||
|
||||
RWebCamInit__deps: ['malloc'],
|
||||
RWebCamInit: function(caps1, caps2, width, height) {
|
||||
if (!navigator) return 0;
|
||||
if (!navigator.mediaDevices.getUserMedia) return 0;
|
||||
$RWebCamReady: function(c) {
|
||||
try {
|
||||
/* try to start video it was paused */
|
||||
if (RWC.contexts[c].videoElement.paused) RWC.contexts[c].videoElement.play();
|
||||
} catch (e) {}
|
||||
return RWC.contexts[c].cameraRunning && !RWC.contexts[c].videoElement.paused && RWC.contexts[c].videoElement.videoWidth != 0 && RWC.contexts[c].videoElement.videoHeight != 0;
|
||||
},
|
||||
|
||||
$RWebCamInitBrowser__proxy: "sync",
|
||||
$RWebCamInitBrowser: function(width, height, glTex, rawFb, debug, proxied) {
|
||||
if (!navigator?.mediaDevices?.getUserMedia) return 0;
|
||||
|
||||
var c = ++RWC.counter;
|
||||
RWC.contexts[c] = {width: width, height: height, glTex: glTex, rawFb: rawFb, debug: debug, proxied: proxied, idealWidth: width, idealHeight: height};
|
||||
return c;
|
||||
},
|
||||
|
||||
RWC.contexts[c] = [];
|
||||
RWC.contexts[c].videoElement = document.createElement("video");
|
||||
RWC.contexts[c].videoElement.classList.add("retroarchWebcamVideo");
|
||||
if (width !== 0 && height !== 0) {
|
||||
RWC.contexts[c].videoElement.width = width;
|
||||
RWC.contexts[c].videoElement.height = height;
|
||||
RWebCamInit__deps: ["malloc", "$RWebCamInitBrowser"],
|
||||
RWebCamInit: function(caps, width, height, debug) {
|
||||
var glTex = Number(caps) & (1 << RWC.RETRO_CAMERA_BUFFER_OPENGL_TEXTURE);
|
||||
var rawFb = Number(caps) & (1 << RWC.RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER);
|
||||
var c = RWebCamInitBrowser(width, height, glTex, rawFb, debug, ENVIRONMENT_IS_PTHREAD);
|
||||
if (!c) return 0;
|
||||
|
||||
if (debug) console.log("RWebCamInit", c);
|
||||
|
||||
if (ENVIRONMENT_IS_PTHREAD) {
|
||||
RWC.contexts[c] = {};
|
||||
RWC.contexts[c].debug = debug;
|
||||
RWC.contexts[c].glTex = glTex;
|
||||
RWC.contexts[c].rawFb = rawFb;
|
||||
}
|
||||
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.mediaDevices.getUserMedia({video: true, audio: false}).then(function(stream) {
|
||||
RWC.contexts[c].videoElement.autoplay = true;
|
||||
RWC.contexts[c].videoElement.srcObject = stream;
|
||||
RWC.contexts[c].runMode = 2;
|
||||
}).catch(function (err) {
|
||||
console.log("webcam request failed", err);
|
||||
RWC.runMode = 0;
|
||||
});
|
||||
|
||||
// for getting/storing texture id in GL mode
|
||||
/* for getting/storing texture id in GL mode */
|
||||
if (!RWC.tmp) RWC.tmp = _malloc(4);
|
||||
return c;
|
||||
},
|
||||
|
||||
RWebCamFree: function(data) {
|
||||
RWC.contexts[data].videoElement.pause();
|
||||
RWC.contexts[data].videoElement = null;
|
||||
RWC.contexts[data] = null;
|
||||
$RWebCamFreeBrowser__proxy: "sync",
|
||||
$RWebCamFreeBrowser: function(c) {
|
||||
RWC.contexts[c] = null;
|
||||
},
|
||||
|
||||
RWebCamStart__deps: ['glGenTextures', 'glBindTexture', 'glGetIntegerv', 'glTexParameteri', 'malloc'],
|
||||
RWebCamStart: function(data) {
|
||||
var ret = 0;
|
||||
if (RWC.contexts[data].glTex) {
|
||||
RWebCamFree__deps: ["$RWebCamFreeBrowser", "RWebCamStop"],
|
||||
RWebCamFree: function(c) {
|
||||
if (RWC.contexts[c].debug) console.log("RWebCamFree", c);
|
||||
if (RWC.contexts[c].running) _RWebCamStop(c); /* need more checks in RA */
|
||||
RWebCamFreeBrowser(c);
|
||||
if (ENVIRONMENT_IS_PTHREAD) RWC.contexts[c] = null;
|
||||
},
|
||||
|
||||
$RWebCamStartBrowser__proxy: "sync",
|
||||
$RWebCamStartBrowser: function(c) {
|
||||
RWC.contexts[c].videoElement = document.createElement("video");
|
||||
RWC.contexts[c].videoElement.classList.add("retroarchWebcamVideo");
|
||||
if (RWC.contexts[c].debug) document.body.appendChild(RWC.contexts[c].videoElement);
|
||||
|
||||
if (RWC.contexts[c].rawFb || RWC.contexts[c].proxied) {
|
||||
RWC.contexts[c].rawFbCanvas = document.createElement("canvas");
|
||||
RWC.contexts[c].rawFbCanvas.classList.add("retroarchWebcamCanvas");
|
||||
if (RWC.contexts[c].debug) document.body.appendChild(RWC.contexts[c].rawFbCanvas);
|
||||
}
|
||||
|
||||
var videoOpts = true;
|
||||
if (RWC.contexts[c].idealWidth && RWC.contexts[c].idealHeight) {
|
||||
/* save us some cropping/scaling, only honored by some browsers */
|
||||
videoOpts = {width: RWC.contexts[c].idealWidth, height: RWC.contexts[c].idealHeight, aspectRatio: RWC.contexts[c].idealWidth / RWC.contexts[c].idealHeight};
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia({audio: false, video: videoOpts}).then(function(stream) {
|
||||
if (!RWC.contexts[c]?.running) {
|
||||
/* too late */
|
||||
for (var track of stream.getVideoTracks()) {
|
||||
track.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
RWC.contexts[c].videoElement.autoplay = true;
|
||||
RWC.contexts[c].videoElement.srcObject = stream;
|
||||
RWC.contexts[c].cameraRunning = true;
|
||||
}).catch(function(e) {
|
||||
console.log("[rwebcam] webcam request failed", e);
|
||||
});
|
||||
|
||||
RWC.contexts[c].running = true;
|
||||
},
|
||||
|
||||
RWebCamStart__deps: ["glGenTextures", "glBindTexture", "glGetIntegerv", "glTexParameteri", "malloc", "$RWebCamStartBrowser"],
|
||||
RWebCamStart: function(c) {
|
||||
if (RWC.contexts[c].debug) console.log("RWebCamStart", c);
|
||||
if (RWC.contexts[c].running) return;
|
||||
if (RWC.contexts[c].glTex) {
|
||||
_glGenTextures(1, RWC.tmp);
|
||||
RWC.contexts[data].glTexId = {{{ makeGetValue('RWC.tmp', '0', 'i32') }}};
|
||||
if (RWC.contexts[data].glTexId !== 0) {
|
||||
// save previous texture
|
||||
RWC.contexts[c].glTexId = {{{ makeGetValue('RWC.tmp', 0, 'i32') }}};
|
||||
if (RWC.contexts[c].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);
|
||||
var prev = {{{ makeGetValue('RWC.tmp', 0, 'i32') }}};
|
||||
_glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, RWC.contexts[c].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;
|
||||
RWC.contexts[c].glFirstFrame = true;
|
||||
}
|
||||
}
|
||||
if (RWC.contexts[data].rawFb) {
|
||||
RWC.contexts[data].rawFbCanvas = document.createElement("canvas");
|
||||
RWC.contexts[data].rawFbCanvas.classList.add("retroarchWebcamCanvas");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
RWebCamStartBrowser(c);
|
||||
RWC.contexts[c].running = true;
|
||||
return 1;
|
||||
},
|
||||
|
||||
RWebCamStop__deps: ['glDeleteTextures', 'free'],
|
||||
RWebCamStop: function(data) {
|
||||
if (RWC.contexts[data].glTexId) {
|
||||
_glDeleteTextures(1, RWC.contexts[data].glTexId);
|
||||
}
|
||||
|
||||
if (RWC.contexts[data].rawFbCanvas) {
|
||||
if (RWC.contexts[data].rawBuffer) {
|
||||
_free(RWC.contexts[data].rawBuffer);
|
||||
RWC.contexts[data].rawBuffer = 0;
|
||||
RWC.contexts[data].rawFbCanvasCtx = null
|
||||
$RWebCamStopBrowser__proxy: "sync",
|
||||
$RWebCamStopBrowser: function(c) {
|
||||
if (RWC.contexts[c].debug && RWC.contexts[c].rawFbCanvas) document.body.removeChild(RWC.contexts[c].rawFbCanvas);
|
||||
RWC.contexts[c].rawFbCanvasCtx = null;
|
||||
RWC.contexts[c].rawFbCanvas = null;
|
||||
RWC.contexts[c].videoElement.pause();
|
||||
if (RWC.contexts[c].cameraRunning) {
|
||||
for (var track of RWC.contexts[c].videoElement.srcObject.getVideoTracks()) {
|
||||
track.stop();
|
||||
}
|
||||
RWC.contexts[data].rawFbCanvas = null;
|
||||
}
|
||||
if (RWC.contexts[c].debug) document.body.removeChild(RWC.contexts[c].videoElement);
|
||||
RWC.contexts[c].videoElement = null;
|
||||
RWC.contexts[c].running = false;
|
||||
},
|
||||
|
||||
RWebCamPoll__deps: ['glBindTexture', 'glGetIntegerv'],
|
||||
RWebCamPoll: function(data, frame_raw_cb, frame_gl_cb) {
|
||||
if (!RWC.ready(data)) return 0;
|
||||
RWebCamStop__deps: ["free", "glDeleteTextures", "$RWebCamStopBrowser"],
|
||||
RWebCamStop: function(c) {
|
||||
if (RWC.contexts[c].debug) console.log("RWebCamStop", c);
|
||||
if (!RWC.contexts[c].running) return;
|
||||
|
||||
if (RWC.contexts[c].glTexId) {
|
||||
_glDeleteTextures(1, RWC.contexts[c].glTexId);
|
||||
}
|
||||
|
||||
if (RWC.contexts[c].rawBuffer) {
|
||||
_free(RWC.contexts[c].rawBuffer);
|
||||
RWC.contexts[c].rawBuffer = 0;
|
||||
}
|
||||
|
||||
RWebCamStopBrowser(c);
|
||||
RWC.contexts[c].running = false;
|
||||
},
|
||||
|
||||
$RWebCamCheckDimensions: function(c) {
|
||||
if (!RWC.contexts[c].width) RWC.contexts[c].width = RWC.contexts[c].videoElement.videoWidth;
|
||||
if (!RWC.contexts[c].height) RWC.contexts[c].height = RWC.contexts[c].videoElement.videoHeight;
|
||||
},
|
||||
|
||||
$RWebCamStoreDimensions__proxy: "sync",
|
||||
$RWebCamStoreDimensions__deps: ["$RWebCamReady", "$RWebCamCheckDimensions"],
|
||||
$RWebCamStoreDimensions: function(c, ptr) {
|
||||
if (!RWebCamReady(c)) return 0;
|
||||
RWebCamCheckDimensions(c);
|
||||
{{{ makeSetValue('ptr', 0, 'RWC.contexts[c].width', 'i32') }}};
|
||||
{{{ makeSetValue('ptr', 4, 'RWC.contexts[c].height', 'i32') }}};
|
||||
return 1;
|
||||
},
|
||||
|
||||
$RWebCamGetImageData__deps: ["$RWebCamReady"],
|
||||
$RWebCamGetImageData: function(c) {
|
||||
if (!RWebCamReady(c)) return 0;
|
||||
if (!RWC.contexts[c].rawFbCanvasCtx) {
|
||||
RWC.contexts[c].rawFbCanvas.width = RWC.contexts[c].width;
|
||||
RWC.contexts[c].rawFbCanvas.height = RWC.contexts[c].height;
|
||||
RWC.contexts[c].rawFbCanvasCtx = RWC.contexts[c].rawFbCanvas.getContext("2d", {willReadFrequently: true});
|
||||
}
|
||||
/* crop to desired aspect ratio if necessary */
|
||||
var oldAspect = RWC.contexts[c].videoElement.videoWidth / RWC.contexts[c].videoElement.videoHeight;
|
||||
var newAspect = RWC.contexts[c].rawFbCanvas.width / RWC.contexts[c].rawFbCanvas.height;
|
||||
var width = RWC.contexts[c].videoElement.videoWidth;
|
||||
var height = RWC.contexts[c].videoElement.videoHeight;
|
||||
var offsetX = 0;
|
||||
var offsetY = 0;
|
||||
if (oldAspect > newAspect) {
|
||||
width = height * newAspect;
|
||||
offsetX = (RWC.contexts[c].videoElement.videoWidth - width) / 2;
|
||||
} else if (oldAspect < newAspect) {
|
||||
height = width / newAspect;
|
||||
offsetY = (RWC.contexts[c].videoElement.videoHeight - height) / 2;
|
||||
}
|
||||
RWC.contexts[c].rawFbCanvasCtx.drawImage(RWC.contexts[c].videoElement,
|
||||
offsetX, offsetY, width, height,
|
||||
0, 0, RWC.contexts[c].rawFbCanvas.width, RWC.contexts[c].rawFbCanvas.height);
|
||||
return RWC.contexts[c].rawFbCanvasCtx.getImageData(0, 0, RWC.contexts[c].rawFbCanvas.width, RWC.contexts[c].rawFbCanvas.height).data;
|
||||
},
|
||||
|
||||
$RWebCamStoreImageData__proxy: "sync",
|
||||
$RWebCamStoreImageData__deps: ["$RWebCamGetImageData"],
|
||||
$RWebCamStoreImageData: function(c, ptr) {
|
||||
var data = RWebCamGetImageData(c);
|
||||
if (!data) return 0;
|
||||
HEAPU8.set(data, ptr);
|
||||
return 1;
|
||||
},
|
||||
|
||||
RWebCamPoll__deps: ["$RWebCamReady", "glBindTexture", "glGetIntegerv", "malloc", "free", "$RWebCamStoreDimensions", "$RWebCamCheckDimensions", "$RWebCamStoreImageData"],
|
||||
RWebCamPoll: function(c, frame_raw_cb, frame_gl_cb) {
|
||||
if (RWC.contexts[c].debug) console.log("RWebCamPoll", c, ENVIRONMENT_IS_PTHREAD, RWC.contexts[c].rawBuffer, RWC.contexts[c].running);
|
||||
if (!RWC.contexts[c].running) return 0; /* need more checks in RA */
|
||||
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;
|
||||
if ((RWC.contexts[c].rawFb && frame_raw_cb !== 0) || ENVIRONMENT_IS_PTHREAD) {
|
||||
if (!RWC.contexts[c].rawBuffer) {
|
||||
if (ENVIRONMENT_IS_PTHREAD) {
|
||||
/* pull dimensions into this thread */
|
||||
var dimensions = _malloc(8);
|
||||
var res = RWebCamStoreDimensions(c, dimensions); /* also checks ready(c) */
|
||||
if (!res) {
|
||||
_free(dimensions);
|
||||
return 0;
|
||||
}
|
||||
RWC.contexts[c].width = {{{ makeGetValue('dimensions', 0, 'i32') }}};
|
||||
RWC.contexts[c].height = {{{ makeGetValue('dimensions', 4, 'i32') }}};
|
||||
_free(dimensions);
|
||||
RWC.contexts[c].length = RWC.contexts[c].width * RWC.contexts[c].height * 4;
|
||||
} else {
|
||||
if (!RWebCamReady(c)) return 0;
|
||||
RWebCamCheckDimensions(c);
|
||||
}
|
||||
RWC.contexts[c].rawBuffer = _malloc(RWC.contexts[c].width * RWC.contexts[c].height * 4 + 1);
|
||||
}
|
||||
/* store at +1 so we can read data as XRGB */
|
||||
if (!RWebCamStoreImageData(c, RWC.contexts[c].rawBuffer + 1)) return 0;
|
||||
}
|
||||
|
||||
if (RWC.contexts[c].glTexId !== 0 && frame_gl_cb !== 0) {
|
||||
var imageSrcGL;
|
||||
if (ENVIRONMENT_IS_PTHREAD) {
|
||||
imageSrcGL = HEAPU8.subarray(RWC.contexts[c].rawBuffer + 1, RWC.contexts[c].rawBuffer + RWC.contexts[c].length + 1);
|
||||
} else {
|
||||
Module.ctx.texSubImage2D(Module.ctx.TEXTURE_2D, 0, 0, 0, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, RWC.contexts[data].videoElement);
|
||||
if (!RWebCamReady(c)) return 0;
|
||||
imageSrcGL = RWC.contexts[c].videoElement;
|
||||
}
|
||||
|
||||
_glGetIntegerv(0x8069 /* GL_TEXTURE_BINDING_2D */, RWC.tmp);
|
||||
var prev = {{{ makeGetValue('RWC.tmp', 0, 'i32') }}};
|
||||
_glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, RWC.contexts[c].glTexId);
|
||||
if (RWC.contexts[c].glFirstFrame) {
|
||||
if (ENVIRONMENT_IS_PTHREAD)
|
||||
Module.ctx.texImage2D(Module.ctx.TEXTURE_2D, 0, Module.ctx.RGBA, RWC.contexts[c].width, RWC.contexts[c].height, 0, Module.ctx.RGBA, Module.ctx.UNSIGNED_BYTE, imageSrcGL);
|
||||
else
|
||||
Module.ctx.texImage2D(Module.ctx.TEXTURE_2D, 0, Module.ctx.RGB, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, imageSrcGL);
|
||||
RWC.contexts[c].glFirstFrame = false;
|
||||
} else {
|
||||
if (ENVIRONMENT_IS_PTHREAD)
|
||||
Module.ctx.texSubImage2D(Module.ctx.TEXTURE_2D, 0, 0, 0, RWC.contexts[c].width, RWC.contexts[c].height, Module.ctx.RGBA, Module.ctx.UNSIGNED_BYTE, imageSrcGL);
|
||||
else
|
||||
Module.ctx.texSubImage2D(Module.ctx.TEXTURE_2D, 0, 0, 0, Module.ctx.RGB, Module.ctx.UNSIGNED_BYTE, imageSrcGL);
|
||||
}
|
||||
_glBindTexture(0x0DE1 /* GL_TEXTURE_2D */, prev);
|
||||
Runtime.dynCall('viii', frame_gl_cb, [RWC.contexts[data].glTexId, 0x0DE1 /* GL_TEXTURE_2D */, 0]);
|
||||
|
||||
{{{ makeDynCall('viip', 'frame_gl_cb') }}}(RWC.contexts[c].glTexId, 0x0DE1 /* GL_TEXTURE_2D */, 0);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
if (RWC.contexts[data].rawFbCanvas && frame_raw_cb !== 0)
|
||||
if (RWC.contexts[c].rawFb && frame_raw_cb !== 0)
|
||||
{
|
||||
if (!RWC.contexts[data].rawFbCanvasCtx) {
|
||||
RWC.contexts[data].rawFbCanvas.width = RWC.contexts[data].videoElement.videoWidth;
|
||||
RWC.contexts[data].rawFbCanvas.height = RWC.contexts[data].videoElement.videoHeight;
|
||||
RWC.contexts[data].rawFbCanvasCtx = RWC.contexts[data].rawFbCanvas.getContext("2d");
|
||||
RWC.contexts[data].rawBuffer = _malloc(RWC.contexts[data].videoElement.videoWidth * RWC.contexts[data].videoElement.videoHeight * 4);
|
||||
}
|
||||
RWC.contexts[data].rawFbCanvasCtx.drawImage(RWC.contexts[data].videoElement, 0, 0, RWC.contexts[data].rawFbCanvas.width, RWC.contexts[data].rawFbCanvas.height);
|
||||
var image = RWC.contexts[data].rawFbCanvasCtx.getImageData(0, 0, RWC.contexts[data].videoElement.videoWidth, RWC.contexts[data].videoElement.videoHeight);
|
||||
Module.HEAPU8.set(image.data, RWC.contexts[data].rawBuffer);
|
||||
Runtime.dynCall('viiii', frame_raw_cb, [RWC.contexts[data].rawBuffer, RWC.contexts[data].videoElement.videoWidth, RWC.contexts[data].videoElement.videoHeight, RWC.contexts[data].videoElement.videoWidth * 4]);
|
||||
|
||||
{{{ makeDynCall('vpiii', 'frame_raw_cb') }}}(RWC.contexts[c].rawBuffer, RWC.contexts[c].width, RWC.contexts[c].height, RWC.contexts[c].width * 4);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
|
@ -140,4 +280,4 @@ var LibraryRWebCam = {
|
|||
};
|
||||
|
||||
autoAddDeps(LibraryRWebCam, '$RWC');
|
||||
mergeInto(LibraryManager.library, LibraryRWebCam);
|
||||
addToLibrary(LibraryRWebCam);
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RetroArch Web Player</title>
|
||||
<script type="text/javascript" src="browserfs.js"></script>
|
||||
|
||||
<style>
|
||||
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
|
||||
textarea.emscripten { border: 0px; font-family: 'Share Tech Mono'; font-size: 12px; width: 100%; overflow:hide; resize:none; color:black; }
|
||||
div.emscripten, h1 { text-align: left; }
|
||||
div.canvas_border { background-color:gray; width:800px; height:600px; margin-left: auto; margin-right: auto; }
|
||||
canvas.emscripten { border: 0px none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<hr/>
|
||||
<div class="emscripten_border" id="canvas_div" style="display: none">
|
||||
<canvas class="emscripten" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()"></canvas>
|
||||
</div>
|
||||
<div class="emscripten emscripten_border" id="openrom">
|
||||
<button id="btnLoad" onclick="document.getElementById('rom').click()">Upload Content</button>
|
||||
<input style="display: none" type="file" id="rom" name="upload" onclick="document.getElementById('btnLoad').click();" onchange="runEmulator(event.target.files);" multiple />
|
||||
<button id="btnStart" onclick="startRetroArch()">Start RetroArch</button>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="emscripten">
|
||||
<input type="checkbox" id="resize"><label for="resize">Resize canvas</label>
|
||||
<input type="checkbox" id="pointerLock" checked><label for="pointerLock">Lock/hide mouse pointer</label>
|
||||
<input type="button" value="Fullscreen" onclick="Module.requestFullScreen(document.getElementById('pointerLock').checked, document.getElementById('resize').checked)"><br>
|
||||
<input type="checkbox" id="vsync"><label for="vsync" id="vsync-label">Enable V-sync (can only be done before loading game)</label><br>
|
||||
<input type="checkbox" id="sdl2"><label for="sdl2" id="sdl2-label">Enable SDL2</label><br>
|
||||
<input type="textbox" id="latency" size="3" maxlength="3" value="96"> <label for="latency" id="latency-label">Audio latency (ms) (increase if you hear pops at fullspeed, can only be done before loading game)</label>
|
||||
</div>
|
||||
|
||||
<div class="emscripten" id="controls">
|
||||
Controls:<br>
|
||||
<br>
|
||||
A button (OK in menu): X<br>
|
||||
B button (Back in menu): Z<br>
|
||||
X Button: S<br>
|
||||
Y Button: A<br>
|
||||
L Button: Q<br>
|
||||
R Button: W<br>
|
||||
D-pad: Arrow Keys<br>
|
||||
Start Button: Enter<br>
|
||||
Select Button: Shift<br>
|
||||
Toggle Menu: F1<br>
|
||||
Fast forward: Spacebar (toggle)<br>
|
||||
Slow motion: E (hold)</br>
|
||||
Save state: F2<br>
|
||||
Load state: F4
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<script type='text/javascript'>
|
||||
var count = 0;
|
||||
function runEmulator(files)
|
||||
{
|
||||
count = files.length;
|
||||
document.getElementById("openrom").innerHTML = '';
|
||||
document.getElementById("openrom").style.display = 'none';
|
||||
for (var i = 0; i < files.length; i++)
|
||||
{
|
||||
filereader = new FileReader();
|
||||
filereader.file_name = files[i].name;
|
||||
filereader.onload = function(){uploadData(this.result, this.file_name)};
|
||||
filereader.readAsArrayBuffer(files[i]);
|
||||
}
|
||||
}
|
||||
function startRetroArch()
|
||||
{
|
||||
Module.FS_createFolder('/', 'etc', true, true);
|
||||
Module.FS_createFolder('/', 'config', true, true);
|
||||
Module.FS_createFolder('/', 'content', true, true);
|
||||
Module.FS_createFolder('/', 'savefiles', true, true);
|
||||
Module.FS_createFolder('/', 'savestates', true, true);
|
||||
var config = 'input_player1_select = shift\n';
|
||||
var latency = parseInt(document.getElementById('latency').value, 10);
|
||||
if (isNaN(latency)) latency = 96;
|
||||
config += 'audio_latency = ' + latency + '\n'
|
||||
if (document.getElementById('vsync').checked)
|
||||
config += 'video_vsync = true\n';
|
||||
else
|
||||
config += 'video_vsync = false\n';
|
||||
Module.FS_createDataFile('/etc', 'retroarch.cfg', config, true, true);
|
||||
document.getElementById('canvas_div').style.display = 'block';
|
||||
document.getElementById('vsync').disabled = true;
|
||||
document.getElementById('vsync-label').style.color = 'gray';
|
||||
document.getElementById('latency').disabled = true;
|
||||
document.getElementById('latency-label').style.color = 'gray';
|
||||
Module['callMain'](Module['arguments']);
|
||||
}
|
||||
|
||||
function uploadData(data, name)
|
||||
{
|
||||
var dataView = new Uint8Array(data);
|
||||
Module.FS_createDataFile('/', name, dataView, true, false);
|
||||
}
|
||||
|
||||
var Module =
|
||||
{
|
||||
noInitialRun: true,
|
||||
arguments: ["-v", "--menu"],
|
||||
preRun: [],
|
||||
postRun: [],
|
||||
print: (function()
|
||||
{
|
||||
return function(text)
|
||||
{
|
||||
text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
};
|
||||
})(),
|
||||
|
||||
printErr: function(text)
|
||||
{
|
||||
var errArgs = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.error(errArgs);
|
||||
},
|
||||
canvas: document.getElementById('canvas'),
|
||||
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left)
|
||||
{
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="browserfs.js"></script>
|
||||
<script type="text/javascript" src="gambatte.js"></script>
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#include <emscripten/threading.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
|
@ -60,7 +61,6 @@
|
|||
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
#include <emscripten/wasm_worker.h>
|
||||
#include <emscripten/threading.h>
|
||||
#include <emscripten/proxying.h>
|
||||
#include <emscripten/atomic.h>
|
||||
#define PLATFORM_SETVAL(type, addr, val) emscripten_atomic_store_##type(addr, val)
|
||||
|
@ -73,10 +73,17 @@
|
|||
#include "platform_emscripten.h"
|
||||
|
||||
void emscripten_mainloop(void);
|
||||
|
||||
/* javascript library functions */
|
||||
void PlatformEmscriptenWatchCanvasSizeAndDpr(double *dpr);
|
||||
void PlatformEmscriptenWatchWindowVisibility(void);
|
||||
void PlatformEmscriptenPowerStateInit(void);
|
||||
void PlatformEmscriptenMemoryUsageInit(void);
|
||||
void PlatformEmscriptenWatchFullscreen(void);
|
||||
void PlatformEmscriptenGLContextEventInit(void);
|
||||
void PlatformEmscriptenSetCanvasSize(int width, int height);
|
||||
void PlatformEmscriptenSetWakeLock(bool state);
|
||||
uint32_t PlatformEmscriptenGetSystemInfo(void);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -88,12 +95,18 @@ typedef struct
|
|||
uint64_t memory_used;
|
||||
uint64_t memory_limit;
|
||||
double device_pixel_ratio;
|
||||
enum platform_emscripten_browser browser;
|
||||
enum platform_emscripten_os os;
|
||||
int raf_interval;
|
||||
int canvas_width;
|
||||
int canvas_height;
|
||||
int power_state_discharge_time;
|
||||
float power_state_level;
|
||||
bool has_async_atomics;
|
||||
bool enable_set_canvas_size;
|
||||
bool disable_detect_enter_fullscreen;
|
||||
bool fullscreen;
|
||||
bool gl_context_lost;
|
||||
volatile bool power_state_charging;
|
||||
volatile bool power_state_supported;
|
||||
volatile bool window_hidden;
|
||||
|
@ -102,6 +115,22 @@ typedef struct
|
|||
|
||||
static emscripten_platform_data_t *emscripten_platform_data = NULL;
|
||||
|
||||
/* defined here since implementation is frontend dependent */
|
||||
void retro_sleep(unsigned msec)
|
||||
{
|
||||
#if defined(EMSCRIPTEN_ASYNCIFY) && !defined(HAVE_THREADS)
|
||||
emscripten_sleep(msec);
|
||||
#elif defined(EMSCRIPTEN_ASYNCIFY)
|
||||
if (emscripten_is_main_browser_thread())
|
||||
emscripten_sleep(msec);
|
||||
else
|
||||
emscripten_thread_sleep(msec);
|
||||
#else
|
||||
/* also works on main thread, but uses busy wait */
|
||||
emscripten_thread_sleep(msec);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* begin exported functions */
|
||||
|
||||
/* saves and states */
|
||||
|
@ -133,6 +162,11 @@ void cmd_undo_load_state(void)
|
|||
|
||||
/* misc */
|
||||
|
||||
void cmd_toggle_fullscreen(void)
|
||||
{
|
||||
command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL);
|
||||
}
|
||||
|
||||
void cmd_take_screenshot(void)
|
||||
{
|
||||
command_event(CMD_EVENT_TAKE_SCREENSHOT, NULL);
|
||||
|
@ -232,7 +266,7 @@ void cmd_cheat_apply_cheats(void)
|
|||
|
||||
/* javascript callbacks */
|
||||
|
||||
void platform_emscripten_update_canvas_dimensions(int width, int height, double *dpr)
|
||||
void platform_emscripten_update_canvas_dimensions_cb(int width, int height, double *dpr)
|
||||
{
|
||||
printf("[INFO] Setting real canvas size: %d x %d\n", width, height);
|
||||
emscripten_set_canvas_element_size("#canvas", width, height);
|
||||
|
@ -243,14 +277,14 @@ void platform_emscripten_update_canvas_dimensions(int width, int height, double
|
|||
PLATFORM_SETVAL(f64, &emscripten_platform_data->device_pixel_ratio, *dpr);
|
||||
}
|
||||
|
||||
void platform_emscripten_update_window_hidden(bool hidden)
|
||||
void platform_emscripten_update_window_hidden_cb(bool hidden)
|
||||
{
|
||||
if (!emscripten_platform_data)
|
||||
return;
|
||||
emscripten_platform_data->window_hidden = hidden;
|
||||
}
|
||||
|
||||
void platform_emscripten_update_power_state(bool supported, int discharge_time, float level, bool charging)
|
||||
void platform_emscripten_update_power_state_cb(bool supported, int discharge_time, float level, bool charging)
|
||||
{
|
||||
if (!emscripten_platform_data)
|
||||
return;
|
||||
|
@ -260,12 +294,40 @@ void platform_emscripten_update_power_state(bool supported, int discharge_time,
|
|||
PLATFORM_SETVAL(f32, &emscripten_platform_data->power_state_level, level);
|
||||
}
|
||||
|
||||
void platform_emscripten_update_memory_usage(uint32_t used1, uint32_t used2, uint32_t limit1, uint32_t limit2)
|
||||
void platform_emscripten_update_memory_usage_cb(uint64_t used, uint64_t limit)
|
||||
{
|
||||
if (!emscripten_platform_data)
|
||||
return;
|
||||
PLATFORM_SETVAL(u64, &emscripten_platform_data->memory_used, used1 | ((uint64_t)used2 << 32));
|
||||
PLATFORM_SETVAL(u64, &emscripten_platform_data->memory_limit, limit1 | ((uint64_t)limit2 << 32));
|
||||
PLATFORM_SETVAL(u64, &emscripten_platform_data->memory_used, used);
|
||||
PLATFORM_SETVAL(u64, &emscripten_platform_data->memory_limit, limit);
|
||||
}
|
||||
|
||||
static void fullscreen_update(void *data)
|
||||
{
|
||||
command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL);
|
||||
}
|
||||
|
||||
void platform_emscripten_update_fullscreen_state_cb(bool state)
|
||||
{
|
||||
if (state == emscripten_platform_data->fullscreen || (state && emscripten_platform_data->disable_detect_enter_fullscreen))
|
||||
return;
|
||||
|
||||
emscripten_platform_data->fullscreen = state;
|
||||
platform_emscripten_run_on_program_thread_async(fullscreen_update, NULL);
|
||||
}
|
||||
|
||||
void platform_emscripten_gl_context_lost_cb(void)
|
||||
{
|
||||
printf("[WARN] WebGL context lost!\n");
|
||||
emscripten_platform_data->gl_context_lost = true;
|
||||
}
|
||||
|
||||
void platform_emscripten_gl_context_restored_cb(void)
|
||||
{
|
||||
printf("[INFO] WebGL context restored.\n");
|
||||
emscripten_platform_data->gl_context_lost = false;
|
||||
/* there might be a better way to do this, but for now this works */
|
||||
command_event(CMD_EVENT_REINIT, NULL);
|
||||
}
|
||||
|
||||
void platform_emscripten_command_raise_flag()
|
||||
|
@ -315,7 +377,7 @@ void platform_emscripten_command_reply(const char *msg, size_t len)
|
|||
|
||||
size_t platform_emscripten_command_read(char **into, size_t max_len)
|
||||
{
|
||||
if (!emscripten_platform_data || !emscripten_platform_data->command_flag)
|
||||
if (!emscripten_platform_data->command_flag)
|
||||
return 0;
|
||||
return MAIN_THREAD_EM_ASM_INT({
|
||||
var next_command = RPE.command_queue.shift();
|
||||
|
@ -334,16 +396,12 @@ size_t platform_emscripten_command_read(char **into, size_t max_len)
|
|||
|
||||
void platform_emscripten_get_canvas_size(int *width, int *height)
|
||||
{
|
||||
if (!emscripten_platform_data)
|
||||
goto error;
|
||||
|
||||
*width = PLATFORM_GETVAL(u32, &emscripten_platform_data->canvas_width);
|
||||
*height = PLATFORM_GETVAL(u32, &emscripten_platform_data->canvas_height);
|
||||
|
||||
if (*width != 0 || *height != 0)
|
||||
return;
|
||||
|
||||
error:
|
||||
*width = 800;
|
||||
*height = 600;
|
||||
RARCH_ERR("[EMSCRIPTEN]: Could not get screen dimensions!\n");
|
||||
|
@ -354,6 +412,15 @@ double platform_emscripten_get_dpr(void)
|
|||
return PLATFORM_GETVAL(f64, &emscripten_platform_data->device_pixel_ratio);
|
||||
}
|
||||
|
||||
unsigned platform_emscripten_get_min_sleep_ms(void)
|
||||
{
|
||||
if (emscripten_platform_data->browser == PLATFORM_EMSCRIPTEN_BROWSER_FIREFOX &&
|
||||
emscripten_platform_data->os == PLATFORM_EMSCRIPTEN_OS_WINDOWS)
|
||||
return 16;
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
bool platform_emscripten_has_async_atomics(void)
|
||||
{
|
||||
return emscripten_platform_data->has_async_atomics;
|
||||
|
@ -366,7 +433,7 @@ bool platform_emscripten_is_window_hidden(void)
|
|||
|
||||
bool platform_emscripten_should_drop_iter(void)
|
||||
{
|
||||
return (emscripten_platform_data->window_hidden && emscripten_platform_data->raf_interval);
|
||||
return (emscripten_platform_data->gl_context_lost || (emscripten_platform_data->window_hidden && emscripten_platform_data->raf_interval));
|
||||
}
|
||||
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
|
@ -413,6 +480,58 @@ void platform_emscripten_set_main_loop_interval(int interval)
|
|||
#endif
|
||||
}
|
||||
|
||||
void platform_emscripten_set_pointer_visibility(bool state)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
if ($0) {
|
||||
Module.canvas.style.removeProperty("cursor");
|
||||
} else {
|
||||
Module.canvas.style.setProperty("cursor", "none");
|
||||
}
|
||||
}, state);
|
||||
}
|
||||
|
||||
static void platform_emscripten_do_set_fullscreen_state(void *data)
|
||||
{
|
||||
bool ran_user_cb = EM_ASM_INT({
|
||||
var func = $0 ? "fullscreenEnter" : "fullscreenExit";
|
||||
if (Module[func]) {
|
||||
Module[func]();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}, emscripten_platform_data->fullscreen);
|
||||
if (ran_user_cb)
|
||||
return;
|
||||
|
||||
if (emscripten_platform_data->fullscreen)
|
||||
emscripten_request_fullscreen("#canvas", false);
|
||||
else
|
||||
emscripten_exit_fullscreen();
|
||||
}
|
||||
|
||||
void platform_emscripten_set_fullscreen_state(bool state)
|
||||
{
|
||||
if (state == emscripten_platform_data->fullscreen)
|
||||
return;
|
||||
|
||||
emscripten_platform_data->fullscreen = state;
|
||||
platform_emscripten_run_on_browser_thread_sync(platform_emscripten_do_set_fullscreen_state, NULL);
|
||||
}
|
||||
|
||||
void platform_emscripten_set_wake_lock(bool state)
|
||||
{
|
||||
PlatformEmscriptenSetWakeLock(state);
|
||||
}
|
||||
|
||||
void platform_emscripten_set_canvas_size(int width, int height)
|
||||
{
|
||||
if (!emscripten_platform_data->enable_set_canvas_size)
|
||||
return;
|
||||
|
||||
PlatformEmscriptenSetCanvasSize(width, height);
|
||||
}
|
||||
|
||||
/* frontend driver impl */
|
||||
|
||||
static void frontend_emscripten_get_env(int *argc, char *argv[],
|
||||
|
@ -575,7 +694,7 @@ static void platform_emscripten_mount_filesystems(void)
|
|||
path_parent_dir(parent, strlen(parent));
|
||||
if (!path_mkdir(parent))
|
||||
{
|
||||
printf("mkdir error %d\n", errno);
|
||||
printf("[OPFS] mkdir error %d\n", errno);
|
||||
abort();
|
||||
}
|
||||
free(parent);
|
||||
|
@ -704,6 +823,7 @@ static int thread_main(int argc, char *argv[])
|
|||
platform_emscripten_mount_filesystems();
|
||||
#endif
|
||||
|
||||
PlatformEmscriptenGLContextEventInit();
|
||||
emscripten_set_main_loop(emscripten_mainloop, 0, 0);
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
emscripten_set_main_loop_timing(EM_TIMING_SETIMMEDIATE, 0);
|
||||
|
@ -737,6 +857,7 @@ static void raf_signaler(void)
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = 0;
|
||||
uint32_t system_info;
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
pthread_attr_t attr;
|
||||
pthread_t thread;
|
||||
|
@ -744,14 +865,58 @@ int main(int argc, char *argv[])
|
|||
/* this never gets freed */
|
||||
emscripten_platform_data = (emscripten_platform_data_t *)calloc(1, sizeof(emscripten_platform_data_t));
|
||||
|
||||
system_info = PlatformEmscriptenGetSystemInfo();
|
||||
emscripten_platform_data->browser = system_info & 0xFFFF;
|
||||
emscripten_platform_data->os = system_info >> 16;
|
||||
|
||||
emscripten_platform_data->enable_set_canvas_size = !!getenv("ENABLE_SET_CANVAS_SIZE");
|
||||
emscripten_platform_data->disable_detect_enter_fullscreen = !!getenv("DISABLE_DETECT_ENTER_FULLSCREEN");
|
||||
emscripten_platform_data->has_async_atomics = EM_ASM_INT({
|
||||
return Atomics?.waitAsync?.toString().includes("[native code]");
|
||||
});
|
||||
|
||||
EM_ASM({
|
||||
/* keyboard events won't work without the canvas being focused */
|
||||
if (!Module.canvas.getAttribute("tabindex"))
|
||||
Module.canvas.setAttribute("tabindex", "-1");
|
||||
Module.canvas.focus();
|
||||
Module.canvas.addEventListener("pointerdown", function() {
|
||||
Module.canvas.focus();
|
||||
}, false);
|
||||
|
||||
/* disable browser right click menu */
|
||||
Module.canvas.addEventListener("contextmenu", function(e) {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
/* background should be black */
|
||||
Module.canvas.style.backgroundColor = "#000000";
|
||||
|
||||
/* border and padding may interfere with pointer event coordinates */
|
||||
Module.canvas.style.setProperty("padding", "0px", "important");
|
||||
Module.canvas.style.setProperty("border", "none", "important");
|
||||
|
||||
/* ensure canvas size is constrained by CSS, otherwise infinite resizing may occur */
|
||||
if (window.getComputedStyle(Module.canvas).display == "inline") {
|
||||
console.warn("[WARN] Canvas should not use display: inline!");
|
||||
Module.canvas.style.display = "inline-block";
|
||||
}
|
||||
var oldWidth = Module.canvas.clientWidth;
|
||||
var oldHeight = Module.canvas.clientHeight;
|
||||
Module.canvas.width = 64;
|
||||
Module.canvas.height = 64;
|
||||
if (oldWidth != Module.canvas.clientWidth || oldHeight != Module.canvas.clientHeight) {
|
||||
console.warn("[WARN] Canvas size should be set using CSS properties!");
|
||||
Module.canvas.style.width = oldWidth + "px";
|
||||
Module.canvas.style.height = oldHeight + "px";
|
||||
}
|
||||
});
|
||||
|
||||
PlatformEmscriptenWatchCanvasSizeAndDpr(malloc(sizeof(double)));
|
||||
PlatformEmscriptenWatchWindowVisibility();
|
||||
PlatformEmscriptenPowerStateInit();
|
||||
PlatformEmscriptenMemoryUsageInit();
|
||||
PlatformEmscriptenWatchFullscreen();
|
||||
|
||||
emscripten_platform_data->raf_interval = 1;
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
|
|
|
@ -20,6 +20,23 @@
|
|||
|
||||
#include <boolean.h>
|
||||
|
||||
enum platform_emscripten_browser
|
||||
{
|
||||
PLATFORM_EMSCRIPTEN_BROWSER_OTHER = 0,
|
||||
PLATFORM_EMSCRIPTEN_BROWSER_CHROMIUM,
|
||||
PLATFORM_EMSCRIPTEN_BROWSER_FIREFOX,
|
||||
PLATFORM_EMSCRIPTEN_BROWSER_SAFARI
|
||||
};
|
||||
|
||||
enum platform_emscripten_os
|
||||
{
|
||||
PLATFORM_EMSCRIPTEN_OS_OTHER = 0,
|
||||
PLATFORM_EMSCRIPTEN_OS_WINDOWS,
|
||||
PLATFORM_EMSCRIPTEN_OS_LINUX,
|
||||
PLATFORM_EMSCRIPTEN_OS_IOS,
|
||||
PLATFORM_EMSCRIPTEN_OS_MACOS
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously run a function on the browser thread.
|
||||
*
|
||||
|
@ -77,6 +94,15 @@ void platform_emscripten_get_canvas_size(int *width, int *height);
|
|||
*/
|
||||
double platform_emscripten_get_dpr(void);
|
||||
|
||||
/**
|
||||
* Get the minimum amount of time that setTimeout (retro_sleep on browser thread) can
|
||||
* sleep for in a loop.
|
||||
* This may vary between browsers: usually 5 ms, but much higher for firefox on windows.
|
||||
*
|
||||
* @return Minimum sleep in milliseconds.
|
||||
*/
|
||||
unsigned platform_emscripten_get_min_sleep_ms(void);
|
||||
|
||||
/**
|
||||
* Check if the browser supports Atomics.waitAsync.
|
||||
*
|
||||
|
@ -128,4 +154,34 @@ void platform_emscripten_exit_fake_block(void);
|
|||
*/
|
||||
void platform_emscripten_set_main_loop_interval(int interval);
|
||||
|
||||
/**
|
||||
* Hide or show the cursor.
|
||||
*
|
||||
* @param state True to show, false to hide.
|
||||
*/
|
||||
void platform_emscripten_set_pointer_visibility(bool state);
|
||||
|
||||
/**
|
||||
* Try to enter or exit fullscreen.
|
||||
*
|
||||
* @param state True to enter, false to exit.
|
||||
*/
|
||||
void platform_emscripten_set_fullscreen_state(bool state);
|
||||
|
||||
/**
|
||||
* Try to prevent the screen from dimming.
|
||||
*
|
||||
* @param state True to request, false to release.
|
||||
*/
|
||||
void platform_emscripten_set_wake_lock(bool state);
|
||||
|
||||
/**
|
||||
* Try to set the real screen dimensions of the canvas.
|
||||
* Will only work if explicitly enabled by the embedder.
|
||||
*
|
||||
* @param width New width
|
||||
* @param height New height
|
||||
*/
|
||||
void platform_emscripten_set_canvas_size(int width, int height);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -63,16 +63,6 @@ static void gfx_ctx_emscripten_check_window(void *data, bool *quit,
|
|||
*quit = false;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_swap_buffers(void *data)
|
||||
{
|
||||
#ifdef HAVE_EGL
|
||||
/* Doesn't really do anything in WebGL, but it might
|
||||
* if we use WebGL workers in the future */
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
egl_swap_buffers(&emscripten->egl);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_get_video_size(void *data,
|
||||
unsigned *width, unsigned *height)
|
||||
{
|
||||
|
@ -181,11 +171,11 @@ error:
|
|||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_set_video_mode(void *data,
|
||||
unsigned width, unsigned height,
|
||||
bool fullscreen)
|
||||
unsigned width, unsigned height, bool fullscreen)
|
||||
{
|
||||
if (g_egl_inited)
|
||||
return false;
|
||||
platform_emscripten_set_fullscreen_state(fullscreen);
|
||||
if (!fullscreen)
|
||||
platform_emscripten_set_canvas_size(width, height);
|
||||
|
||||
g_egl_inited = true;
|
||||
return true;
|
||||
|
@ -212,9 +202,20 @@ static void gfx_ctx_emscripten_input_driver(void *data,
|
|||
*input_data = rwebinput;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_has_focus(void *data) { return g_egl_inited; }
|
||||
static bool gfx_ctx_emscripten_has_focus(void *data) {
|
||||
return g_egl_inited && !platform_emscripten_is_window_hidden();
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_suppress_screensaver(void *data, bool enable) { return false; }
|
||||
static bool gfx_ctx_emscripten_suppress_screensaver(void *data, bool enable)
|
||||
{
|
||||
platform_emscripten_set_wake_lock(enable);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_show_mouse(void *data, bool state)
|
||||
{
|
||||
platform_emscripten_set_pointer_visibility(state);
|
||||
}
|
||||
|
||||
static float gfx_ctx_emscripten_translate_aspect(void *data,
|
||||
unsigned width, unsigned height) { return (float)width / height; }
|
||||
|
@ -259,11 +260,11 @@ const gfx_ctx_driver_t gfx_ctx_emscripten = {
|
|||
gfx_ctx_emscripten_translate_aspect,
|
||||
NULL, /* update_title */
|
||||
gfx_ctx_emscripten_check_window,
|
||||
NULL, /* set_resize */
|
||||
NULL, /* set_resize: no-op */
|
||||
gfx_ctx_emscripten_has_focus,
|
||||
gfx_ctx_emscripten_suppress_screensaver,
|
||||
false,
|
||||
gfx_ctx_emscripten_swap_buffers,
|
||||
true, /* has_windowed */
|
||||
NULL, /* swap_buffers: no-op */
|
||||
gfx_ctx_emscripten_input_driver,
|
||||
#ifdef HAVE_EGL
|
||||
egl_get_proc_address,
|
||||
|
@ -272,11 +273,11 @@ const gfx_ctx_driver_t gfx_ctx_emscripten = {
|
|||
#endif
|
||||
gfx_ctx_emscripten_init_egl_image_buffer,
|
||||
gfx_ctx_emscripten_write_egl_image,
|
||||
NULL,
|
||||
gfx_ctx_emscripten_show_mouse,
|
||||
"egl_emscripten",
|
||||
gfx_ctx_emscripten_get_flags,
|
||||
gfx_ctx_emscripten_set_flags,
|
||||
gfx_ctx_emscripten_bind_hw_render,
|
||||
NULL,
|
||||
NULL
|
||||
NULL, /* get_context_data */
|
||||
NULL /* make_current */
|
||||
};
|
||||
|
|
|
@ -57,12 +57,6 @@ static void gfx_ctx_emscripten_webgl_check_window(void *data, bool *quit,
|
|||
*quit = false;
|
||||
}
|
||||
|
||||
/* https://github.com/emscripten-core/emscripten/issues/17816#issuecomment-1249719343 */
|
||||
static void gfx_ctx_emscripten_webgl_swap_buffers(void *data)
|
||||
{
|
||||
(void)data;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_get_video_size(void *data,
|
||||
unsigned *width, unsigned *height)
|
||||
{
|
||||
|
@ -114,7 +108,7 @@ static void *gfx_ctx_emscripten_webgl_init(void *video_driver)
|
|||
|
||||
EmscriptenWebGLContextAttributes attrs = {0};
|
||||
emscripten_webgl_init_context_attributes(&attrs);
|
||||
attrs.alpha = false;
|
||||
attrs.alpha = true;
|
||||
attrs.depth = true;
|
||||
attrs.stencil = true;
|
||||
attrs.antialias = false;
|
||||
|
@ -152,57 +146,24 @@ error:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_set_canvas_size(int width, int height)
|
||||
{
|
||||
#ifdef NO_CANVAS_RESIZE
|
||||
return false;
|
||||
#endif
|
||||
double dpr = platform_emscripten_get_dpr();
|
||||
EMSCRIPTEN_RESULT r = emscripten_set_element_css_size("#canvas", (double)width / dpr, (double)height / dpr);
|
||||
RARCH_LOG("[EMSCRIPTEN/WebGL]: set canvas size to %d, %d\n", width, height);
|
||||
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
RARCH_ERR("[EMSCRIPTEN/WebGL]: error resizing canvas: %d\n", r);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_set_video_mode(void *data,
|
||||
unsigned width, unsigned height,
|
||||
bool fullscreen)
|
||||
unsigned width, unsigned height, bool fullscreen)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
if (!emscripten || !emscripten->ctx)
|
||||
return false;
|
||||
|
||||
if (width != 0 && height != 0)
|
||||
{
|
||||
if (!gfx_ctx_emscripten_webgl_set_canvas_size(width, height))
|
||||
return false;
|
||||
}
|
||||
emscripten->fb_width = width;
|
||||
emscripten->fb_height = height;
|
||||
platform_emscripten_set_fullscreen_state(fullscreen);
|
||||
if (!fullscreen)
|
||||
platform_emscripten_set_canvas_size(width, height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gfx_ctx_emscripten_webgl_set_resize(void *data, unsigned width, unsigned height)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
if (!emscripten || !emscripten->ctx)
|
||||
return false;
|
||||
return gfx_ctx_emscripten_webgl_set_canvas_size(width, height);
|
||||
}
|
||||
|
||||
static enum gfx_ctx_api gfx_ctx_emscripten_webgl_get_api(void *data) { return GFX_CTX_OPENGL_ES_API; }
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_bind_api(void *data,
|
||||
enum gfx_ctx_api api, unsigned major, unsigned minor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
enum gfx_ctx_api api, unsigned major, unsigned minor) { return true; }
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_input_driver(void *data,
|
||||
const char *name,
|
||||
|
@ -216,10 +177,19 @@ static void gfx_ctx_emscripten_webgl_input_driver(void *data,
|
|||
static bool gfx_ctx_emscripten_webgl_has_focus(void *data)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
return emscripten && emscripten->ctx;
|
||||
return emscripten && emscripten->ctx && !platform_emscripten_is_window_hidden();
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_suppress_screensaver(void *data, bool enable) { return false; }
|
||||
static bool gfx_ctx_emscripten_webgl_suppress_screensaver(void *data, bool enable)
|
||||
{
|
||||
platform_emscripten_set_wake_lock(enable);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_show_mouse(void *data, bool state)
|
||||
{
|
||||
platform_emscripten_set_pointer_visibility(state);
|
||||
}
|
||||
|
||||
static float gfx_ctx_emscripten_webgl_translate_aspect(void *data,
|
||||
unsigned width, unsigned height) { return (float)width / height; }
|
||||
|
@ -260,16 +230,16 @@ const gfx_ctx_driver_t gfx_ctx_emscripten_webgl = {
|
|||
gfx_ctx_emscripten_webgl_translate_aspect,
|
||||
NULL, /* update_title */
|
||||
gfx_ctx_emscripten_webgl_check_window,
|
||||
gfx_ctx_emscripten_webgl_set_resize,
|
||||
NULL, /* set_resize: no-op */
|
||||
gfx_ctx_emscripten_webgl_has_focus,
|
||||
gfx_ctx_emscripten_webgl_suppress_screensaver,
|
||||
false,
|
||||
gfx_ctx_emscripten_webgl_swap_buffers,
|
||||
true, /* has_windowed */
|
||||
NULL, /* swap_buffers: no-op: https://github.com/emscripten-core/emscripten/issues/17816#issuecomment-1249719343 */
|
||||
gfx_ctx_emscripten_webgl_input_driver,
|
||||
gfx_ctx_emscripten_webgl_get_proc_address,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
gfx_ctx_emscripten_webgl_show_mouse,
|
||||
"webgl_emscripten",
|
||||
gfx_ctx_emscripten_webgl_get_flags,
|
||||
gfx_ctx_emscripten_webgl_set_flags,
|
||||
|
|
|
@ -87,11 +87,21 @@ typedef struct rwebinput_mouse_states
|
|||
uint8_t buttons;
|
||||
} rwebinput_mouse_state_t;
|
||||
|
||||
typedef struct rwebinput_motion_states
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
bool enabled;
|
||||
} rwebinput_motion_state_t;
|
||||
|
||||
typedef struct rwebinput_input
|
||||
{
|
||||
rwebinput_mouse_state_t mouse; /* double alignment */
|
||||
rwebinput_keyboard_event_queue_t keyboard; /* ptr alignment */
|
||||
rwebinput_pointer_state_t pointer[MAX_TOUCH]; /* int alignment */
|
||||
rwebinput_motion_state_t accelerometer; /* float alignment */
|
||||
rwebinput_motion_state_t gyroscope; /* float alignment */
|
||||
unsigned pointer_count;
|
||||
bool keys[RETROK_LAST];
|
||||
bool pointerlock_active;
|
||||
|
@ -406,6 +416,26 @@ static EM_BOOL rwebinput_pointerlockchange_cb(int event_type,
|
|||
return EM_TRUE;
|
||||
}
|
||||
|
||||
static EM_BOOL rwebinput_devicemotion_cb(int event_type,
|
||||
const EmscriptenDeviceMotionEvent *device_motion_event, void *user_data)
|
||||
{
|
||||
rwebinput_input_t *rwebinput = (rwebinput_input_t*)user_data;
|
||||
|
||||
/* TODO: what units does mGBA want? does something need to be changed on the core side? */
|
||||
/* given in m/s^2 (inverted) */
|
||||
rwebinput->accelerometer.x = device_motion_event->accelerationIncludingGravityX / -9.8;
|
||||
rwebinput->accelerometer.y = device_motion_event->accelerationIncludingGravityY / -9.8;
|
||||
rwebinput->accelerometer.z = device_motion_event->accelerationIncludingGravityZ / -9.8;
|
||||
/* XYZ == BetaGammaAlpha according to W3C? in my testing it is AlphaBetaGamma... */
|
||||
/* libretro wants radians/s but it is too fast in mGBA, see above comment */
|
||||
/* given in degrees/s */
|
||||
rwebinput->gyroscope.x = device_motion_event->rotationRateAlpha / 180;
|
||||
rwebinput->gyroscope.y = device_motion_event->rotationRateBeta / 180;
|
||||
rwebinput->gyroscope.z = device_motion_event->rotationRateGamma / 180;
|
||||
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
static void *rwebinput_input_init(const char *joypad_driver)
|
||||
{
|
||||
EMSCRIPTEN_RESULT r;
|
||||
|
@ -746,6 +776,84 @@ static void rwebinput_input_free(void *data)
|
|||
free(data);
|
||||
}
|
||||
|
||||
static bool rwebinput_set_sensor_state(void *data, unsigned port,
|
||||
enum retro_sensor_action action, unsigned rate)
|
||||
{
|
||||
rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
|
||||
EMSCRIPTEN_RESULT r;
|
||||
bool old_state = rwebinput->accelerometer.enabled || rwebinput->gyroscope.enabled;
|
||||
bool new_state;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case RETRO_SENSOR_ACCELEROMETER_ENABLE:
|
||||
rwebinput->accelerometer.enabled = true;
|
||||
break;
|
||||
case RETRO_SENSOR_ACCELEROMETER_DISABLE:
|
||||
rwebinput->accelerometer.enabled = false;
|
||||
break;
|
||||
case RETRO_SENSOR_GYROSCOPE_ENABLE:
|
||||
rwebinput->gyroscope.enabled = true;
|
||||
break;
|
||||
case RETRO_SENSOR_GYROSCOPE_DISABLE:
|
||||
rwebinput->gyroscope.enabled = false;
|
||||
break;
|
||||
case RETRO_SENSOR_ILLUMINANCE_ENABLE:
|
||||
case RETRO_SENSOR_ILLUMINANCE_DISABLE:
|
||||
return false; /* not supported (browsers removed support for now) */
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
new_state = rwebinput->accelerometer.enabled || rwebinput->gyroscope.enabled;
|
||||
|
||||
if (!old_state && new_state)
|
||||
{
|
||||
r = emscripten_set_devicemotion_callback(rwebinput, false, rwebinput_devicemotion_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
RARCH_ERR(
|
||||
"[EMSCRIPTEN/INPUT] failed to create devicemotion callback: %d\n", r);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (old_state && !new_state)
|
||||
{
|
||||
r = emscripten_set_devicemotion_callback(rwebinput, false, NULL);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
RARCH_ERR(
|
||||
"[EMSCRIPTEN/INPUT] failed to remove devicemotion callback: %d\n", r);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static float rwebinput_get_sensor_input(void *data, unsigned port, unsigned id)
|
||||
{
|
||||
rwebinput_input_t *rwebinput = (rwebinput_input_t*)data;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case RETRO_SENSOR_ACCELEROMETER_X:
|
||||
return rwebinput->accelerometer.x;
|
||||
case RETRO_SENSOR_ACCELEROMETER_Y:
|
||||
return rwebinput->accelerometer.y;
|
||||
case RETRO_SENSOR_ACCELEROMETER_Z:
|
||||
return rwebinput->accelerometer.z;
|
||||
case RETRO_SENSOR_GYROSCOPE_X:
|
||||
return rwebinput->gyroscope.x;
|
||||
case RETRO_SENSOR_GYROSCOPE_Y:
|
||||
return rwebinput->gyroscope.y;
|
||||
case RETRO_SENSOR_GYROSCOPE_Z:
|
||||
return rwebinput->gyroscope.z;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
static void rwebinput_process_keyboard_events(
|
||||
rwebinput_input_t *rwebinput,
|
||||
rwebinput_keyboard_event_t *event)
|
||||
|
@ -840,8 +948,8 @@ input_driver_t input_rwebinput = {
|
|||
rwebinput_input_poll,
|
||||
rwebinput_input_state,
|
||||
rwebinput_input_free,
|
||||
NULL,
|
||||
NULL,
|
||||
rwebinput_set_sensor_state,
|
||||
rwebinput_get_sensor_input,
|
||||
rwebinput_get_capabilities,
|
||||
"rwebinput",
|
||||
rwebinput_grab_mouse,
|
||||
|
|
|
@ -30,9 +30,18 @@
|
|||
#define NUM_BUTTONS 64
|
||||
#define NUM_AXES 64
|
||||
|
||||
struct rwebpad_joypad_rumble_state
|
||||
{
|
||||
uint16_t pending_strong;
|
||||
uint16_t pending_weak;
|
||||
uint16_t strong;
|
||||
uint16_t weak;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct EmscriptenGamepadEvent pads[DEFAULT_MAX_PADS];
|
||||
struct rwebpad_joypad_rumble_state rumble[DEFAULT_MAX_PADS];
|
||||
bool live_pads[DEFAULT_MAX_PADS];
|
||||
} rwebpad_joypad_data_t;
|
||||
|
||||
|
@ -45,6 +54,9 @@ static EM_BOOL rwebpad_gamepad_cb(int event_type,
|
|||
unsigned vid = 1;
|
||||
unsigned pid = 1;
|
||||
|
||||
if (gamepad_event->index >= DEFAULT_MAX_PADS)
|
||||
return EM_FALSE;
|
||||
|
||||
switch (event_type)
|
||||
{
|
||||
case EMSCRIPTEN_EVENT_GAMEPADCONNECTED:
|
||||
|
@ -93,12 +105,11 @@ static void *rwebpad_joypad_init(void *data)
|
|||
return (void*)(-1);
|
||||
}
|
||||
|
||||
static const char *rwebpad_joypad_name(unsigned pad)
|
||||
static const char *rwebpad_joypad_name(unsigned port)
|
||||
{
|
||||
if (pad >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[pad]) {
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port])
|
||||
return "";
|
||||
}
|
||||
return rwebpad_joypad_data->pads[pad].id;
|
||||
return rwebpad_joypad_data->pads[port].id;
|
||||
}
|
||||
|
||||
static int32_t rwebpad_joypad_button(unsigned port, uint16_t joykey)
|
||||
|
@ -116,7 +127,8 @@ static void rwebpad_joypad_get_buttons(unsigned port, input_bits_t *state)
|
|||
{
|
||||
EmscriptenGamepadEvent gamepad_state;
|
||||
unsigned i;
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port]) {
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port])
|
||||
{
|
||||
BIT256_CLEAR_ALL_PTR(state);
|
||||
return;
|
||||
}
|
||||
|
@ -151,9 +163,8 @@ static int16_t rwebpad_joypad_axis_state(
|
|||
|
||||
static int16_t rwebpad_joypad_axis(unsigned port, uint32_t joyaxis)
|
||||
{
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port]) {
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port])
|
||||
return 0;
|
||||
}
|
||||
return rwebpad_joypad_axis_state(&rwebpad_joypad_data->pads[port], port, joyaxis);
|
||||
}
|
||||
|
||||
|
@ -192,14 +203,41 @@ static int16_t rwebpad_joypad_state(
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void rwebpad_joypad_update_rumble(unsigned port)
|
||||
{
|
||||
bool rumble_old_state = rwebpad_joypad_data->rumble[port].strong || rwebpad_joypad_data->rumble[port].weak;
|
||||
bool rumble_new_state = rwebpad_joypad_data->rumble[port].pending_strong || rwebpad_joypad_data->rumble[port].pending_weak;
|
||||
rwebpad_joypad_data->rumble[port].strong = rwebpad_joypad_data->rumble[port].pending_strong;
|
||||
rwebpad_joypad_data->rumble[port].weak = rwebpad_joypad_data->rumble[port].pending_weak;
|
||||
|
||||
if (rumble_new_state)
|
||||
{
|
||||
EM_ASM({
|
||||
try {
|
||||
JSEvents?.lastGamepadState?.[$0]?.vibrationActuator?.playEffect?.("dual-rumble", {startDelay: 0, duration: 200, strongMagnitude: $1 / 65536, weakMagnitude: $2 / 65536});
|
||||
} catch (e) {}
|
||||
}, port, rwebpad_joypad_data->rumble[port].strong, rwebpad_joypad_data->rumble[port].weak);
|
||||
}
|
||||
else if (rumble_old_state && !rumble_new_state)
|
||||
{
|
||||
EM_ASM({
|
||||
try {
|
||||
JSEvents?.lastGamepadState?.[$0]?.vibrationActuator?.reset?.();
|
||||
} catch (e) {}
|
||||
}, port);
|
||||
}
|
||||
}
|
||||
|
||||
static void rwebpad_joypad_do_poll(void *data)
|
||||
{
|
||||
int i;
|
||||
unsigned port;
|
||||
emscripten_sample_gamepad_data();
|
||||
for (i = 0; i < DEFAULT_MAX_PADS; i++)
|
||||
for (port = 0; port < DEFAULT_MAX_PADS; port++)
|
||||
{
|
||||
if (rwebpad_joypad_data->live_pads[i])
|
||||
emscripten_get_gamepad_status(i, &rwebpad_joypad_data->pads[i]);
|
||||
if (!rwebpad_joypad_data->live_pads[port])
|
||||
continue;
|
||||
emscripten_get_gamepad_status(port, &rwebpad_joypad_data->pads[port]);
|
||||
rwebpad_joypad_update_rumble(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,9 +246,29 @@ static void rwebpad_joypad_poll(void)
|
|||
platform_emscripten_run_on_browser_thread_sync(rwebpad_joypad_do_poll, NULL);
|
||||
}
|
||||
|
||||
static bool rwebpad_joypad_query_pad(unsigned pad)
|
||||
static bool rwebpad_joypad_query_pad(unsigned port)
|
||||
{
|
||||
return rwebpad_joypad_data->live_pads[pad];
|
||||
return rwebpad_joypad_data->live_pads[port];
|
||||
}
|
||||
|
||||
static bool rwebpad_joypad_set_rumble(unsigned port, enum retro_rumble_effect effect, uint16_t strength)
|
||||
{
|
||||
if (port >= DEFAULT_MAX_PADS || !rwebpad_joypad_data->live_pads[port])
|
||||
return false;
|
||||
|
||||
switch (effect)
|
||||
{
|
||||
case RETRO_RUMBLE_STRONG:
|
||||
rwebpad_joypad_data->rumble[port].pending_strong = strength;
|
||||
break;
|
||||
case RETRO_RUMBLE_WEAK:
|
||||
rwebpad_joypad_data->rumble[port].pending_weak = strength;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rwebpad_joypad_destroy(void) { }
|
||||
|
@ -224,7 +282,7 @@ input_device_driver_t rwebpad_joypad = {
|
|||
rwebpad_joypad_get_buttons,
|
||||
rwebpad_joypad_axis,
|
||||
rwebpad_joypad_poll,
|
||||
NULL, /* set_rumble */
|
||||
rwebpad_joypad_set_rumble,
|
||||
NULL, /* set_rumble_gain */
|
||||
NULL, /* set_sensor_state */
|
||||
NULL, /* get_sensor_input */
|
||||
|
|
|
@ -96,6 +96,8 @@
|
|||
#define DEFAULT_MAX_PADS 4
|
||||
#elif defined(DINGUX)
|
||||
#define DEFAULT_MAX_PADS 2
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#define DEFAULT_MAX_PADS 4
|
||||
#else
|
||||
#define DEFAULT_MAX_PADS 16
|
||||
#endif /* defined(ANDROID) */
|
||||
|
|
|
@ -39,9 +39,6 @@
|
|||
#include <psp2/kernel/threadmgr.h>
|
||||
#elif defined(_3DS)
|
||||
#include <3ds.h>
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/threading.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
@ -101,18 +98,15 @@ static int nanosleepDOS(const struct timespec *rqtp, struct timespec *rmtp)
|
|||
#define retro_sleep(msec) (usleep(1000 * (msec)))
|
||||
#elif defined(WIIU)
|
||||
#define retro_sleep(msec) (OSSleepTicks(ms_to_ticks((msec))))
|
||||
#elif defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY) && !defined(HAVE_THREADS)
|
||||
#define retro_sleep(msec) (emscripten_sleep(msec))
|
||||
#elif defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY)
|
||||
static INLINE void retro_sleep(unsigned msec)
|
||||
{
|
||||
if (emscripten_is_main_browser_thread())
|
||||
emscripten_sleep(msec);
|
||||
else
|
||||
emscripten_thread_sleep(msec);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
/* defined in frontend */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void retro_sleep(unsigned msec);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#define retro_sleep(msec) (emscripten_thread_sleep(msec))
|
||||
#endif
|
||||
#else
|
||||
static INLINE void retro_sleep(unsigned msec)
|
||||
{
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="webplayer-container">
|
||||
<canvas class="webplayer" id="canvas" tabindex="1"></canvas>
|
||||
<canvas class="webplayer" id="canvas"></canvas>
|
||||
<div id="webplayer-preview"></div>
|
||||
</div>
|
||||
<script src="libretro.js"></script>
|
||||
|
|
|
@ -339,6 +339,9 @@ label {
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
left: round(50%, 1px);
|
||||
top: round(50%, 1px);
|
||||
transform: translate(round(-50%, 1px), round(-50%, 1px));
|
||||
max-height: calc(100vh - 40px);
|
||||
max-width: calc(100vw - 40px);
|
||||
width: 750px;
|
||||
|
@ -388,6 +391,19 @@ label {
|
|||
margin-top: 0px;
|
||||
}
|
||||
|
||||
/* debug */
|
||||
|
||||
.retroarchWebcamVideo, .retroarchWebcamCanvas {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
.retroarchWebcamCanvas {
|
||||
top: 112px;
|
||||
}
|
||||
|
||||
/* mobile */
|
||||
|
||||
@media only screen and (max-width: 720px) {
|
||||
|
|
|
@ -170,6 +170,10 @@ var Module = {
|
|||
module.ENV["OPFS_MOUNT"] = "/home/web_user";
|
||||
}
|
||||
],
|
||||
locateFile: function(path, prefix) {
|
||||
if (path.endsWith(".js")) return typeof this.mainScriptUrlOrBlob == "string" ? this.mainScriptUrlOrBlob : URL.createObjectURL(this.mainScriptUrlOrBlob);
|
||||
return path;
|
||||
},
|
||||
onRuntimeInitialized: function() {
|
||||
appInitialized();
|
||||
},
|
||||
|
@ -313,19 +317,15 @@ function startRetroArch() {
|
|||
|
||||
btnMenu.classList.remove("disabled");
|
||||
btnMenu.addEventListener("click", function() {
|
||||
Module._cmd_toggle_menu();
|
||||
Module.retroArchSend("MENU_TOGGLE");
|
||||
});
|
||||
|
||||
btnFullscreen.classList.remove("disabled");
|
||||
btnFullscreen.addEventListener("click", function() {
|
||||
Module.requestFullscreen(false);
|
||||
Module.retroArchSend("FULLSCREEN_TOGGLE");
|
||||
});
|
||||
|
||||
// ensure the canvas is focused so that keyboard events work
|
||||
Module.canvas.focus();
|
||||
Module.canvas.addEventListener("pointerdown", function() {
|
||||
Module.canvas.focus();
|
||||
}, false);
|
||||
// refocus the canvas so that keyboard events work
|
||||
menuBar.addEventListener("pointerdown", function() {
|
||||
setTimeout(function() {
|
||||
Module.canvas.focus();
|
||||
|
|
|
@ -188,7 +188,7 @@
|
|||
<span class="fa fa-chevron-down" id="icnShowMenu"></span> <span class="sr-only">Show Top Navigation</span>
|
||||
</button>
|
||||
</div>
|
||||
<canvas class="webplayer" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()" style="display: none"></canvas>
|
||||
<canvas class="webplayer" id="canvas" style="display: none"></canvas>
|
||||
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960" height="720" alt="RetroArch Logo">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -187,18 +187,14 @@ function startRetroArch() {
|
|||
selectFiles(e.target.files);
|
||||
});
|
||||
$('#btnMenu').removeClass("disabled").removeAttr("disabled").click(function() {
|
||||
Module._cmd_toggle_menu();
|
||||
Module.retroArchSend("MENU_TOGGLE");
|
||||
Module.canvas.focus();
|
||||
});
|
||||
$('#btnFullscreen').removeClass("disabled").removeAttr("disabled").click(function() {
|
||||
Module.requestFullscreen(false);
|
||||
Module.retroArchSend("FULLSCREEN_TOGGLE");
|
||||
Module.canvas.focus();
|
||||
});
|
||||
|
||||
Module.canvas.focus();
|
||||
Module.canvas.addEventListener("pointerdown", function() {
|
||||
Module.canvas.focus();
|
||||
}, false);
|
||||
Module.callMain(Module.arguments);
|
||||
}
|
||||
|
||||
|
|
|
@ -6029,7 +6029,11 @@ void emscripten_mainloop(void)
|
|||
bool runloop_is_slowmotion = (runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false;
|
||||
bool runloop_is_paused = (runloop_flags & RUNLOOP_FLAG_PAUSED) ? true : false;
|
||||
|
||||
/* firefox especially seems to bug without this */
|
||||
/* Prevents the program from running in any of the following conditions:
|
||||
* 1. requestAnimationFrame is being used and the window is not visible.
|
||||
* Firefox likes to call requestAnimationFrame at 1 FPS when the window isn't focused,
|
||||
* we want to avoid this.
|
||||
* 2. The GL context is lost and hasn't been recovered yet. */
|
||||
if (platform_emscripten_should_drop_iter())
|
||||
return;
|
||||
|
||||
|
|
Loading…
Reference in New Issue