SDL: Merge branch 'main' into no-ie

From eebbf3457ccb7ebca9a83cd77e8673a5f822e772 Mon Sep 17 00:00:00 2001
From: Charlie Birks <[EMAIL REDACTED]>
Date: Fri, 25 Feb 2022 17:19:25 +0000
Subject: [PATCH 001/459] emscripten: Use emscripten_webgl_ API directly

---
 src/video/emscripten/SDL_emscriptenopengles.c | 138 +++++++++++-------
 src/video/emscripten/SDL_emscriptenopengles.h |  16 +-
 src/video/emscripten/SDL_emscriptenvideo.c    |  24 ---
 src/video/emscripten/SDL_emscriptenvideo.h    |   7 -
 4 files changed, 96 insertions(+), 89 deletions(-)

diff --git a/src/video/emscripten/SDL_emscriptenopengles.c b/src/video/emscripten/SDL_emscriptenopengles.c
index 10c6285cf325..a3219372c147 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 <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;
 
-/* EGL implementation of SDL OpenGL support */
+int
+Emscripten_GLES_LoadLibrary(_THIS, const char *path)
+{
+    return 0;
+}
+
+void
+Emscripten_GLES_UnloadLibrary(_THIS)
+{
+}
+
+void *
+Emscripten_GLES_GetProcAddress(_THIS, const char *proc)
+{
+    return emscripten_webgl_get_proc_address(proc);
+}
 
 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_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);
     }
 
-    /* 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");
+    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 (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;
+
+
+    return (SDL_GLContext)context;
 }
 
-SDL_EGL_CreateContext_impl(Emscripten)
-SDL_EGL_MakeCurrent_impl(Emscripten)
+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;
+}
+
+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 && 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
index 9d178f6902d9..6d58ac10dfd4 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 550031d3fbea..46813a721798 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 e87788d3f815..20481235e5b5 100644
--- a/src/video/emscripten/SDL_emscriptenvideo.h
+++ b/src/video/emscripten/SDL_emscriptenvideo.h
@@ -28,15 +28,8 @@
 #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;
 

From 539efc1bbaec197cb0d6663824fd29fd71060785 Mon Sep 17 00:00:00 2001
From: Charlie Birks <charlie@daft.games>
Date: Sat, 26 Feb 2022 12:24:32 +0000
Subject: [PATCH 002/459] 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 a3219372c147..ccec124ce20f 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 20481235e5b5..4cd0a5ce59e2 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 <charlie@daftgames.net>
Date: Sun, 8 Apr 2018 16:54:29 +0100
Subject: [PATCH 003/459] 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 03fea04efa30..05e4aa7453f2 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 <charlie@daft.games>
Date: Sat, 26 Feb 2022 14:52:08 +0000
Subject: [PATCH 004/459] 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 48f4f3689a70..6fee3e5fce47 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 46813a721798..c8efac48f850 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();

From cfab203f91239a039be49a953242ad07db032b6d Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc@chromium.org>
Date: Thu, 29 Sep 2022 07:36:14 -0700
Subject: [PATCH 005/459] emscripten: Remove use of EM_ASM from SDL_timer code.

Instead use the native emscripten timer API.

See https://github.com/emscripten-core/emscripten/issues/17941
---
 .github/workflows/emscripten.yml |  2 +-
 src/timer/SDL_timer.c            | 37 ++++++++++++++++----------------
 2 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml
index 06aee9e3a5d5..3e8647c41712 100644
--- a/.github/workflows/emscripten.yml
+++ b/.github/workflows/emscripten.yml
@@ -9,7 +9,7 @@ jobs:
       - uses: actions/checkout@v2
       - uses: mymindstorm/setup-emsdk@v10
         with:
-          version: 2.0.31
+          version: 2.0.32
       - name: Configure CMake
         run: |
           emcmake cmake -S . -B build \
diff --git a/src/timer/SDL_timer.c b/src/timer/SDL_timer.c
index 7b7692df0fb6..6741e97cb52d 100644
--- a/src/timer/SDL_timer.c
+++ b/src/timer/SDL_timer.c
@@ -375,11 +375,15 @@ SDL_RemoveTimer(SDL_TimerID id)
 #else
 
 #include <emscripten/emscripten.h>
+#include <emscripten/eventloop.h>
 
 typedef struct _SDL_TimerMap
 {
     int timerID;
     int timeoutID;
+    Uint32 interval;
+    SDL_TimerCallback callback;
+    void *param;
     struct _SDL_TimerMap *next;
 } SDL_TimerMap;
 
@@ -391,18 +395,14 @@ typedef struct {
 static SDL_TimerData SDL_timer_data;
 
 static void
-SDL_Emscripten_TimerHelper(SDL_TimerMap *entry, Uint32 interval, SDL_TimerCallback callback, void *param)
+SDL_Emscripten_TimerHelper(void *userdata)
 {
-    Uint32 new_timeout;
-
-    new_timeout = callback(interval, param);
-
-    if (new_timeout != 0) {
-        entry->timeoutID = EM_ASM_INT({
-            return Browser.safeSetTimeout(function() {
-                dynCall('viiii', $0, [$1, $2, $3, $4]);
-            }, $2);
-        }, &SDL_Emscripten_TimerHelper, entry, interval, callback, param);
+    SDL_TimerMap *entry = (SDL_TimerMap*)userdata;
+    entry->interval = entry->callback(entry->interval, entry->param);
+    if (entry->interval > 0) {
+        entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
+                                                  entry->interval,
+                                                  entry);
     }
 }
 
@@ -437,12 +437,13 @@ SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *param)
         return 0;
     }
     entry->timerID = ++data->nextID;
+    entry->callback = callback;
+    entry->param = param;
+    entry->interval = interval;
 
-    entry->timeoutID = EM_ASM_INT({
-        return Browser.safeSetTimeout(function() {
-            dynCall('viiii', $0, [$1, $2, $3, $4]);
-        }, $2);
-    }, &SDL_Emscripten_TimerHelper, entry, interval, callback, param);
+    entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
+                                              entry->interval,
+                                              entry);
 
     entry->next = data->timermap;
     data->timermap = entry;
@@ -470,9 +471,7 @@ SDL_RemoveTimer(SDL_TimerID id)
     }
 
     if (entry) {
-        EM_ASM_({
-            window.clearTimeout($0);
-        }, entry->timeoutID);
+        emscripten_clear_timeout(entry->timeoutID);
         SDL_free(entry);
 
         return SDL_TRUE;

From 1b895912a217e80aa8e2ac1c6377a018579d1c0a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <icculus@icculus.org>
Date: Fri, 30 Sep 2022 14:23:36 -0400
Subject: [PATCH 006/459] docs: Note the lowest supported Emscripten version.

Reference Issue #6304.
---
 docs/README-emscripten.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docs/README-emscripten.md b/docs/README-emscripten.md
index b535cc60a125..7e9880b0879d 100644
--- a/docs/README-emscripten.md
+++ b/docs/README-emscripten.md
@@ -1,6 +1,10 @@
 Emscripten
 ================================================================================
 
+SDL currently requires at least Emscripten 2.0.32 to build. Newer versions
+are likely to work, as well.
+
+
 Build:
 
     $ mkdir build

From 82e341bc9e914e753fe7842834d37b16aebc4ad5 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <slouken@libsdl.org>
Date: Fri, 30 Sep 2022 11:40:29 -0700
Subject: [PATCH 007/459] Android: use real editable text and mimic the edit
 operations to generate key events

This fixes issues where the IME and the output would get out of sync
---
 Android.mk                                    |   0
 .../main/java/org/libsdl/app/SDLActivity.java | 174 +++++++++++++-----
 2 files changed, 133 insertions(+), 41 deletions(-)
 mode change 100644 => 100755 Android.mk

diff --git a/Android.mk b/Android.mk
old mode 100644
new mode 100755
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index 8d4039c1ba52..c9019dad6c2a 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -44,6 +44,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
@@ -1874,9 +1875,17 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
 
 class SDLInputConnection extends BaseInputConnection {
 
+    protected EditText mEditText;
+    protected int m_nLastContentLength = 0;
+
     public SDLInputConnection(View targetView, boolean fullEditor) {
         super(targetView, fullEditor);
+        mEditText = new EditText(SDL.getContext());
+    }
 
+    @Override
+    public Editable getEditable() {
+        return mEditText.getEditableText();
     }
 
     @Override
@@ -1899,57 +1908,154 @@ public boolean sendKeyEvent(KeyEvent event) {
             }
         }
 
-
         return super.sendKeyEvent(event);
     }
 
     @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
+        replaceText(text, newCursorPosition, false);
+
+        return super.commitText(text, newCursorPosition);
+    }
+
+    @Override
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        replaceText(text, newCursorPosition, true);
 
-        /* Generate backspaces for the text we're going to replace */
+        return super.setComposingText(text, newCursorPosition);
+    }
+
+    @Override
+    public boolean setComposingRegion(int start, int end) {
         final Editable content = getEditable();
         if (content != null) {
-            int a = getComposingSpanStart(content);
-            int b = getComposingSpanEnd(content);
-            if (a == -1 || b == -1) {
-                a = Selection.getSelectionStart(content);
-                b = Selection.getSelectionEnd(content);
-            }
-            if (a < 0) a = 0;
-            if (b < 0) b = 0;
-            if (b < a) {
+            int a = start;
+            int b = end;
+            if (a > b) {
                 int tmp = a;
                 a = b;
                 b = tmp;
             }
-            int backspaces = (b - a);
 
-            for (int i = 0; i < backspaces; i++) {
-                nativeGenerateScancodeForUnichar('\b');
+            // Clip the end points to be within the content bounds.
+            final int length = content.length();
+            if (a < 0) {
+                a = 0;
+            }
+            if (b < 0) {
+                b = 0;
+            }
+            if (a > length) {
+                a = length;
             }
+            if (b > length) {
+                b = length;
+            }
+
+            deleteText(a, b);
         }
 
-        for (int i = 0; i < text.length(); i++) {
-            char c = text.charAt(i);
-            if (c == '\n') {
-                if (SDLActivity.onNativeSoftReturnKey()) {
-                    return true;
+        return super.setComposingRegion(start, end);
+    }
+
+    @Override
+    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+        final Editable content = getEditable();
+        if (content != null) {
+            int a = Selection.getSelectionStart(content);
+            int b = Selection.getSelectionEnd(content);
+
+            if (a > b) {
+                int tmp = a;
+                a = b;
+                b = tmp;
+            }
+
+            // ignore the composing text.
+            int ca = getComposingSpanStart(content);
+            int cb = getComposingSpanEnd(content);
+            if (cb < ca) {
+                int tmp = ca;
+                ca = cb;
+                cb = tmp;
+            }
+
+            if (ca != -1 && cb != -1) {
+                if (ca < a) {
+                    a = ca;
+                }
+                if (cb > b) {
+                    b = cb;
                 }
             }
-            nativeGenerateScancodeForUnichar(c);
-        }
 
-        SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition);
+            if (beforeLength > 0) {
+                int start = a - beforeLength;
+                if (start < 0) {
+                    start = 0;
+                }
+                deleteText(start, a);
+            }
+        }
 
-        return super.commitText(text, newCursorPosition);
+        return super.deleteSurroundingText(beforeLength, afterLength);
     }
 
-    @Override
-    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+    protected void replaceText(CharSequence text, int newCursorPosition, boolean composing) {
+        final Editable content = getEditable();
+        if (content == null) {
+            return;
+        }
+        
+        // delete composing text set previously.
+        int a = getComposingSpanStart(content);
+        int b = getComposingSpanEnd(content);
+
+        if (b < a) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+        if (a == -1 || b == -1) {
+            a = Selection.getSelectionStart(content);
+            b = Selection.getSelectionEnd(content);
+            if (a < 0) {
+                a = 0;
+            }
+            if (b < 0) {
+                b = 0;
+            }
+            if (b < a) {
+                int tmp = a;
+                a = b;
+                b = tmp;
+            }
+        }
 
-        nativeSetComposingText(text.toString(), newCursorPosition);
+        deleteText(a, b);
 
-        return super.setComposingText(text, newCursorPosition);
+        if (composing) {
+            nativeSetComposingText(text.toString(), newCursorPosition);
+        } else {
+            for (int i = 0; i < text.length(); i++) {
+                char c = text.charAt(i);
+                if (c == '\n') {
+                    if (SDLActivity.onNativeSoftReturnKey()) {
+                        return;
+                    }
+                }
+                ++m_nLastContentLength;
+                nativeGenerateScancodeForUnichar(c);
+            }
+            SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition);
+        }
+    }
+
+    protected void deleteText(int start, int end) {
+        while (m_nLastContentLength > start) {
+            --m_nLastContentLength;
+            nativeGenerateScancodeForUnichar('\b');
+        }
     }
 
     public static native void nativeCommitText(String text, int newCursorPosition);
@@ -1958,20 +2064,6 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) {
 
     public native void nativeSetComposingText(String text, int newCursorPosition);
 
-    @Override
-    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
-        // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
-        // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
-        if (beforeLength > 0 && afterLength == 0) {
-            // backspace(s)
-            while (beforeLength-- > 0) {
-                nativeGenerateScancodeForUnichar('\b');
-            }
-            return true;
-        }
-
-        return super.deleteSurroundingText(beforeLength, afterLength);
-    }
 }
 
 class SDLClipboardHandler implements

From 7567c4cb00d66818779466deed7721a470a50d3b Mon Sep 17 00:00:00 2001
From: Ozkan Sezer <sezeroz@gmail.com>
Date: Fri, 30 Sep 2022 21:51:11 +0300
Subject: [PATCH 008/459] revert executable permissions from Android.mk

---
 Android.mk | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
 mode change 100755 => 100644 Android.mk

diff --git a/Android.mk b/Android.mk
old mode 100755
new mode 100644
index ce67ae2d775e..facc54afb7ef
--- a/Android.mk
+++ b/Android.mk
@@ -75,7 +75,6 @@ LOCAL_CFLAGS += \
 	-Wstrict-prototypes \
 	-Wkeyword-macro \
 
-
 # Warnings we haven't fixed (yet)
 LOCAL_CFLAGS += -Wno-unused-parameter -Wno-sign-compare
 
@@ -91,6 +90,7 @@ LOCAL_STATIC_LIBRARIES := cpufeatures
 
 include $(BUILD_SHARED_LIBRARY)
 
+
 ###########################
 #
 # SDL static library
@@ -109,6 +109,7 @@ LOCAL_EXPORT_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid
 
 include $(BUILD_STATIC_LIBRARY)
 
+
 ###########################
 #
 # SDL main static library

From e6640ef2d46a1baa34b4d893df661d17f0070b76 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <icculus@icculus.org>
Date: Fri, 30 Sep 2022 14:53:07 -0400
Subject: [PATCH 009/459] coreaudio: Possibly fixed another shutdown race
 condition.

Reference Issue #6159.
---
 src/audio/coreaudio/SDL_coreaudio.m | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index 1a6871904d8f..63ce6a5d3001 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -701,6 +701,14 @@ static B

(Patch may be truncated, please check the link at the top of this post.)