From b6d3b502752cf3656e0acd6f8d05d33fe33b7024 Mon Sep 17 00:00:00 2001 From: Eric Toombs Date: Wed, 19 Dec 2018 15:03:53 -0500 Subject: [PATCH] wayland native: support for next-frame rendering using presentation-time I added two functions to the wayland native API: glfwWaylandPresSupported() checks the compositor for presentation-time support. glfwWaylandSwapPres() swaps the buffers, then blocks until the last-rendered frame is displayed on the screen, processing events for the whole time. It returns the frame's presentation time with respect to the glfwGetTime() clock. --- include/GLFW/glfw3native.h | 25 +++++++++++ src/CMakeLists.txt | 4 ++ src/wl_init.c | 23 ++++++++++ src/wl_platform.h | 6 +++ src/wl_window.c | 86 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+) diff --git a/include/GLFW/glfw3native.h b/include/GLFW/glfw3native.h index 6bddc432f..955357f93 100644 --- a/include/GLFW/glfw3native.h +++ b/include/GLFW/glfw3native.h @@ -413,6 +413,31 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* monitor); * @ingroup native */ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* window); + +/*! @brief Check to see if the Wayland server supports the presentation-time + * protocol. + * + * @return GLFW_TRUE iff presentation-time is supported. + * + * @since Added in version 3.3. + * + * @ingroup native + */ +GLFWAPI int glfwWaylandPresSupported(void); + +/*! @brief Swap the buffers, then use the Wayland presentation-time protocol to + * wait for the new frame to be presented. GLFW window events are processed for + * the whole time this function waits for the frame to be presented. + * + * @return The presentation time of the frame, with respect to the same time + * measure glfwGetTime() uses, or 0.0 if the frame was not shown due to a + * vblank miss. + * + * @since Added in version 3.3. + * + * @ingroup native + */ +GLFWAPI double glfwWaylandSwapPres(GLFWwindow* window); #endif #if defined(GLFW_EXPOSE_NATIVE_EGL) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6bf4ff0d7..8f03a85f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,10 @@ elseif (_GLFW_WAYLAND) PROTOCOL "${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml" BASENAME idle-inhibit-unstable-v1) + ecm_add_wayland_client_protocol(glfw_SOURCES + PROTOCOL + "${WAYLAND_PROTOCOLS_PKGDATADIR}/stable/presentation-time/presentation-time.xml" + BASENAME presentation-time) elseif (_GLFW_OSMESA) set(glfw_HEADERS ${common_HEADERS} null_platform.h null_joystick.h posix_time.h posix_thread.h osmesa_context.h) diff --git a/src/wl_init.c b/src/wl_init.c index c6b209bc7..97063e94b 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -780,12 +780,28 @@ static const struct xdg_wm_base_listener wmBaseListener = { wmBaseHandlePing }; +static void +presSetClockId(void *data, struct wp_presentation *pres, uint32_t clockId) +{ + if (clockId == CLOCK_MONOTONIC) + { + // TODO: support for clocks other than CLOCK_MONOTONIC. + _glfw.wl.pres = pres; + } +} + +static const struct wp_presentation_listener presListener = { + presSetClockId +}; + static void registryHandleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + struct wp_presentation* pres; + if (strcmp(interface, "wl_compositor") == 0) { _glfw.wl.compositorVersion = min(3, version); @@ -871,6 +887,13 @@ static void registryHandleGlobal(void* data, &zwp_idle_inhibit_manager_v1_interface, 1); } + else if (strcmp(interface, wp_presentation_interface.name) == 0) + { + pres = wl_registry_bind(registry, name, + &wp_presentation_interface, + 1); + wp_presentation_add_listener(pres, &presListener, NULL); + } } static void registryHandleGlobalRemove(void *data, diff --git a/src/wl_platform.h b/src/wl_platform.h index c17ebe88b..b7f5feb82 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -62,6 +62,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" +#include "wayland-presentation-time-client-protocol.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) @@ -218,6 +219,9 @@ typedef struct _GLFWwindowWayland int focus; } decorations; + GLFWbool presWaiting; + double presTime; + } _GLFWwindowWayland; // Wayland-specific global data @@ -326,6 +330,8 @@ typedef struct _GLFWlibraryWayland PFN_wl_egl_window_resize window_resize; } egl; + struct wp_presentation* pres; + } _GLFWlibraryWayland; // Wayland-specific per-monitor data diff --git a/src/wl_window.c b/src/wl_window.c index 98a646590..522feab98 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -37,6 +37,7 @@ #include #include #include +#include static void shellSurfaceHandlePing(void* data, @@ -962,6 +963,13 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, window->wl.monitorsCount = 0; window->wl.monitorsSize = 1; + if (_glfw.wl.pres) + { + window->wl.presWaiting = GLFW_FALSE; + // Tell the user we missed the first vblank. + window->wl.presTime = 0.; + } + return GLFW_TRUE; } @@ -1855,3 +1863,81 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) return window->wl.surface; } + +static void feedbackSyncOutput(void* data, + struct wp_presentation_feedback* fback, + struct wl_output* output) +{ +} + +static void feedbackPresented(void* data, + struct wp_presentation_feedback* fback, + uint32_t tvSecHi, + uint32_t tvSecLo, + uint32_t tvNsec, + uint32_t refreshNsec, + uint32_t seqHi, + uint32_t seqLo, + uint32_t flags) +{ + _GLFWwindow* window; + uint64_t sec; + uint64_t nsec; + + window = (_GLFWwindow*) data; + + window->wl.presWaiting = GLFW_FALSE; + + sec = (uint64_t) tvSecLo + ((uint64_t) tvSecHi << 32); + nsec = sec * 1000000000 + (uint64_t) tvNsec; + window->wl.presTime = (double) (nsec - _glfw.timer.offset) * 1e-9; + + wp_presentation_feedback_destroy(fback); +} + +static void feedbackDiscarded(void* data, + struct wp_presentation_feedback* fback) +{ + _GLFWwindow* window = (_GLFWwindow*) data; + window->wl.presWaiting = GLFW_FALSE; + window->wl.presTime = 0.; + + wp_presentation_feedback_destroy(fback); +} + +static const struct wp_presentation_feedback_listener feedbackListener = { + feedbackSyncOutput, + feedbackPresented, + feedbackDiscarded +}; + +GLFWAPI int glfwWaylandPresSupported(void) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE); + return _glfw.wl.pres != NULL; +} + +GLFWAPI double glfwWaylandSwapPres(GLFWwindow* handle) +{ + _GLFWwindow* window; + struct wp_presentation_feedback* fback; + + _GLFW_REQUIRE_INIT_OR_RETURN(0.); + assert(handle); + assert(_glfw.wl.pres); + + window = (_GLFWwindow*) handle; + + glfwSwapBuffers(handle); + + while (window->wl.presWaiting) + { + handleEvents(-1); + } + + window->wl.presWaiting = GLFW_TRUE; + fback = wp_presentation_feedback(_glfw.wl.pres, window->wl.surface); + wp_presentation_feedback_add_listener(fback, &feedbackListener, window); + + return window->wl.presTime; +}