Wayland: Fix EGL buffer swap blocking indefinitely

This re-implements the wait for a surface frame that previously happened
inside EGL, so that we can add a reasonable timeout.  That allows
applications to make progress despite the window being suspended.

This initial implementation still has a race between glfwHideWindow and
glfwSwapBuffers when rendering on a separate thread.

Related to #1350
Related to #2582
Related to #2640
Related to #2719
Related to #2723
Related to #2800
This commit is contained in:
Camilla Löwy
2026-02-17 19:44:39 +01:00
parent e59b5e1fda
commit 413ba1dceb
7 changed files with 164 additions and 3 deletions
+2
View File
@@ -152,6 +152,8 @@ information on what to include when reporting a bug.
- [Wayland] Bugfix: The `libwayland-client` library was not unloaded at termination
- [Wayland] Bugfix: Scroll events were sent twice on some versions of GNOME (#2494)
- [Wayland] Bugfix: Two-dimensional scroll input was emitted as separate axes
- [Wayland] Bugfix: `glfwSwapBuffers` would halt with nonzero swap interval when window
was suspended (#1350,#2582,#2640,#2719,#2723,#2800)
- [X11] Bugfix: Running without a WM could trigger an assert (#2593,#2601,#2631)
- [X11] Bugfix: Occasional crash when an idle display awakes (#2766)
- [X11] Bugfix: Prevent BadWindow when creating small windows with a content scale
+6 -1
View File
@@ -368,7 +368,12 @@ GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
window->context.getProcAddress("glGetIntegerv");
window->context.GetString = (PFNGLGETSTRINGPROC)
window->context.getProcAddress("glGetString");
if (!window->context.GetIntegerv || !window->context.GetString)
window->context.Flush = (PFNGLFLUSHPROC)
window->context.getProcAddress("glFlush");
if (!window->context.GetIntegerv ||
!window->context.GetString ||
!window->context.Flush)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
+25 -1
View File
@@ -32,7 +32,6 @@
#include <stdlib.h>
#include <assert.h>
// Return a description of the specified EGL error
//
static const char* getEGLErrorString(EGLint error)
@@ -254,6 +253,12 @@ static void makeContextCurrentEGL(_GLFWwindow* window)
getEGLErrorString(eglGetError()));
return;
}
#if defined(_GLFW_WAYLAND)
// NOTE: Disable EGL vsync so we can substitute our own that includes a timeout
if (_glfw.platform.platformID == GLFW_PLATFORM_WAYLAND)
eglSwapInterval(_glfw.egl.display, 0);
#endif // _GLFW_WAYLAND
}
else
{
@@ -287,6 +292,15 @@ static void swapBuffersEGL(_GLFWwindow* window)
// NOTE: Swapping buffers on a hidden window on Wayland makes it visible
if (!window->wl.visible)
return;
// NOTE: We wait for a frame manually so we can add a timeout,
// as the EGL implementation will wait indefinitely
if (window->wl.egl.interval > 0)
{
window->context.Flush();
if (!_glfwWaitForEGLFrameWayland(window))
return;
}
}
#endif
@@ -295,6 +309,16 @@ static void swapBuffersEGL(_GLFWwindow* window)
static void swapIntervalEGL(int interval)
{
#if defined(_GLFW_WAYLAND)
if (_glfw.platform.platformID == GLFW_PLATFORM_WAYLAND)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
assert(window != NULL);
window->wl.egl.interval = interval;
return;
}
#endif
eglSwapInterval(_glfw.egl.display, interval);
}
+2
View File
@@ -109,6 +109,7 @@ typedef void (APIENTRY * PFNGLCLEARPROC)(GLbitfield);
typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGPROC)(GLenum);
typedef void (APIENTRY * PFNGLGETINTEGERVPROC)(GLenum,GLint*);
typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGIPROC)(GLenum,GLuint);
typedef void (APIENTRY * PFNGLFLUSHPROC)(void);
#define EGL_SUCCESS 0x3000
#define EGL_NOT_INITIALIZED 0x3001
@@ -498,6 +499,7 @@ struct _GLFWcontext
PFNGLGETSTRINGIPROC GetStringi;
PFNGLGETINTEGERVPROC GetIntegerv;
PFNGLGETSTRINGPROC GetString;
PFNGLFLUSHPROC Flush;
void (*makeCurrent)(_GLFWwindow*);
void (*swapBuffers)(_GLFWwindow*);
+22 -1
View File
@@ -578,6 +578,14 @@ int _glfwInitWayland(void)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_display_get_fd");
_glfw.wl.client.display_prepare_read = (PFN_wl_display_prepare_read)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_display_prepare_read");
_glfw.wl.client.display_create_queue = (PFN_wl_display_create_queue)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_display_create_queue");
_glfw.wl.client.display_prepare_read_queue = (PFN_wl_display_prepare_read_queue)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_display_prepare_read_queue");
_glfw.wl.client.display_dispatch_queue_pending = (PFN_wl_display_dispatch_queue_pending)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_display_dispatch_queue_pending");
_glfw.wl.client.event_queue_destroy = (PFN_wl_event_queue_destroy)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_event_queue_destroy");
_glfw.wl.client.proxy_marshal = (PFN_wl_proxy_marshal)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_marshal");
_glfw.wl.client.proxy_add_listener = (PFN_wl_proxy_add_listener)
@@ -600,6 +608,12 @@ int _glfwInitWayland(void)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_get_version");
_glfw.wl.client.proxy_marshal_flags = (PFN_wl_proxy_marshal_flags)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_marshal_flags");
_glfw.wl.client.proxy_create_wrapper = (PFN_wl_proxy_create_wrapper)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_create_wrapper");
_glfw.wl.client.proxy_wrapper_destroy = (PFN_wl_proxy_wrapper_destroy)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_wrapper_destroy");
_glfw.wl.client.proxy_set_queue = (PFN_wl_proxy_set_queue)
_glfwPlatformGetModuleSymbol(_glfw.wl.client.handle, "wl_proxy_set_queue");
if (!_glfw.wl.client.display_flush ||
!_glfw.wl.client.display_cancel_read ||
@@ -609,6 +623,10 @@ int _glfwInitWayland(void)
!_glfw.wl.client.display_roundtrip ||
!_glfw.wl.client.display_get_fd ||
!_glfw.wl.client.display_prepare_read ||
!_glfw.wl.client.display_create_queue ||
!_glfw.wl.client.display_prepare_read_queue ||
!_glfw.wl.client.display_dispatch_queue_pending ||
!_glfw.wl.client.event_queue_destroy ||
!_glfw.wl.client.proxy_marshal ||
!_glfw.wl.client.proxy_add_listener ||
!_glfw.wl.client.proxy_destroy ||
@@ -617,7 +635,10 @@ int _glfwInitWayland(void)
!_glfw.wl.client.proxy_get_user_data ||
!_glfw.wl.client.proxy_set_user_data ||
!_glfw.wl.client.proxy_get_tag ||
!_glfw.wl.client.proxy_set_tag)
!_glfw.wl.client.proxy_set_tag ||
!_glfw.wl.client.proxy_create_wrapper ||
!_glfw.wl.client.proxy_wrapper_destroy ||
!_glfw.wl.client.proxy_set_queue)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to load libwayland-client entry point");
+27
View File
@@ -53,6 +53,10 @@ typedef void (* PFN_wl_display_disconnect)(struct wl_display*);
typedef int (* PFN_wl_display_roundtrip)(struct wl_display*);
typedef int (* PFN_wl_display_get_fd)(struct wl_display*);
typedef int (* PFN_wl_display_prepare_read)(struct wl_display*);
typedef struct wl_event_queue* (* PFN_wl_display_create_queue)(struct wl_display*);
typedef void (* PFN_wl_event_queue_destroy)(struct wl_event_queue*);
typedef int (* PFN_wl_display_prepare_read_queue)(struct wl_display*,struct wl_event_queue*);
typedef int (* PFN_wl_display_dispatch_queue_pending)(struct wl_display*,struct wl_event_queue*);
typedef void (* PFN_wl_proxy_marshal)(struct wl_proxy*,uint32_t,...);
typedef int (* PFN_wl_proxy_add_listener)(struct wl_proxy*,void(**)(void),void*);
typedef void (* PFN_wl_proxy_destroy)(struct wl_proxy*);
@@ -64,6 +68,9 @@ typedef void (* PFN_wl_proxy_set_tag)(struct wl_proxy*,const char*const*);
typedef const char* const* (* PFN_wl_proxy_get_tag)(struct wl_proxy*);
typedef uint32_t (* PFN_wl_proxy_get_version)(struct wl_proxy*);
typedef struct wl_proxy* (* PFN_wl_proxy_marshal_flags)(struct wl_proxy*,uint32_t,const struct wl_interface*,uint32_t,uint32_t,...);
typedef void* (* PFN_wl_proxy_create_wrapper)(void*);
typedef void (* PFN_wl_proxy_wrapper_destroy)(void*);
typedef void (* PFN_wl_proxy_set_queue)(struct wl_proxy*,struct wl_event_queue*);
#define wl_display_flush _glfw.wl.client.display_flush
#define wl_display_cancel_read _glfw.wl.client.display_cancel_read
#define wl_display_dispatch_pending _glfw.wl.client.display_dispatch_pending
@@ -72,6 +79,10 @@ typedef struct wl_proxy* (* PFN_wl_proxy_marshal_flags)(struct wl_proxy*,uint32_
#define wl_display_roundtrip _glfw.wl.client.display_roundtrip
#define wl_display_get_fd _glfw.wl.client.display_get_fd
#define wl_display_prepare_read _glfw.wl.client.display_prepare_read
#define wl_display_create_queue _glfw.wl.client.display_create_queue
#define wl_display_prepare_read_queue _glfw.wl.client.display_prepare_read_queue
#define wl_display_dispatch_queue_pending _glfw.wl.client.display_dispatch_queue_pending
#define wl_event_queue_destroy _glfw.wl.client.event_queue_destroy
#define wl_proxy_marshal _glfw.wl.client.proxy_marshal
#define wl_proxy_add_listener _glfw.wl.client.proxy_add_listener
#define wl_proxy_destroy _glfw.wl.client.proxy_destroy
@@ -83,6 +94,9 @@ typedef struct wl_proxy* (* PFN_wl_proxy_marshal_flags)(struct wl_proxy*,uint32_
#define wl_proxy_set_tag _glfw.wl.client.proxy_set_tag
#define wl_proxy_get_version _glfw.wl.client.proxy_get_version
#define wl_proxy_marshal_flags _glfw.wl.client.proxy_marshal_flags
#define wl_proxy_create_wrapper _glfw.wl.client.proxy_create_wrapper
#define wl_proxy_wrapper_destroy _glfw.wl.client.proxy_wrapper_destroy
#define wl_proxy_set_queue _glfw.wl.client.proxy_set_queue
struct wl_shm;
struct wl_output;
@@ -366,7 +380,11 @@ typedef struct _GLFWwindowWayland
struct wl_callback* callback;
struct {
struct wl_event_queue* queue;
struct wl_egl_window* window;
struct wl_callback* callback;
struct wl_surface* wrapper;
int interval;
} egl;
struct {
@@ -537,6 +555,10 @@ typedef struct _GLFWlibraryWayland
PFN_wl_display_roundtrip display_roundtrip;
PFN_wl_display_get_fd display_get_fd;
PFN_wl_display_prepare_read display_prepare_read;
PFN_wl_display_create_queue display_create_queue;
PFN_wl_display_prepare_read_queue display_prepare_read_queue;
PFN_wl_display_dispatch_queue_pending display_dispatch_queue_pending;
PFN_wl_event_queue_destroy event_queue_destroy;
PFN_wl_proxy_marshal proxy_marshal;
PFN_wl_proxy_add_listener proxy_add_listener;
PFN_wl_proxy_destroy proxy_destroy;
@@ -548,6 +570,9 @@ typedef struct _GLFWlibraryWayland
PFN_wl_proxy_set_tag proxy_set_tag;
PFN_wl_proxy_get_version proxy_get_version;
PFN_wl_proxy_marshal_flags proxy_marshal_flags;
PFN_wl_proxy_create_wrapper proxy_create_wrapper;
PFN_wl_proxy_wrapper_destroy proxy_wrapper_destroy;
PFN_wl_proxy_set_queue proxy_set_queue;
} client;
struct {
@@ -706,3 +731,5 @@ void _glfwUpdateBufferScaleFromOutputsWayland(_GLFWwindow* window);
void _glfwAddSeatListenerWayland(struct wl_seat* seat);
void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device);
GLFWbool _glfwWaitForEGLFrameWayland(_GLFWwindow* window);
+80
View File
@@ -2277,6 +2277,18 @@ static const struct xdg_activation_token_v1_listener xdgActivationListener =
xdgActivationHandleDone
};
static void callbackHandleFrame(void* userData, struct wl_callback* callback, uint32_t data)
{
_GLFWwindow* window = userData;
wl_callback_destroy(callback);
window->wl.egl.callback = NULL;
}
static const struct wl_callback_listener frameCallbackListener =
{
callbackHandleFrame
};
void _glfwAddSeatListenerWayland(struct wl_seat* seat)
{
wl_seat_add_listener(seat, &seatListener, NULL);
@@ -2287,6 +2299,46 @@ void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device)
wl_data_device_add_listener(device, &dataDeviceListener, NULL);
}
GLFWbool _glfwWaitForEGLFrameWayland(_GLFWwindow* window)
{
for (int i = 0; i < window->wl.egl.interval; i++)
{
double timeout = 0.02;
while (window->wl.egl.callback)
{
while (wl_display_prepare_read_queue(_glfw.wl.display, window->wl.egl.queue) != 0)
wl_display_dispatch_queue_pending(_glfw.wl.display, window->wl.egl.queue);
if (!flushDisplay())
{
wl_display_cancel_read(_glfw.wl.display);
return GLFW_FALSE;
}
struct pollfd fd = { wl_display_get_fd(_glfw.wl.display), POLLIN };
if (!_glfwPollPOSIX(&fd, 1, &timeout))
{
wl_display_cancel_read(_glfw.wl.display);
return GLFW_FALSE;
}
wl_display_read_events(_glfw.wl.display);
wl_display_dispatch_queue_pending(_glfw.wl.display, window->wl.egl.queue);
}
window->wl.egl.callback = wl_surface_frame(window->wl.egl.wrapper);
wl_callback_add_listener(window->wl.egl.callback, &frameCallbackListener, window);
// Commit the old buffer for every frame but the last one so we get new callbacks
if (i + 1 < window->wl.egl.interval)
wl_surface_commit(window->wl.surface);
}
// If the window is hidden when the wait is over then don't swap
return window->wl.visible;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -2315,6 +2367,25 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window,
return GLFW_FALSE;
}
window->wl.egl.queue = wl_display_create_queue(_glfw.wl.display);
if (!window->wl.egl.queue)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create EGL frame queue");
return GLFW_FALSE;
}
window->wl.egl.wrapper = wl_proxy_create_wrapper(window->wl.surface);
if (!window->wl.egl.wrapper)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create surface wrapper");
return GLFW_FALSE;
}
wl_proxy_set_queue((struct wl_proxy*) window->wl.egl.wrapper,
window->wl.egl.queue);
if (!_glfwInitEGL())
return GLFW_FALSE;
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
@@ -2386,6 +2457,15 @@ void _glfwDestroyWindowWayland(_GLFWwindow* window)
if (window->wl.fallback.buffer)
wl_buffer_destroy(window->wl.fallback.buffer);
if (window->wl.egl.callback)
wl_callback_destroy(window->wl.egl.callback);
if (window->wl.egl.wrapper)
wl_proxy_wrapper_destroy(window->wl.egl.wrapper);
if (window->wl.egl.queue)
wl_event_queue_destroy(window->wl.egl.queue);
if (window->wl.egl.window)
wl_egl_window_destroy(window->wl.egl.window);