diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7a544eb6..9bfcdf86 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,6 +10,7 @@ video tutorials. - Matt Arsenault - Takuro Ashie - ashishgamedev + - avagordon01 - David Avedissian - Luca Bacci - Keith Bauer @@ -75,6 +76,7 @@ video tutorials. - Nikita Fediuchin - Felipe Ferreira - Michael Fogleman + - Folling - forworldm - Jason Francis - Gerald Franz @@ -161,6 +163,7 @@ video tutorials. - Bryce Mehring - Jonathan Mercier - Marcel Metz + - Lucas Michaudel - Liam Middlebrook - mightgoyardstill - Mihail diff --git a/README.md b/README.md index 40443e4d..0723e835 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ information on what to include when reporting a bug. - [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: Mouse wheel scroll distance was incorrect on some compositors + - [Wayland] Bugfix: `glfwSwapBuffers` would halt with nonzero swap interval when window + was suspended (#1350,#2582,#2640,#2719,#2723,#2800,#2827) - [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 diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 5d9cff2e..16689a94 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -6174,6 +6174,10 @@ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * + * @remark __Wayland:__ When the swap interval is greater than zero and the + * window is not in view, this function may take a few extra milliseconds to + * return. + * * @remark __EGL:__ The context of the specified window must be current on the * calling thread. * diff --git a/src/context.c b/src/context.c index d92ed402..a584cb12 100644 --- a/src/context.c +++ b/src/context.c @@ -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); diff --git a/src/egl_context.c b/src/egl_context.c index 7586d5a3..4d436d39 100644 --- a/src/egl_context.c +++ b/src/egl_context.c @@ -254,6 +254,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 +293,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 +310,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); } diff --git a/src/internal.h b/src/internal.h index 15049a3e..56418c8d 100644 --- a/src/internal.h +++ b/src/internal.h @@ -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*); diff --git a/src/wl_init.c b/src/wl_init.c index 50421eb2..25ceba0d 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -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"); diff --git a/src/wl_platform.h b/src/wl_platform.h index 9bd3b0ef..16b735d1 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -51,6 +51,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*); @@ -62,6 +66,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 @@ -70,6 +77,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 @@ -81,6 +92,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; @@ -364,7 +378,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); + diff --git a/src/wl_window.c b/src/wl_window.c index b9dbaa3b..22d2f6ee 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -2296,6 +2296,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); @@ -2306,6 +2318,39 @@ void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device) wl_data_device_add_listener(device, &dataDeviceListener, NULL); } +GLFWbool _glfwWaitForEGLFrameWayland(_GLFWwindow* window) +{ + 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); + + // If the window is hidden when the wait is over then don't swap + return window->wl.visible; +} ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// @@ -2334,6 +2379,27 @@ 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); + + window->wl.egl.interval = 1; + if (!_glfwInitEGL()) return GLFW_FALSE; if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) @@ -2405,6 +2471,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);