From eebbf3457ccb7ebca9a83cd77e8673a5f822e772 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Fri, 25 Feb 2022 17:19:25 +0000 Subject: [PATCH 1/4] emscripten: Use emscripten_webgl_ API directly --- src/video/emscripten/SDL_emscriptenopengles.c | 148 +++++++++++------- src/video/emscripten/SDL_emscriptenopengles.h | 16 +- src/video/emscripten/SDL_emscriptenvideo.c | 24 --- src/video/emscripten/SDL_emscriptenvideo.h | 7 - 4 files changed, 101 insertions(+), 94 deletions(-) diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c index 10c6285cf3..a3219372c1 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.c +++ b/src/video/emscripten/SDL_emscriptenopengles.c @@ -20,83 +20,123 @@ */ #include "../../SDL_internal.h" -#if SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL +#if SDL_VIDEO_DRIVER_EMSCRIPTEN #include +#include #include #include "SDL_emscriptenvideo.h" #include "SDL_emscriptenopengles.h" #include "SDL_hints.h" -#define LOAD_FUNC(NAME) _this->egl_data->NAME = NAME; - -/* EGL implementation of SDL OpenGL support */ int -Emscripten_GLES_LoadLibrary(_THIS, const char *path) { - /*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(); - } - - /* 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"); - } - - if (_this->egl_data->eglInitialize(_this->egl_data->egl_display, NULL, NULL) != EGL_TRUE) { - return SDL_SetError("Could not initialize EGL"); - } - - if (path) { - SDL_strlcpy(_this->gl_config.driver_path, path, sizeof(_this->gl_config.driver_path) - 1); - } else { - *_this->gl_config.driver_path = '\0'; - } - +Emscripten_GLES_LoadLibrary(_THIS, const char *path) +{ return 0; } -SDL_EGL_CreateContext_impl(Emscripten) -SDL_EGL_MakeCurrent_impl(Emscripten) +void +Emscripten_GLES_UnloadLibrary(_THIS) +{ +} + +void * +Emscripten_GLES_GetProcAddress(_THIS, const char *proc) +{ + return emscripten_webgl_get_proc_address(proc); +} + +int +Emscripten_GLES_SetSwapInterval(_THIS, int interval) +{ + 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_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; + context = emscripten_webgl_create_context(window_data->canvas_id, &attribs); + + if (context < 0) { + SDL_SetError("Could not create webgl context"); + return NULL; + } + + if (emscripten_webgl_make_context_current(context) != EMSCRIPTEN_RESULT_SUCCESS) { + emscripten_webgl_destroy_context(context); + return NULL; + } + + + return (SDL_GLContext)context; +} + +void +Emscripten_GLES_DeleteContext(_THIS, SDL_GLContext context) +{ + 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; } -#endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN && SDL_VIDEO_OPENGL_EGL */ +int +Emscripten_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context) +{ + /* ignores window, as it isn't possible to reuse contexts across canvases */ + 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 */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/emscripten/SDL_emscriptenopengles.h b/src/video/emscripten/SDL_emscriptenopengles.h index 9d178f6902..6d58ac10df 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.h +++ b/src/video/emscripten/SDL_emscriptenopengles.h @@ -23,25 +23,23 @@ #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 index 550031d3fb..46813a7217 100644 --- 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" @@ -110,7 +109,6 @@ 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; @@ -120,7 +118,6 @@ 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; @@ -259,21 +256,6 @@ 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 */ @@ -329,12 +311,6 @@ 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); diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h index e87788d3f8..20481235e5 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.h +++ b/src/video/emscripten/SDL_emscriptenvideo.h @@ -28,15 +28,8 @@ #include #include -#if SDL_VIDEO_OPENGL_EGL -#include -#endif - typedef struct SDL_WindowData { -#if SDL_VIDEO_OPENGL_EGL - EGLSurface egl_surface; -#endif SDL_Window *window; SDL_Surface *surface; From 539efc1bbaec197cb0d6663824fd29fd71060785 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sat, 26 Feb 2022 12:24:32 +0000 Subject: [PATCH 2/4] emscripten: Return an error for webgl context limitations --- src/video/emscripten/SDL_emscriptenopengles.c | 30 ++++++++++++++++++- src/video/emscripten/SDL_emscriptenvideo.h | 2 ++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c index a3219372c1..ccec124ce2 100644 --- a/src/video/emscripten/SDL_emscriptenopengles.c +++ b/src/video/emscripten/SDL_emscriptenopengles.c @@ -94,6 +94,12 @@ Emscripten_GLES_CreateContext(_THIS, SDL_Window * window) 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; + } + context = emscripten_webgl_create_context(window_data->canvas_id, &attribs); if (context < 0) { @@ -106,6 +112,7 @@ Emscripten_GLES_CreateContext(_THIS, SDL_Window * window) return NULL; } + window_data->gl_context = (SDL_GLContext)context; return (SDL_GLContext)context; } @@ -113,6 +120,18 @@ Emscripten_GLES_CreateContext(_THIS, SDL_Window * window) 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); } @@ -129,7 +148,16 @@ Emscripten_GLES_SwapWindow(_THIS, SDL_Window * window) int Emscripten_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context) { - /* ignores window, as it isn't possible to reuse contexts across canvases */ + /* 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"); } diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h index 20481235e5..4cd0a5ce59 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.h +++ b/src/video/emscripten/SDL_emscriptenvideo.h @@ -33,6 +33,8 @@ typedef struct SDL_WindowData SDL_Window *window; SDL_Surface *surface; + SDL_GLContext gl_context; + char *canvas_id; float pixel_ratio; From b5aedaad5923edd88ca33e6dab4b86503e8cb0f3 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sun, 8 Apr 2018 16:54:29 +0100 Subject: [PATCH 3/4] emscripten: Modify UpdateWindowFramebuffer To work with multiple canvases --- src/video/emscripten/SDL_emscriptenframebuffer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/video/emscripten/SDL_emscriptenframebuffer.c b/src/video/emscripten/SDL_emscriptenframebuffer.c index 03fea04efa..05e4aa7453 100644 --- a/src/video/emscripten/SDL_emscriptenframebuffer.c +++ b/src/video/emscripten/SDL_emscriptenframebuffer.c @@ -75,12 +75,15 @@ int Emscripten_UpdateWindowFramebuffer(_THIS, SDL_Window * window, const SDL_Rec var w = $0; var h = $1; var pixels = $2; + var canvasId = UTF8ToString($3); + var canvas = document.querySelector(canvasId); + //TODO: this should store a context per canvas if (!Module['SDL2']) Module['SDL2'] = {}; var SDL2 = Module['SDL2']; - if (SDL2.ctxCanvas !== Module['canvas']) { - SDL2.ctx = Module['createContext'](Module['canvas'], false, true); - SDL2.ctxCanvas = Module['canvas']; + if (SDL2.ctxCanvas !== canvas) { + SDL2.ctx = Module['createContext'](canvas, false, true); + SDL2.ctxCanvas = canvas; } if (SDL2.w !== w || SDL2.h !== h || SDL2.imageCtx !== SDL2.ctx) { SDL2.image = SDL2.ctx.createImageData(w, h); @@ -156,7 +159,7 @@ int Emscripten_UpdateWindowFramebuffer(_THIS, SDL_Window * window, const SDL_Rec } SDL2.ctx.putImageData(SDL2.image, 0, 0); - }, surface->w, surface->h, surface->pixels); + }, surface->w, surface->h, surface->pixels, data->canvas_id); if (emscripten_has_asyncify() && SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_ASYNCIFY, SDL_TRUE)) { /* give back control to browser for screen refresh */ From d75fb0995dcf533bbc03bdac1c7d41caad678d68 Mon Sep 17 00:00:00 2001 From: Charlie Birks Date: Sat, 26 Feb 2022 14:52:08 +0000 Subject: [PATCH 4/4] emscripten: Add a hint for specifying the canvas selector Now that we're not going through EGL, this is easy --- include/SDL_hints.h | 9 +++++++++ src/video/emscripten/SDL_emscriptenvideo.c | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 48f4f3689a..6fee3e5fce 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -366,6 +366,15 @@ extern "C" { */ #define SDL_HINT_EMSCRIPTEN_ASYNCIFY "SDL_EMSCRIPTEN_ASYNCIFY" +/** + * \brief Specify the CSS selector used for the "default" window/canvas + * + * This hint only applies to the emscripten platform + * + * The default value is "#canvas" + */ +#define SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR "SDL_EMSCRIPTEN_CANVAS_SELECTOR" + /** * \brief override the binding element for keyboard inputs for Emscripten builds * diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 46813a7217..c8efac48f8 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -215,6 +215,7 @@ Emscripten_CreateWindow(_THIS, SDL_Window * window) SDL_WindowData *wdata; double scaled_w, scaled_h; double css_w, css_h; + const char *selector; /* Allocate window internal data */ wdata = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData)); @@ -222,7 +223,12 @@ Emscripten_CreateWindow(_THIS, SDL_Window * window) return SDL_OutOfMemory(); } - wdata->canvas_id = SDL_strdup("#canvas"); + selector = SDL_GetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR); + if (!selector) { + selector = "#canvas"; + } + + wdata->canvas_id = SDL_strdup(selector); if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { wdata->pixel_ratio = emscripten_get_device_pixel_ratio();