diff --git a/include/wlr/render/drm_syncobj.h b/include/wlr/render/drm_syncobj.h index 8b3609ee..be3dce2d 100644 --- a/include/wlr/render/drm_syncobj.h +++ b/include/wlr/render/drm_syncobj.h @@ -3,6 +3,7 @@ #include #include +#include /** * A synchronization timeline. @@ -33,6 +34,17 @@ struct wlr_drm_syncobj_timeline { size_t n_refs; }; +struct wlr_drm_syncobj_timeline_waiter { + struct { + struct wl_signal ready; + } events; + + // private state + + int ev_fd; + struct wl_event_source *event_source; +}; + /** * Create a new synchronization timeline. */ @@ -50,6 +62,30 @@ struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_ref(struct wlr_drm_syn * Unreference a synchronization timeline. */ void wlr_drm_syncobj_timeline_unref(struct wlr_drm_syncobj_timeline *timeline); +/** + * Check if a timeline point has been signalled or has materialized. + * + * Flags can be: + * + * - DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT to wait for the point to be + * signalled + * - DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE to only wait for a fence to + * materialize + */ +bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, + uint64_t point, uint32_t flags, bool *result); +/** + * Asynchronously wait for a timeline point. + * + * See wlr_drm_syncobj_timeline_check() for a definition of flags. + */ +bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter *waiter, + struct wlr_drm_syncobj_timeline *timeline, uint64_t point, uint32_t flags, + struct wl_event_loop *loop); +/** + * Cancel a timeline waiter. + */ +void wlr_drm_syncobj_timeline_waiter_finish(struct wlr_drm_syncobj_timeline_waiter *waiter); /** * Export a timeline point as a sync_file FD. * diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index f4b1c249..af3e79fc 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -1,9 +1,17 @@ #include #include #include +#include +#include #include #include +#include "config.h" + +#if HAVE_EVENTFD +#include +#endif + struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_create(int drm_fd) { struct wlr_drm_syncobj_timeline *timeline = calloc(1, sizeof(*timeline)); if (timeline == NULL) { @@ -114,3 +122,91 @@ out: drmSyncobjDestroy(timeline->drm_fd, syncobj_handle); return ok; } + +bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, + uint64_t point, uint32_t flags, bool *result) { + int etime; +#if defined(__FreeBSD__) + etime = ETIMEDOUT; +#else + etime = ETIME; +#endif + + uint32_t signaled_point; + int ret = drmSyncobjTimelineWait(timeline->drm_fd, &timeline->handle, &point, 1, 0, flags, &signaled_point); + if (ret != 0 && ret != -etime) { + wlr_log_errno(WLR_ERROR, "drmSyncobjWait() failed"); + return false; + } + + *result = ret == 0; + return true; +} + +static int handle_eventfd_ready(int ev_fd, uint32_t mask, void *data) { + struct wlr_drm_syncobj_timeline_waiter *waiter = data; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + wlr_log(WLR_ERROR, "Failed to wait for render timeline: eventfd error"); + } + + if (mask & WL_EVENT_READABLE) { + uint64_t ev_fd_value; + if (read(ev_fd, &ev_fd_value, sizeof(ev_fd_value)) <= 0) { + wlr_log(WLR_ERROR, "Failed to wait for render timeline: read() failed"); + } + } + + wl_signal_emit_mutable(&waiter->events.ready, NULL); + return 0; +} + +bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter *waiter, + struct wlr_drm_syncobj_timeline *timeline, uint64_t point, uint32_t flags, + struct wl_event_loop *loop) { + int ev_fd; +#if HAVE_EVENTFD + ev_fd = eventfd(0, EFD_CLOEXEC); + if (ev_fd < 0) { + wlr_log_errno(WLR_ERROR, "eventfd() failed"); + } +#else + ev_fd = -1; + wlr_log(WLR_ERROR, "eventfd() is unavailable"); +#endif + if (ev_fd < 0) { + return NULL; + } + + struct drm_syncobj_eventfd syncobj_eventfd = { + .handle = timeline->handle, + .flags = flags, + .point = point, + .fd = ev_fd, + }; + if (drmIoctl(timeline->drm_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobj_eventfd) != 0) { + wlr_log_errno(WLR_ERROR, "DRM_IOCTL_SYNCOBJ_EVENTFD failed"); + close(ev_fd); + return NULL; + } + + struct wl_event_source *source = wl_event_loop_add_fd(loop, ev_fd, WL_EVENT_READABLE, handle_eventfd_ready, waiter); + if (source == NULL) { + wlr_log(WLR_ERROR, "Failed to add FD to event loop"); + close(ev_fd); + return NULL; + } + + *waiter = (struct wlr_drm_syncobj_timeline_waiter){ + .ev_fd = ev_fd, + .event_source = source, + }; + wl_signal_init(&waiter->events.ready); + return true; +} + +void wlr_drm_syncobj_timeline_waiter_finish(struct wlr_drm_syncobj_timeline_waiter *waiter) { + wl_list_remove(&waiter->events.ready.listener_list); + wl_event_source_remove(waiter->event_source); + close(waiter->ev_fd); +} diff --git a/render/meson.build b/render/meson.build index 6cae5751..8c71ff07 100644 --- a/render/meson.build +++ b/render/meson.build @@ -23,6 +23,8 @@ else wlr_files += files('dmabuf_fallback.c') endif +internal_config.set10('HAVE_EVENTFD', cc.has_header('sys/eventfd.h')) + if 'gles2' in renderers or 'auto' in renderers egl = dependency('egl', required: 'gles2' in renderers) gbm = dependency('gbm', required: 'gles2' in renderers)