Emscripten improvements (#17422)

This commit is contained in:
BinBashBanana 2025-01-28 20:29:16 -08:00 committed by GitHub
parent 104561e7aa
commit ed1810de86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1006 additions and 613 deletions

2
.gitignore vendored
View File

@ -196,6 +196,8 @@ retroarch.js
retroarch.js.mem
*.bc
*.wasm
*.wasm.map
obj-emscripten/
# only ignore .js files in the repo root
/*.js

View File

@ -7,6 +7,9 @@
- CHEEVOS: Include achievement state in netplay states
- CLOUDSYNC: Fix Windows path issues
- CLOUDSYNC: Workaround for duplicated requests bug
- EMSCRIPTEN: Scale window to correct size
- EMSCRIPTEN: Additional platform functions
- EMSCRIPTEN/RWEBINPUT: Add touch input support
- GENERAL: Fix save state auto increment
- GENERAL: Fix softpatching with periods/dots in the file name
- GENERAL: Fix compilation with --enable-videocore
@ -34,6 +37,7 @@
- SAVESTATES: Reset state index when loading new content
- UWP: Fix slang shader compilation
- VIDEO: Enable BFI setting for mobile platforms (mind the warnings)
- VIDEO/OpenGLES: Fix FP/sRGB FBO support
- VIDEO/SHADERS: Allow exact refresh rate sync with shader subframes
- WEBPLAYER: Update core list for 1.20.0

View File

@ -1570,10 +1570,11 @@ ifeq ($(HAVE_GL_CONTEXT), 1)
DEF_FLAGS += $(OPENGLES_CFLAGS)
ifeq ($(HAVE_OPENGLES3), 1)
DEFINES += -DHAVE_OPENGLES3
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es3.o
else
DEFINES += -DHAVE_OPENGLES2
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es2.o
endif
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es2.o
else
DEFINES += -DHAVE_GL_SYNC
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_gl.o

View File

@ -25,7 +25,6 @@ HAVE_REWIND = 1
HAVE_AUDIOMIXER = 1
HAVE_CC_RESAMPLER = 1
HAVE_EGL = 1
HAVE_OPENGLES = 1
HAVE_RJPEG = 0
HAVE_RPNG = 1
HAVE_EMSCRIPTEN = 1
@ -58,34 +57,49 @@ HAVE_CHD ?= 0
# You have been warned.
HAVE_RWEBAUDIO = 0
# help diagnose GL problems (can cause issues in normal operation)
GL_DEBUG ?= 0
# enable javascript filesystem tracking
FS_DEBUG = 1
HAVE_OPENGLES ?= 1
HAVE_OPENGLES3 ?= 0
ASYNC ?= 0
ifeq ($(LIBRETRO), mupen64plus)
ASYNC = 1
endif
LTO ?= 0
ifeq ($(LIBRETRO), tyrquake)
LTO = 0
endif
PTHREAD ?= 0
MEMORY ?= 134217728
STACK_SIZE ?= 4194304
INITIAL_HEAP ?= 134217728
PRECISE_F32 = 1
# 4194304 ----- 4 MiB (Stack: recommended)
# 8388608 ----- 8 MiB
# 16777216 ---- 16 MiB
# 33554432 ---- 32 MiB
# 67108864 ---- 64 MiB
# 134217728 --- 128 MiB (Heap: recommended) (Stack: recommended for some cores [mupen64plus_next])
# 268435456 --- 256 MiB (Heap: recommended for some cores [mupen64plus_next])
# 536870912 --- 512 MiB (Heap: needed for some cores [mednafen_psx(_hw)])
# 1073741824 -- 1 GiB
# 1610612736 -- 1.5 GiB
# 2147483648 -- 2 GiB
OBJDIR := obj-emscripten
#if you compile with SDL2 flag add this Emscripten flag "-s USE_SDL=2" to LDFLAGS:
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_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
LIBS := -s USE_ZLIB=1
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s TOTAL_MEMORY=$(MEMORY) -s NO_EXIT_RUNTIME=0 -s FULL_ES2=1 \
-s "EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH', 'ERRNO_CODES']" \
-s ALLOW_MEMORY_GROWTH=1 -s "EXPORTED_FUNCTIONS=['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_load_state', '_cmd_take_screenshot']" \
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \
-s EXPORTED_RUNTIME_METHODS=callMain,FS,PATH,ERRNO_CODES,stringToNewUTF8,UTF8ToString \
-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=1 \
--js-library emscripten/library_errno_codes.js \
--js-library emscripten/library_rwebcam.js
--extern-pre-js emscripten/pre.js \
--js-library emscripten/library_rwebcam.js \
--js-library emscripten/library_platform_emscripten.js
ifeq ($(HAVE_RWEBAUDIO), 1)
LDFLAGS += --js-library emscripten/library_rwebaudio.js
@ -94,11 +108,11 @@ endif
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
ASYNC = 1
override ASYNC = 1
endif
ifneq ($(PTHREAD), 0)
LDFLAGS += -s WASM_MEM_MAX=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD)
LDFLAGS += -s MAXIMUM_MEMORY=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD)
CFLAGS += -pthread
HAVE_THREADS=1
else
@ -108,10 +122,26 @@ endif
ifeq ($(ASYNC), 1)
LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192
ifeq ($(DEBUG), 1)
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
endif
endif
ifeq ($(HAVE_OPENGLES), 1)
ifeq ($(HAVE_OPENGLES3), 1)
LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2
else
LDFLAGS += -s FULL_ES2=1
endif
endif
ifeq ($(GL_DEBUG), 1)
LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1
endif
ifeq ($(FS_DEBUG), 1)
LDFLAGS += -s FS_DEBUG=1
endif
ifeq ($(HAVE_SDL2), 1)
LIBS += -s USE_SDL=2
DEFINES += -DHAVE_SDL2
@ -121,12 +151,14 @@ include Makefile.common
CFLAGS += $(DEF_FLAGS) -Ideps -Ideps/stb
libretro :=
libretro =
libretro_new =
ifeq ($(HAVE_STATIC_DUMMY),1)
DEFINES += -DHAVE_STATIC_DUMMY
else
libretro += libretro_emscripten.bc
libretro = libretro_emscripten.bc
libretro_new = libretro_emscripten.a
endif
ifneq ($(V), 1)
@ -135,22 +167,16 @@ endif
ifeq ($(DEBUG), 1)
LDFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
CFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s SAFE_HEAP_LOG=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
CFLAGS += -O0 -g -gsource-map
else
LDFLAGS += -O3 -s WASM=1
LDFLAGS += -O3
# WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake)
LDFLAGS += -s PRECISE_F32=$(PRECISE_F32)
ifeq ($(LTO), 1)
LDFLAGS += --llvm-lto 3
LDFLAGS += -flto
endif
CFLAGS += -O3
endif
# 128 * 1024, double the usual emscripten stack size
LDFLAGS += -s STACK_SIZE=131072
LDFLAGS += --extern-pre-js emscripten/pre.js
CFLAGS += -Wall -I. -Ilibretro-common/include -Ideps/7zip -std=gnu99
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
@ -158,8 +184,9 @@ RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
all: $(TARGET)
$(TARGET): $(RARCH_OBJ) $(libretro)
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro) $(LIBS) $(LDFLAGS)
@$(if $(libretro), mv -f $(libretro) $(libretro_new),)
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
$(OBJDIR)/%.o: %.c
@mkdir -p $(dir $@)

View File

@ -122,11 +122,13 @@ void cheat_manager_apply_cheats(void)
void cheat_manager_set_code(unsigned i, const char *str)
{
cheat_manager_t *cheat_st = &cheat_manager_state;
if (!cheat_st->cheats)
if (!cheat_st->cheats || string_is_empty(str))
return;
if (!string_is_empty(str))
strcpy(cheat_st->cheats[i].code, str);
if (cheat_st->cheats[i].code)
free(cheat_st->cheats[i].code);
cheat_st->cheats[i].code = strdup(str);
cheat_st->cheats[i].state = true;
}

View File

@ -260,6 +260,7 @@ enum event_command
CMD_EVENT_PRESENCE_UPDATE,
CMD_EVENT_OVERLAY_NEXT,
CMD_EVENT_OSK_TOGGLE,
CMD_EVENT_RELOAD_CONFIG,
#ifdef HAVE_MICROPHONE
/* Stops all enabled microphones. */
CMD_EVENT_MICROPHONE_STOP,

View File

@ -764,6 +764,9 @@
#define DEFAULT_MENU_TICKER_SPEED 2.0f
#define DEFAULT_MENU_TICKER_SMOOTH true
/* Don't skip rendering assets based on the absence of other assets */
#define DEFAULT_MENU_IGNORE_MISSING_ASSETS false
#if defined(HAVE_THREADS)
#define DEFAULT_MENU_SAVESTATE_RESUME true
#else

View File

@ -1933,6 +1933,7 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("menu_dynamic_wallpaper_enable", &settings->bools.menu_dynamic_wallpaper_enable, true, DEFAULT_MENU_DYNAMIC_WALLPAPER_ENABLE, false);
SETTING_BOOL("menu_ticker_smooth", &settings->bools.menu_ticker_smooth, true, DEFAULT_MENU_TICKER_SMOOTH, false);
SETTING_BOOL("menu_scroll_fast", &settings->bools.menu_scroll_fast, true, DEFAULT_MENU_SCROLL_FAST, false);
SETTING_BOOL("menu_ignore_missing_assets", &settings->bools.menu_ignore_missing_assets, true, DEFAULT_MENU_IGNORE_MISSING_ASSETS, false);
SETTING_BOOL("settings_show_drivers", &settings->bools.settings_show_drivers, true, DEFAULT_SETTINGS_SHOW_DRIVERS, false);
SETTING_BOOL("settings_show_video", &settings->bools.settings_show_video, true, DEFAULT_SETTINGS_SHOW_VIDEO, false);

View File

@ -832,6 +832,7 @@ typedef struct settings
bool menu_disable_left_analog;
bool menu_disable_right_analog;
bool menu_ticker_smooth;
bool menu_ignore_missing_assets;
bool settings_show_drivers;
bool settings_show_video;
bool settings_show_audio;

View File

@ -61,6 +61,7 @@ mkdir -p ../pkg/${platform}/build/rom
# Emscripten
elif [ $PLATFORM = "emscripten" ] ; then
platform=emscripten
# todo: change this to a
EXT=bc
if [ -z "$EMSCRIPTEN" ] ; then
@ -199,15 +200,10 @@ for f in `ls -v *_${platform}.${EXT}`; do
echo Buildbot: building ${name} for ${platform}
name=`echo "$f" | sed "s/\(_libretro_${platform}\|\).${EXT}$//"`
async=0
pthread=${pthread:-0}
lto=0
whole_archive=
big_stack=
if [ $PLATFORM = "emscripten" ]; then
async=1 #emscripten needs async to sleep
fi
if [ $name = "nxengine" ] ; then
echo "Applying whole archive linking..."
whole_archive="WHOLE_ARCHIVE_LINK=1"
@ -215,10 +211,31 @@ for f in `ls -v *_${platform}.${EXT}`; do
echo "Applying big stack..."
lto=0
big_stack="BIG_STACK=1"
elif [ $name = "mupen64plus" ] ; then
async=1
elif [ $name = "dosbox" ] ; then
fi
if [ $PLATFORM = "emscripten" ]; then
async=0
pthread=${pthread:-0}
gles3=0
stack_mem=4194304
heap_mem=134217728
if [ $name = "mupen64plus_next" ] ; then
gles3=1
async=1
stack_mem=134217728
heap_mem=268435456
elif [ $name = "parallel_n64" ] ; then
gles3=1
async=1
elif [ $name = "mednafen_psx" ] ; then
heap_mem=536870912
elif [ $name = "mednafen_psx_hw" ] ; then
gles3=1
heap_mem=536870912
elif [ $name = "dosbox" ] ; then
async=1
elif [ $name = "scummvm" ] ; then
async=1
fi
fi
echo "-- Building core: $name --"
if [ $PLATFORM = "unix" ]; then
@ -227,15 +244,21 @@ for f in `ls -v *_${platform}.${EXT}`; do
cp -f "$f" ../libretro_${platform}.${EXT}
fi
echo NAME: $name
echo ASYNC: $async
echo LTO: $lto
if [ $PLATFORM = "emscripten" ]; then
echo ASYNC: $async
echo PTHREAD: $pthread
echo GLES3: $gles3
echo STACK_MEMORY: $stack_mem
echo HEAP_MEMORY: $heap_mem
fi
# Do cleanup if this is a big stack core
if [ "$big_stack" = "BIG_STACK=1" ] ; then
if [ $MAKEFILE_GRIFFIN = "yes" ]; then
make -C ../ -f Makefile.griffin platform=${platform} clean || exit 1
elif [ $PLATFORM = "emscripten" ]; then
make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 clean || exit 1
make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto HAVE_OPENGLES3=$gles3 -j7 clean || exit 1
elif [ $PLATFORM = "unix" ]; then
make -C ../ -f Makefile LINK=g++ LTO=$lto -j7 clean || exit 1
else
@ -247,8 +270,8 @@ for f in `ls -v *_${platform}.${EXT}`; do
if [ $MAKEFILE_GRIFFIN = "yes" ]; then
make -C ../ -f Makefile.griffin $OPTS platform=${platform} $whole_archive $big_stack -j3 || exit 1
elif [ $PLATFORM = "emscripten" ]; then
echo "BUILD COMMAND: make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 LIBRETRO=${name} TARGET=${name}_libretro.js"
make -C ../ -f Makefile.emscripten $OPTS PTHREAD=$pthread ASYNC=$async LTO=$lto -j7 LIBRETRO=${name} TARGET=${name}_libretro.js || exit 1
echo "BUILD COMMAND: make -C ../ -f Makefile.emscripten PTHREAD=$pthread ASYNC=$async LTO=$lto HAVE_OPENGLES3=$gles3 STACK_SIZE=$stack_mem INITIAL_HEAP=$heap_mem -j7 LIBRETRO=${name} TARGET=${name}_libretro.js"
make -C ../ -f Makefile.emscripten $OPTS PTHREAD=$pthread ASYNC=$async LTO=$lto HAVE_OPENGLES3=$gles3 STACK_SIZE=$stack_mem INITIAL_HEAP=$heap_mem -j7 LIBRETRO=${name} TARGET=${name}_libretro.js || exit 1
elif [ $PLATFORM = "unix" ]; then
make -C ../ -f Makefile LINK=g++ $whole_archive $big_stack -j3 || exit 1
elif [ $PLATFORM = "ctr" ]; then
@ -318,6 +341,9 @@ for f in `ls -v *_${platform}.${EXT}`; do
if [ $pthread != 0 ] ; then
mv -f ../${name}_libretro.worker.js ../pkg/emscripten/${name}_libretro.worker.js
fi
if [ -f ../${name}_libretro.wasm.map ] ; then
mv -f ../${name}_libretro.wasm.map ../pkg/emscripten/${name}_libretro.wasm.map
fi
fi
# Do manual executable step

View File

@ -1,15 +0,0 @@
//"use strict";
// HACK: This is a dummy library that forces ERRNO_CODES to be used, so it's not optimized away.
// Needed for BrowserFS.
var LibraryErrnoCodes = {
dummyErrnoCodes__deps: ['$ERRNO_CODES'],
dummyErrnoCodes: function() {
if (!ERRNO_CODES) {
console.error("ERRNO_CODES not imported!");
}
}
};
mergeInto(LibraryManager.library, LibraryErrnoCodes);

View File

@ -0,0 +1,78 @@
//"use strict";
var LibraryPlatformEmscripten = {
$RPE: {
powerState: {
supported: false,
dischargeTime: 0,
level: 0,
charging: false
},
powerStateChange: function(e) {
RPE.powerState.dischargeTime = Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF;
RPE.powerState.level = e.target.level;
RPE.powerState.charging = e.target.charging;
}
},
PlatformEmscriptenWatchCanvasSize: function() {
RPE.observer = new ResizeObserver(function(e) {
var width, height;
var entry = e.find(i => i.target == Module.canvas);
if (!entry) return;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else {
width = Math.round(entry.contentRect.width * window.devicePixelRatio);
height = Math.round(entry.contentRect.height * window.devicePixelRatio);
}
Module.setCanvasSize(width, height);
Module.print("Setting real canvas size: " + width + " x " + height);
});
RPE.observer.observe(Module.canvas);
window.addEventListener("resize", function(e) {
RPE.observer.unobserve(Module.canvas);
RPE.observer.observe(Module.canvas);
}, false);
},
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});
RPE.powerState.supported = true;
});
},
PlatformEmscriptenPowerStateGetSupported: function() {
return RPE.powerState.supported;
},
PlatformEmscriptenPowerStateGetDischargeTime: function() {
return RPE.powerState.dischargeTime;
},
PlatformEmscriptenPowerStateGetLevel: function() {
return RPE.powerState.level;
},
PlatformEmscriptenPowerStateGetCharging: function() {
return RPE.powerState.charging;
},
PlatformEmscriptenGetTotalMem: function() {
if (!performance.memory) return 0;
return performance.memory.jsHeapSizeLimit || 0;
},
PlatformEmscriptenGetFreeMem: function() {
if (!performance.memory) return 0;
return (performance.memory.jsHeapSizeLimit || 0) - (performance.memory.usedJSHeapSize || 0);
}
};
autoAddDeps(LibraryPlatformEmscripten, '$RPE');
mergeInto(LibraryManager.library, LibraryPlatformEmscripten);

View File

@ -17,18 +17,13 @@ var LibraryRWebCam = {
RWebCamInit__deps: ['malloc'],
RWebCamInit: function(caps1, caps2, width, height) {
if (!navigator) return 0;
navigator.getMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (!navigator.getMedia) return 0;
if (!navigator.mediaDevices.getUserMedia) return 0;
var c = ++RWC.counter;
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;
@ -37,11 +32,11 @@ var LibraryRWebCam = {
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) {
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then(function(stream) {
RWC.contexts[c].videoElement.autoplay = true;
RWC.contexts[c].videoElement.src = URL.createObjectURL(stream);
RWC.contexts[c].videoElement.srcObject = stream;
RWC.contexts[c].runMode = 2;
}, function (err) {
}).catch(function (err) {
console.log("webcam request failed", err);
RWC.runMode = 0;
});
@ -53,7 +48,6 @@ var LibraryRWebCam = {
RWebCamFree: function(data) {
RWC.contexts[data].videoElement.pause();
URL.revokeObjectURL(RWC.contexts[data].videoElement.src);
RWC.contexts[data].videoElement = null;
RWC.contexts[data] = null;
},
@ -81,6 +75,7 @@ var LibraryRWebCam = {
}
if (RWC.contexts[data].rawFb) {
RWC.contexts[data].rawFbCanvas = document.createElement("canvas");
RWC.contexts[data].rawFbCanvas.classList.add("retroarchWebcamCanvas");
ret = 1;
}

View File

@ -46,9 +46,22 @@
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "../../tasks/tasks_internal.h"
#include "../../cheat_manager.h"
#include "../../audio/audio_driver.h"
void dummyErrnoCodes(void);
void emscripten_mainloop(void);
void PlatformEmscriptenWatchCanvasSize(void);
void PlatformEmscriptenPowerStateInit(void);
bool PlatformEmscriptenPowerStateGetSupported(void);
int PlatformEmscriptenPowerStateGetDischargeTime(void);
float PlatformEmscriptenPowerStateGetLevel(void);
bool PlatformEmscriptenPowerStateGetCharging(void);
uint64_t PlatformEmscriptenGetTotalMem(void);
uint64_t PlatformEmscriptenGetFreeMem(void);
//// begin exported functions
// saves and states
void cmd_savefiles(void)
{
@ -65,11 +78,113 @@ void cmd_load_state(void)
command_event(CMD_EVENT_LOAD_STATE, NULL);
}
void cmd_undo_save_state(void)
{
command_event(CMD_EVENT_UNDO_SAVE_STATE, NULL);
}
void cmd_undo_load_state(void)
{
command_event(CMD_EVENT_UNDO_LOAD_STATE, NULL);
}
// misc
void cmd_take_screenshot(void)
{
command_event(CMD_EVENT_TAKE_SCREENSHOT, NULL);
}
void cmd_toggle_menu(void)
{
command_event(CMD_EVENT_MENU_TOGGLE, NULL);
}
void cmd_reload_config(void)
{
command_event(CMD_EVENT_RELOAD_CONFIG, NULL);
}
void cmd_toggle_grab_mouse(void)
{
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
}
void cmd_toggle_game_focus(void)
{
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, NULL);
}
void cmd_reset(void)
{
command_event(CMD_EVENT_RESET, NULL);
}
void cmd_toggle_pause(void)
{
command_event(CMD_EVENT_PAUSE_TOGGLE, NULL);
}
void cmd_pause(void)
{
command_event(CMD_EVENT_PAUSE, NULL);
}
void cmd_unpause(void)
{
command_event(CMD_EVENT_UNPAUSE, NULL);
}
void cmd_set_volume(float volume) {
audio_set_float(AUDIO_ACTION_VOLUME_GAIN, volume);
}
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
bool cmd_set_shader(const char *path)
{
return command_set_shader(NULL, path);
}
#endif
// cheats
void cmd_cheat_set_code(unsigned index, const char *str)
{
cheat_manager_set_code(index, str);
}
const char *cmd_cheat_get_code(unsigned index)
{
return cheat_manager_get_code(index);
}
void cmd_cheat_toggle_index(bool apply_cheats_after_toggle, unsigned index)
{
cheat_manager_toggle_index(apply_cheats_after_toggle, index);
}
bool cmd_cheat_get_code_state(unsigned index)
{
return cheat_manager_get_code_state(index);
}
bool cmd_cheat_realloc(unsigned new_size)
{
return cheat_manager_realloc(new_size, CHEAT_HANDLER_TYPE_EMU);
}
unsigned cmd_cheat_get_size(void)
{
return cheat_manager_get_size();
}
void cmd_cheat_apply_cheats(void)
{
cheat_manager_apply_cheats();
}
//// end exported functions
static void frontend_emscripten_get_env(int *argc, char *argv[],
void *args, void *params_data)
{
@ -154,16 +269,45 @@ static void frontend_emscripten_get_env(int *argc, char *argv[],
#endif
}
static enum frontend_powerstate frontend_emscripten_get_powerstate(int *seconds, int *percent)
{
enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE;
if (!PlatformEmscriptenPowerStateGetSupported())
return ret;
if (!PlatformEmscriptenPowerStateGetCharging())
ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
else if (PlatformEmscriptenPowerStateGetLevel() == 1)
ret = FRONTEND_POWERSTATE_CHARGED;
else
ret = FRONTEND_POWERSTATE_CHARGING;
*seconds = PlatformEmscriptenPowerStateGetDischargeTime();
*percent = (int)(PlatformEmscriptenPowerStateGetLevel() * 100);
return ret;
}
static uint64_t frontend_emscripten_get_total_mem(void)
{
return PlatformEmscriptenGetTotalMem();
}
static uint64_t frontend_emscripten_get_free_mem(void)
{
return PlatformEmscriptenGetFreeMem();
}
int main(int argc, char *argv[])
{
dummyErrnoCodes();
PlatformEmscriptenWatchCanvasSize();
PlatformEmscriptenPowerStateInit();
EM_ASM({
specialHTMLTargets["!canvas"] = Module.canvas;
});
emscripten_set_canvas_element_size("!canvas", 800, 600);
emscripten_set_element_css_size("!canvas", 800.0, 600.0);
emscripten_set_main_loop(emscripten_mainloop, 0, 0);
rarch_main(argc, argv, NULL);
@ -171,39 +315,39 @@ int main(int argc, char *argv[])
}
frontend_ctx_driver_t frontend_ctx_emscripten = {
frontend_emscripten_get_env, /* environment_get */
NULL, /* init */
NULL, /* deinit */
NULL, /* exitspawn */
NULL, /* process_args */
NULL, /* exec */
NULL, /* set_fork */
NULL, /* shutdown */
NULL, /* get_name */
NULL, /* get_os */
NULL, /* get_rating */
NULL, /* load_content */
NULL, /* get_architecture */
NULL, /* get_powerstate */
NULL, /* parse_drive_list */
NULL, /* get_total_mem */
NULL, /* get_free_mem */
NULL, /* install_sighandlers */
NULL, /* get_signal_handler_state */
NULL, /* set_signal_handler_state */
NULL, /* destroy_signal_handler_state */
NULL, /* attach_console */
NULL, /* detach_console */
NULL, /* get_lakka_version */
NULL, /* set_screen_brightness */
NULL, /* watch_path_for_changes */
NULL, /* check_for_path_changes */
NULL, /* set_sustained_performance_mode */
NULL, /* get_cpu_model_name */
NULL, /* get_user_language */
NULL, /* is_narrator_running */
NULL, /* accessibility_speak */
NULL, /* set_gamemode */
"emscripten", /* ident */
NULL /* get_video_driver */
frontend_emscripten_get_env, /* environment_get */
NULL, /* init */
NULL, /* deinit */
NULL, /* exitspawn */
NULL, /* process_args */
NULL, /* exec */
NULL, /* set_fork */
NULL, /* shutdown */
NULL, /* get_name */
NULL, /* get_os */
NULL, /* get_rating */
NULL, /* load_content */
NULL, /* get_architecture */
frontend_emscripten_get_powerstate, /* get_powerstate */
NULL, /* parse_drive_list */
frontend_emscripten_get_total_mem, /* get_total_mem */
frontend_emscripten_get_free_mem, /* get_free_mem */
NULL, /* install_sighandlers */
NULL, /* get_signal_handler_state */
NULL, /* set_signal_handler_state */
NULL, /* destroy_signal_handler_state */
NULL, /* attach_console */
NULL, /* detach_console */
NULL, /* get_lakka_version */
NULL, /* set_screen_brightness */
NULL, /* watch_path_for_changes */
NULL, /* check_for_path_changes */
NULL, /* set_sustained_performance_mode */
NULL, /* get_cpu_model_name */
NULL, /* get_user_language */
NULL, /* is_narrator_running */
NULL, /* accessibility_speak */
NULL, /* set_gamemode */
"emscripten", /* ident */
NULL /* get_video_driver */
};

View File

@ -185,9 +185,8 @@ enum gl2_renderchain_flags
{
GL2_CHAIN_FLAG_EGL_IMAGES = (1 << 0),
GL2_CHAIN_FLAG_HAS_FP_FBO = (1 << 1),
GL2_CHAIN_FLAG_HAS_SRGB_FBO_GLES3 = (1 << 2),
GL2_CHAIN_FLAG_HAS_SRGB_FBO = (1 << 3),
GL2_CHAIN_FLAG_HW_RENDER_DEPTH_INIT = (1 << 4)
GL2_CHAIN_FLAG_HAS_SRGB_FBO = (1 << 2),
GL2_CHAIN_FLAG_HW_RENDER_DEPTH_INIT = (1 << 3)
};
typedef struct video_shader_ctx_scale
@ -1616,9 +1615,7 @@ static void gl2_create_fbo_texture(gl2_t *gl,
if (video_ctx_scaling)
video_smooth = false;
#endif
#ifndef HAVE_OPENGLES
bool force_srgb_disable = settings->bools.video_force_srgb_disable;
#endif
GLuint base_filt = video_smooth ? GL_LINEAR : GL_NEAREST;
GLuint base_mip_filt = video_smooth ?
GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST;
@ -1650,19 +1647,21 @@ static void gl2_create_fbo_texture(gl2_t *gl,
RARCH_ERR("[GL]: Floating-point FBO was requested, but is not supported. Falling back to UNORM. Result may band/clip/etc.!\n");
}
#if !defined(HAVE_OPENGLES2)
if ( fp_fbo
&& (chain->flags & GL2_CHAIN_FLAG_HAS_FP_FBO))
{
RARCH_LOG("[GL]: FBO pass #%d is floating-point.\n", i);
gl2_load_texture_image(GL_TEXTURE_2D, 0, GL_RGBA32F,
gl2_load_texture_image(GL_TEXTURE_2D, 0,
#ifdef HAVE_OPENGLES2
GL_RGBA,
#else
GL_RGBA32F,
#endif
gl->fbo_rect[i].width, gl->fbo_rect[i].height,
0, GL_RGBA, GL_FLOAT, NULL);
}
else
#endif
{
#ifndef HAVE_OPENGLES
bool srgb_fbo = (chain->fbo_scale[i].flags & FBO_SCALE_FLAG_SRGB_FBO) ? true : false;
if (!fp_fbo && srgb_fbo)
@ -1678,27 +1677,23 @@ static void gl2_create_fbo_texture(gl2_t *gl,
&& (chain->flags & GL2_CHAIN_FLAG_HAS_SRGB_FBO))
{
RARCH_LOG("[GL]: FBO pass #%d is sRGB.\n", i);
gl2_load_texture_image(GL_TEXTURE_2D, 0,
#ifdef HAVE_OPENGLES2
/* EXT defines are same as core GLES3 defines,
* but GLES3 variant requires different arguments. */
glTexImage2D(GL_TEXTURE_2D,
0, GL_SRGB_ALPHA_EXT,
gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0,
(chain->flags & GL2_CHAIN_FLAG_HAS_SRGB_FBO_GLES3)
? GL_RGBA
: GL_SRGB_ALPHA_EXT,
GL_UNSIGNED_BYTE, NULL);
GL_SRGB_ALPHA_EXT,
#else
gl2_load_texture_image(GL_TEXTURE_2D,
0, GL_SRGB8_ALPHA8,
gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
GL_SRGB8_ALPHA8,
#endif
gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0,
#ifdef HAVE_OPENGLES2
GL_SRGB_ALPHA_EXT,
#else
GL_RGBA,
#endif
GL_UNSIGNED_BYTE, NULL);
}
else
#endif
{
#if defined(HAVE_OPENGLES2)
#if defined(HAVE_OPENGLES)
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA,
gl->fbo_rect[i].width, gl->fbo_rect[i].height, 0,
@ -2479,11 +2474,6 @@ static void gl2_renderchain_resolve_extensions(gl2_t *gl,
chain->flags |= GL2_CHAIN_FLAG_HAS_FP_FBO;
else
chain->flags &= ~GL2_CHAIN_FLAG_HAS_FP_FBO;
/* GLES3 has unpack_subimage and sRGB in core. */
if (gl_check_capability(GL_CAPS_SRGB_FBO_ES3))
chain->flags |= GL2_CHAIN_FLAG_HAS_SRGB_FBO_GLES3;
else
chain->flags &= ~GL2_CHAIN_FLAG_HAS_SRGB_FBO_GLES3;
if (!force_srgb_disable)
{
@ -3892,7 +3882,6 @@ static bool gl2_resolve_extensions(gl2_t *gl, const char *context_ident, const v
RARCH_WARN("[GL]: GLES implementation does not have BGRA8888 extension.\n"
"[GL]: 32-bit path will require conversion.\n");
}
/* TODO/FIXME - No extensions for float FBO currently. */
#endif
#ifdef GL_DEBUG

View File

@ -38,8 +38,6 @@ typedef struct
#ifdef HAVE_EGL
egl_ctx_data_t egl;
#endif
int initial_width;
int initial_height;
unsigned fb_width;
unsigned fb_height;
} emscripten_ctx_data_t;
@ -54,76 +52,29 @@ static void gfx_ctx_emscripten_swap_interval(void *data, int interval)
static void gfx_ctx_emscripten_get_canvas_size(int *width, int *height)
{
EmscriptenFullscreenChangeEvent fullscreen_status;
bool is_fullscreen = false;
EMSCRIPTEN_RESULT r = emscripten_get_fullscreen_status(&fullscreen_status);
EMSCRIPTEN_RESULT r = emscripten_get_canvas_element_size("!canvas", width, height);
if (r == EMSCRIPTEN_RESULT_SUCCESS)
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
if (fullscreen_status.isFullscreen)
{
is_fullscreen = true;
*width = fullscreen_status.screenWidth;
*height = fullscreen_status.screenHeight;
}
}
if (!is_fullscreen)
{
r = emscripten_get_canvas_element_size("!canvas", width, height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
*width = 800;
*height = 600;
RARCH_ERR("[EMSCRIPTEN/EGL]: Could not get screen dimensions: %d\n",r);
}
*width = 800;
*height = 600;
RARCH_ERR("[EMSCRIPTEN/EGL]: Could not get screen dimensions: %d\n",r);
}
}
static void gfx_ctx_emscripten_check_window(void *data, bool *quit,
bool *resize, unsigned *width, unsigned *height)
{
EMSCRIPTEN_RESULT r;
int input_width;
int input_height;
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
gfx_ctx_emscripten_get_canvas_size(&input_width, &input_height);
if (input_width == 0 || input_height == 0)
{
input_width = emscripten->initial_width;
input_height = emscripten->initial_height;
emscripten->fb_width = emscripten->fb_height = 0;
}
*width = (unsigned)input_width;
*height = (unsigned)input_height;
*resize = false;
if ( (input_width != emscripten->fb_width)
|| (input_height != emscripten->fb_height))
{
r = emscripten_set_canvas_element_size("!canvas",
input_width, input_height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
RARCH_ERR("[EMSCRIPTEN/EGL]: error resizing canvas: %d\n", r);
/* fix Module.requestFullscreen messing with the canvas size */
r = emscripten_set_element_css_size("!canvas",
(double)input_width, (double)input_height);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
RARCH_ERR("[EMSCRIPTEN/EGL]: error resizing canvas css: %d\n", r);
*resize = true;
}
emscripten->fb_width = (unsigned)input_width;
emscripten->fb_height = (unsigned)input_height;
*quit = false;
*width = emscripten->fb_width = (unsigned)input_width;
*height = emscripten->fb_height = (unsigned)input_height;
*quit = false;
*resize = false;
}
static void gfx_ctx_emscripten_swap_buffers(void *data)
@ -148,6 +99,25 @@ static void gfx_ctx_emscripten_get_video_size(void *data,
*height = emscripten->fb_height;
}
static bool gfx_ctx_emscripten_get_metrics(void *data,
enum display_metric_types type, float *value)
{
switch (type)
{
// there is no way to get the actual DPI in emscripten, so return a standard value instead.
// this is needed for menu touch/pointer swipe scrolling to work.
case DISPLAY_METRIC_DPI:
*value = 150.0f;
break;
default:
*value = 0.0f;
return false;
}
return true;
}
static void gfx_ctx_emscripten_destroy(void *data)
{
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
@ -190,14 +160,6 @@ static void *gfx_ctx_emscripten_init(void *video_driver)
if (!emscripten)
return NULL;
/* TODO/FIXME - why is this conditional here - shouldn't these always
* be grabbed? */
if ( (emscripten->initial_width == 0)
|| (emscripten->initial_height == 0))
emscripten_get_canvas_element_size("!canvas",
&emscripten->initial_width,
&emscripten->initial_height);
#ifdef HAVE_EGL
if (g_egl_inited)
{
@ -310,7 +272,7 @@ const gfx_ctx_driver_t gfx_ctx_emscripten = {
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
NULL, /* get_metrics */
gfx_ctx_emscripten_get_metrics,
gfx_ctx_emscripten_translate_aspect,
NULL, /* update_title */
gfx_ctx_emscripten_check_window,

View File

@ -23,14 +23,18 @@
#include <encodings/crc32.h>
#include <encodings/utf.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include "../input_driver.h"
#include "../input_types.h"
#include "../input_keymaps.h"
#include "../../tasks/tasks_internal.h"
#include "../../configuration.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "../../command.h"
/* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button */
#define RWEBINPUT_MOUSE_BTNL 0
@ -39,6 +43,8 @@
#define RWEBINPUT_MOUSE_BTN4 3
#define RWEBINPUT_MOUSE_BTN5 4
#define MAX_TOUCH 32
typedef struct rwebinput_key_to_code_map_entry
{
const char *key;
@ -58,26 +64,36 @@ typedef struct rwebinput_keyboard_event_queue
size_t max_size;
} rwebinput_keyboard_event_queue_t;
typedef struct rwebinput_pointer_states
{
int x;
int y;
int id;
} rwebinput_pointer_state_t;
typedef struct rwebinput_mouse_states
{
double pending_scroll_x;
double pending_scroll_y;
double scroll_x;
double scroll_y;
signed x;
signed y;
signed pending_delta_x;
signed pending_delta_y;
signed delta_x;
signed delta_y;
int x;
int y;
int pending_delta_x;
int pending_delta_y;
int delta_x;
int delta_y;
uint8_t buttons;
} rwebinput_mouse_state_t;
typedef struct rwebinput_input
{
rwebinput_mouse_state_t mouse; /* double alignment */
rwebinput_keyboard_event_queue_t keyboard; /* ptr alignment */
rwebinput_mouse_state_t mouse; /* double alignment */
rwebinput_keyboard_event_queue_t keyboard; /* ptr alignment */
rwebinput_pointer_state_t pointer[MAX_TOUCH]; /* int alignment */
unsigned pointer_count;
bool keys[RETROK_LAST];
bool pointerlock_active;
} rwebinput_input_t;
/* KeyboardEvent.keyCode has been deprecated for a while and doesn't have
@ -255,11 +271,39 @@ static EM_BOOL rwebinput_mouse_cb(int event_type,
uint8_t mask = 1 << mouse_event->button;
rwebinput->mouse.x = mouse_event->targetX;
rwebinput->mouse.y = mouse_event->targetY;
// note: movementX/movementY are pre-scaled in chromium (but not firefox)
// see https://github.com/w3c/pointerlock/issues/42
rwebinput->mouse.pending_delta_x += mouse_event->movementX;
rwebinput->mouse.pending_delta_y += mouse_event->movementY;
if (rwebinput->pointerlock_active)
{
unsigned video_width, video_height;
video_driver_get_size(&video_width, &video_height);
rwebinput->mouse.x += mouse_event->movementX;
rwebinput->mouse.y += mouse_event->movementY;
/* Clamp X */
if (rwebinput->mouse.x < 0)
rwebinput->mouse.x = 0;
if (rwebinput->mouse.x >= video_width)
rwebinput->mouse.x = (int)(video_width - 1);
/* Clamp Y */
if (rwebinput->mouse.y < 0)
rwebinput->mouse.y = 0;
if (rwebinput->mouse.y >= video_height)
rwebinput->mouse.y = (int)(video_height - 1);
}
else
{
double dpr = emscripten_get_device_pixel_ratio();
rwebinput->mouse.x = (int)(mouse_event->targetX * dpr);
rwebinput->mouse.y = (int)(mouse_event->targetY * dpr);
}
if (event_type == EMSCRIPTEN_EVENT_MOUSEDOWN)
rwebinput->mouse.buttons |= mask;
else if (event_type == EMSCRIPTEN_EVENT_MOUSEUP)
@ -273,8 +317,90 @@ static EM_BOOL rwebinput_wheel_cb(int event_type,
{
rwebinput_input_t *rwebinput = (rwebinput_input_t*)user_data;
rwebinput->mouse.pending_scroll_x += wheel_event->deltaX;
rwebinput->mouse.pending_scroll_y += wheel_event->deltaY;
double dpr = emscripten_get_device_pixel_ratio();
rwebinput->mouse.pending_scroll_x += wheel_event->deltaX * dpr;
rwebinput->mouse.pending_scroll_y += wheel_event->deltaY * dpr;
return EM_TRUE;
}
static EM_BOOL rwebinput_touch_cb(int event_type,
const EmscriptenTouchEvent *touch_event, void *user_data)
{
rwebinput_input_t *rwebinput = (rwebinput_input_t*)user_data;
unsigned touches_max = MIN(touch_event->numTouches, MAX_TOUCH);
unsigned touches_released = 0;
switch (event_type)
{
case EMSCRIPTEN_EVENT_TOUCHSTART:
case EMSCRIPTEN_EVENT_TOUCHMOVE:
for (unsigned touch = 0; touch < touches_max; touch++)
{
if (!(touch_event->touches[touch].isChanged) && rwebinput->pointer[touch].id == touch_event->touches[touch].identifier)
continue;
double dpr = emscripten_get_device_pixel_ratio();
rwebinput->pointer[touch].x = (int)(touch_event->touches[touch].targetX * dpr);
rwebinput->pointer[touch].y = (int)(touch_event->touches[touch].targetY * dpr);
rwebinput->pointer[touch].id = touch_event->touches[touch].identifier;
}
break;
case EMSCRIPTEN_EVENT_TOUCHEND:
case EMSCRIPTEN_EVENT_TOUCHCANCEL:
// note: touches_max/numTouches is out of date here - it uses the old value from before the release
// note 2: I'm unsure if multiple touches can trigger the same touchend anyway...
if (touches_max > 1)
{
for (unsigned touch_up = 0; touch_up < touches_max; touch_up++)
{
if (touch_event->touches[touch_up].isChanged)
{
memmove(rwebinput->pointer + touch_up - touches_released,
rwebinput->pointer + touch_up - touches_released + 1,
(touches_max - touch_up - 1) * sizeof(rwebinput_pointer_state_t));
touches_released++;
}
}
}
else
touches_released = 1;
if (touches_max > touches_released)
touches_max -= touches_released;
else
touches_max = 0;
break;
}
rwebinput->pointer_count = touches_max;
return EM_TRUE;
}
static EM_BOOL rwebinput_pointerlockchange_cb(int event_type,
const EmscriptenPointerlockChangeEvent *pointerlock_change_event, void *user_data)
{
rwebinput_input_t *rwebinput = (rwebinput_input_t*)user_data;
rwebinput->pointerlock_active = pointerlock_change_event->isActive;
if (!pointerlock_change_event->isActive)
{
input_driver_state_t *input_st = input_state_get_ptr();
if (input_st->game_focus_state.enabled)
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_OFF;
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
}
if (input_st->flags & INP_FLAG_GRAB_MOUSE_STATE)
{
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
}
}
return EM_TRUE;
}
@ -350,6 +476,47 @@ static void *rwebinput_input_init(const char *joypad_driver)
"[EMSCRIPTEN/INPUT] failed to create wheel callback: %d\n", r);
}
r = emscripten_set_touchstart_callback("!canvas", rwebinput, false,
rwebinput_touch_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
RARCH_ERR(
"[EMSCRIPTEN/INPUT] failed to create touchstart callback: %d\n", r);
}
r = emscripten_set_touchend_callback("!canvas", rwebinput, false,
rwebinput_touch_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
RARCH_ERR(
"[EMSCRIPTEN/INPUT] failed to create touchend callback: %d\n", r);
}
r = emscripten_set_touchmove_callback("!canvas", rwebinput, false,
rwebinput_touch_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
RARCH_ERR(
"[EMSCRIPTEN/INPUT] failed to create touchmove callback: %d\n", r);
}
r = emscripten_set_touchcancel_callback("!canvas", rwebinput, false,
rwebinput_touch_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
RARCH_ERR(
"[EMSCRIPTEN/INPUT] failed to create touchcancel callback: %d\n", r);
}
r = emscripten_set_pointerlockchange_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, rwebinput, false,
rwebinput_pointerlockchange_cb);
if (r != EMSCRIPTEN_RESULT_SUCCESS)
{
RARCH_ERR(
"[EMSCRIPTEN/INPUT] failed to create pointerlockchange callback: %d\n", r);
}
input_keymaps_init_keyboard_lut(rarch_key_map_rwebinput);
return rwebinput;
@ -505,24 +672,41 @@ static int16_t rwebinput_input_state(
return rwebinput_mouse_state(&rwebinput->mouse, id, device == RARCH_DEVICE_MOUSE_SCREEN);
case RETRO_DEVICE_POINTER:
case RARCH_DEVICE_POINTER_SCREEN:
if (idx == 0)
{
struct video_viewport vp = {0};
rwebinput_mouse_state_t
*mouse = &rwebinput->mouse;
bool screen = device ==
RARCH_DEVICE_POINTER_SCREEN;
bool pointer_down = false;
unsigned pointer_count = rwebinput->pointer_count;
int x = 0;
int y = 0;
int16_t res_x = 0;
int16_t res_y = 0;
int16_t res_screen_x = 0;
int16_t res_screen_y = 0;
if (pointer_count && idx < pointer_count)
{
x = rwebinput->pointer[idx].x;
y = rwebinput->pointer[idx].y;
pointer_down = true;
}
else if (idx == 0)
{
x = mouse->x;
y = mouse->y;
pointer_down = !!(mouse->buttons & (1 << RWEBINPUT_MOUSE_BTNL));
pointer_count = 1;
}
else
return 0;
if (!(video_driver_translate_coord_viewport_confined_wrap(
&vp, mouse->x, mouse->y,
&vp, x, y,
&res_x, &res_y, &res_screen_x, &res_screen_y)))
return 0;
if (screen)
if (device == RARCH_DEVICE_POINTER_SCREEN)
{
res_x = res_screen_x;
res_y = res_screen_y;
@ -535,7 +719,9 @@ static int16_t rwebinput_input_state(
case RETRO_DEVICE_ID_POINTER_Y:
return res_y;
case RETRO_DEVICE_ID_POINTER_PRESSED:
return !!(mouse->buttons & (1 << RWEBINPUT_MOUSE_BTNL));
return (pointer_down && !input_driver_pointer_is_offscreen(res_x, res_y));
case RETRO_DEVICE_ID_POINTER_COUNT:
return pointer_count;
case RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN:
return input_driver_pointer_is_offscreen(res_x, res_y);
default:

View File

@ -287,15 +287,12 @@ bool gl_check_capability(enum gl_capability_enum enum_idx)
}
break;
case GL_CAPS_FP_FBO:
/* GLES - No extensions for float FBO currently. */
#ifndef HAVE_OPENGLES
if (gl_check_capability(GL_CAPS_FBO))
{
/* Float FBO is core in 3.2. */
if (gl_query_core_context_in_use() || gl_query_extension("ARB_texture_float"))
if (gl_query_core_context_in_use() || gl_query_extension("ARB_texture_float") || gl_query_extension("OES_texture_float_linear"))
return true;
}
#endif
break;
case GL_CAPS_BGRA8888:
#ifdef HAVE_OPENGLES

View File

@ -39,7 +39,7 @@
#include <psp2/kernel/threadmgr.h>
#elif defined(_3DS)
#include <3ds.h>
#elif defined(EMSCRIPTEN_FIXME)
#elif defined(EMSCRIPTEN)
#include <emscripten/emscripten.h>
#else
#include <time.h>
@ -100,7 +100,7 @@ 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_FIXME)
#elif defined(EMSCRIPTEN)
#define retro_sleep(msec) (emscripten_sleep(msec))
#else
static INLINE void retro_sleep(unsigned msec)

View File

@ -452,7 +452,8 @@ enum ozone_handle_flags2
OZONE_FLAG2_RESET_DEPTH = (1 << 8),
OZONE_FLAG2_PENDING_CURSOR_IN_SIDEBAR = (1 << 9),
OZONE_FLAG2_IS_QUICK_MENU = (1 << 10),
OZONE_FLAG2_IS_PLAYLISTS_TAB = (1 << 11)
OZONE_FLAG2_IS_PLAYLISTS_TAB = (1 << 11),
OZONE_FLAG2_IGNORE_MISSING_ASSETS = (1 << 12)
};
struct ozone_handle
@ -1732,7 +1733,7 @@ static void ozone_set_color_theme(
ozone->theme->message_background,
sizeof(ozone->theme_dynamic.message_background));
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS)
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS || ozone->flags2 & OZONE_FLAG2_IGNORE_MISSING_ASSETS)
ozone_restart_cursor_animation(ozone);
ozone_last_color_theme = color_theme;
@ -3215,7 +3216,7 @@ static void ozone_draw_cursor(
/* Draw the cursor */
if ( (ozone->theme->name)
&& (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS))
&& (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS || ozone->flags2 & OZONE_FLAG2_IGNORE_MISSING_ASSETS))
ozone_draw_cursor_slice(ozone,
p_disp,
userdata,
@ -7024,7 +7025,7 @@ static void ozone_draw_messagebox(
dispctx->blend_begin(userdata);
/* Avoid drawing a black box if there's no assets */
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS)
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS || ozone->flags2 & OZONE_FLAG2_IGNORE_MISSING_ASSETS)
{
/* Note: The fact that we use a texture slice here
* makes things very messy
@ -9432,9 +9433,14 @@ static void ozone_context_reset(void *data, bool is_threaded)
if (ozone)
{
settings_t *settings = config_get_ptr();
ozone->flags |= OZONE_FLAG_HAS_ALL_ASSETS;
ozone_set_layout(ozone, config_get_ptr()->bools.ozone_collapse_sidebar, is_threaded);
if (settings->bools.menu_ignore_missing_assets)
ozone->flags2 |= OZONE_FLAG2_IGNORE_MISSING_ASSETS;
ozone_set_layout(ozone, settings->bools.ozone_collapse_sidebar, is_threaded);
/* Textures init */
for (i = 0; i < OZONE_TEXTURE_LAST; i++)
@ -9527,7 +9533,7 @@ static void ozone_context_reset(void *data, bool is_threaded)
ozone_update_thumbnail_image(ozone);
ozone_update_savestate_thumbnail_image(ozone);
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS)
if (ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS || ozone->flags2 & OZONE_FLAG2_IGNORE_MISSING_ASSETS)
ozone_restart_cursor_animation(ozone);
/* Screensaver */

View File

@ -1,10 +1,10 @@
# RetroArch Web Player
The RetroArch Web Player is RetroArch compiled through [Emscripten](http://kripken.github.io/emscripten-site/). The following outlines how to compile RetroArch using Emscripten, and running it in your browser.
The RetroArch Web Player is RetroArch compiled through [Emscripten](https://emscripten.org/). The following outlines how to compile RetroArch using Emscripten, and running it in your browser.
## Compiling
To compile RetroArch with Emscripten, you'll first have to [download and install the Emscripten SDK](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html) at 3.1.46:
To compile RetroArch with Emscripten, you'll first have to [download and install the Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) at 3.1.46:
```
git clone https://github.com/emscripten-core/emsdk.git

View File

@ -1,91 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>RetroArch Web Player</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
<!-- Material Design Bootstrap -->
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
<link href="libretro.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="https://web.libretro.com/media/retroarch.ico" />
</head>
<body>
<!--Navbar-->
<nav class="navbar navbar-dark bg-primary">
<div class="container">
<!--navbar content-->
<div class="navbar-toggleable-xs">
<head>
<meta charset="utf-8">
<title>RetroArch Web Player</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
<!-- Material Design Bootstrap -->
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
<link href="libretro.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="https://web.libretro.com/media/retroarch.ico" />
</head>
<body>
<!--Navbar-->
<nav class="navbar navbar-dark bg-primary">
<div class="container">
<!--navbar content-->
<div class="navbar-toggleable-xs">
<!--Links-->
<ul class="nav navbar-nav">
<div class="dropdown">
<li class="nav-item dropdown">
<li class="nav-item dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Core Selection</button>
<div class="dropdown-menu dropdown-primary" aria-labelledby="dropdownMenu1" data-dropdown-in="fadeIn" data-dropdown-out="fadeOut" id="core-selector">
<a class="dropdown-item" href="." data-core="chailove">ChaiLove</a>
<a class="dropdown-item" href="." data-core="fceumm">FCEUmm</a>
<a class="dropdown-item" href="." data-core="gambatte">Gambatte</a>
<a class="dropdown-item" href="." data-core="genesis_plus_gx">Genesis Plus GX</a>
<a class="dropdown-item" href="." data-core="lutro">Lutro</a>
<a class="dropdown-item" href="." data-core="nestopia">Nestopia (NES)</a>
<a class="dropdown-item" href="." data-core="snes9x2010">Snes9x 2010 (SNES)</a>
<a class="dropdown-item" href="." data-core="theodore">Theodore (Thomson TO8/TO9)</a>
<a class="dropdown-item" href="." data-core="vba_next">VBA Next (Gameboy Advance)</a>
<a class="dropdown-item" href="." data-core="chailove">ChaiLove</a>
<a class="dropdown-item" href="." data-core="fceumm">FCEUmm</a>
<a class="dropdown-item" href="." data-core="gambatte">Gambatte</a>
<a class="dropdown-item" href="." data-core="genesis_plus_gx">Genesis Plus GX</a>
<a class="dropdown-item" href="." data-core="lutro">Lutro</a>
<a class="dropdown-item" href="." data-core="nestopia">Nestopia (NES)</a>
<a class="dropdown-item" href="." data-core="snes9x">Snes9x (SNES)</a>
<a class="dropdown-item" href="." data-core="snes9x2010">Snes9x 2010 (SNES)</a>
<a class="dropdown-item" href="." data-core="theodore">Theodore (Thomson TO8/TO9)</a>
<a class="dropdown-item" href="." data-core="vba_next">VBA Next (Gameboy Advance)</a>
</div>
<button class="btn btn-primary disabled" id="btnRun" onclick="startRetroArch()" disabled>
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
<button class="btn btn-primary disabled" id="btnRun" disabled>
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
</button>
<button class="btn btn-primary disabled" id="btnAdd" onclick="document.getElementById('btnRom').click()" disabled>
<span class="fa fa-plus" id="icnAdd"></span> Add Content
<button class="btn btn-primary disabled" id="btnAdd" disabled>
<span class="fa fa-plus" id="icnAdd"></span> Add Content
</button>
<button class="btn btn-primary tooltip-enable" id="btnClean" onclick="cleanupStorage();" title="Cleanup storage">
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
<input style="display: none" type="file" id="btnRom" name="upload" multiple />
<button class="btn btn-primary tooltip-enable" id="btnClean" title="Cleanup storage">
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
</button>
<input class="btn btn-primary disabled" style="display: none" type="file" id="btnRom" name="upload" onclick="document.getElementById('btnAdd').click();" onchange="selectFiles(event.target.files)" multiple />
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" onclick="keyPress('F1');" title="Menu toggle" disabled>
<span class="fa fa-bars" id="btnMenu"></span> <span class="sr-only">Menu</span>
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" title="Menu toggle" disabled>
<span class="fa fa-bars" id="icnMenu"></span> <span class="sr-only">Menu</span>
</button>
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" onclick="Module.requestFullscreen(false)" title="Fullscreen" disabled>
<span class="fa fa-desktop" id="icnAdd"></span> <span class="sr-only">Fullscreen</span>
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" title="Fullscreen" disabled>
<span class="fa fa-desktop" id="icnFullscreen"></span> <span class="sr-only">Fullscreen</span>
</button>
</li>
</div>
</li>
</div>
</ul>
</div>
<!--/.navbar content-->
</div>
</nav>
<div class="bg-inverse webplayer-container">
<div class="container">
<div class="webplayer_border text-xs-center" id="canvas_div">
<canvas class="webplayer" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()" style="display: none"></canvas>
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960px" height="720px" alt="RetroArch Logo">
</div>
<!--/.navbar content-->
</div>
</nav>
<div class="bg-inverse webplayer-container">
<div class="container">
<div class="webplayer_border text-xs-center" id="canvas_div">
<canvas class="webplayer" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()" style="display: none"></canvas>
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960" height="720" alt="RetroArch Logo">
</div>
</div>
</div>
</div>
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
<script src="analytics.js"></script>
<!--script src="//wzrd.in/standalone/browserfs@0.6.1"></script-->
<script src="browserfs.min.js"></script>
<script src="libretro.js"></script>
<div align="center">
<a href="https://www.patreon.com/libretro">
<img src="https://patreon_public_assets.s3.amazonaws.com/sized/becomeAPatronBanner.png" width="350" height="116"></a>
</div>
</body>
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
<script src="analytics.js"></script>
<!--script src="//wzrd.in/standalone/browserfs@0.6.1"></script-->
<script src="browserfs.min.js"></script>
<script src="libretro.js"></script>
<div align="center">
<a href="https://www.patreon.com/libretro">
<img src="https://patreon_public_assets.s3.amazonaws.com/sized/becomeAPatronBanner.png" alt="Become a patron" width="350" height="116"></a>
</div>
</body>
</html>

View File

@ -1,30 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>RetroArch Web Player</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
<!-- Material Design Bootstrap -->
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
<link href="libretro.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="media/retroarch.ico" />
</head>
<body>
<!--Navbar-->
<nav class="navbar navbar-dark bg-primary">
<div class="container">
<!--navbar content-->
<div class="navbar-toggleable-xs">
<head>
<meta charset="utf-8">
<title>RetroArch Web Player</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
<!-- Material Design Bootstrap -->
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
<link href="libretro.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="media/retroarch.ico" />
</head>
<body>
<!--Navbar-->
<nav class="navbar navbar-dark bg-primary">
<div class="container">
<!--navbar content-->
<div class="navbar-toggleable-xs">
<!--Links-->
<ul class="nav navbar-nav">
<div class="dropdown">
<li class="nav-item dropdown">
<li class="nav-item dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Core Selection</button>
<div class="dropdown-menu dropdown-primary" aria-labelledby="dropdownMenu1" data-dropdown-in="fadeIn" data-dropdown-out="fadeOut" id="core-selector">
<a class="dropdown-item" href="." data-core="2048">2048</a>
@ -117,92 +115,90 @@
<a class="dropdown-item" href="." data-core="x1">Sharp X1 (X Millenium)</a>
<a class="dropdown-item" href="." data-core="xrick">Rick Dangerous (XRick)</a>
</div>
<button class="btn btn-primary disabled" id="btnRun" onclick="startRetroArch()" disabled>
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
<button class="btn btn-primary disabled" id="btnRun" disabled>
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
</button>
<button class="btn btn-primary disabled" id="btnAdd" onclick="document.getElementById('btnRom').click()" disabled>
<span class="fa fa-plus" id="icnAdd"></span> Add Content
<button class="btn btn-primary disabled" id="btnAdd" disabled>
<span class="fa fa-plus" id="icnAdd"></span> Add Content
</button>
<button class="btn btn-primary tooltip-enable" id="btnClean" onclick="cleanupStorage();" title="Cleanup storage">
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
<input style="display: none" type="file" id="btnRom" name="upload" multiple />
<button class="btn btn-primary tooltip-enable" id="btnClean" title="Cleanup storage">
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
</button>
<input class="btn btn-primary disabled" style="display: none" type="file" id="btnRom" name="upload" onclick="document.getElementById('btnAdd').click();" onchange="selectFiles(event.target.files)" multiple />
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" onclick="keyPress('F1');" title="Menu toggle" disabled>
<span class="fa fa-bars" id="btnMenu"></span> <span class="sr-only">Menu</span>
</button>
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" onclick="Module.requestFullscreen(false)" title="Fullscreen" disabled>
<span class="fa fa-desktop" id="icnAdd"></span> <span class="sr-only">Fullscreen</span>
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" title="Menu toggle" disabled>
<span class="fa fa-bars" id="icnMenu"></span> <span class="sr-only">Menu</span>
</button>
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" title="Fullscreen" disabled>
<span class="fa fa-desktop" id="icnFullscreen"></span> <span class="sr-only">Fullscreen</span>
</button>
<button type="button" class="btn btn-primary tooltip-enable" data-toggle="modal" data-target="#helpModal">Help</button>
</li>
</div>
</li>
</div>
</ul>
<div class="toggleMenu">
<button class="btn btn-primary" id="btnHideMenu" title="Toggle Menu">
<span class="fa fa-chevron-up" id="icnHideMenu"></span> <span class="sr-only">Hide Top Navigation</span>
<span class="fa fa-chevron-up" id="icnHideMenu"></span> <span class="sr-only">Hide Top Navigation</span>
</button>
</div>
</div>
<!-- Basics steps modal for Web Libretro -->
<div class="modal fade" id="helpModal" role="dialog" style="color:black;">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h1 class="modal-title">Basics</h1>
</div>
<div class="modal-body">
<h3><b>Load Core</b></h3>
<p>Load your core by clicking on the first tab. Scroll down until you reach the desired Core. We will use Nestopia for now. Don't forget - Content must be compatible with the matched Core.</p>
<li>Nes: <i>NESTOPIA</i></li>
<li>Game Boy / Color: <i>Gambatte</i></li>
</ul>
<p>etc.</p>
<p></p>
<h3><b>Load Content</b></h3>
<p>After selecting Core, click Run. After RetroArch opens, click Add Content and select your compatible ROM.</p>
<li>Nestopia > <i>YourGame.nes</i></li>
<li>Gambatte > <i>YourGame.gbc</i></li>
</ul>
<p>etc.</p>
<p></p>
<h3><b><span class="fa fa-trash-o"></span> Cleanup Storage</b></h3>
<p>The trashcan erases your existing configuration and presets. If the Web Player doesn't start, you should click the trashcan and refresh the cache in your browser (usually F5 or Shift+F5).</p>
<p></p>
<h3><b><span class="fa fa-bars"></span> Quick Menu</b></h3>
<p>If you click on the three line icons, the Quick Menu will open here as in RetroArch.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
<!-- Basics steps modal for Web Libretro -->
<div class="modal fade" id="helpModal" role="dialog" style="color:black;">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h1 class="modal-title">Basics</h1>
</div>
<div class="modal-body">
<h3><b>Load Core</b></h3>
<p>Load your core by clicking on the first tab. Scroll down until you reach the desired Core. We will use Nestopia for now. Don't forget - Content must be compatible with the matched Core.</p>
<ul>
<li>Nes: <i>NESTOPIA</i></li>
<li>Game Boy / Color: <i>Gambatte</i></li>
</ul>
<p>etc.</p>
<p></p>
<h3><b>Load Content</b></h3>
<p>After selecting Core, click Run. After RetroArch opens, click Add Content and select your compatible ROM.</p>
<ul>
<li>Nestopia > <i>YourGame.nes</i></li>
<li>Gambatte > <i>YourGame.gbc</i></li>
</ul>
<p>etc.</p>
<p></p>
<h3><b><span class="fa fa-trash-o"></span> Cleanup Storage</b></h3>
<p>The trashcan erases your existing configuration and presets. If the Web Player doesn't start, you should click the trashcan and refresh the cache in your browser (usually F5 or Shift+F5).</p>
<p></p>
<h3><b><span class="fa fa-bars"></span> Quick Menu</b></h3>
<p>If you click on the three line icons, the Quick Menu will open here as in RetroArch.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<!--/.navbar content-->
</nav>
<div class="bg-inverse webplayer-container">
<div class="webplayer_border text-xs-center" id="canvas_div">
<div class="showMenu">
<button type="button" class="btn btn-link">
<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>
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960" height="720" alt="RetroArch Logo">
</div>
</div>
</div>
</div>
</div>
<!--/.navbar content-->
</div>
</nav>
<div class="bg-inverse webplayer-container">
<div class="webplayer_border text-xs-center" id="canvas_div">
<div class="showMenu">
<button type="button" class="btn btn-link">
<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>
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960px" height="720px" alt="RetroArch Logo">
</div>
</div>
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
<script src="analytics.js"></script>
<!--script src="//wzrd.in/standalone/browserfs@0.6.1"></script-->
<script src="browserfs.min.js"></script>
<script src="libretro.js"></script>
</body>
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
<script src="analytics.js"></script>
<!--script src="//wzrd.in/standalone/browserfs@0.6.1"></script-->
<script src="browserfs.min.js"></script>
<script src="libretro.js"></script>
</body>
</html>

View File

@ -81,12 +81,22 @@
}
}
/**
* Disable the border around the player.
*/
canvas.webplayer {
border: none;
outline: none;
width: 800px;
height: 600px;
}
/**
* Hack to make emscripten stop messing with the canvas size while in fullscreen.
* Foiled again!
*/
:fullscreen canvas.webplayer {
min-width: 100vw;
max-width: 100vw;
min-height: 100vh;
max-height: 100vh;
}
textarea {

View File

@ -6,24 +6,23 @@
var BrowserFS = BrowserFS;
var afs;
var initializationCount = 0;
var setImmediate;
var Module = {
noInitialRun: true,
arguments: ["-v", "--menu"],
encoder: new TextEncoder(),
message_queue:[],
message_out:[],
message_accum:"",
message_queue: [],
message_out: [],
message_accum: "",
retroArchSend: function(msg) {
let bytes = this.encoder.encode(msg+"\n");
this.message_queue.push([bytes,0]);
let bytes = this.encoder.encode(msg + "\n");
this.message_queue.push([bytes, 0]);
},
retroArchRecv: function() {
let out = this.message_out.shift();
if(out == null && this.message_accum != "") {
if (out == null && this.message_accum != "") {
out = this.message_accum;
this.message_accum = "";
}
@ -33,35 +32,36 @@ var Module = {
function(module) {
function stdin() {
// Return ASCII code of character, or null if no input
while(module.message_queue.length > 0){
while (module.message_queue.length > 0) {
var msg = module.message_queue[0][0];
var index = module.message_queue[0][1];
if(index >= msg.length) {
if (index >= msg.length) {
module.message_queue.shift();
} else {
module.message_queue[0][1] = index+1;
module.message_queue[0][1] = index + 1;
// assumption: msg is a uint8array
return msg[index];
}
}
return null;
}
function stdout(c) {
if(c == null) {
if (c == null) {
// flush
if(module.message_accum != "") {
if (module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
let s = String.fromCharCode(c);
if(s == "\n") {
if(module.message_accum != "") {
if (s == "\n") {
if (module.message_accum != "") {
module.message_out.push(module.message_accum);
module.message_accum = "";
}
} else {
module.message_accum = module.message_accum+s;
module.message_accum = module.message_accum + s;
}
}
}
@ -69,88 +69,71 @@ var Module = {
}
],
postRun: [],
onRuntimeInitialized: function()
{
appInitialized();
},
print: function(text)
{
console.log(text);
},
printErr: function(text)
{
console.error(text);
},
canvas: document.getElementById("canvas"),
onRuntimeInitialized: function() {
appInitialized();
},
print: function(text) {
console.log("stdout:", text);
},
printErr: function(text) {
console.log("stderr:", text);
},
canvas: document.getElementById("canvas"),
totalDependencies: 0,
monitorRunDependencies: function(left)
{
this.totalDependencies = Math.max(this.totalDependencies, left);
}
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
}
};
function cleanupStorage()
{
function cleanupStorage() {
localStorage.clear();
if (BrowserFS.FileSystem.IndexedDB.isAvailable())
{
if (BrowserFS.FileSystem.IndexedDB.isAvailable()) {
var req = indexedDB.deleteDatabase("RetroArch");
req.onsuccess = function () {
req.onsuccess = function() {
console.log("Deleted database successfully");
};
req.onerror = function () {
console.log("Couldn't delete database");
req.onerror = function() {
console.error("Couldn't delete database");
};
req.onblocked = function () {
console.log("Couldn't delete database due to the operation being blocked");
req.onblocked = function() {
console.error("Couldn't delete database due to the operation being blocked");
};
}
document.getElementById("btnClean").disabled = true;
}
function idbfsInit()
{
function idbfsInit() {
$('#icnLocal').removeClass('fa-globe');
$('#icnLocal').addClass('fa-spinner fa-spin');
var imfs = new BrowserFS.FileSystem.InMemory();
if (BrowserFS.FileSystem.IndexedDB.isAvailable())
{
if (BrowserFS.FileSystem.IndexedDB.isAvailable()) {
afs = new BrowserFS.FileSystem.AsyncMirror(imfs,
new BrowserFS.FileSystem.IndexedDB(function(e, fs)
{
if (e)
{
//fallback to imfs
afs = new BrowserFS.FileSystem.InMemory();
console.log("WEBPLAYER: error: " + e + " falling back to in-memory filesystem");
appInitialized();
}
else
{
// initialize afs by copying files from async storage to sync storage.
afs.initialize(function (e)
{
if (e)
{
new BrowserFS.FileSystem.IndexedDB(function(e, fs) {
if (e) {
// fallback to imfs
afs = new BrowserFS.FileSystem.InMemory();
console.log("WEBPLAYER: error: " + e + " falling back to in-memory filesystem");
console.error("WEBPLAYER: error: " + e + " falling back to in-memory filesystem");
appInitialized();
} else {
// initialize afs by copying files from async storage to sync storage.
afs.initialize(function(e) {
if (e) {
afs = new BrowserFS.FileSystem.InMemory();
console.error("WEBPLAYER: error: " + e + " falling back to in-memory filesystem");
appInitialized();
} else {
idbfsSyncComplete();
}
});
}
else
{
idbfsSyncComplete();
}
});
}
},
"RetroArch"));
},
"RetroArch"));
}
}
function idbfsSyncComplete()
{
function idbfsSyncComplete() {
$('#icnLocal').removeClass('fa-spinner').removeClass('fa-spin');
$('#icnLocal').addClass('fa-check');
console.log("WEBPLAYER: idbfs setup successful");
@ -158,69 +141,66 @@ function idbfsSyncComplete()
appInitialized();
}
function appInitialized()
{
/* Need to wait for the file system, the wasm runtime, and the zip download
to complete before enabling the Run button. */
initializationCount++;
if (initializationCount == 3)
{
setupFileSystem("browser");
preLoadingComplete();
}
}
function appInitialized() {
/* Need to wait for the file system, the wasm runtime, and the zip download
to complete before enabling the Run button. */
initializationCount++;
if (initializationCount == 3) {
setupFileSystem("browser");
preLoadingComplete();
}
}
function preLoadingComplete()
{
/* Make the Preview image clickable to start RetroArch. */
$('.webplayer-preview').addClass('loaded').click(function () {
function preLoadingComplete() {
// Make the Preview image clickable to start RetroArch.
$('.webplayer-preview').addClass('loaded').click(function() {
startRetroArch();
return false;
});
document.getElementById("btnRun").disabled = false;
$('#btnRun').removeClass('disabled');
});
$('#btnRun').removeClass('disabled').removeAttr("disabled").click(function() {
startRetroArch();
return false;
});
}
var zipTOC;
function zipfsInit() {
// 256 MB max bundle size
let buffer = new ArrayBuffer(256*1024*1024);
let bufferView = new Uint8Array(buffer);
let idx = 0;
// bundle should be in five parts (this can be changed later)
Promise.all([fetch("assets/frontend/bundle.zip.aa"),
fetch("assets/frontend/bundle.zip.ab"),
fetch("assets/frontend/bundle.zip.ac"),
fetch("assets/frontend/bundle.zip.ad"),
fetch("assets/frontend/bundle.zip.ae")
]).then(function(resps) {
Promise.all(resps.map((r) => r.arrayBuffer())).then(function(buffers) {
for (let buf of buffers) {
if (idx+buf.byteLength > buffer.maxByteLength) {
console.log("WEBPLAYER: error: bundle.zip is too large");
}
bufferView.set(new Uint8Array(buf), idx, buf.byteLength);
idx += buf.byteLength;
}
BrowserFS.FileSystem.ZipFS.computeIndex(BrowserFS.BFSRequire('buffer').Buffer(new Uint8Array(buffer, 0, idx)), function(toc) {
zipTOC = toc;
appInitialized();
});
})
});
// 256 MB max bundle size
let buffer = new ArrayBuffer(256 * 1024 * 1024);
let bufferView = new Uint8Array(buffer);
let idx = 0;
// bundle should be in five parts (this can be changed later)
Promise.all([fetch("assets/frontend/bundle.zip.aa"),
fetch("assets/frontend/bundle.zip.ab"),
fetch("assets/frontend/bundle.zip.ac"),
fetch("assets/frontend/bundle.zip.ad"),
fetch("assets/frontend/bundle.zip.ae")
]).then(function(resps) {
Promise.all(resps.map((r) => r.arrayBuffer())).then(function(buffers) {
for (let buf of buffers) {
if (idx + buf.byteLength > buffer.maxByteLength) {
console.error("WEBPLAYER: error: bundle.zip is too large");
}
bufferView.set(new Uint8Array(buf), idx, buf.byteLength);
idx += buf.byteLength;
}
BrowserFS.FileSystem.ZipFS.computeIndex(BrowserFS.BFSRequire('buffer').Buffer(new Uint8Array(buffer, 0, idx)), function(toc) {
zipTOC = toc;
appInitialized();
});
})
});
}
function setupFileSystem(backend)
{
/* create a mountable filesystem that will server as a root
mountpoint for browserfs */
var mfs = new BrowserFS.FileSystem.MountableFileSystem();
/* create an XmlHttpRequest filesystem for the bundled data */
function setupFileSystem(backend) {
// create a mountable filesystem that will server as a root mountpoint for browserfs
var mfs = new BrowserFS.FileSystem.MountableFileSystem();
// create an XmlHttpRequest filesystem for the bundled data
var xfs1 = new BrowserFS.FileSystem.ZipFS(zipTOC);
/* create an XmlHttpRequest filesystem for core assets */
var xfs2 = new BrowserFS.FileSystem.XmlHttpRequest
(".index-xhr", "assets/cores/");
// create an XmlHttpRequest filesystem for core assets
var xfs2 = new BrowserFS.FileSystem.XmlHttpRequest(".index-xhr", "assets/cores/");
console.log("WEBPLAYER: initializing filesystem: " + backend);
mfs.mount('/home/web_user/retroarch/userdata', afs);
@ -229,75 +209,81 @@ function setupFileSystem(backend)
mfs.mount('/home/web_user/retroarch/userdata/content/downloads', xfs2);
BrowserFS.initialize(mfs);
var BFS = new BrowserFS.EmscriptenFS(Module.FS, Module.PATH, Module.ERRNO_CODES);
Module.FS.mount(BFS, {root: '/home'}, '/home');
Module.FS.mount(BFS, {
root: '/home'
}, '/home');
console.log("WEBPLAYER: " + backend + " filesystem initialization successful");
}
/**
* Retrieve the value of the given GET parameter.
*/
// Retrieve the value of the given GET parameter.
function getParam(name) {
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results) {
return results[1] || null;
}
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results) {
return results[1] || null;
}
}
function startRetroArch()
{
function startRetroArch() {
$('.webplayer').show();
$('.webplayer-preview').hide();
document.getElementById("btnRun").disabled = true;
$('#btnFullscreen').removeClass('disabled');
$('#btnMenu').removeClass('disabled');
$('#btnAdd').removeClass('disabled');
$('#btnRom').removeClass('disabled');
$('#btnAdd').removeClass("disabled").removeAttr("disabled").click(function() {
$('#btnRom').click();
});
$('#btnRom').removeAttr("disabled").change(function(e) {
selectFiles(e.target.files);
});
$('#btnMenu').removeClass("disabled").removeAttr("disabled").click(function() {
Module._cmd_toggle_menu();
Module.canvas.focus();
});
$('#btnFullscreen').removeClass("disabled").removeAttr("disabled").click(function() {
Module.requestFullscreen(false);
Module.canvas.focus();
});
document.getElementById("btnAdd").disabled = false;
document.getElementById("btnRom").disabled = false;
document.getElementById("btnMenu").disabled = false;
document.getElementById("btnFullscreen").disabled = false;
Module["canvas"] = document.getElementById("canvas");
Module["canvas"].addEventListener("click", () => Module["canvas"].focus());
Module['callMain'](Module['arguments']);
Module['resumeMainLoop']();
Module['canvas'].focus();
Module.canvas.focus();
Module.canvas.addEventListener("pointerdown", function() {
Module.canvas.focus();
}, false);
Module.callMain(Module.arguments);
}
function selectFiles(files)
{
function selectFiles(files) {
$('#btnAdd').addClass('disabled');
$('#icnAdd').removeClass('fa-plus');
$('#icnAdd').addClass('fa-spinner spinning');
var count = files.length;
for (var i = 0; i < count; i++)
{
for (var i = 0; i < count; i++) {
filereader = new FileReader();
filereader.file_name = files[i].name;
filereader.readAsArrayBuffer(files[i]);
filereader.onload = function(){uploadData(this.result, this.file_name)};
filereader.onloadend = function(evt)
{
filereader.onload = function() {
uploadData(this.result, this.file_name)
};
filereader.onloadend = function(evt) {
console.log("WEBPLAYER: file: " + this.file_name + " upload complete");
if (evt.target.readyState == FileReader.DONE)
{
if (evt.target.readyState == FileReader.DONE) {
$('#btnAdd').removeClass('disabled');
$('#icnAdd').removeClass('fa-spinner spinning');
$('#icnAdd').addClass('fa-plus');
}
}
}
}
}
function uploadData(data,name)
{
function uploadData(data, name) {
var dataView = new Uint8Array(data);
Module.FS.createDataFile('/', name, dataView, true, false);
var data = Module.FS.readFile(name,{ encoding: 'binary' });
Module.FS.writeFile('/home/web_user/retroarch/userdata/content/' + name, data ,{ encoding: 'binary' });
var data = Module.FS.readFile(name, {
encoding: 'binary'
});
Module.FS.writeFile('/home/web_user/retroarch/userdata/content/' + name, data, {
encoding: 'binary'
});
Module.FS.unlink(name);
}
@ -306,8 +292,7 @@ function switchCore(corename) {
}
function switchStorage(backend) {
if (backend != localStorage.getItem("backend"))
{
if (backend != localStorage.getItem("backend")) {
localStorage.setItem("backend", backend);
location.reload();
}
@ -315,21 +300,24 @@ function switchStorage(backend) {
// When the browser has loaded everything.
$(function() {
// Enable all available ToolTips.
// Enable data clear
$('#btnClean').click(function() {
cleanupStorage();
});
// Enable all available ToolTips.
$('.tooltip-enable').tooltip({
placement: 'right'
});
// Allow hiding the top menu.
$('.showMenu').hide();
$('#btnHideMenu, .showMenu').click(function () {
$('#btnHideMenu, .showMenu').click(function() {
$('nav').slideToggle('slow');
$('.showMenu').toggle('slow');
});
/**
* Attempt to disable some default browser keys.
*/
// Attempt to disable some default browser keys.
var keys = {
9: "tab",
13: "enter",
@ -351,20 +339,20 @@ $(function() {
116: "F5",
117: "F6",
118: "F7",
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12"
};
window.addEventListener('keydown', function (e) {
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12"
};
window.addEventListener('keydown', function(e) {
if (keys[e.which]) {
e.preventDefault();
}
});
// Switch the core when selecting one.
$('#core-selector a').click(function () {
$('#core-selector a').click(function() {
var coreChoice = $(this).data('core');
switchCore(coreChoice);
});
@ -381,8 +369,8 @@ function loadCore(core) {
var coreTitle = $('#core-selector a[data-core="' + core + '"]').addClass('active').text();
$('#dropdownMenu1').text(coreTitle);
// Load the Core's related JavaScript.
import("./"+core+"_libretro.js").then(script => {
script.default(Module).then(mod => {
import("./" + core + "_libretro.js").then(script => {
script.default(Module).then(mod => {
Module = mod;
$('#icnRun').removeClass('fa-spinner').removeClass('fa-spin');
$('#icnRun').addClass('fa-play');
@ -390,18 +378,12 @@ function loadCore(core) {
$('#lblLocal').addClass('active');
idbfsInit();
zipfsInit();
}).catch(err => { console.error("Couldn't instantiate module",err); throw err; });
}).catch(err => { console.error("Couldn't load script",err); throw err; });
}
function keyPress(k)
{
function kp(k, event) {
var oEvent = new KeyboardEvent(event, { code: k });
document.dispatchEvent(oEvent);
document.getElementById('canvas').focus();
}
kp(k, "keydown");
setTimeout(function(){kp(k, "keyup")}, 50);
}
}).catch(err => {
console.error("Couldn't instantiate module", err);
throw err;
});
}).catch(err => {
console.error("Couldn't load script", err);
throw err;
});
}

View File

@ -3945,6 +3945,9 @@ bool command_event(enum event_command cmd, void *data)
}
#endif
break;
case CMD_EVENT_RELOAD_CONFIG:
config_load(global_get_ptr());
break;
case CMD_EVENT_DSP_FILTER_INIT:
#ifdef HAVE_DSP_FILTER
{