From 5a1f18666a4bf407316403b4ecf3dd079773fe6a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 18:47:46 +0200 Subject: [PATCH 01/10] render/timeline: introduce wlr_timeline wlr_timeline is a synchronization primitive based on drm_syncobj timelines. They are heavily inspired from Vulkan timeline semaphores [1]. [1]: https://www.khronos.org/blog/vulkan-timeline-semaphores --- include/wlr/render/timeline.h | 60 ++++++++++++++++++++++++ render/meson.build | 1 + render/timeline.c | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 include/wlr/render/timeline.h create mode 100644 render/timeline.c diff --git a/include/wlr/render/timeline.h b/include/wlr/render/timeline.h new file mode 100644 index 0000000000..80705cb56f --- /dev/null +++ b/include/wlr/render/timeline.h @@ -0,0 +1,60 @@ +#ifndef WLR_RENDER_TIMELINE_H +#define WLR_RENDER_TIMELINE_H + +#include +#include + +/** + * A synchronization timeline. + * + * Timelines are used to synchronize accesses to buffers. Given a producer + * (writing contents to a buffer) and a consumer (reading from the buffer), the + * compositor needs to synchronize back-and-forth between these two users. The + * consumer needs to wait for the producer to signal that they're done with the + * writes, and the producer needs to wait for the consumer to signal that + * they're done with the reads. + * + * Timelines provide synchronization points in the form of monotonically + * increasing 64-bit integer values. + * + * wlroots timelines are designed after Vulkan timeline semaphores. For more + * information on the Vulkan APIs, see: + * https://www.khronos.org/blog/vulkan-timeline-semaphores + * + * wlroots timelines are powered by DRM synchronization objects (drm_syncobj): + * https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#drm-sync-objects + */ +struct wlr_render_timeline; + +/** + * Create a new synchronization timeline. + */ +struct wlr_render_timeline *wlr_render_timeline_create(int drm_fd); +/** + * Destroy a synchronization timeline. + */ +void wlr_render_timeline_destroy(struct wlr_render_timeline *timeline); +/** + * Export a timeline point as a sync_file FD. + * + * The returned sync_file will be signalled when the provided point is + * signalled on the timeline. + * + * This allows inter-operation with other APIs which don't support drm_syncobj + * yet. The synchronization point needs to have already materialized: + * wait-before-signal is not supported. + */ +int wlr_render_timeline_export_sync_file(struct wlr_render_timeline *timeline, + uint64_t src_point); +/** + * Import a timeline point from a sync_file FD. + * + * The provided timeline point will be signalled when the provided sync_file is. + * + * This allows inter-operation with other APIs which don't support drm_syncobj + * yet. + */ +bool wlr_render_timeline_import_sync_file(struct wlr_render_timeline *timeline, + uint64_t dst_point, int sync_file_fd); + +#endif diff --git a/render/meson.build b/render/meson.build index 2d65188762..18cc60f94b 100644 --- a/render/meson.build +++ b/render/meson.build @@ -10,6 +10,7 @@ wlr_files += files( 'drm_format_set.c', 'pixel_format.c', 'swapchain.c', + 'timeline.c', 'wlr_renderer.c', 'wlr_texture.c', ) diff --git a/render/timeline.c b/render/timeline.c new file mode 100644 index 0000000000..6acf3e8ee7 --- /dev/null +++ b/render/timeline.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +struct wlr_render_timeline { + int drm_fd; + uint32_t handle; +}; + +struct wlr_render_timeline *wlr_render_timeline_create(int drm_fd) { + struct wlr_render_timeline *timeline = calloc(1, sizeof(*timeline)); + if (timeline == NULL) { + return NULL; + } + timeline->drm_fd = drm_fd; + + if (drmSyncobjCreate(drm_fd, 0, &timeline->handle) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjCreate failed"); + free(timeline); + return NULL; + } + + return timeline; +} + +void wlr_render_timeline_destroy(struct wlr_render_timeline *timeline) { + drmSyncobjDestroy(timeline->drm_fd, timeline->handle); + free(timeline); +} + +int wlr_render_timeline_export_sync_file(struct wlr_render_timeline *timeline, + uint64_t src_point) { + int sync_file_fd = -1; + + uint32_t syncobj_handle; + if (drmSyncobjCreate(timeline->drm_fd, 0, &syncobj_handle) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjCreate failed"); + return -1; + } + + if (drmSyncobjTransfer(timeline->drm_fd, syncobj_handle, 0, + timeline->handle, src_point, 0) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjTransfer failed"); + goto out; + } + + if (drmSyncobjExportSyncFile(timeline->drm_fd, + syncobj_handle, &sync_file_fd) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjExportSyncFile failed"); + goto out; + } + +out: + drmSyncobjDestroy(timeline->drm_fd, syncobj_handle); + return sync_file_fd; +} + +bool wlr_render_timeline_import_sync_file(struct wlr_render_timeline *timeline, + uint64_t dst_point, int sync_file_fd) { + bool ok = false; + + uint32_t syncobj_handle; + if (drmSyncobjCreate(timeline->drm_fd, 0, &syncobj_handle) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjCreate failed"); + return -1; + } + + if (drmSyncobjImportSyncFile(timeline->drm_fd, syncobj_handle, + sync_file_fd) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjImportSyncFile failed"); + goto out; + } + + if (drmSyncobjTransfer(timeline->drm_fd, timeline->handle, dst_point, + syncobj_handle, 0, 0) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjTransfer failed"); + goto out; + } + + ok = true; + +out: + drmSyncobjDestroy(timeline->drm_fd, syncobj_handle); + return ok; +} From 7a661e4c38b3628fab39202903be12baab21b8b4 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 19:01:40 +0200 Subject: [PATCH 02/10] render/timeline: add wlr_render_timeline_import_dmabuf Same as wlr_render_timeline_import_sync_file, but takes a DMA-BUF as argument. Depends on [1]. [1]: https://lore.kernel.org/dri-devel/20210610210925.642582-5-jason@jlekstrand.net/ --- include/wlr/render/timeline.h | 11 +++++++++++ render/timeline.c | 32 +++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/include/wlr/render/timeline.h b/include/wlr/render/timeline.h index 80705cb56f..f4d07e6340 100644 --- a/include/wlr/render/timeline.h +++ b/include/wlr/render/timeline.h @@ -56,5 +56,16 @@ int wlr_render_timeline_export_sync_file(struct wlr_render_timeline *timeline, */ bool wlr_render_timeline_import_sync_file(struct wlr_render_timeline *timeline, uint64_t dst_point, int sync_file_fd); +/** + * Import a timeline point from a DMA-BUF's implicit fence. + * + * The provided timeline point will be signalled when the DMA-BUF's producer + * has finished their write operations. + * + * This allows inter-operation with other APIs which don't support drm_syncobj + * nor sync_file yet. + */ +bool wlr_render_timeline_import_dmabuf(struct wlr_render_timeline *timeline, + uint64_t dst_point, int dmabuf_fd); #endif diff --git a/render/timeline.c b/render/timeline.c index 6acf3e8ee7..a69f06d18b 100644 --- a/render/timeline.c +++ b/render/timeline.c @@ -1,5 +1,7 @@ -#include +#include #include +#include +#include #include #include @@ -84,3 +86,31 @@ bool wlr_render_timeline_import_sync_file(struct wlr_render_timeline *timeline, drmSyncobjDestroy(timeline->drm_fd, syncobj_handle); return ok; } + +#ifndef DMA_BUF_IOCTL_EXPORT_SYNC_FILE + +struct dma_buf_export_sync_file { + __u32 flags; + __s32 fd; +}; + +#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) + +#endif + +bool wlr_render_timeline_import_dmabuf(struct wlr_render_timeline *timeline, + uint64_t dst_point, int dmabuf_fd) { + struct dma_buf_export_sync_file ioctl_data = { + .flags = DMA_BUF_SYNC_READ, + }; + if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, + &ioctl_data) != 0) { + wlr_log_errno(WLR_ERROR, "drmIoctl(EXPORT_SYNC_FILE) failed"); + return false; + } + + bool ok = wlr_render_timeline_import_sync_file(timeline, + ioctl_data.fd, dst_point); + close(ioctl_data.fd); + return ok; +} From dab20d831163e58cf7d81975ae8b26f4bf577bbd Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 19:08:19 +0200 Subject: [PATCH 03/10] render: add timeline wlr_renderer API --- include/wlr/render/interface.h | 4 ++++ include/wlr/render/wlr_renderer.h | 29 +++++++++++++++++++++++++++++ render/wlr_renderer.c | 16 ++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h index 4207bd304d..3665de521c 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -48,6 +48,10 @@ struct wlr_renderer_impl { uint32_t (*get_render_buffer_caps)(struct wlr_renderer *renderer); struct wlr_texture *(*texture_from_buffer)(struct wlr_renderer *renderer, struct wlr_buffer *buffer); + bool (*wait_timeline)(struct wlr_renderer *renderer, + struct wlr_render_timeline *timeline, uint64_t src_point); + bool (*signal_timeline)(struct wlr_renderer *renderer, + struct wlr_render_timeline *timeline, uint64_t dst_point); }; void wlr_renderer_init(struct wlr_renderer *renderer, diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index bae2fa9763..c7474e8757 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -23,6 +23,7 @@ struct wlr_drm_format_set; struct wlr_buffer; struct wlr_box; struct wlr_fbox; +struct wlr_render_timeline; struct wlr_renderer { const struct wlr_renderer_impl *impl; @@ -113,6 +114,34 @@ bool wlr_renderer_init_wl_display(struct wlr_renderer *r, */ int wlr_renderer_get_drm_fd(struct wlr_renderer *r); +/** + * Wait for a timeline synchronization point before continuing rendering + * operations. + * + * The renderer implementation is allowed to wait sooner, e.g. wait before + * executing any rendering operation since the previous wlr_renderer_begin() + * call. + * + * When a compositor calls wlr_renderer_wait_timeline(), the renderer is + * allowed to skip implicit wait synchronization. Compositors are not allowed + * to mix implicit and explicit wait synchronization usage. + */ +bool wlr_renderer_wait_timeline(struct wlr_renderer *renderer, + struct wlr_render_timeline *timeline, uint64_t src_point); +/** + * Signal a timeline synchronization point when all previous rendering + * operations have finished. + * + * The renderer implementation is allowed to signal later, e.g. signal when all + * rendering operations up to wlr_renderer_end() have finished. + * + * When a compositor calls wlr_renderer_signal_timeline(), the renderer is + * allowed to skip implicit signal synchronization. Compositors are not allowed + * to mix implicit and explicit signal synchronization usage. + */ +bool wlr_renderer_signal_timeline(struct wlr_renderer *renderer, + struct wlr_render_timeline *timeline, uint64_t dst_point); + /** * Destroys this wlr_renderer. Textures must be destroyed separately. */ diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 8a1f1e9418..46e0d4ff62 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -308,3 +308,19 @@ int wlr_renderer_get_drm_fd(struct wlr_renderer *r) { } return r->impl->get_drm_fd(r); } + +bool wlr_renderer_wait_timeline(struct wlr_renderer *r, + struct wlr_render_timeline *timeline, uint64_t src_point) { + if (!r->impl->wait_timeline) { + return false; + } + return r->impl->wait_timeline(r, timeline, src_point); +} + +bool wlr_renderer_signal_timeline(struct wlr_renderer *r, + struct wlr_render_timeline *timeline, uint64_t dst_point) { + if (!r->impl->signal_timeline) { + return false; + } + return r->impl->signal_timeline(r, timeline, dst_point); +} From 1bcec230995580a76600fabfcf98fdbe93692c69 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 16 Mar 2020 17:24:25 +0100 Subject: [PATCH 04/10] render/egl: add support for explicit sync extensions --- include/render/egl.h | 8 +++++ include/wlr/render/egl.h | 4 +++ render/egl.c | 74 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/include/render/egl.h b/include/render/egl.h index 55e9ce80f1..06e7f6a9d8 100644 --- a/include/render/egl.h +++ b/include/render/egl.h @@ -60,4 +60,12 @@ void wlr_egl_save_context(struct wlr_egl_context *context); */ bool wlr_egl_restore_context(struct wlr_egl_context *context); +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd); + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync); + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync); + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync); + #endif diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h index 903b70a3fc..9c3d954a76 100644 --- a/include/wlr/render/egl.h +++ b/include/wlr/render/egl.h @@ -62,6 +62,10 @@ struct wlr_egl { PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT; + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; + PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; + PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR; } procs; struct wlr_drm_format_set dmabuf_texture_formats; diff --git a/render/egl.c b/render/egl.c index 712a61c81b..bd3fb49e25 100644 --- a/render/egl.c +++ b/render/egl.c @@ -313,6 +313,18 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, return false; } + if (check_egl_ext(display_exts_str, "EGL_KHR_fence_sync") && + check_egl_ext(display_exts_str, "EGL_ANDROID_native_fence_sync")) { + load_egl_proc(&egl->procs.eglCreateSyncKHR, "eglCreateSyncKHR"); + load_egl_proc(&egl->procs.eglDestroySyncKHR, "eglDestroySyncKHR"); + load_egl_proc(&egl->procs.eglDupNativeFenceFDANDROID, + "eglDupNativeFenceFDANDROID"); + } + + if (check_egl_ext(display_exts_str, "EGL_KHR_wait_sync")) { + load_egl_proc(&egl->procs.eglWaitSyncKHR, "eglWaitSyncKHR"); + } + wlr_log(WLR_INFO, "Supported EGL display extensions: %s", display_exts_str); if (device_exts_str != NULL) { wlr_log(WLR_INFO, "Supported EGL device extensions: %s", device_exts_str); @@ -897,3 +909,65 @@ int wlr_egl_dup_drm_fd(struct wlr_egl *egl) { return render_fd; } + +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd) { + if (!egl->procs.eglCreateSyncKHR) { + return EGL_NO_SYNC_KHR; + } + + EGLint attribs[3] = { EGL_NONE }; + int dup_fd = -1; + if (fence_fd >= 0) { + dup_fd = fcntl(fence_fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) { + wlr_log_errno(WLR_ERROR, "dup failed"); + return EGL_NO_SYNC_KHR; + } + + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = dup_fd; + attribs[2] = EGL_NONE; + } + + EGLSyncKHR sync = egl->procs.eglCreateSyncKHR(egl->display, + EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync == EGL_NO_SYNC_KHR) { + wlr_log(WLR_ERROR, "eglCreateSyncKHR failed"); + if (dup_fd >= 0) { + close(dup_fd); + } + } + return sync; +} + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (sync == EGL_NO_SYNC_KHR) { + return; + } + assert(egl->procs.eglDestroySyncKHR); + if (egl->procs.eglDestroySyncKHR(egl->display, sync) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglDestroySyncKHR failed"); + } +} + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync) { + if (!egl->procs.eglDupNativeFenceFDANDROID) { + return -1; + } + + int fd = egl->procs.eglDupNativeFenceFDANDROID(egl->display, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + wlr_log(WLR_ERROR, "eglDupNativeFenceFDANDROID failed"); + return -1; + } + + return fd; +} + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (egl->procs.eglWaitSyncKHR(egl->display, sync, 0) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglWaitSyncKHR failed"); + return false; + } + return true; +} From 22281837b1a0da3e498562dbb913ee2db2ed782b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 19:20:07 +0200 Subject: [PATCH 05/10] render/gles2: implement explicit sync APIs --- render/gles2/renderer.c | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 01efaf1de9..c6083a3316 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -490,6 +491,48 @@ static int gles2_get_drm_fd(struct wlr_renderer *wlr_renderer) { return renderer->drm_fd; } +static bool gles2_wait_timeline(struct wlr_renderer *wlr_renderer, + struct wlr_render_timeline *timeline, uint64_t src_point) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + int sync_file_fd = wlr_render_timeline_export_sync_file(timeline, src_point); + if (sync_file_fd < 0) { + return false; + } + + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd); + close(sync_file_fd); + if (sync == EGL_NO_SYNC_KHR) { + return false; + } + + bool ok = wlr_egl_wait_sync(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + return ok; +} + +static bool gles2_signal_timeline(struct wlr_renderer *wlr_renderer, + struct wlr_render_timeline *timeline, uint64_t dst_point) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1); + if (sync == EGL_NO_SYNC_KHR) { + return false; + } + + int sync_file_fd = wlr_egl_dup_fence_fd(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (sync_file_fd < 0) { + return false; + } + + bool ok = wlr_render_timeline_import_sync_file(timeline, dst_point, sync_file_fd); + close(sync_file_fd); + return ok; +} + static uint32_t gles2_get_render_buffer_caps(struct wlr_renderer *wlr_renderer) { return WLR_BUFFER_CAP_DMABUF; } @@ -554,6 +597,8 @@ static const struct wlr_renderer_impl renderer_impl = { .get_drm_fd = gles2_get_drm_fd, .get_render_buffer_caps = gles2_get_render_buffer_caps, .texture_from_buffer = gles2_texture_from_buffer, + .wait_timeline = gles2_wait_timeline, + .signal_timeline = gles2_signal_timeline, }; void push_gles2_debug_(struct wlr_gles2_renderer *renderer, From 5effb64dc3aff00b83f565572f05a4561869e178 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 19:29:00 +0200 Subject: [PATCH 06/10] output: add explicit sync API --- include/wlr/types/wlr_output.h | 34 ++++++++++++++++++++++++++++++++++ types/wlr_output.c | 23 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6c07372b95..04bad997a2 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -62,6 +62,8 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, + WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 8, + WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 9, }; enum wlr_output_state_mode_type { @@ -94,6 +96,13 @@ struct wlr_output_state { // only valid if WLR_OUTPUT_STATE_GAMMA_LUT uint16_t *gamma_lut; size_t gamma_lut_size; + + // only valid if WLR_OUTPUT_STATE_WAIT_TIMELINE + struct wlr_render_timeline *wait_timeline; + uint64_t wait_point; + // only valid if WLR_OUTPUT_STATE_SIGNAL_TIMELINE + struct wlr_render_timeline *signal_timeline; + uint64_t signal_point; }; struct wlr_output_impl; @@ -361,6 +370,31 @@ uint32_t wlr_output_preferred_read_format(struct wlr_output *output); */ void wlr_output_set_damage(struct wlr_output *output, pixman_region32_t *damage); +/** + * Set a timeline point to wait on before displaying the next frame. + * + * The timeline must continue to remain valid until the next call to + * wlr_output_commit() or wlr_output_rollback(). Committing a wait timeline + * point without a buffer is invalid. + * + * There is only a single wait timeline point, waiting for multiple timeline + * points is unsupported. + */ +void wlr_output_set_wait_timeline(struct wlr_output *output, + struct wlr_render_timeline *timeline, uint64_t src_point); +/** + * Set a timeline point to be signalled when the frame is no longer being used + * by the backend. + * + * The timeline must continue to remain valid until the next call to + * wlr_output_commit() or wlr_output_rollback(). Committing a signal timeline + * point without a buffer is invalid. + * + * There is only a single signal timeline point, signalling multiple timeline + * points is unsupported. + */ +void wlr_output_set_signal_timeline(struct wlr_output *output, + struct wlr_render_timeline *timeline, uint64_t dst_point); /** * Test whether the pending output state would be accepted by the backend. If * this function returns true, `wlr_output_commit` can only fail due to a diff --git a/types/wlr_output.c b/types/wlr_output.c index b6167d6d02..1b891cea57 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -613,6 +613,20 @@ void wlr_output_set_damage(struct wlr_output *output, output->pending.committed |= WLR_OUTPUT_STATE_DAMAGE; } +void wlr_output_set_wait_timeline(struct wlr_output *output, + struct wlr_render_timeline *timeline, uint64_t src_point) { + output->pending.committed |= WLR_OUTPUT_STATE_WAIT_TIMELINE; + output->pending.wait_timeline = timeline; + output->pending.wait_point = src_point; +} + +void wlr_output_set_signal_timeline(struct wlr_output *output, + struct wlr_render_timeline *timeline, uint64_t dst_point) { + output->pending.committed |= WLR_OUTPUT_STATE_SIGNAL_TIMELINE; + output->pending.signal_timeline = timeline; + output->pending.signal_point = dst_point; +} + static void output_state_clear_gamma_lut(struct wlr_output_state *state) { free(state->gamma_lut); state->gamma_lut = NULL; @@ -758,6 +772,15 @@ static bool output_basic_test(struct wlr_output *output) { return false; } } + } else { + if (output->pending.committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + wlr_log(WLR_DEBUG, "Tried to set wait timeline without a buffer"); + return false; + } + if (output->pending.committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + wlr_log(WLR_DEBUG, "Tried to set signal timeline without a buffer"); + return false; + } } bool enabled = output->enabled; From 7e9187afaecc2b82a984e99a21700e62c8322424 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 20:09:38 +0200 Subject: [PATCH 07/10] backend/drm: add support for explicit sync APIs --- backend/drm/atomic.c | 54 ++++++++++++++++++++++++++++++++ backend/drm/drm.c | 11 ++++++- backend/drm/legacy.c | 7 +++++ backend/drm/properties.c | 2 ++ include/backend/drm/properties.h | 2 ++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 70f55afff6..c1a2d35cff 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -163,6 +165,41 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, atom->failed = true; } +static int set_plane_in_fence_fd(struct atomic *atom, struct wlr_drm_plane *plane, + struct wlr_render_timeline *timeline, uint64_t src_point) { + if (!plane->props.in_fence_fd) { + wlr_log(WLR_ERROR, "Missing IN_FENCE_FD property"); + goto error; + } + + int sync_file_fd = wlr_render_timeline_export_sync_file(timeline, src_point); + if (sync_file_fd < 0) { + goto error; + } + + atomic_add(atom, plane->id, plane->props.in_fence_fd, sync_file_fd); + + return sync_file_fd; + +error: + wlr_log(WLR_ERROR, "Failed to set plane %"PRIu32" IN_FENCE_FD", plane->id); + atom->failed = true; + return -1; +} + +static void set_crtc_out_fence_ptr(struct atomic *atom, struct wlr_drm_crtc *crtc, + int *fd_ptr) { + if (!crtc->props.out_fence_ptr) { + wlr_log(WLR_ERROR, + "CRTC %"PRIu32" is missing the OUT_FENCE_PTR property", + crtc->id); + atom->failed = true; + return; + } + + atomic_add(atom, crtc->id, crtc->props.out_fence_ptr, (uintptr_t)fd_ptr); +} + static bool atomic_crtc_commit(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, bool test_only) { @@ -228,6 +265,8 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, flags |= DRM_MODE_ATOMIC_NONBLOCK; } + int in_fence_fd = -1, out_fence_fd = -1; + struct atomic atom; atomic_begin(&atom); atomic_add(&atom, conn->id, conn->props.crtc_id, active ? crtc->id : 0); @@ -249,6 +288,13 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, atomic_add(&atom, crtc->primary->id, crtc->primary->props.fb_damage_clips, fb_damage_clips); } + if (state->base->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + in_fence_fd = set_plane_in_fence_fd(&atom, crtc->primary, + state->base->wait_timeline, state->base->wait_point); + } + if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + set_crtc_out_fence_ptr(&atom, crtc, &out_fence_fd); + } if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { set_plane_props(&atom, drm, crtc->cursor, crtc->id, @@ -288,6 +334,14 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob"); } + close(in_fence_fd); + + if (out_fence_fd > 0) { + ok = wlr_render_timeline_import_sync_file(state->base->signal_timeline, + state->base->signal_point, out_fence_fd); + close(out_fence_fd); + } + return ok; } diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 191ad064da..d65f3d0ed2 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -36,7 +36,9 @@ static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | - WLR_OUTPUT_STATE_GAMMA_LUT; + WLR_OUTPUT_STATE_GAMMA_LUT | + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE; bool check_drm_features(struct wlr_drm_backend *drm) { if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width)) { @@ -491,6 +493,13 @@ static bool drm_connector_test(struct wlr_output *output) { } } + if ((output->pending.committed & (WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) && conn->backend->parent) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Sync timelines are unsupported in multi-GPU mode"); + return false; + } + struct wlr_drm_connector_state pending = {0}; drm_connector_state_init(&pending, conn, &output->pending); diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index 37d8147afa..880dab9b98 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -54,6 +54,13 @@ static bool legacy_crtc_test(struct wlr_drm_connector *conn, } } + if (state->base->committed & (WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Sync timelines are unsupported with legacy KMS interface"); + return false; + } + return true; } diff --git a/backend/drm/properties.c b/backend/drm/properties.c index f269659337..db819828de 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -38,6 +38,7 @@ static const struct prop_info crtc_info[] = { { "GAMMA_LUT", INDEX(gamma_lut) }, { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) }, { "MODE_ID", INDEX(mode_id) }, + { "OUT_FENCE_PTR", INDEX(out_fence_ptr) }, { "VRR_ENABLED", INDEX(vrr_enabled) }, #undef INDEX }; @@ -51,6 +52,7 @@ static const struct prop_info plane_info[] = { { "CRTC_Y", INDEX(crtc_y) }, { "FB_DAMAGE_CLIPS", INDEX(fb_damage_clips) }, { "FB_ID", INDEX(fb_id) }, + { "IN_FENCE_FD", INDEX(in_fence_fd) }, { "IN_FORMATS", INDEX(in_formats) }, { "SRC_H", INDEX(src_h) }, { "SRC_W", INDEX(src_w) }, diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index 10c1ccd09f..eadbdece86 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -39,6 +39,7 @@ union wlr_drm_crtc_props { uint32_t active; uint32_t mode_id; + uint32_t out_fence_ptr; }; uint32_t props[6]; }; @@ -62,6 +63,7 @@ union wlr_drm_plane_props { uint32_t fb_id; uint32_t crtc_id; uint32_t fb_damage_clips; + uint32_t in_fence_fd; }; uint32_t props[14]; }; From 9d04df39fcfc31ae1ad64c52689ed6f5a56fdd0e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 15 Mar 2020 15:02:04 +0100 Subject: [PATCH 08/10] linux-explicit-synchronization-v1: new protocol implementation This patch adds support for the linux-explicit-synchronization-unstable-v1 protocol. To test, run weston-simple-dmabuf-egl. --- .../wlr_linux_explicit_synchronization_v1.h | 80 ++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_linux_explicit_synchronization_v1.c | 364 ++++++++++++++++++ 4 files changed, 446 insertions(+) create mode 100644 include/wlr/types/wlr_linux_explicit_synchronization_v1.h create mode 100644 types/wlr_linux_explicit_synchronization_v1.c diff --git a/include/wlr/types/wlr_linux_explicit_synchronization_v1.h b/include/wlr/types/wlr_linux_explicit_synchronization_v1.h new file mode 100644 index 0000000000..b412957be6 --- /dev/null +++ b/include/wlr/types/wlr_linux_explicit_synchronization_v1.h @@ -0,0 +1,80 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LINUX_EXPLICIT_SYNCHRONIZATION_V1_H +#define WLR_TYPES_WLR_LINUX_EXPLICIT_SYNCHRONIZATION_V1_H + +#include +#include + +struct wlr_linux_surface_synchronization_v1_state { + int acquire_fence_fd; + struct wlr_linux_buffer_release_v1 *buffer_release; +}; + +struct wlr_linux_surface_synchronization_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + + // private state + + struct wlr_addon addon; + + struct wlr_linux_surface_synchronization_v1_state pending, current; + + struct wl_listener surface_commit; +}; + +struct wlr_linux_buffer_release_v1 { + struct wl_resource *resource; +}; + +struct wlr_linux_explicit_synchronization_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wl_listener display_destroy; +}; + +/** + * Advertise explicit synchronization support to clients. + * + * The compositor must be prepared to handle fences coming from clients and to + * send release fences correctly. In particular, both the renderer and the + * backend need to support explicit synchronization. + */ +struct wlr_linux_explicit_synchronization_v1 * +wlr_linux_explicit_synchronization_v1_create(struct wl_display *display); + +/** + * Signal the provided timeline synchronization point when the last submitted + * buffer is ready to be acquired. + */ +bool wlr_linux_explicit_synchronization_v1_signal_surface_timeline( + struct wlr_linux_explicit_synchronization_v1 *explicit_sync, + struct wlr_surface *surface, struct wlr_render_timeline *timeline, + uint64_t dst_point); + +/** + * Send a timeline synchronization point to the client which can be used to + * wait for the buffer to be released. + * + * The synchronization point must already be materialized: wait-before-submit + * is not supported. + */ +bool wlr_linux_explicit_synchronization_v1_wait_surface_timeline( + struct wlr_linux_explicit_synchronization_v1 *explicit_sync, + struct wlr_surface *surface, struct wlr_render_timeline *timeline, + uint64_t src_point); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 8d8b2502e5..ef1321f30c 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -26,6 +26,7 @@ protocols = { 'idle-inhibit-unstable-v1': wl_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', 'keyboard-shortcuts-inhibit-unstable-v1': wl_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', 'linux-dmabuf-unstable-v1': wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', + 'linux-explicit-synchronization-unstable-v1': wl_protocol_dir / 'unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml', 'pointer-constraints-unstable-v1': wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 'pointer-gestures-unstable-v1': wl_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml', 'primary-selection-unstable-v1': wl_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', diff --git a/types/meson.build b/types/meson.build index bb1e8c8f93..6adc144b2a 100644 --- a/types/meson.build +++ b/types/meson.build @@ -38,6 +38,7 @@ wlr_files += files( 'wlr_keyboard_shortcuts_inhibit_v1.c', 'wlr_layer_shell_v1.c', 'wlr_linux_dmabuf_v1.c', + 'wlr_linux_explicit_synchronization_v1.c', 'wlr_matrix.c', 'wlr_output_damage.c', 'wlr_output_layout.c', diff --git a/types/wlr_linux_explicit_synchronization_v1.c b/types/wlr_linux_explicit_synchronization_v1.c new file mode 100644 index 0000000000..aebf4ec55f --- /dev/null +++ b/types/wlr_linux_explicit_synchronization_v1.c @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include +#include +#include "linux-explicit-synchronization-unstable-v1-protocol.h" +#include "util/signal.h" + +#define LINUX_EXPLICIT_SYNCHRONIZATION_V1_VERSION 2 + +static const struct zwp_linux_explicit_synchronization_v1_interface + explicit_sync_impl; +static const struct zwp_linux_surface_synchronization_v1_interface + surface_sync_impl; + +static struct wlr_linux_explicit_synchronization_v1 * +explicit_sync_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_linux_explicit_synchronization_v1_interface, + &explicit_sync_impl)); + return wl_resource_get_user_data(resource); +} + +// Returns NULL if the surface sync is inert +static struct wlr_linux_surface_synchronization_v1 * +surface_sync_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_linux_surface_synchronization_v1_interface, + &surface_sync_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_linux_buffer_release_v1 *buffer_release_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_linux_buffer_release_v1_interface, NULL)); + return wl_resource_get_user_data(resource); +} + +static void buffer_release_destroy( + struct wlr_linux_buffer_release_v1 *buffer_release, int fence_fd) { + if (buffer_release == NULL) { + return; + } + if (fence_fd >= 0) { + zwp_linux_buffer_release_v1_send_fenced_release( + buffer_release->resource, fence_fd); + } else { + zwp_linux_buffer_release_v1_send_immediate_release( + buffer_release->resource); + } + wl_resource_destroy(buffer_release->resource); +} + +static void buffer_release_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_linux_buffer_release_v1 *buffer_release = + buffer_release_from_resource(resource); + free(buffer_release); +} + +static void surface_sync_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void surface_sync_handle_set_acquire_fence(struct wl_client *client, + struct wl_resource *resource, int fence_fd) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + surface_sync_from_resource(resource); + if (surface_sync == NULL) { + close(fence_fd); + wl_resource_post_error(resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "the surface has been destroyed"); + return; + } + + if (surface_sync->pending.acquire_fence_fd >= 0) { + close(fence_fd); + wl_resource_post_error(resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_FENCE, + "a fence FD was already set for this commit"); + return; + } + + // TODO: check that the FD is a sync_file + + surface_sync->pending.acquire_fence_fd = fence_fd; +} + +static void surface_sync_handle_get_release(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + surface_sync_from_resource(resource); + if (surface_sync == NULL) { + wl_resource_post_error(resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "the surface has been destroyed"); + return; + } + + if (surface_sync->pending.buffer_release != NULL) { + wl_resource_post_error(resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE, + "a buffer release has already been requested for this commit"); + return; + } + + struct wlr_linux_buffer_release_v1 *buffer_release = + calloc(1, sizeof(*buffer_release)); + if (buffer_release == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + uint32_t version = wl_resource_get_version(resource); + buffer_release->resource = wl_resource_create(client, + &zwp_linux_buffer_release_v1_interface, version, id); + if (buffer_release->resource == NULL) { + wl_resource_post_no_memory(resource); + free(buffer_release); + return; + } + wl_resource_set_implementation(buffer_release->resource, NULL, + buffer_release, buffer_release_handle_resource_destroy); + + surface_sync->pending.buffer_release = buffer_release; +} + +static const struct zwp_linux_surface_synchronization_v1_interface + surface_sync_impl = { + .destroy = surface_sync_handle_destroy, + .set_acquire_fence = surface_sync_handle_set_acquire_fence, + .get_release = surface_sync_handle_get_release, +}; + +static void surface_sync_state_init( + struct wlr_linux_surface_synchronization_v1_state *state) { + memset(state, 0, sizeof(*state)); + state->acquire_fence_fd = -1; +} + +static void surface_sync_state_finish( + struct wlr_linux_surface_synchronization_v1_state *state) { + if (state->acquire_fence_fd >= 0) { + close(state->acquire_fence_fd); + } + buffer_release_destroy(state->buffer_release, -1); +} + +static void surface_sync_destroy( + struct wlr_linux_surface_synchronization_v1 *surface_sync) { + if (surface_sync == NULL) { + return; + } + wl_list_remove(&surface_sync->surface_commit.link); + wlr_addon_finish(&surface_sync->addon); + wl_resource_set_user_data(surface_sync->resource, NULL); + surface_sync_state_finish(&surface_sync->pending); + surface_sync_state_finish(&surface_sync->current); + free(surface_sync); +} + +static void surface_sync_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + wl_container_of(listener, surface_sync, surface_commit); + + if (surface_sync->pending.acquire_fence_fd >= 0 && + surface_sync->surface->buffer == NULL) { + wl_resource_post_error(surface_sync->resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "acquire fence FD set but no buffer attached"); + return; + } + + if (surface_sync->pending.buffer_release != NULL && + surface_sync->surface->buffer == NULL) { + wl_resource_post_error(surface_sync->resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "buffer release requested but no buffer attached"); + return; + } + + surface_sync_state_finish(&surface_sync->current); + surface_sync->current = surface_sync->pending; + surface_sync_state_init(&surface_sync->pending); +} + +static void surface_sync_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + surface_sync_from_resource(resource); + surface_sync_destroy(surface_sync); +} + +static void surface_sync_handle_surface_destroy(struct wlr_addon *addon) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + wl_container_of(addon, surface_sync, addon); + surface_sync_destroy(surface_sync); +} + +static const struct wlr_addon_interface addon_impl = { + .name = "zwp_linux_surface_synchronization_v1", + .destroy = surface_sync_handle_surface_destroy, +}; + +static void explicit_sync_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_linux_surface_synchronization_v1 *surface_sync_from_surface( + struct wlr_linux_explicit_synchronization_v1 *explicit_sync, + struct wlr_surface *surface) { + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, explicit_sync, &addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_linux_surface_synchronization_v1 *surface_sync = + wl_container_of(addon, surface_sync, addon); + return surface_sync; +} + +static void explicit_sync_handle_get_synchronization(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_linux_explicit_synchronization_v1 *explicit_sync = + explicit_sync_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (surface_sync_from_surface(explicit_sync, surface) != NULL) { + wl_resource_post_error(resource, + ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS, + "zwp_linux_surface_synchronization_v1 already created for this surface"); + return; + } + + struct wlr_linux_surface_synchronization_v1 *surface_sync = + calloc(1, sizeof(*surface_sync)); + if (surface_sync == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + uint32_t version = wl_resource_get_version(resource); + surface_sync->resource = wl_resource_create(client, + &zwp_linux_surface_synchronization_v1_interface, version, id); + if (surface_sync->resource == NULL) { + wl_resource_post_no_memory(resource); + free(surface_sync); + return; + } + wl_resource_set_implementation(surface_sync->resource, + &surface_sync_impl, surface_sync, surface_sync_handle_resource_destroy); + + surface_sync->surface = surface; + surface_sync_state_init(&surface_sync->pending); + surface_sync_state_init(&surface_sync->current); + + surface_sync->surface_commit.notify = surface_sync_handle_surface_commit; + wl_signal_add(&surface->events.commit, &surface_sync->surface_commit); + + wlr_addon_init(&surface_sync->addon, &surface->addons, explicit_sync, + &addon_impl); +} + +static const struct zwp_linux_explicit_synchronization_v1_interface + explicit_sync_impl = { + .destroy = explicit_sync_handle_destroy, + .get_synchronization = explicit_sync_handle_get_synchronization, +}; + +static void explicit_sync_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_linux_explicit_synchronization_v1 *explicit_sync = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_linux_explicit_synchronization_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &explicit_sync_impl, + explicit_sync, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_linux_explicit_synchronization_v1 *explicit_sync = + wl_container_of(listener, explicit_sync, display_destroy); + wlr_signal_emit_safe(&explicit_sync->events.destroy, NULL); + wl_list_remove(&explicit_sync->display_destroy.link); + wl_global_destroy(explicit_sync->global); + free(explicit_sync); +} + +struct wlr_linux_explicit_synchronization_v1 * +wlr_linux_explicit_synchronization_v1_create(struct wl_display *display) { + struct wlr_linux_explicit_synchronization_v1 *explicit_sync = + calloc(1, sizeof(*explicit_sync)); + if (explicit_sync == NULL) { + return NULL; + } + + wl_signal_init(&explicit_sync->events.destroy); + + explicit_sync->global = wl_global_create(display, + &zwp_linux_explicit_synchronization_v1_interface, + LINUX_EXPLICIT_SYNCHRONIZATION_V1_VERSION, explicit_sync, + explicit_sync_bind); + if (explicit_sync->global == NULL) { + free(explicit_sync); + return NULL; + } + + explicit_sync->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &explicit_sync->display_destroy); + + return explicit_sync; +} + +bool wlr_linux_explicit_synchronization_v1_signal_surface_timeline( + struct wlr_linux_explicit_synchronization_v1 *explicit_sync, + struct wlr_surface *surface, struct wlr_render_timeline *timeline, + uint64_t dst_point) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + surface_sync_from_surface(explicit_sync, surface); + if (!surface_sync) { + // TODO: fallback to DMA-BUF fence export + return false; + } + + return wlr_render_timeline_import_sync_file(timeline, dst_point, + surface_sync->current.acquire_fence_fd); +} + +bool wlr_linux_explicit_synchronization_v1_wait_surface_timeline( + struct wlr_linux_explicit_synchronization_v1 *explicit_sync, + struct wlr_surface *surface, struct wlr_render_timeline *timeline, + uint64_t src_point) { + struct wlr_linux_surface_synchronization_v1 *surface_sync = + surface_sync_from_surface(explicit_sync, surface); + if (!surface_sync) { + return true; + } + + struct wlr_linux_buffer_release_v1 *buffer_release = + surface_sync->current.buffer_release; + surface_sync->current.buffer_release = NULL; + if (!buffer_release) { + return true; + } + + int fence_fd = wlr_render_timeline_export_sync_file(timeline, src_point); + if (fence_fd < 0) { + return false; + } + + buffer_release_destroy(buffer_release, fence_fd); + close(fence_fd); + return true; +} From 98909f0c80a3d2afb092012bc9ab5ba2cd6c9305 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 22:00:39 +0200 Subject: [PATCH 09/10] examples/explicit-sync: new example --- examples/explicit-sync.c | 254 +++++++++++++++++++++++++++++++++++++++ examples/meson.build | 4 + 2 files changed, 258 insertions(+) create mode 100644 examples/explicit-sync.c diff --git a/examples/explicit-sync.c b/examples/explicit-sync.c new file mode 100644 index 0000000000..607b762fb8 --- /dev/null +++ b/examples/explicit-sync.c @@ -0,0 +1,254 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple compositor with explicit synchronization support. Input is + * unimplemented. + * + * New surfaces are stacked on top of the existing ones as they appear. */ + +struct server { + struct wl_display *display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_linux_explicit_synchronization_v1 *explicit_sync_v1; + + struct wl_list outputs; + struct wl_list surfaces; + + struct wl_listener new_output; + struct wl_listener new_surface; +}; + +struct surface { + struct wlr_surface *wlr; + struct wl_list link; + + struct wlr_render_timeline *timeline; + + struct wl_listener destroy; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr; + + struct wlr_render_timeline *in_timeline, *out_timeline; + + struct wl_listener frame; +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); + struct wlr_renderer *renderer = output->server->renderer; + + if (!wlr_output_attach_render(output->wlr, NULL)) { + return; + } + + wlr_renderer_begin(renderer, output->wlr->width, output->wlr->height); + wlr_renderer_clear(renderer, (float[]){0.25f, 0.25f, 0.25f, 1}); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + int pos = 0; + struct surface *surface; + wl_list_for_each(surface, &output->server->surfaces, link) { + pos += 50; + + struct wlr_texture *texture = wlr_surface_get_texture(surface->wlr); + if (texture == NULL) { + continue; + } + + uint64_t surface_point = surface->wlr->current.seq; + if (!wlr_linux_explicit_synchronization_v1_signal_surface_timeline( + output->server->explicit_sync_v1, surface->wlr, + surface->timeline, surface_point)) { + wlr_log(WLR_ERROR, "Failed to signal surface timeline"); + continue; + } + if (!wlr_renderer_wait_timeline(renderer, + surface->timeline, surface_point)) { + wlr_log(WLR_ERROR, "Failed to wait for surface timeline"); + continue; + } + + wlr_render_texture(renderer, texture, output->wlr->transform_matrix, + pos, pos, 1.0); + + wlr_surface_send_frame_done(surface->wlr, &now); + } + + uint64_t output_point = output->wlr->commit_seq; + if (!wlr_renderer_signal_timeline(renderer, output->in_timeline, + output_point)) { + wlr_log(WLR_ERROR, "Failed to signal renderer timeline"); + return; + } + + wlr_renderer_end(renderer); + + wlr_output_set_wait_timeline(output->wlr, + output->in_timeline, output_point); + wlr_output_set_signal_timeline(output->wlr, + output->out_timeline, output_point); + + if (!wlr_output_commit(output->wlr)) { + wlr_log(WLR_ERROR, "Failed to commit output"); + return; + } + + wl_list_for_each(surface, &output->server->surfaces, link) { + if (!wlr_linux_explicit_synchronization_v1_wait_surface_timeline( + output->server->explicit_sync_v1, surface->wlr, + output->out_timeline, output_point)) { + wlr_log(WLR_ERROR, "Failed to wait for surface timeline"); + } + } +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + int drm_fd = wlr_renderer_get_drm_fd(server->renderer); + struct wlr_render_timeline *in_timeline = wlr_render_timeline_create(drm_fd); + struct wlr_render_timeline *out_timeline = wlr_render_timeline_create(drm_fd); + if (in_timeline == NULL || out_timeline == NULL) { + return; + } + + struct output *output = + calloc(1, sizeof(struct output)); + output->wlr = wlr_output; + output->server = server; + output->in_timeline = in_timeline; + output->out_timeline = out_timeline; + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + wl_list_insert(&server->outputs, &output->link); + + if (!wl_list_empty(&wlr_output->modes)) { + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + wlr_output_set_mode(wlr_output, mode); + wlr_output_commit(wlr_output); + } + + wlr_output_create_global(wlr_output); +} + +static void surface_handle_destroy(struct wl_listener *listener, void *data) { + struct surface *surface = wl_container_of(listener, surface, destroy); + wlr_render_timeline_destroy(surface->timeline); + wl_list_remove(&surface->destroy.link); + wl_list_remove(&surface->link); + free(surface); +} + +static void server_handle_new_surface(struct wl_listener *listener, + void *data) { + struct server *server = wl_container_of(listener, server, new_surface); + struct wlr_surface *wlr_surface = data; + + int drm_fd = wlr_renderer_get_drm_fd(server->renderer); + struct wlr_render_timeline *timeline = wlr_render_timeline_create(drm_fd); + if (timeline == NULL) { + return; + } + + struct surface *surface = calloc(1, sizeof(struct surface)); + surface->wlr = wlr_surface; + surface->timeline = timeline; + surface->destroy.notify = surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &surface->destroy); + + wl_list_insert(&server->surfaces, &surface->link); +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + const char *startup_cmd = NULL; + int c; + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + } + if (optind < argc) { + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + + struct server server = {0}; + server.display = wl_display_create(); + server.backend = wlr_backend_autocreate(server.display); + server.renderer = wlr_backend_get_renderer(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.display); + + struct wlr_compositor *compositor = + wlr_compositor_create(server.display, server.renderer); + + wlr_xdg_shell_create(server.display); + + server.explicit_sync_v1 = + wlr_linux_explicit_synchronization_v1_create(server.display); + + wl_list_init(&server.outputs); + wl_list_init(&server.surfaces); + + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.new_surface.notify = server_handle_new_surface; + wl_signal_add(&compositor->events.new_surface, &server.new_surface); + + const char *socket = wl_display_add_socket_auto(server.display); + if (!socket) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd != NULL) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + } + } + + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.display); + + wl_display_destroy_clients(server.display); + wl_display_destroy(server.display); + return EXIT_SUCCESS; +} diff --git a/examples/meson.build b/examples/meson.build index 1e428e81b2..c86b165d9b 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -55,6 +55,10 @@ compositors = { 'src': 'scene-graph.c', 'proto': ['xdg-shell'], }, + 'explicit-sync': { + 'src': 'explicit-sync.c', + 'proto': ['xdg-shell'], + }, } clients = { From f80f89b5604c8d4f6f483e526ee2b97a4b19a436 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 20 Oct 2021 22:22:53 +0200 Subject: [PATCH 10/10] linux-explicit-synchronization-v1: extract fence from DMA-BUF --- types/wlr_linux_explicit_synchronization_v1.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/types/wlr_linux_explicit_synchronization_v1.c b/types/wlr_linux_explicit_synchronization_v1.c index aebf4ec55f..115e243aca 100644 --- a/types/wlr_linux_explicit_synchronization_v1.c +++ b/types/wlr_linux_explicit_synchronization_v1.c @@ -327,13 +327,20 @@ bool wlr_linux_explicit_synchronization_v1_signal_surface_timeline( uint64_t dst_point) { struct wlr_linux_surface_synchronization_v1 *surface_sync = surface_sync_from_surface(explicit_sync, surface); - if (!surface_sync) { - // TODO: fallback to DMA-BUF fence export - return false; + if (surface_sync) { + return wlr_render_timeline_import_sync_file(timeline, dst_point, + surface_sync->current.acquire_fence_fd); } - return wlr_render_timeline_import_sync_file(timeline, dst_point, - surface_sync->current.acquire_fence_fd); + // Client doesn't support explicit sync, try to extract the fence from the + // DMA-BUF + struct wlr_dmabuf_attributes dmabuf; + if (surface->buffer == NULL || + !wlr_buffer_get_dmabuf(&surface->buffer->base, &dmabuf)) { + return false; + } + return wlr_render_timeline_import_dmabuf(timeline, dst_point, + dmabuf.fd[0]); } bool wlr_linux_explicit_synchronization_v1_wait_surface_timeline(