SDL: video: Improve the EGL attribute callbacks.

From 9a5d5a2839d41fc6250a7f8c48ab4fa54386ac8c Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 3 Oct 2024 20:09:58 -0400
Subject: [PATCH] video: Improve the EGL attribute callbacks.

Now it has access to information it needs and it's clear who is reponsible
for memory management.

Fixes #11039.
---
 include/SDL3/SDL_video.h | 69 ++++++++++++++++++++++++++++++++--------
 src/video/SDL_egl.c      |  7 ++--
 2 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index 9fba67c200e38..55bfd72bd0350 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -239,12 +239,60 @@ typedef intptr_t SDL_EGLAttrib;
 typedef int SDL_EGLint;
 
 /**
- * EGL attribute initialization callback types.
+ * EGL platform attribute initialization callback.
+ *
+ * This is called when SDL is attempting to create an EGL context, to let
+ * the app add extra attributes to its eglGetPlatformDisplay() call.
+ *
+ * The callback should return a pointer to an EGL attribute array terminated
+ * with `EGL_NONE`. If this function returns NULL, the SDL_CreateWindow
+ * process will fail gracefully.
+ *
+ * The returned pointer should be allocated with SDL_malloc() and will be
+ * passed to SDL_free().
+ *
+ * The arrays returned by each callback will be appended to the existing
+ * attribute arrays defined by SDL.
+ *
+ * \param userdata an app-controlled pointer that is passed to the callback.
+ * \returns a newly-allocated array of attributes, terminated with `EGL_NONE`.
  *
  * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_EGL_SetAttributeCallbacks
  */
 typedef SDL_EGLAttrib *(SDLCALL *SDL_EGLAttribArrayCallback)(void *userdata);
-typedef SDL_EGLint *(SDLCALL *SDL_EGLIntArrayCallback)(void *userdata);
+
+/**
+ * EGL surface/context attribute initialization callback types.
+ *
+ * This is called when SDL is attempting to create an EGL surface, to let
+ * the app add extra attributes to its eglCreateWindowSurface() or
+ * eglCreateContext calls.
+ *
+ * For convenience, the EGLDisplay and EGLConfig to use are provided to the
+ * callback.
+ *
+ * The callback should return a pointer to an EGL attribute array terminated
+ * with `EGL_NONE`. If this function returns NULL, the SDL_CreateWindow
+ * process will fail gracefully.
+ *
+ * The returned pointer should be allocated with SDL_malloc() and will be
+ * passed to SDL_free().
+ *
+ * The arrays returned by each callback will be appended to the existing
+ * attribute arrays defined by SDL.
+ *
+ * \param userdata an app-controlled pointer that is passed to the callback.
+ * \param display the EGL display to be used.
+ * \param config the EGL config to be used.
+ * \returns a newly-allocated array of attributes, terminated with `EGL_NONE`.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_EGL_SetAttributeCallbacks
+ */
+typedef SDL_EGLint *(SDLCALL *SDL_EGLIntArrayCallback)(void *userdata, SDL_EGLDisplay display, SDL_EGLConfig config);
 
 /**
  * An enumeration of OpenGL configuration attributes.
@@ -2742,28 +2790,23 @@ extern SDL_DECLSPEC SDL_EGLSurface SDLCALL SDL_EGL_GetWindowSurface(SDL_Window *
  * Sets the callbacks for defining custom EGLAttrib arrays for EGL
  * initialization.
  *
- * Each callback should return a pointer to an EGL attribute array terminated
- * with EGL_NONE. Callbacks may return NULL pointers to signal an error, which
- * will cause the SDL_CreateWindow process to fail gracefully.
- *
- * The arrays returned by each callback will be appended to the existing
- * attribute arrays defined by SDL.
+ * Callbacks that aren't needed can be set to NULL.
  *
  * NOTE: These callback pointers will be reset after SDL_GL_ResetAttributes.
  *
  * \param platformAttribCallback callback for attributes to pass to
- *                               eglGetPlatformDisplay.
+ *                               eglGetPlatformDisplay. May be NULL.
  * \param surfaceAttribCallback callback for attributes to pass to
- *                              eglCreateSurface.
+ *                              eglCreateSurface. May be NULL.
  * \param contextAttribCallback callback for attributes to pass to
- *                              eglCreateContext.
+ *                              eglCreateContext. May be NULL.
  * \param userdata a pointer that is passed to the callbacks.
  *
  * \since This function is available since SDL 3.0.0.
  */
 extern SDL_DECLSPEC void SDLCALL SDL_EGL_SetAttributeCallbacks(SDL_EGLAttribArrayCallback platformAttribCallback,
-                                                              SDL_EGLIntArrayCallback surfaceAttribCallback,
-                                                              SDL_EGLIntArrayCallback contextAttribCallback, void *userdata);
+                                                               SDL_EGLIntArrayCallback surfaceAttribCallback,
+                                                               SDL_EGLIntArrayCallback contextAttribCallback, void *userdata);
 
 /**
  * Set the swap interval for the current OpenGL context.
diff --git a/src/video/SDL_egl.c b/src/video/SDL_egl.c
index 19a92fc6b85e1..4caf2db5b3b44 100644
--- a/src/video/SDL_egl.c
+++ b/src/video/SDL_egl.c
@@ -537,6 +537,7 @@ bool SDL_EGL_LoadLibrary(SDL_VideoDevice *_this, const char *egl_path, NativeDis
                 }
             }
             _this->egl_data->egl_display = _this->egl_data->eglGetPlatformDisplay(platform, (void *)(uintptr_t)native_display, attribs);
+            SDL_free(attribs);
         } else {
             if (SDL_EGL_HasExtension(_this, SDL_EGL_CLIENT_EXTENSION, "EGL_EXT_platform_base")) {
                 _this->egl_data->eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)SDL_EGL_GetProcAddressInternal(_this, "eglGetPlatformDisplayEXT");
@@ -1038,7 +1039,7 @@ SDL_GLContext SDL_EGL_CreateContext(SDL_VideoDevice *_this, EGLSurface egl_surfa
     if (_this->egl_contextattrib_callback) {
         const int maxAttribs = sizeof(attribs) / sizeof(attribs[0]);
         EGLint *userAttribs, *userAttribP;
-        userAttribs = _this->egl_contextattrib_callback(_this->egl_attrib_callback_userdata);
+        userAttribs = _this->egl_contextattrib_callback(_this->egl_attrib_callback_userdata, _this->egl_data->egl_display, _this->egl_data->egl_config);
         if (!userAttribs) {
             _this->gl_config.driver_loaded = 0;
             *_this->gl_config.driver_path = '\0';
@@ -1056,6 +1057,7 @@ SDL_GLContext SDL_EGL_CreateContext(SDL_VideoDevice *_this, EGLSurface egl_surfa
             attribs[attr++] = *userAttribP++;
             attribs[attr++] = *userAttribP++;
         }
+        SDL_free(userAttribs);
     }
 
     attribs[attr++] = EGL_NONE;
@@ -1264,7 +1266,7 @@ EGLSurface SDL_EGL_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, Nat
     if (_this->egl_surfaceattrib_callback) {
         const int maxAttribs = sizeof(attribs) / sizeof(attribs[0]);
         EGLint *userAttribs, *userAttribP;
-        userAttribs = _this->egl_surfaceattrib_callback(_this->egl_attrib_callback_userdata);
+        userAttribs = _this->egl_surfaceattrib_callback(_this->egl_attrib_callback_userdata, _this->egl_data->egl_display, _this->egl_data->egl_config);
         if (!userAttribs) {
             _this->gl_config.driver_loaded = 0;
             *_this->gl_config.driver_path = '\0';
@@ -1282,6 +1284,7 @@ EGLSurface SDL_EGL_CreateSurface(SDL_VideoDevice *_this, SDL_Window *window, Nat
             attribs[attr++] = *userAttribP++;
             attribs[attr++] = *userAttribP++;
         }
+        SDL_free(userAttribs);
     }
 
     attribs[attr++] = EGL_NONE;