Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Emscripten builds support #3630

Merged
merged 34 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f20ecc7
seqdisp.cpp: Add cancel functionality to on-demand video provider
past-due Jan 29, 2024
4c2e887
[CMake] Add EXCLUDE_FROM_ALL
past-due Feb 6, 2024
0b9850f
game.cpp: Remove unnecessary include
past-due Feb 7, 2024
5728d35
Update .gitignore
past-due Feb 7, 2024
3119b06
Initial Emscripten support
past-due Feb 7, 2024
17abf2e
[GitHub Actions] Initial Emscripten CI
past-due Feb 8, 2024
81ae708
Emscripten: Generate a service worker using workbox-cli
past-due Feb 9, 2024
79f4dc8
[GitHub Actions] Emscripten: Install workbox-cli
past-due Feb 9, 2024
f800fc0
Add Emscripten docs
past-due Feb 10, 2024
88dfc99
[GitHub Actions] Emscripten: Package and upload artifacts
past-due Feb 10, 2024
34e9946
Emscripten: Adjust paths to optional packages
past-due Feb 10, 2024
1297016
Emscripten: Attempt to opt-in to persistent storage on user-initiated…
past-due Feb 10, 2024
8face72
Emscripten: Add storage persistence status to Options modal
past-due Feb 11, 2024
f0872fa
[GitHub Actions] Emscripten: Refactoring, support deployment
past-due Feb 12, 2024
c189ab8
[CMake] Add default packaging config for Emscripten
past-due Feb 12, 2024
94bc609
Emscripten: Adjust config dir suffix
past-due Feb 12, 2024
37cb0d3
[CMake] Silence basis_universal_host_build output
past-due Feb 12, 2024
ac42b7a
[GitHub Actions]: Update release workflow
past-due Feb 12, 2024
40d7bd1
Emscripten: Various tweaks
past-due Feb 12, 2024
59fb48e
[OpenGL] Emscripten: Explicitly enable WebGL extensions
past-due Feb 12, 2024
ee1a2f9
debug.cpp: Adjust defaults for Emscripten
past-due Feb 12, 2024
75da34d
Emscripten: Various shell tweaks
past-due Feb 12, 2024
f03e3d8
Emscripten: Better error-handling for syncfs
past-due Feb 13, 2024
aaaaecc
Emscripten: Add beacon
past-due Feb 13, 2024
b77198e
[GitHub Actions] Update linter, exclude Emscripten JS
past-due Feb 13, 2024
52c19de
Emscripten: Update wz-workbox-config.js
past-due Feb 13, 2024
c90aab1
Emscripten: Improve WebAssembly.Memory allocation strategy
past-due Feb 13, 2024
d7d7111
[CMake] Emscripten: Add bdrops/missionend.png
past-due Feb 14, 2024
0b11aef
Emscripten: Improve WebAssembly.Memory limit defaults, add UI
past-due Feb 14, 2024
5d5471e
display3d.cpp: Trigger abort() in crash handler test on Emscripten
past-due Feb 14, 2024
bf25ed9
Emscripten: Add UI for displaying uncaught runtime errors
past-due Feb 14, 2024
44c5675
Emscripten: Always persist FS changes on saveGame
past-due Feb 14, 2024
306c8e0
warzoneconfig.cpp: Default Emscripten FMVmode to 2x
past-due Feb 14, 2024
1a17497
Emscripten: Disable the Options nav button during loading
past-due Feb 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .ci/emscripten/toolchain/Toolchain-Emscripten.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Toolchain for compiling for Emscripten

# Note:
# This is a bit of a hack.
# We actually want to use the Emscripten.cmake toolchain provided by Emscripten...
# However we also want to override and enable pthreads for everything

if(NOT DEFINED ENV{EMSCRIPTEN_ROOT})
find_path(EMSCRIPTEN_ROOT "emcc")
else()
set(EMSCRIPTEN_ROOT "$ENV{EMSCRIPTEN_ROOT}")
endif()

if(NOT EMSCRIPTEN_ROOT)
if(NOT DEFINED ENV{EMSDK})
message(FATAL_ERROR "The emcc compiler not found in PATH")
endif()
set(EMSCRIPTEN_ROOT "$ENV{EMSDK}/upstream/emscripten")
endif()

if(NOT EXISTS "${EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake")
message(FATAL_ERROR "Emscripten.cmake toolchain file not found")
endif()

include("${EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake")

# Always enable PThreads
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_PTHREADS=1 -s USE_SDL=0")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s USE_PTHREADS=1 -s USE_SDL=0")

# Enable optimizations for release builds
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
13 changes: 13 additions & 0 deletions .ci/vcpkg/overlay-ports/sdl2/alsa-dep-fix.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in
index cc8bcf26d..ead829767 100644
--- a/SDL2Config.cmake.in
+++ b/SDL2Config.cmake.in
@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake")

set(SDL_ALSA @SDL_ALSA@)
set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@)
-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static)
+if(SDL_ALSA)
sdlFindALSA()
endif()
unset(SDL_ALSA)
13 changes: 13 additions & 0 deletions .ci/vcpkg/overlay-ports/sdl2/deps.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 65a98efbe..2f99f28f1 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -352,7 +352,7 @@ endmacro()
# - HAVE_SDL_LOADSO opt
macro(CheckLibSampleRate)
if(SDL_LIBSAMPLERATE)
- find_package(SampleRate QUIET)
+ find_package(SampleRate CONFIG REQUIRED)
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
set(HAVE_LIBSAMPLERATE TRUE)
set(HAVE_LIBSAMPLERATE_H TRUE)
320 changes: 320 additions & 0 deletions .ci/vcpkg/overlay-ports/sdl2/emscripten-webgl.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c
--- a/src/video/emscripten/SDL_emscriptenopengles.c
+++ b/src/video/emscripten/SDL_emscriptenopengles.c
@@ -20,81 +20,139 @@
*/
#include "../../SDL_internal.h"

-#if SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL
+#if SDL_VIDEO_DRIVER_EMSCRIPTEN

#include <emscripten/emscripten.h>
+#include <emscripten/html5_webgl.h>
#include <GLES2/gl2.h>

#include "SDL_emscriptenvideo.h"
#include "SDL_emscriptenopengles.h"
#include "SDL_hints.h"

-#define LOAD_FUNC(NAME) _this->egl_data->NAME = NAME;
+int Emscripten_GLES_LoadLibrary(_THIS, const char *path)
+{
+ return 0;
+}
+
+void Emscripten_GLES_UnloadLibrary(_THIS)
+{
+}

-/* EGL implementation of SDL OpenGL support */
+void * Emscripten_GLES_GetProcAddress(_THIS, const char *proc)
+{
+ return emscripten_webgl_get_proc_address(proc);
+}

-int Emscripten_GLES_LoadLibrary(_THIS, const char *path)
+int Emscripten_GLES_SetSwapInterval(_THIS, int interval)
{
- /*we can't load EGL dynamically*/
- _this->egl_data = (struct SDL_EGL_VideoData *) SDL_calloc(1, sizeof(SDL_EGL_VideoData));
- if (!_this->egl_data) {
- return SDL_OutOfMemory();
+ if (interval < 0) {
+ return SDL_SetError("Late swap tearing currently unsupported");
+ } else if(interval == 0) {
+ emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, 0);
+ } else {
+ emscripten_set_main_loop_timing(EM_TIMING_RAF, interval);
}
+ return 0;
+}
+
+int Emscripten_GLES_GetSwapInterval(_THIS)
+{
+ int mode, value;
+
+ emscripten_get_main_loop_timing(&mode, &value);
+
+ if(mode == EM_TIMING_RAF)
+ return value;
+
+ return 0;
+}
+
+SDL_GLContext Emscripten_GLES_CreateContext(_THIS, SDL_Window * window)
+{
+ SDL_WindowData *window_data;
+
+ EmscriptenWebGLContextAttributes attribs;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;

- /* Emscripten forces you to manually cast eglGetProcAddress to the real
- function type; grep for "__eglMustCastToProperFunctionPointerType" in
- Emscripten's egl.h for details. */
- _this->egl_data->eglGetProcAddress = (void *(EGLAPIENTRY *)(const char *)) eglGetProcAddress;
-
- LOAD_FUNC(eglGetDisplay);
- LOAD_FUNC(eglInitialize);
- LOAD_FUNC(eglTerminate);
- LOAD_FUNC(eglChooseConfig);
- LOAD_FUNC(eglGetConfigAttrib);
- LOAD_FUNC(eglCreateContext);
- LOAD_FUNC(eglDestroyContext);
- LOAD_FUNC(eglCreateWindowSurface);
- LOAD_FUNC(eglDestroySurface);
- LOAD_FUNC(eglMakeCurrent);
- LOAD_FUNC(eglSwapBuffers);
- LOAD_FUNC(eglSwapInterval);
- LOAD_FUNC(eglWaitNative);
- LOAD_FUNC(eglWaitGL);
- LOAD_FUNC(eglBindAPI);
- LOAD_FUNC(eglQueryString);
- LOAD_FUNC(eglGetError);
-
- _this->egl_data->egl_display = _this->egl_data->eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (!_this->egl_data->egl_display) {
- return SDL_SetError("Could not get EGL display");
+ emscripten_webgl_init_context_attributes(&attribs);
+
+ attribs.alpha = _this->gl_config.alpha_size > 0;
+ attribs.depth = _this->gl_config.depth_size > 0;
+ attribs.stencil = _this->gl_config.stencil_size > 0;
+ attribs.antialias = _this->gl_config.multisamplebuffers == 1;
+
+ if(_this->gl_config.major_version == 3)
+ attribs.majorVersion = 2; /* WebGL 2.0 ~= GLES 3.0 */
+
+ window_data = (SDL_WindowData *) window->driverdata;
+
+ if (window_data->gl_context) {
+ SDL_SetError("Cannot create multiple webgl contexts per window");
+ return NULL;
}

- if (_this->egl_data->eglInitialize(_this->egl_data->egl_display, NULL, NULL) != EGL_TRUE) {
- return SDL_SetError("Could not initialize EGL");
+ context = emscripten_webgl_create_context(window_data->canvas_id, &attribs);
+
+ if (context < 0) {
+ SDL_SetError("Could not create webgl context");
+ return NULL;
}

- if (path) {
- SDL_strlcpy(_this->gl_config.driver_path, path, sizeof(_this->gl_config.driver_path) - 1);
- } else {
- *_this->gl_config.driver_path = '\0';
+ if (emscripten_webgl_make_context_current(context) != EMSCRIPTEN_RESULT_SUCCESS) {
+ emscripten_webgl_destroy_context(context);
+ return NULL;
}

- return 0;
+ window_data->gl_context = (SDL_GLContext)context;
+
+ return (SDL_GLContext)context;
}

-SDL_EGL_CreateContext_impl(Emscripten)
-SDL_EGL_MakeCurrent_impl(Emscripten)
+void Emscripten_GLES_DeleteContext(_THIS, SDL_GLContext context)
+{
+ SDL_Window *window;
+
+ /* remove the context from its window */
+ for (window = _this->windows; window != NULL; window = window->next) {
+ SDL_WindowData *window_data;
+ window_data = (SDL_WindowData *) window->driverdata;
+
+ if (window_data->gl_context == context) {
+ window_data->gl_context = NULL;
+ }
+ }
+
+ emscripten_webgl_destroy_context((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context);
+}

int Emscripten_GLES_SwapWindow(_THIS, SDL_Window *window)
{
- EGLBoolean ret = SDL_EGL_SwapBuffers(_this, ((SDL_WindowData *) window->driverdata)->egl_surface);
if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, SDL_TRUE)) {
/* give back control to browser for screen refresh */
emscripten_sleep(0);
}
- return ret;
+ return 0;
+}
+
+int Emscripten_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
+{
+ /* it isn't possible to reuse contexts across canvases */
+ if (window && context) {
+ SDL_WindowData *window_data;
+ window_data = (SDL_WindowData *) window->driverdata;
+
+ if (context != window_data->gl_context) {
+ return SDL_SetError("Cannot make context current to another window");
+ }
+ }
+
+ if (emscripten_webgl_make_context_current((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context) != EMSCRIPTEN_RESULT_SUCCESS) {
+ return SDL_SetError("Unable to make context current");
+ }
+ return 0;
}

-#endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL */
+#endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN */

/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/emscripten/SDL_emscriptenopengles.h b/src/video/emscripten/SDL_emscriptenopengles.h
--- a/src/video/emscripten/SDL_emscriptenopengles.h
+++ b/src/video/emscripten/SDL_emscriptenopengles.h
@@ -23,25 +23,24 @@
#ifndef SDL_emscriptenopengles_h_
#define SDL_emscriptenopengles_h_

-#if SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL
+#if SDL_VIDEO_DRIVER_EMSCRIPTEN

#include "../SDL_sysvideo.h"
-#include "../SDL_egl_c.h"

/* OpenGLES functions */
-#define Emscripten_GLES_GetAttribute SDL_EGL_GetAttribute
-#define Emscripten_GLES_GetProcAddress SDL_EGL_GetProcAddress
-#define Emscripten_GLES_UnloadLibrary SDL_EGL_UnloadLibrary
-#define Emscripten_GLES_SetSwapInterval SDL_EGL_SetSwapInterval
-#define Emscripten_GLES_GetSwapInterval SDL_EGL_GetSwapInterval
-#define Emscripten_GLES_DeleteContext SDL_EGL_DeleteContext

extern int Emscripten_GLES_LoadLibrary(_THIS, const char *path);
+extern void Emscripten_GLES_UnloadLibrary(_THIS);
+extern void * Emscripten_GLES_GetProcAddress(_THIS, const char *proc);
+extern int Emscripten_GLES_SetSwapInterval(_THIS, int interval);
+extern int Emscripten_GLES_GetSwapInterval(_THIS);
+
extern SDL_GLContext Emscripten_GLES_CreateContext(_THIS, SDL_Window * window);
+extern void Emscripten_GLES_DeleteContext(_THIS, SDL_GLContext context);
extern int Emscripten_GLES_SwapWindow(_THIS, SDL_Window * window);
extern int Emscripten_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context);

-#endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL */
+#endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN */

#endif /* SDL_emscriptenopengles_h_ */

diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c
--- a/src/video/emscripten/SDL_emscriptenvideo.c
+++ b/src/video/emscripten/SDL_emscriptenvideo.c
@@ -27,7 +27,6 @@
#include "SDL_hints.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
-#include "../SDL_egl_c.h"
#include "../../events/SDL_events_c.h"

#include "SDL_emscriptenvideo.h"
@@ -106,7 +105,6 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void)
device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer;
device->DestroyWindowFramebuffer = Emscripten_DestroyWindowFramebuffer;

-#if SDL_VIDEO_OPENGL_EGL
device->GL_LoadLibrary = Emscripten_GLES_LoadLibrary;
device->GL_GetProcAddress = Emscripten_GLES_GetProcAddress;
device->GL_UnloadLibrary = Emscripten_GLES_UnloadLibrary;
@@ -116,7 +114,6 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void)
device->GL_GetSwapInterval = Emscripten_GLES_GetSwapInterval;
device->GL_SwapWindow = Emscripten_GLES_SwapWindow;
device->GL_DeleteContext = Emscripten_GLES_DeleteContext;
-#endif

device->free = Emscripten_DeleteDevice;

@@ -247,21 +244,6 @@ static int Emscripten_CreateWindow(_THIS, SDL_Window *window)
}
}

-#if SDL_VIDEO_OPENGL_EGL
- if (window->flags & SDL_WINDOW_OPENGL) {
- if (!_this->egl_data) {
- if (SDL_GL_LoadLibrary(NULL) < 0) {
- return -1;
- }
- }
- wdata->egl_surface = SDL_EGL_CreateSurface(_this, 0);
-
- if (wdata->egl_surface == EGL_NO_SURFACE) {
- return SDL_SetError("Could not create GLES window surface");
- }
- }
-#endif
-
wdata->window = window;

/* Setup driver data for this window */
@@ -314,12 +296,6 @@ static void Emscripten_DestroyWindow(_THIS, SDL_Window *window)
data = (SDL_WindowData *)window->driverdata;

Emscripten_UnregisterEventHandlers(data);
-#if SDL_VIDEO_OPENGL_EGL
- if (data->egl_surface != EGL_NO_SURFACE) {
- SDL_EGL_DestroySurface(_this, data->egl_surface);
- data->egl_surface = EGL_NO_SURFACE;
- }
-#endif

/* We can't destroy the canvas, so resize it to zero instead */
emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
@@ -341,6 +317,7 @@ static void Emscripten_SetWindowFullscreen(_THIS, SDL_Window *window, SDL_VideoD
SDL_bool is_desktop_fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP;
int res;

+ SDL_zero(strategy);
strategy.scaleMode = is_desktop_fullscreen ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;

if (!is_desktop_fullscreen) {
diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h
--- a/src/video/emscripten/SDL_emscriptenvideo.h
+++ b/src/video/emscripten/SDL_emscriptenvideo.h
@@ -28,18 +28,13 @@
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>

-#if SDL_VIDEO_OPENGL_EGL
-#include <EGL/egl.h>
-#endif
-
typedef struct SDL_WindowData
{
-#if SDL_VIDEO_OPENGL_EGL
- EGLSurface egl_surface;
-#endif
SDL_Window *window;
SDL_Surface *surface;

+ SDL_GLContext gl_context;
+
char *canvas_id;

float pixel_ratio;
Loading
Loading