From 152ba829a1a50735f26f5e0540d623817cc9f6b2 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Tue, 26 Aug 2025 16:23:12 -0400 Subject: [PATCH] video: Try to reconfigure the window for OpenGL without destroying it When attaching a renderer (GL based specifically) to a window that was not created with the appropriate flags, the window would be destroyed and recreated to configure it for the desired rendering backend. While most of the issues with this have been mitigated over time, there can still be some undesirable side effects from doing so on certain platforms. If the window was just created and was never configured for any graphics context, it is possible that the reconfiguration can be done without destroying the window first. The Wayland implementation fixes an issue when creating a window with the fullscreen flag on wlroots based Wayland compositors, and can likely be extended to other platforms to avoid unnecessarily destroying/recreating a window in the very common case where a window is created, followed immediately by attaching a renderer. --- src/render/opengl/SDL_render_gl.c | 2 +- src/render/opengles2/SDL_render_gles2.c | 2 +- src/video/SDL_sysvideo.h | 2 + src/video/SDL_video.c | 70 +++++++++++++++++++++++++ src/video/wayland/SDL_waylandvideo.c | 1 + src/video/wayland/SDL_waylandwindow.c | 43 +++++++++++++++ src/video/wayland/SDL_waylandwindow.h | 1 + 7 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 81c930237c..8d59923658 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -1666,7 +1666,7 @@ static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Pr SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR); - if (!SDL_RecreateWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { + if (!SDL_ReconfigureWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { goto error; } } diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index 4cec2f21e0..ec4211fd02 100644 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -2176,7 +2176,7 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR); - if (!SDL_RecreateWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { + if (!SDL_ReconfigureWindow(window, (window_flags & ~(SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)) | SDL_WINDOW_OPENGL)) { goto error; } } diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index f1f7985bb2..3de1c53b88 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -314,6 +314,7 @@ struct SDL_VideoDevice bool (*ApplyWindowProgress)(SDL_VideoDevice *_this, SDL_Window *window); bool (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); bool (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window); + bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); /* * * */ /* @@ -574,6 +575,7 @@ extern void SDL_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, extern void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor); extern bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags); +extern bool SDL_ReconfigureWindow(SDL_Window *window, SDL_WindowFlags flags); extern bool SDL_HasWindows(void); extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y); extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index c6e4112dd6..4ce3248dda 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -2634,6 +2634,66 @@ SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y return window; } +static bool SDL_ReconfigureWindowInternal(SDL_Window *window, SDL_WindowFlags flags) +{ + bool loaded_opengl = false; + bool loaded_vulkan = false; + + if (!_this->ReconfigureWindow) { + return false; + } + + // Only attempt to reconfigure if the window has no existing graphics flags. + if (window->flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN)) { + return false; + } + + const SDL_WindowFlags graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN); + if (graphics_flags & (graphics_flags - 1)) { + return SDL_SetError("Conflicting window flags specified"); + } + + if ((flags & SDL_WINDOW_OPENGL) && !_this->GL_CreateContext) { + return SDL_ContextNotSupported("OpenGL"); + } + if ((flags & SDL_WINDOW_VULKAN) && !_this->Vulkan_CreateSurface) { + return SDL_ContextNotSupported("Vulkan"); + } + if ((flags & SDL_WINDOW_METAL) && !_this->Metal_CreateView) { + return SDL_ContextNotSupported("Metal"); + } + + SDL_DestroyWindowSurface(window); + + if (graphics_flags & SDL_WINDOW_OPENGL) { + loaded_opengl = SDL_GL_LoadLibrary(NULL); + if (!loaded_opengl) { + return false; + } + } else if (graphics_flags & SDL_WINDOW_VULKAN) { + loaded_vulkan = SDL_GL_LoadLibrary(NULL); + if (!loaded_vulkan) { + return false; + } + } + + // Try to reconfigure the window for the requested graphics flags. + if (!_this->ReconfigureWindow(_this, window, graphics_flags)) { + if (loaded_opengl) { + SDL_GL_UnloadLibrary(); + } + if (loaded_vulkan) { + SDL_Vulkan_UnloadLibrary(); + } + + return false; + } + + window->flags |= graphics_flags; + + return true; +} + bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) { bool loaded_opengl = false; @@ -2788,6 +2848,16 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) return true; } +bool SDL_ReconfigureWindow(SDL_Window *window, SDL_WindowFlags flags) +{ + // Try to reconfigure the window for the desired flags first, before completely destroying and recreating it. + if (!SDL_ReconfigureWindowInternal(window, flags)) { + return SDL_RecreateWindow(window, flags); + } + + return true; +} + bool SDL_HasWindows(void) { return _this && _this->windows; diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 68fb9961c8..3286f8aec7 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -660,6 +660,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols) device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; device->SyncWindow = Wayland_SyncWindow; device->SetWindowFocusable = Wayland_SetWindowFocusable; + device->ReconfigureWindow = Wayland_ReconfigureWindow; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index a8dd4251f8..54f3c71201 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2543,6 +2543,49 @@ bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, b return true; } +bool Wayland_ReconfigureWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags) +{ + SDL_WindowData *data = window->internal; + + if (data->shell_surface_status == WAYLAND_SHELL_SURFACE_STATUS_SHOWN) { + // Window is already mapped; abort. + return false; + } + + /* The caller guarantees that only one of the GL or Vulkan flags will be set, + * and the window will have no previous video flags. + */ + if (flags & SDL_WINDOW_OPENGL) { + if (!data->egl_window) { + data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->current.pixel_width, data->current.pixel_height); + } + +#ifdef SDL_VIDEO_OPENGL_EGL + // Create the GLES window surface + data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->egl_window); + + if (data->egl_surface == EGL_NO_SURFACE) { + return false; // SDL_EGL_CreateSurface should have set error + } +#endif + + if (!data->gles_swap_frame_event_queue) { + data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display); + data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface); + WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue); + data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper); + wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data); + } + + return true; + } else if (flags & SDL_WINDOW_VULKAN) { + // Nothing to configure for Vulkan. + return true; + } + + return false; +} + bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) { SDL_WindowData *data; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 945b753365..e16f2d9d1a 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -253,6 +253,7 @@ extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *win extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled); extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool Wayland_ReconfigureWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); extern void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, SDL_DisplayData *display_data);