From 37faecc77062360dea1c9cbefa5fcf35767337f2 Mon Sep 17 00:00:00 2001 From: Erico Nunes Date: Fri, 24 Apr 2026 12:40:32 +0200 Subject: [PATCH] compositor: Support linux-drm-syncobj-v1 Add support for the linux-drm-syncobj-v1 protocol for explicit synchronization. This protocol supersedes linux-explicit-synchronization-unstable-v1 which has been supported in Weston for a long time but was not adopted in the rest of the ecosystem. For the acquire point, the implementation uses the DRM syncobj eventfd mechanism and the wait is entirely in CPU. The acquire fence cannot be simply imported into GPU rendering jobs, as this protocol allows for client commits containing a timeline point for which a fence has not even been materialized yet. Additionally, adding a client's fence as a dependency for the compositor's GPU rendering job has a risk of causing the compositor to miss a refresh cycle due to a slow client. Therefore, the eventfd method is used which ensures that the fence has materialized in the kernel as well as efficiently waiting for it to be signalled. This builds upon the deferred content update framework used by the fifo and commit-timing protocols. For the release point, a similar method as already existed for the linux-explicit-synchronization-unstable-v1 protocol is followed. The timeline release point is not immediately signalled after a GPU rendering job, as the buffer may still be referred to by the compositor. Therefore, the release point signalling is still mainly driven by the compositor's buffer refcount, so effectively by CPU. A GPU fence is still transferred to the timeline point at buffer's release when the buffer had been used for the compositor's GPU rendering (and most likely already signalled by the GPU by that time) just to ensure that any remaining compositor GPU job has finished before the client reuses the buffer. Signed-off-by: Erico Nunes --- clients/meson.build | 22 + clients/simple-syncobj-egl.c | 1605 ++++++++++++++++++ include/libweston/libweston.h | 28 + libweston/backend-drm/drm-internal.h | 1 + libweston/backend-drm/drm.c | 3 + libweston/backend-drm/state-helpers.c | 5 + libweston/backend-drm/state-propose.c | 5 + libweston/backend-headless/headless.c | 3 + libweston/backend-wayland/wayland.c | 4 + libweston/backend-x11/x11.c | 3 + libweston/compositor.c | 7 + libweston/libweston-internal.h | 18 + libweston/meson.build | 3 + libweston/renderer-gl/gl-renderer-internal.h | 1 + libweston/renderer-gl/gl-renderer.c | 162 +- libweston/renderer-gl/meson.build | 2 +- libweston/renderer-vulkan/vulkan-renderer.c | 152 +- libweston/surface-state.c | 22 + libweston/syncobj.c | 571 +++++++ libweston/syncobj.h | 59 + protocol/meson.build | 1 + 21 files changed, 2606 insertions(+), 71 deletions(-) create mode 100644 clients/simple-syncobj-egl.c create mode 100644 libweston/syncobj.c create mode 100644 libweston/syncobj.h diff --git a/clients/meson.build b/clients/meson.build index 7cf590669..1549ea6b3 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -111,6 +111,28 @@ simple_clients = [ 'deps': [ 'egl', 'glesv2', 'gbm' ], 'options': [ 'renderer-gl' ] }, + { + 'name': 'syncobj-egl', + 'sources': [ + 'simple-syncobj-egl.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + linux_drm_syncobj_v1_client_protocol_h, + linux_drm_syncobj_v1_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + weston_direct_display_client_protocol_h, + weston_direct_display_protocol_c, + ], + 'dep_objs': [ + dep_wayland_client, + dep_libdrm, + dep_libm, + dep_matrix_c, + ], + 'deps': [ 'egl', 'glesv2', 'gbm' ], + 'options': [ 'renderer-gl' ] + }, { 'name': 'dmabuf-v4l', 'sources': [ diff --git a/clients/simple-syncobj-egl.c b/clients/simple-syncobj-egl.c new file mode 100644 index 000000000..2e059512c --- /dev/null +++ b/clients/simple-syncobj-egl.c @@ -0,0 +1,1605 @@ +/* + * Copyright © 2026 Erico Nunes + * + * based on simple-dmabuf-egl.c: + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2014,2018 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "shared/helpers.h" +#include "shared/platform.h" +#include "shared/weston-drm-fourcc.h" +#include +#include "xdg-shell-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "weston-direct-display-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" + +#include +#include +#include +#include + +#include +#include "shared/weston-egl-ext.h" + +/* Possible options that affect the displayed image */ +#define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ +#define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ +#define OPT_MANDELBROT (1 << 2) /* render mandelbrot */ +#define OPT_DIRECT_DISPLAY (1 << 3) /* direct-display */ + +#define MAX_BUFFER_PLANES 4 + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + struct zwp_linux_dmabuf_v1 *dmabuf; + struct weston_direct_display_v1 *direct_display; + struct wp_linux_drm_syncobj_manager_v1 *wl_syncobj; + uint32_t format; + bool format_supported; + uint64_t *modifiers; + int modifiers_count; + int req_dmabuf_immediate; + struct { + EGLDisplay display; + EGLContext context; + EGLConfig conf; + bool has_dma_buf_import_modifiers; + bool has_no_config_context; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + PFNEGLCREATESYNCKHRPROC create_sync; + PFNEGLDESTROYSYNCKHRPROC destroy_sync; + PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; + PFNEGLWAITSYNCKHRPROC wait_sync; + } egl; + struct { + int drm_fd; + struct gbm_device *device; + } gbm; +}; + +struct buffer_syncobj_timeline { + uint64_t timeline_point; + int fd; + uint32_t handle; +}; + +struct buffer { + struct display *display; + struct wl_buffer *buffer; + + struct gbm_bo *bo; + + int width; + int height; + int format; + uint64_t modifier; + int plane_count; + int dmabuf_fds[MAX_BUFFER_PLANES]; + uint32_t strides[MAX_BUFFER_PLANES]; + uint32_t offsets[MAX_BUFFER_PLANES]; + + EGLImageKHR egl_image; + GLuint gl_texture; + GLuint gl_fbo; + + /** Timeline to track points that must be passed before the server can start reading from the buffer */ + struct wp_linux_drm_syncobj_timeline_v1 *wl_syncobj_timeline_acquire; + struct buffer_syncobj_timeline syncobj_timeline_acquire; + + /** Timeline to track points that indicate that the server is done reading from this buffer. */ + struct wp_linux_drm_syncobj_timeline_v1 *wl_syncobj_timeline_release; + struct buffer_syncobj_timeline syncobj_timeline_release; +}; + +#define NUM_BUFFERS 4 + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct wp_linux_drm_syncobj_surface_v1 *wl_syncobj_surface; + struct buffer buffers[NUM_BUFFERS]; + struct wl_callback *callback; + bool initialized; + bool wait_for_configure; + struct { + GLuint program; + GLuint pos; + GLuint color; + GLuint offset_uniform; + GLuint reflection_uniform; + } gl; + bool render_mandelbrot; + + int next_buffer; +}; + +static sig_atomic_t running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +static void +buffer_free(struct buffer *buf) +{ + int i; + + if (buf->gl_fbo) + glDeleteFramebuffers(1, &buf->gl_fbo); + + if (buf->gl_texture) + glDeleteTextures(1, &buf->gl_texture); + + if (buf->egl_image) { + buf->display->egl.destroy_image(buf->display->egl.display, + buf->egl_image); + } + + if (buf->buffer) + wl_buffer_destroy(buf->buffer); + + if (buf->bo) + gbm_bo_destroy(buf->bo); + + for (i = 0; i < buf->plane_count; ++i) { + if (buf->dmabuf_fds[i] >= 0) + close(buf->dmabuf_fds[i]); + } +} + +static void +create_succeeded(void *data, + struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *new_buffer) +{ + struct buffer *buffer = data; + + buffer->buffer = new_buffer; + + zwp_linux_buffer_params_v1_destroy(params); +} + +static void +create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) +{ + struct buffer *buffer = data; + + buffer->buffer = NULL; + running = 0; + + zwp_linux_buffer_params_v1_destroy(params); + + fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + create_succeeded, + create_failed +}; + +static bool +create_fbo_for_buffer(struct display *display, struct buffer *buffer) +{ + static const int general_attribs = 3; + static const int plane_attribs = 5; + static const int entries_per_attrib = 2; + EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) * + entries_per_attrib + 1]; + unsigned int atti = 0; + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = buffer->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = buffer->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = buffer->format; + +#define ADD_PLANE_ATTRIBS(plane_idx) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ + attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ + attribs[atti++] = (int) buffer->offsets[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ + attribs[atti++] = (int) buffer->strides[plane_idx]; \ + if (display->egl.has_dma_buf_import_modifiers) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ + attribs[atti++] = buffer->modifier >> 32; \ + } \ + } + + if (buffer->plane_count > 0) + ADD_PLANE_ATTRIBS(0); + + if (buffer->plane_count > 1) + ADD_PLANE_ATTRIBS(1); + + if (buffer->plane_count > 2) + ADD_PLANE_ATTRIBS(2); + + if (buffer->plane_count > 3) + ADD_PLANE_ATTRIBS(3); + +#undef ADD_PLANE_ATTRIBS + + attribs[atti] = EGL_NONE; + + assert(atti < ARRAY_LENGTH(attribs)); + + buffer->egl_image = display->egl.create_image(display->egl.display, + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + NULL, attribs); + if (buffer->egl_image == EGL_NO_IMAGE_KHR) { + fprintf(stderr, "EGLImageKHR creation failed\n"); + return false; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + glGenTextures(1, &buffer->gl_texture); + glBindTexture(GL_TEXTURE_2D, buffer->gl_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); + + glGenFramebuffers(1, &buffer->gl_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, buffer->gl_texture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "FBO creation failed\n"); + return false; + } + + return true; +} + +static int +create_syncobj_timeline(struct display *display, uint32_t *handle_out) +{ + int fd; + uint32_t handle; + + if (drmSyncobjCreate(display->gbm.drm_fd, 0, &handle)) { + return -1; + } + + if (drmSyncobjHandleToFD(display->gbm.drm_fd, handle, &fd)) { + return -1; + } + + *handle_out = handle; + + return fd; +} + +static int +create_dmabuf_buffer(struct display *display, struct buffer *buffer, + int width, int height, uint32_t opts) +{ + static uint32_t flags = 0; + struct zwp_linux_buffer_params_v1 *params; + int i; + + buffer->display = display; + buffer->width = width; + buffer->height = height; + buffer->format = display->format; + + if (display->modifiers_count > 0) { +#ifdef HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 + buffer->bo = gbm_bo_create_with_modifiers2(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + display->modifiers, + display->modifiers_count, + GBM_BO_USE_RENDERING); +#else + buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + display->modifiers, + display->modifiers_count); +#endif + if (buffer->bo) + buffer->modifier = gbm_bo_get_modifier(buffer->bo); + } + + if (!buffer->bo) { + buffer->bo = gbm_bo_create(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + GBM_BO_USE_RENDERING); + buffer->modifier = DRM_FORMAT_MOD_INVALID; + } + + if (!buffer->bo) { + fprintf(stderr, "create_bo failed\n"); + goto error; + } + + buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); + for (i = 0; i < buffer->plane_count; ++i) { + int ret; + union gbm_bo_handle handle; + + handle = gbm_bo_get_handle_for_plane(buffer->bo, i); + if (handle.s32 == -1) { + fprintf(stderr, "error: failed to get gbm_bo_handle\n"); + goto error; + } + + ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0, + &buffer->dmabuf_fds[i]); + if (ret < 0 || buffer->dmabuf_fds[i] < 0) { + fprintf(stderr, "error: failed to get dmabuf_fd\n"); + goto error; + } + buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i); + buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i); + } + + params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + + if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) + weston_direct_display_v1_enable(display->direct_display, params); + + for (i = 0; i < buffer->plane_count; ++i) { + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fds[i], + i, + buffer->offsets[i], + buffer->strides[i], + buffer->modifier >> 32, + buffer->modifier & 0xffffffff); + } + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); + if (display->req_dmabuf_immediate) { + buffer->buffer = + zwp_linux_buffer_params_v1_create_immed(params, + buffer->width, + buffer->height, + buffer->format, + flags); + } + else { + zwp_linux_buffer_params_v1_create(params, + buffer->width, + buffer->height, + buffer->format, + flags); + } + + /* acquire timelines */ + buffer->syncobj_timeline_acquire.fd = create_syncobj_timeline(display, &buffer->syncobj_timeline_acquire.handle); + assert(buffer->syncobj_timeline_acquire.fd > 0); + + buffer->wl_syncobj_timeline_acquire = wp_linux_drm_syncobj_manager_v1_import_timeline(display->wl_syncobj, buffer->syncobj_timeline_acquire.fd); + assert(buffer->wl_syncobj_timeline_acquire); + + /* release timelines */ + buffer->syncobj_timeline_release.fd = create_syncobj_timeline(display, &buffer->syncobj_timeline_release.handle); + assert(buffer->syncobj_timeline_release.fd > 0); + + buffer->wl_syncobj_timeline_release = wp_linux_drm_syncobj_manager_v1_import_timeline(display->wl_syncobj, buffer->syncobj_timeline_release.fd); + assert(buffer->wl_syncobj_timeline_release); + + if (!create_fbo_for_buffer(display, buffer)) + goto error; + + return 0; + +error: + buffer_free(buffer); + return -1; +} + +static void +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->initialized && window->wait_for_configure) + redraw(window, NULL, 0); + window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static const char *vert_shader_text = + "uniform float offset;\n" + "uniform mat4 reflection;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = reflection * (pos + vec4(offset, offset, 0.0, 0.0));\n" + " v_color = color;\n" + "}\n"; + +static const char *frag_shader_text = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +static const char *vert_shader_mandelbrot_text = + "uniform float offset;\n" + "uniform mat4 reflection;\n" + "attribute vec4 pos;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " v_pos = pos.xy;\n" + " gl_Position = reflection * (pos + vec4(offset, offset, 0.0, 0.0));\n" + "}\n"; + + +/* Mandelbrot set shader using the escape time algorithm. */ +static const char *frag_shader_mandelbrot_text = + "precision mediump float;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " const int max_iteration = 500;\n" + " // Scale and translate position to get a nice mandelbrot drawing for\n" + " // the used v_pos x and y range (-0.5 to 0.5).\n" + " float x0 = 3.0 * v_pos.x - 0.5;\n" + " float y0 = 3.0 * v_pos.y;\n" + " float x = 0.0;\n" + " float y = 0.0;\n" + " int iteration = 0;\n" + " while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n" + " float xtemp = x * x - y * y + x0;\n" + " y = 2.0 * x * y + y0;\n" + " x = xtemp;\n" + " ++iteration;\n" + " }\n" + " float red = iteration == max_iteration ?\n" + " 0.0 : 1.0 - fract(float(iteration) / 20.0);\n" + " gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n" + "}\n"; + +static GLuint +create_shader(const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + assert(shader != 0); + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "Error: compiling %s: %.*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + return 0; + } + + return shader; +} + +static GLuint +create_and_link_program(GLuint vert, GLuint frag) +{ + GLint status; + GLuint program = glCreateProgram(); + + glAttachShader(program, vert); + glAttachShader(program, frag); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%.*s\n", len, log); + return 0; + } + + return program; +} + +static bool +window_set_up_gl(struct window *window) +{ + GLuint vert = create_shader( + window->render_mandelbrot ? vert_shader_mandelbrot_text : + vert_shader_text, + GL_VERTEX_SHADER); + GLuint frag = create_shader( + window->render_mandelbrot ? frag_shader_mandelbrot_text : + frag_shader_text, + GL_FRAGMENT_SHADER); + + window->gl.program = create_and_link_program(vert, frag); + + glDeleteShader(vert); + glDeleteShader(frag); + + window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); + window->gl.color = glGetAttribLocation(window->gl.program, "color"); + + glUseProgram(window->gl.program); + + window->gl.offset_uniform = + glGetUniformLocation(window->gl.program, "offset"); + window->gl.reflection_uniform = + glGetUniformLocation(window->gl.program, "reflection"); + + return window->gl.program != 0; +} + +static void +destroy_window(struct window *window) +{ + int i; + + if (window->gl.program) + glDeleteProgram(window->gl.program); + + if (window->callback) + wl_callback_destroy(window->callback); + + for (i = 0; i < NUM_BUFFERS; i++) { + if (window->buffers[i].buffer) + buffer_free(&window->buffers[i]); + } + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + if (window->wl_syncobj_surface) + wp_linux_drm_syncobj_surface_v1_destroy(window->wl_syncobj_surface); + wl_surface_destroy(window->surface); + free(window); +} + +static struct window * +create_window(struct display *display, int width, int height, int opts) +{ + struct window *window; + int i; + int ret; + + window = zalloc(sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->width = width; + window->height = height; + window->surface = wl_compositor_create_surface(display->compositor); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + + assert(window->xdg_surface); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + xdg_surface_get_toplevel(window->xdg_surface); + + assert(window->xdg_toplevel); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "simple-syncobj-egl"); + xdg_toplevel_set_app_id(window->xdg_toplevel, + "org.freedesktop.weston.simple-syncobj-egl"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + } else { + assert(0); + } + + window->wl_syncobj_surface = wp_linux_drm_syncobj_manager_v1_get_surface(window->display->wl_syncobj, window->surface); + assert(window->wl_syncobj_surface); + for (i = 0; i < NUM_BUFFERS; ++i) { + int j; + for (j = 0; j < MAX_BUFFER_PLANES; ++j) + window->buffers[i].dmabuf_fds[j] = -1; + + } + + for (i = 0; i < NUM_BUFFERS; ++i) { + ret = create_dmabuf_buffer(display, &window->buffers[i], + width, height, opts); + + if (ret < 0) + goto error; + } + + window->render_mandelbrot = opts & OPT_MANDELBROT; + + if (!window_set_up_gl(window)) + goto error; + + return window; + +error: + if (window) + destroy_window(window); + + return NULL; +} + +static bool +import_fd_to_current_point(struct buffer *buffer, int sync_fd) +{ + bool ret = false; + uint32_t tmp_syncobj; + struct display *d = buffer->display; + + if (drmSyncobjCreate(d->gbm.drm_fd, 0, &tmp_syncobj) != 0) { + return false; + } + + if (drmSyncobjImportSyncFile(d->gbm.drm_fd, tmp_syncobj, sync_fd) != 0) { + goto end; + } + + /* Import our sync_fd at the point to be signalled */ + if (drmSyncobjTransfer(d->gbm.drm_fd, + buffer->syncobj_timeline_acquire.handle, buffer->syncobj_timeline_acquire.timeline_point, + tmp_syncobj, 0, + 0) != 0) { + goto end; + } + + ret = true; + +end: + drmSyncobjDestroy(d->gbm.drm_fd, tmp_syncobj); + + return ret; +} + +static int +create_egl_fence_fd(struct window *window) +{ + struct display *d = window->display; + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + NULL); + int fd; + + assert(sync != EGL_NO_SYNC_KHR); + /* We need to flush before we can get the fence fd. */ + glFlush(); + fd = d->egl.dup_native_fence_fd(d->egl.display, sync); + assert(fd >= 0); + + d->egl.destroy_sync(d->egl.display, sync); + + return fd; +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *b = &window->buffers[window->next_buffer]; + + window->next_buffer = (window->next_buffer + 1) % NUM_BUFFERS; + + return b; +} + +static const struct wl_callback_listener frame_listener; + +/* Renders a square moving from the lower left corner to the + * upper right corner of the window. The square's vertices have + * the following colors: + * + * green +-----+ yellow + * | | + * | | + * red +-----+ blue + */ +static void +render(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + static const GLfloat verts[4][2] = { + { -0.5, -0.5 }, + { -0.5, 0.5 }, + { 0.5, -0.5 }, + { 0.5, 0.5 } + }; + static const GLfloat colors[4][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + { 1, 1, 0 } + }; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + struct weston_matrix reflection; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + weston_matrix_init(&reflection); + /* perform a reflection about x-axis to keep the same orientation of + * the vertices colors, as outlined in the comment at the beginning + * of this function. + * + * We need to render upside-down, because rendering through an FBO + * causes the bottom of the image to be written to the top pixel row of + * the buffer, y-flipping the image. + * + * Reflection is a specialized version of scaling with the + * following matrix: + * + * [1, 0, 0] + * [0, -1, 0] + * [0, 0, 1] + */ + weston_matrix_scale(&reflection, 1, -1, 1); + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + glUniformMatrix4fv(window->gl.reflection_uniform, 1, GL_FALSE, + (GLfloat *) reflection.M.colmaj); + + glClearColor(0.0,0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.color); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.color); +} + +static void +render_mandelbrot(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + /* Split drawing in a square grid consisting of grid_side * grid_side + * cells. */ + static const int grid_side = 4; + GLfloat norm_cell_side = 1.0 / grid_side; + int num_cells = grid_side * grid_side; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + int i; + struct weston_matrix reflection; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + weston_matrix_init(&reflection); + weston_matrix_scale(&reflection, 1, -1, 1); + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + glUniformMatrix4fv(window->gl.reflection_uniform, 1, GL_FALSE, + (GLfloat *) reflection.M.colmaj); + + glClearColor(0.6, 0.6, 0.6, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + for (i = 0; i < num_cells; ++i) { + /* Calculate the vertex coordinates of the current grid cell. */ + int row = i / grid_side; + int col = i % grid_side; + GLfloat left = -0.5 + norm_cell_side * col; + GLfloat right = left + norm_cell_side; + GLfloat top = 0.5 - norm_cell_side * row; + GLfloat bottom = top - norm_cell_side; + GLfloat verts[4][2] = { + { left, bottom }, + { left, top }, + { right, bottom }, + { right, top } + }; + + /* ... and draw it. */ + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(window->gl.pos); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + } +} + + +static void +wait_for_buffer_release_fence(struct buffer *buffer) +{ + struct display *d = buffer->display; + uint32_t tmp_syncobj; + int release_fence_fd; + + if (drmSyncobjCreate(d->gbm.drm_fd, 0, &tmp_syncobj) != 0) + assert(0); + + /* Wait for fence to materialize, otherwise the transfer/export below will fail */ + drmSyncobjTimelineWait(d->gbm.drm_fd, + &buffer->syncobj_timeline_release.handle, + &buffer->syncobj_timeline_release.timeline_point, + 1, INT64_MAX, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, + NULL); + + if (drmSyncobjTransfer(d->gbm.drm_fd, + tmp_syncobj, 0, + buffer->syncobj_timeline_release.handle, buffer->syncobj_timeline_release.timeline_point, + 0) != 0) { + perror("drmSyncobjTransfer"); + assert(0); + } + + if (drmSyncobjExportSyncFile(d->gbm.drm_fd, tmp_syncobj, &release_fence_fd) != 0) { + assert(0); + } + + EGLint attrib_list[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, release_fence_fd, + EGL_NONE, + }; + + /* EGLSyncKHR takes ownership of the fence fd. */ + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attrib_list); + int ret; + + assert(sync); + + if (d->egl.wait_sync) + ret = d->egl.wait_sync(d->egl.display, sync, 0); + else + ret = d->egl.client_wait_sync(d->egl.display, sync, 0, + EGL_FOREVER_KHR); + assert(ret == EGL_TRUE); + + ret = d->egl.destroy_sync(d->egl.display, sync); + assert(ret == EGL_TRUE); + + drmSyncobjDestroy(d->gbm.drm_fd, tmp_syncobj); +} + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + struct buffer *buffer; + + buffer = window_next_buffer(window); + assert(buffer); + + if (buffer->syncobj_timeline_release.timeline_point) + wait_for_buffer_release_fence(buffer); + + if (window->render_mandelbrot) + render_mandelbrot(window, buffer); + else + render(window, buffer); + + /* We will signal this acquire value ourselves when GPU work is done. */ + buffer->syncobj_timeline_acquire.timeline_point++; + + /* The compositor will signal this value when it is done with the image. */ + buffer->syncobj_timeline_release.timeline_point++; + + int fence_fd = create_egl_fence_fd(window); + + import_fd_to_current_point(buffer, fence_fd); + close(fence_fd); + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + + uint64_t acquire_point = buffer->syncobj_timeline_acquire.timeline_point; + uint64_t release_point = buffer->syncobj_timeline_release.timeline_point; + + wp_linux_drm_syncobj_surface_v1_set_acquire_point(window->wl_syncobj_surface, + buffer->wl_syncobj_timeline_acquire, + (uint32_t)(acquire_point >> 32), + (uint32_t)(acquire_point & 0xffffffff)); + wp_linux_drm_syncobj_surface_v1_set_release_point(window->wl_syncobj_surface, + buffer->wl_syncobj_timeline_release, + (uint32_t)(release_point >> 32), + (uint32_t)(release_point & 0xffffffff)); + + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + if (callback) + wl_callback_destroy(callback); + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + wl_surface_commit(window->surface); +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct display *d = data; + uint64_t modifier = u64_from_u32s(modifier_hi, modifier_lo); + + if (format != d->format) { + return; + } + + d->format_supported = true; + + if (modifier != DRM_FORMAT_MOD_INVALID) { + ++d->modifiers_count; + d->modifiers = realloc(d->modifiers, + d->modifiers_count * sizeof(*d->modifiers)); + d->modifiers[d->modifiers_count - 1] = modifier; + } +} + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) +{ + /* XXX: deprecated */ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifiers +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + if (version < 3) + return; + d->dmabuf = wl_registry_bind(registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); + } else if (strcmp(interface, "wp_linux_drm_syncobj_manager_v1") == 0) { + d->wl_syncobj = wl_registry_bind( + registry, id, + &wp_linux_drm_syncobj_manager_v1_interface, 1); + } else if (strcmp(interface, "weston_direct_display_v1") == 0) { + d->direct_display = wl_registry_bind(registry, + id, &weston_direct_display_v1_interface, 1); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +destroy_display(struct display *display) +{ + if (display->gbm.device) + gbm_device_destroy(display->gbm.device); + + if (display->gbm.drm_fd >= 0) + close(display->gbm.drm_fd); + + if (display->egl.context != EGL_NO_CONTEXT) + eglDestroyContext(display->egl.display, display->egl.context); + + if (display->egl.display != EGL_NO_DISPLAY) + eglTerminate(display->egl.display); + + free(display->modifiers); + + if (display->direct_display) + weston_direct_display_v1_destroy(display->direct_display); + + if (display->wl_syncobj) + wp_linux_drm_syncobj_manager_v1_destroy(display->wl_syncobj); + + if (display->dmabuf) + zwp_linux_dmabuf_v1_destroy(display->dmabuf); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + if (display->registry) + wl_registry_destroy(display->registry); + + if (display->display) { + wl_display_flush(display->display); + wl_display_disconnect(display->display); + } + + free(display); +} + +static bool +display_set_up_egl(struct display *display) +{ + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLint major, minor, ret, count; + const char *egl_extensions = NULL; + const char *gl_extensions = NULL; + + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + display->egl.display = + weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, + display->gbm.device, NULL); + if (display->egl.display == EGL_NO_DISPLAY) { + fprintf(stderr, "Failed to create EGLDisplay\n"); + goto error; + } + + if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { + fprintf(stderr, "Failed to initialize EGLDisplay\n"); + goto error; + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + fprintf(stderr, "Failed to bind OpenGL ES API\n"); + goto error; + } + + egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); + assert(egl_extensions != NULL); + + if (!weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import")) { + fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); + goto error; + } + + if (!weston_check_egl_extension(egl_extensions, + "EGL_KHR_surfaceless_context")) { + fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); + goto error; + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_KHR_no_config_context")) { + display->egl.has_no_config_context = true; + } + + if (display->egl.has_no_config_context) { + display->egl.conf = EGL_NO_CONFIG_KHR; + } else { + fprintf(stderr, + "Warning: EGL_KHR_no_config_context not supported\n"); + ret = eglChooseConfig(display->egl.display, config_attribs, + &display->egl.conf, 1, &count); + assert(ret && count >= 1); + } + + display->egl.context = eglCreateContext(display->egl.display, + display->egl.conf, + EGL_NO_CONTEXT, + context_attribs); + if (display->egl.context == EGL_NO_CONTEXT) { + fprintf(stderr, "Failed to create EGLContext\n"); + goto error; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + gl_extensions = (const char *) glGetString(GL_EXTENSIONS); + assert(gl_extensions != NULL); + + if (!weston_check_egl_extension(gl_extensions, + "GL_OES_EGL_image")) { + fprintf(stderr, "GL_OES_EGL_image not supported\n"); + goto error; + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + display->egl.has_dma_buf_import_modifiers = true; + display->egl.query_dma_buf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + assert(display->egl.query_dma_buf_modifiers); + } + + display->egl.create_image = + (void *) eglGetProcAddress("eglCreateImageKHR"); + assert(display->egl.create_image); + + display->egl.destroy_image = + (void *) eglGetProcAddress("eglDestroyImageKHR"); + assert(display->egl.destroy_image); + + display->egl.image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + assert(display->egl.image_target_texture_2d); + + if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && + weston_check_egl_extension(egl_extensions, + "EGL_ANDROID_native_fence_sync")) { + display->egl.create_sync = + (void *) eglGetProcAddress("eglCreateSyncKHR"); + assert(display->egl.create_sync); + + display->egl.destroy_sync = + (void *) eglGetProcAddress("eglDestroySyncKHR"); + assert(display->egl.destroy_sync); + + display->egl.client_wait_sync = + (void *) eglGetProcAddress("eglClientWaitSyncKHR"); + assert(display->egl.client_wait_sync); + + display->egl.dup_native_fence_fd = + (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); + assert(display->egl.dup_native_fence_fd); + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_KHR_wait_sync")) { + display->egl.wait_sync = + (void *) eglGetProcAddress("eglWaitSyncKHR"); + assert(display->egl.wait_sync); + } + + return true; + +error: + return false; +} + +static bool +display_update_supported_modifiers_for_egl(struct display *d) +{ + uint64_t *egl_modifiers = NULL; + int num_egl_modifiers = 0; + EGLBoolean ret; + int i; + bool try_modifiers = d->egl.has_dma_buf_import_modifiers; + + if (try_modifiers) { + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + d->format, + 0, /* max_modifiers */ + NULL, /* modifiers */ + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE) { + fprintf(stderr, "Failed to query num EGL modifiers for format\n"); + goto error; + } + } + + if (!num_egl_modifiers) + try_modifiers = false; + + /* If EGL doesn't support modifiers, don't use them at all. */ + if (!try_modifiers) { + d->modifiers_count = 0; + free(d->modifiers); + d->modifiers = NULL; + return true; + } + + egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers)); + + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + d->format, + num_egl_modifiers, + egl_modifiers, + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE) { + fprintf(stderr, "Failed to query EGL modifiers for format\n"); + goto error; + } + + /* Poor person's set intersection: d->modifiers INTERSECT + * egl_modifiers. If a modifier is not supported, replace it with + * DRM_FORMAT_MOD_INVALID in the d->modifiers array. + */ + for (i = 0; i < d->modifiers_count; ++i) { + uint64_t mod = d->modifiers[i]; + bool egl_supported = false; + int j; + + for (j = 0; j < num_egl_modifiers; ++j) { + if (egl_modifiers[j] == mod) { + egl_supported = true; + break; + } + } + + if (!egl_supported) + d->modifiers[i] = DRM_FORMAT_MOD_INVALID; + } + + free(egl_modifiers); + + return true; + +error: + free(egl_modifiers); + + return false; +} + +static bool +display_set_up_gbm(struct display *display, char const* drm_render_node) +{ + display->gbm.drm_fd = open(drm_render_node, O_RDWR); + if (display->gbm.drm_fd < 0) { + fprintf(stderr, "Failed to open drm render node %s\n", + drm_render_node); + return false; + } + + display->gbm.device = gbm_create_device(display->gbm.drm_fd); + if (display->gbm.device == NULL) { + fprintf(stderr, "Failed to create gbm device\n"); + return false; + } + + return true; +} + +static struct display * +create_display(char const *drm_render_node, uint32_t format, int opts) +{ + struct display *display = NULL; + + display = zalloc(sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + goto error; + } + + display->gbm.drm_fd = -1; + + display->display = wl_display_connect(NULL); + assert(display->display); + + display->format = format; + display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; + + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->dmabuf == NULL) { + fprintf(stderr, "No zwp_linux_dmabuf global\n"); + goto error; + } + + wl_display_roundtrip(display->display); + + if (!display->format_supported) { + fprintf(stderr, "format 0x%"PRIX32" is not available\n", + display->format); + goto error; + } + + /* GBM needs to be initialized before EGL, so that we have a valid + * render node gbm_device to create the EGL display from. */ + if (!display_set_up_gbm(display, drm_render_node)) + goto error; + + if (!display_set_up_egl(display)) + goto error; + + if (!display_update_supported_modifiers_for_egl(display)) + goto error; + + if (!display->wl_syncobj) { + fprintf(stderr, + "Error: wp_linux_drm_syncobj_manager_v1 not supported.\n"); + goto error; + } + + if (!display->egl.dup_native_fence_fd) { + fprintf(stderr, + "Error: EGL_ANDROID_native_fence_sync not supported.\n"); + goto error; + } + + if (!display->egl.wait_sync) { + fprintf(stderr, + "Warning: EGL_KHR_wait_sync not supported,\n" + " will not use server-side wait\n"); + } + + return display; + +error: + if (display != NULL) + destroy_display(display); + return NULL; +} + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +print_usage_and_exit(void) +{ + printf("usage flags:\n" + "\t'-i,--import-immediate=<>'" + "\n\t\t0 to import dmabuf via roundtrip, " + "\n\t\t1 to enable import without roundtrip\n" + "\t'-d,--drm-render-node=<>'" + "\n\t\tthe full path to the drm render node to use\n" + "\t'-s,--size=<>'" + "\n\t\tthe window size in pixels (default: 256)\n" + "\t'-f,--format=0x<>'" + "\n\t\tthe DRM format code to use\n" + "\t'-m,--mandelbrot'" + "\n\t\trender a mandelbrot set with multiple draw calls\n" + "\t'-g,--direct-display'" + "\n\t\tenables weston-direct-display extension to attempt " + "direct scan-out;\n\t\tnote this will cause the image to be " + "displayed inverted as GL uses a\n\t\tdifferent texture " + "coordinate system\n"); + exit(0); +} + +static int +is_true(const char* c) +{ + if (!strcmp(c, "1")) + return 1; + else if (!strcmp(c, "0")) + return 0; + else + print_usage_and_exit(); + + return 0; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + uint32_t format = DRM_FORMAT_XRGB8888; + int opts = 0; + char const *drm_render_node = "/dev/dri/renderD128"; + int c, option_index, ret = 0; + int window_size = 256; + + static struct option long_options[] = { + {"import-immediate", required_argument, 0, 'i' }, + {"drm-render-node", required_argument, 0, 'd' }, + {"size", required_argument, 0, 's' }, + {"format", required_argument, 0, 'f' }, + {"mandelbrot", no_argument, 0, 'm' }, + {"direct-display", no_argument, 0, 'g' }, + {"help", no_argument , 0, 'h' }, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "hi:d:s:f:mg", + long_options, &option_index)) != -1) { + switch (c) { + case 'i': + if (is_true(optarg)) + opts |= OPT_IMMEDIATE; + break; + case 'd': + drm_render_node = optarg; + break; + case 's': + window_size = strtol(optarg, NULL, 10); + break; + case 'm': + opts |= OPT_MANDELBROT; + break; + case 'g': + opts |= OPT_DIRECT_DISPLAY; + break; + case 'f': + format = strtoul(optarg, NULL, 0); + break; + default: + print_usage_and_exit(); + } + } + + display = create_display(drm_render_node, format, opts); + if (!display) + return 1; + window = create_window(display, window_size, window_size, opts); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + /* Here we retrieve the linux-dmabuf objects if executed without immed, + * or error */ + wl_display_roundtrip(display->display); + + if (!running) + return 1; + + window->initialized = true; + + if (!window->wait_for_configure) + redraw(window, NULL, 0); + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "simple-syncobj-egl exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index aa787386c..6ed8b011c 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1799,6 +1799,25 @@ struct weston_view { struct weston_log_pacer subsurface_parent_log_pacer; }; +struct weston_syncobj_release { + uint32_t ref_count; + uint32_t release_timeline_handle; + uint64_t release_point; + int fence_fd; + struct weston_renderer *renderer; +}; + +struct weston_syncobj_release_reference { + struct weston_syncobj_release *syncobj_release; +}; + +struct weston_syncobj_state { + uint64_t acquire_point; + uint32_t acquire_timeline_handle; + bool acquire_point_valid; + struct weston_syncobj_acquire_entry *acquire_entry; +}; + enum weston_surface_status { /** nothing has changed */ WESTON_SURFACE_CLEAN = 0, @@ -1876,6 +1895,10 @@ struct weston_surface_state { bool fifo_barrier; bool fifo_wait; + /* linux-drm-syncobj-v1 */ + struct weston_syncobj_state syncobj; + struct weston_syncobj_release_reference syncobj_release_ref; + /* commit_timing_v1 */ struct weston_commit_timing_target update_time; }; @@ -2076,6 +2099,11 @@ struct weston_surface { bool fifo_barrier; /* Cleared after display */ struct wl_list fifo_barrier_link; /* output::fifo_barrier_surfaces */ + /* linux-drm-syncobj-v1 */ + struct weston_syncobj_surface *syncobj_surface; + struct weston_syncobj_state syncobj; + struct weston_syncobj_release_reference syncobj_release_ref; + /** commit_timing_v1 */ struct weston_commit_timer *commit_timer; }; diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index d4ba085d0..87b273af6 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -409,6 +409,7 @@ struct drm_plane_state { struct { struct weston_buffer_reference buffer; struct weston_buffer_release_reference release; + struct weston_syncobj_release_reference syncobj_release; } fb_ref; struct weston_paint_node *paint_node; /**< maintained for drm_assign_planes only */ diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 1d2bd3901..ac55a1e69 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -70,6 +70,7 @@ #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" #include "linux-explicit-synchronization.h" +#include "syncobj.h" static const char default_seat[] = "seat0"; @@ -4822,6 +4823,8 @@ drm_backend_create(struct weston_compositor *compositor, if (linux_explicit_synchronization_setup(compositor) < 0) weston_log("Error: initializing explicit " " synchronization support failed.\n"); + if (syncobj_setup(compositor) != 0) + weston_log("Error: initializing drm syncobj support failed.\n"); } if (device->atomic_modeset) diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index dbcef7696..acb3c8a20 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -33,6 +33,7 @@ #include #include "drm-internal.h" +#include "syncobj.h" #include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" #include "shared/xalloc.h" @@ -112,6 +113,7 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) weston_buffer_reference(&state->fb_ref.buffer, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&state->fb_ref.release, NULL); + weston_syncobj_release_reference(&state->fb_ref.syncobj_release, NULL); free(state); } } @@ -172,9 +174,12 @@ drm_plane_state_duplicate(struct drm_output_state *state_output, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&dst->fb_ref.release, src->fb_ref.release.buffer_release); + weston_syncobj_release_reference(&dst->fb_ref.syncobj_release, + src->fb_ref.syncobj_release.syncobj_release); } else { assert(!src->fb_ref.buffer.buffer); assert(!src->fb_ref.release.buffer_release); + assert(!src->fb_ref.syncobj_release.syncobj_release); } dst->output_state = state_output; dst->complete = false; diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 9e4c1f19b..e859b3f68 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -37,6 +37,7 @@ #include #include "drm-internal.h" +#include "syncobj.h" #include "color.h" #include "color-representation.h" @@ -217,6 +218,8 @@ drm_output_try_paint_node_on_plane(struct drm_plane_handle *handle, BUFFER_MAY_BE_ACCESSED); weston_buffer_release_reference(&state->fb_ref.release, surface->buffer_release_ref.buffer_release); + weston_syncobj_release_reference(&state->fb_ref.syncobj_release, + surface->syncobj_release_ref.syncobj_release); return state; @@ -1138,6 +1141,8 @@ drm_output_propose_state_try_reuse(struct weston_output *output_base, BUFFER_MAY_BE_ACCESSED); weston_buffer_release_reference(&pstate->fb_ref.release, pnode->surface->buffer_release_ref.buffer_release); + weston_syncobj_release_reference(&pstate->fb_ref.syncobj_release, + pnode->surface->syncobj_release_ref.syncobj_release); } if (device->reused_state_failures > DRM_MAX_REUSE_FAILURES) { diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index d454381ae..bfce8dc27 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -39,6 +39,7 @@ #include #include "shared/helpers.h" #include "linux-explicit-synchronization.h" +#include "syncobj.h" #include "pixel-formats.h" #include "pixman-renderer.h" #include "renderer-gl/gl-renderer.h" @@ -757,6 +758,8 @@ headless_backend_create(struct weston_compositor *compositor, * testing. */ if (linux_explicit_synchronization_setup(compositor) < 0) goto err_input; + if (syncobj_setup(compositor) != 0) + weston_log("Error: initializing drm syncobj support failed.\n"); } ret = weston_plugin_api_register(compositor, diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index 6ad1d7b49..4d71e34b7 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -64,6 +64,7 @@ #include "linux-dmabuf.h" #include #include +#include "syncobj.h" #define WINDOW_TITLE "Weston Compositor" @@ -2907,6 +2908,9 @@ wayland_backend_create(struct weston_compositor *compositor, wl_event_source_check(b->parent.wl_source); + if (syncobj_setup(compositor) != 0) + weston_log("Error: initializing drm syncobj support failed.\n"); + return b; err_renderer: compositor->renderer->destroy(compositor); diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 182baf6aa..42ad56adc 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -65,6 +65,7 @@ #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" #include "linux-explicit-synchronization.h" +#include "syncobj.h" #include #include @@ -2060,6 +2061,8 @@ x11_backend_create(struct weston_compositor *compositor, if (linux_explicit_synchronization_setup(compositor) < 0) weston_log("Error: initializing explicit " " synchronization support failed.\n"); + if (syncobj_setup(compositor) != 0) + weston_log("Error: initializing drm syncobj support failed.\n"); } ret = weston_plugin_api_register(compositor, diff --git a/libweston/compositor.c b/libweston/compositor.c index c2822ad44..660275e1a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -88,6 +88,7 @@ #include "id-number-allocator.h" #include "output-capture.h" #include "pixman-renderer.h" +#include "syncobj.h" #include "renderer-gl/gl-renderer.h" #include "weston-trace.h" #include "renderer-vulkan/vulkan-renderer.h" @@ -2773,6 +2774,7 @@ weston_surface_unref(struct weston_surface *surface) weston_buffer_reference(&surface->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&surface->buffer_release_ref, NULL); + weston_syncobj_release_reference(&surface->syncobj_release_ref, NULL); pixman_region32_fini(&surface->damage); pixman_region32_fini(&surface->opaque); @@ -3440,6 +3442,8 @@ output_accumulate_damage(struct weston_output *output) BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference( &pnode->surface->buffer_release_ref, NULL); + weston_syncobj_release_reference( + &pnode->surface->syncobj_release_ref, NULL); } } } @@ -5188,6 +5192,9 @@ surface_commit(struct wl_client *client, struct wl_resource *resource) return; } + if (!weston_surface_check_pending_syncobj_valid(surface)) + return; + if (!weston_surface_check_pending_color_representation_valid(surface)) return; diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h index 65f754c25..4b72bc8fb 100644 --- a/libweston/libweston-internal.h +++ b/libweston/libweston-internal.h @@ -201,6 +201,24 @@ struct weston_renderer { uint32_t format, const uint64_t *modifiers, unsigned int count); + int (*syncobj_import_timeline)(struct weston_renderer *renderer, + int timeline_fd, + uint32_t *timeline_handle); + + int (*syncobj_timeline_set_eventfd)(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int ev_fd); + + int (*syncobj_timeline_import_syncobj)(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int sync_fd); + + int (*syncobj_timeline_signal)(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point); + enum weston_renderer_type type; const struct gl_renderer_interface *gl; const struct vulkan_renderer_interface *vulkan; diff --git a/libweston/meson.build b/libweston/meson.build index b2a8cac8d..7414d0543 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -40,6 +40,7 @@ srcs_libweston = [ 'pixman-renderer.c', 'plugin-registry.c', 'surface-state.c', + 'syncobj.c', 'timeline.c', 'touch-calibration.c', 'weston-log-wayland.c', @@ -57,6 +58,8 @@ srcs_libweston = [ fifo_v1_server_protocol_h, linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, + linux_drm_syncobj_v1_protocol_c, + linux_drm_syncobj_v1_server_protocol_h, linux_explicit_synchronization_unstable_v1_protocol_c, linux_explicit_synchronization_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index a15574f05..d6720d6fc 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -439,6 +439,7 @@ struct gl_renderer { EGLDeviceEXT egl_device; const char *drm_device; + int drm_fd; struct weston_drm_format_array supported_dmabuf_formats; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index cfc06a15b..451d0863d 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -43,6 +43,8 @@ #include #endif +#include + #include #include "linux-sync-file.h" @@ -58,6 +60,7 @@ #include "linux-explicit-synchronization.h" #include "output-capture.h" #include "pixel-formats.h" +#include "syncobj.h" #include "shared/fd-util.h" #include "shared/helpers.h" @@ -313,6 +316,7 @@ struct gl_surface_state { * rather than either buffer or surface state */ struct weston_buffer_reference buffer_ref; struct weston_buffer_release_reference buffer_release_ref; + struct weston_syncobj_release_reference syncobj_release_ref; /* Whether this surface was used in the current output repaint. Used only in the context of a gl_renderer_repaint_output call. */ @@ -2746,48 +2750,31 @@ update_buffer_release_fences(struct weston_compositor *compositor, continue; gs = get_surface_state(pnode->surface); - buffer_release = gs->buffer_release_ref.buffer_release; - if (!gs->used_in_output_repaint || !buffer_release) + if (!gs->used_in_output_repaint) continue; fence_fd = gl_renderer_create_fence_fd(output); - - /* If we have a buffer_release then it means we support fences, - * and we should be able to create the release fence. If we - * can't, something has gone horribly wrong, so disconnect the - * client. - */ - if (fence_fd == -1) { - linux_explicit_synchronization_send_server_error( - buffer_release->resource, - "Failed to create release fence"); - fd_clear(&buffer_release->fence_fd); + if (fence_fd == -1) continue; + + /* The fence is not imported to the syncobj timeline here, + * because that would cause the release fence to be signalled + * when this repaint is done with it. + * The weston buffer may still be used by the compositor later, + * so defer importing this to the syncobj timeline at the + * compositor's buffer release */ + if (gs->syncobj_release_ref.syncobj_release) { + int fd = (gs->buffer_release_ref.buffer_release) ? + dup(fence_fd) : fence_fd; + fd_update(&gs->syncobj_release_ref.syncobj_release->fence_fd, fd); } - /* At the moment it is safe to just replace the fence_fd, - * discarding the previous one: - * - * 1. If the previous fence fd represents a sync fence from - * a previous repaint cycle, that fence fd is now not - * sufficient to provide the release guarantee and should - * be replaced. - * - * 2. If the fence fd represents a sync fence from another - * output in the same repaint cycle, it's fine to replace - * it since we are rendering to all outputs using the same - * EGL context, so a fence issued for a later output rendering - * is guaranteed to signal after fences for previous output - * renderings. - * - * Note that the above is only valid if the buffer_release - * fences only originate from the GL renderer, which guarantees - * a total order of operations and fences. If we introduce - * fences from other sources (e.g., plane out-fences), we will - * need to merge fences instead. - */ - fd_update(&buffer_release->fence_fd, fence_fd); + buffer_release = gs->buffer_release_ref.buffer_release; + if (buffer_release) + fd_update(&buffer_release->fence_fd, fence_fd); + else if (!gs->syncobj_release_ref.syncobj_release) + close(fence_fd); } } @@ -3411,6 +3398,7 @@ done: weston_buffer_reference(&gs->buffer_ref, buffer, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&gs->syncobj_release_ref, NULL); } static void @@ -4394,6 +4382,8 @@ success: BUFFER_MAY_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, es->buffer_release_ref.buffer_release); + weston_syncobj_release_reference(&gs->syncobj_release_ref, + es->syncobj_release_ref.syncobj_release); return; out: @@ -4401,6 +4391,7 @@ out: weston_buffer_reference(&gs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&gs->syncobj_release_ref, NULL); } static void @@ -4553,6 +4544,7 @@ surface_state_destroy(struct gl_surface_state *gs, struct gl_renderer *gr) weston_buffer_reference(&gs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&gs->syncobj_release_ref, NULL); free(gs); } @@ -5116,6 +5108,87 @@ gl_renderer_allocator_create(struct gl_renderer *gr, return allocator; } +static int +gl_renderer_syncobj_import_timeline(struct weston_renderer *renderer, + int timeline_fd, + uint32_t *timeline_handle) +{ + struct gl_renderer *gr = + container_of(renderer, struct gl_renderer, base); + uint32_t handle; + + if (gr->drm_fd < 0) + return -1; + + if (drmSyncobjFDToHandle(gr->drm_fd, timeline_fd, &handle)) + return -1; + + *timeline_handle = handle; + return 0; +} + +static int +gl_renderer_syncobj_timeline_import_syncobj(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int sync_fd) +{ + struct gl_renderer *gr = + container_of(renderer, struct gl_renderer, base); + uint32_t tmp_syncobj; + + if (gr->drm_fd < 0) + return -1; + + if (drmSyncobjCreate(gr->drm_fd, 0, &tmp_syncobj) != 0) + return -1; + + if (drmSyncobjImportSyncFile(gr->drm_fd, tmp_syncobj, sync_fd) != 0) { + drmSyncobjDestroy(gr->drm_fd, tmp_syncobj); + return -1; + } + + if (drmSyncobjTransfer(gr->drm_fd, timeline_handle, point, + tmp_syncobj, 0, 0) != 0) { + drmSyncobjDestroy(gr->drm_fd, tmp_syncobj); + return -1; + } + + drmSyncobjDestroy(gr->drm_fd, tmp_syncobj); + return 0; +} + +static int +gl_renderer_syncobj_timeline_signal(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point) +{ + struct gl_renderer *gr = + container_of(renderer, struct gl_renderer, base); + + if (gr->drm_fd < 0) + return -1; + + return drmSyncobjTimelineSignal(gr->drm_fd, &timeline_handle, + &point, 1); +} + +static int +gl_renderer_syncobj_timeline_set_eventfd(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int ev_fd) +{ + struct gl_renderer *gr = + container_of(renderer, struct gl_renderer, base); + + if (gr->drm_fd < 0) + return -1; + + return drmSyncobjEventfd(gr->drm_fd, timeline_handle, point, + ev_fd, 0); +} + static void gl_renderer_destroy(struct weston_compositor *ec) { @@ -5164,6 +5237,10 @@ gl_renderer_destroy(struct weston_compositor *ec) weston_log_scope_destroy(gr->shader_scope); weston_log_scope_destroy(gr->extensions_scope); weston_log_scope_destroy(gr->paint_node_scope); + + if (gr->drm_fd >= 0) + close(gr->drm_fd); + free(gr); ec->renderer = NULL; } @@ -5238,10 +5315,25 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.buffer_init = gl_renderer_buffer_init; gr->base.output_set_border = gl_renderer_output_set_border; gr->base.type = WESTON_RENDERER_GL; + gr->drm_fd = -1; if (gl_renderer_setup_egl_display(gr, options->egl_native_display) < 0) goto fail; + if (gr->drm_device) { + gr->drm_fd = open(gr->drm_device, O_RDWR | O_CLOEXEC); + if (gr->drm_fd >= 0) { + gr->base.syncobj_import_timeline = + gl_renderer_syncobj_import_timeline; + gr->base.syncobj_timeline_set_eventfd = + gl_renderer_syncobj_timeline_set_eventfd; + gr->base.syncobj_timeline_import_syncobj = + gl_renderer_syncobj_timeline_import_syncobj; + gr->base.syncobj_timeline_signal = + gl_renderer_syncobj_timeline_signal; + } + } + gr->allocator = gl_renderer_allocator_create(gr, options); if (!gr->allocator) weston_log("failed to initialize allocator\n"); diff --git a/libweston/renderer-gl/meson.build b/libweston/renderer-gl/meson.build index 4614cf86f..ccca800e4 100644 --- a/libweston/renderer-gl/meson.build +++ b/libweston/renderer-gl/meson.build @@ -34,7 +34,7 @@ deps_renderer_gl = [ dep_libm, dep_pixman, dep_libweston_private, - dep_libdrm_headers, + dep_libdrm, dep_vertex_clipping ] diff --git a/libweston/renderer-vulkan/vulkan-renderer.c b/libweston/renderer-vulkan/vulkan-renderer.c index fa53e99a1..3d028e9ef 100644 --- a/libweston/renderer-vulkan/vulkan-renderer.c +++ b/libweston/renderer-vulkan/vulkan-renderer.c @@ -52,6 +52,7 @@ #include "linux-explicit-synchronization.h" #include "output-capture.h" #include "pixel-formats.h" +#include "syncobj.h" #include "shared/fd-util.h" #include "shared/helpers.h" @@ -245,6 +246,7 @@ struct vulkan_surface_state { * rather than either buffer or surface state */ struct weston_buffer_reference buffer_ref; struct weston_buffer_release_reference buffer_release_ref; + struct weston_syncobj_release_reference syncobj_release_ref; /* Whether this surface was used in the current output repaint. Used only in the context of a vulkan_renderer_repaint_output call. */ @@ -527,6 +529,7 @@ surface_state_destroy(struct vulkan_surface_state *vs, struct vulkan_renderer *v weston_buffer_reference(&vs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&vs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&vs->syncobj_release_ref, NULL); free(vs); } @@ -2015,48 +2018,31 @@ update_buffer_release_fences(struct weston_compositor *compositor, continue; vs = get_surface_state(pnode->surface); - buffer_release = vs->buffer_release_ref.buffer_release; - if (!vs->used_in_output_repaint || !buffer_release) + if (!vs->used_in_output_repaint) continue; fence_fd = vulkan_renderer_create_fence_fd(output); - - /* If we have a buffer_release then it means we support fences, - * and we should be able to create the release fence. If we - * can't, something has gone horribly wrong, so disconnect the - * client. - */ - if (fence_fd == -1) { - linux_explicit_synchronization_send_server_error( - buffer_release->resource, - "Failed to create release fence"); - fd_clear(&buffer_release->fence_fd); + if (fence_fd == -1) continue; + + /* The fence is not imported to the syncobj timeline here, + * because that would cause the release fence to be signalled + * when this repaint is done with it. + * The weston buffer may still be used by the compositor later, + * so defer importing this to the syncobj timeline at the + * compositor's buffer release */ + if (vs->syncobj_release_ref.syncobj_release) { + int fd = (vs->buffer_release_ref.buffer_release) ? + dup(fence_fd) : fence_fd; + fd_update(&vs->syncobj_release_ref.syncobj_release->fence_fd, fd); } - /* At the moment it is safe to just replace the fence_fd, - * discarding the previous one: - * - * 1. If the previous fence fd represents a sync fence from - * a previous repaint cycle, that fence fd is now not - * sufficient to provide the release guarantee and should - * be replaced. - * - * 2. If the fence fd represents a sync fence from another - * output in the same repaint cycle, it's fine to replace - * it since we are rendering to all outputs using the same - * EGL context, so a fence issued for a later output rendering - * is guaranteed to signal after fences for previous output - * renderings. - * - * Note that the above is only valid if the buffer_release - * fences only originate from the GL renderer, which guarantees - * a total order of operations and fences. If we introduce - * fences from other sources (e.g., plane out-fences), we will - * need to merge fences instead. - */ - fd_update(&buffer_release->fence_fd, fence_fd); + buffer_release = vs->buffer_release_ref.buffer_release; + if (buffer_release) + fd_update(&buffer_release->fence_fd, fence_fd); + else if (!vs->syncobj_release_ref.syncobj_release) + close(fence_fd); } } @@ -2921,6 +2907,7 @@ done: weston_buffer_reference(&vs->buffer_ref, buffer, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&vs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&vs->syncobj_release_ref, NULL); } static void @@ -3162,6 +3149,8 @@ success: BUFFER_MAY_BE_ACCESSED); weston_buffer_release_reference(&vs->buffer_release_ref, es->buffer_release_ref.buffer_release); + weston_syncobj_release_reference(&vs->syncobj_release_ref, + es->syncobj_release_ref.syncobj_release); return; out: @@ -3169,6 +3158,7 @@ out: weston_buffer_reference(&vs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&vs->buffer_release_ref, NULL); + weston_syncobj_release_reference(&vs->syncobj_release_ref, NULL); } static void @@ -3714,6 +3704,87 @@ vulkan_renderer_output_surfaceless_create(struct weston_output *output, return 0; } +static int +vulkan_renderer_syncobj_import_timeline(struct weston_renderer *renderer, + int timeline_fd, + uint32_t *timeline_handle) +{ + struct vulkan_renderer *vr = + container_of(renderer, struct vulkan_renderer, base); + uint32_t handle; + + if (vr->drm_fd < 0) + return -1; + + if (drmSyncobjFDToHandle(vr->drm_fd, timeline_fd, &handle)) + return -1; + + *timeline_handle = handle; + return 0; +} + +static int +vulkan_renderer_syncobj_timeline_import_syncobj(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int sync_fd) +{ + struct vulkan_renderer *vr = + container_of(renderer, struct vulkan_renderer, base); + uint32_t tmp_syncobj; + + if (vr->drm_fd < 0) + return -1; + + if (drmSyncobjCreate(vr->drm_fd, 0, &tmp_syncobj) != 0) + return -1; + + if (drmSyncobjImportSyncFile(vr->drm_fd, tmp_syncobj, sync_fd) != 0) { + drmSyncobjDestroy(vr->drm_fd, tmp_syncobj); + return -1; + } + + if (drmSyncobjTransfer(vr->drm_fd, timeline_handle, point, + tmp_syncobj, 0, 0) != 0) { + drmSyncobjDestroy(vr->drm_fd, tmp_syncobj); + return -1; + } + + drmSyncobjDestroy(vr->drm_fd, tmp_syncobj); + return 0; +} + +static int +vulkan_renderer_syncobj_timeline_signal(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point) +{ + struct vulkan_renderer *vr = + container_of(renderer, struct vulkan_renderer, base); + + if (vr->drm_fd < 0) + return -1; + + return drmSyncobjTimelineSignal(vr->drm_fd, &timeline_handle, + &point, 1); +} + +static int +vulkan_renderer_syncobj_timeline_set_eventfd(struct weston_renderer *renderer, + uint32_t timeline_handle, + uint64_t point, + int ev_fd) +{ + struct vulkan_renderer *vr = + container_of(renderer, struct vulkan_renderer, base); + + if (vr->drm_fd < 0) + return -1; + + return drmSyncobjEventfd(vr->drm_fd, timeline_handle, point, + ev_fd, 0); +} + static void vulkan_renderer_destroy(struct weston_compositor *ec) { @@ -4429,6 +4500,17 @@ vulkan_renderer_display_create(struct weston_compositor *ec, if (vulkan_device_has(vr, EXTENSION_EXT_PHYSICAL_DEVICE_DRM)) vr->drm_fd = open_drm_device_node(vr); + if (vr->drm_fd >= 0) { + vr->base.syncobj_import_timeline = + vulkan_renderer_syncobj_import_timeline; + vr->base.syncobj_timeline_set_eventfd = + vulkan_renderer_syncobj_timeline_set_eventfd; + vr->base.syncobj_timeline_import_syncobj = + vulkan_renderer_syncobj_timeline_import_syncobj; + vr->base.syncobj_timeline_signal = + vulkan_renderer_syncobj_timeline_signal; + } + ec->capabilities |= WESTON_CAP_ROTATION_ANY; ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; diff --git a/libweston/surface-state.c b/libweston/surface-state.c index 59643dc76..e98c70902 100644 --- a/libweston/surface-state.c +++ b/libweston/surface-state.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "libweston-internal.h" #include "backend.h" @@ -162,6 +163,9 @@ weston_surface_state_init(struct weston_surface *surface, state->fifo_barrier = false; state->fifo_wait = false; + state->syncobj.acquire_point_valid = false; + state->syncobj.acquire_entry = NULL; + state->update_time.valid = false; state->update_time.satisfied = false; state->update_time.time.tv_sec = 0; @@ -189,10 +193,13 @@ weston_surface_state_fini(struct weston_surface_state *state) fd_clear(&state->acquire_fence_fd); weston_buffer_release_reference(&state->buffer_release_ref, NULL); + weston_syncobj_release_reference(&state->syncobj_release_ref, NULL); weston_color_profile_unref(state->color_profile); state->color_profile = NULL; state->render_intent = NULL; + + weston_syncobj_state_fini(&state->syncobj); } static enum weston_surface_status @@ -523,6 +530,9 @@ weston_surface_apply_state(struct weston_surface *surface, weston_fifo_surface_set_barrier(surface); state->fifo_barrier = false; + weston_syncobj_release_move(&surface->syncobj_release_ref, + &state->syncobj_release_ref); + if (weston_surface_status_invalidates_visibility(status)) surface->output_visibility_dirty_mask |= surface->output_mask; @@ -701,6 +711,10 @@ weston_surface_state_merge_from(struct weston_surface_state *dst, dst->fifo_wait = src->fifo_wait; src->fifo_wait = false; + weston_syncobj_state_move(&dst->syncobj, &src->syncobj); + weston_syncobj_release_move(&dst->syncobj_release_ref, + &src->syncobj_release_ref); + dst->update_time = src->update_time; weston_commit_timing_clear_target(&src->update_time); @@ -835,6 +849,9 @@ weston_surface_state_ready(struct weston_surface *surface, if (!weston_commit_timing_surface_state_ready(surface, state)) return false; + if (!weston_syncobj_surface_state_ready(surface, state)) + return false; + return true; } @@ -857,6 +874,11 @@ weston_surface_commit(struct weston_surface *surface) state = &sub->cached; } + /* Set up the eventfd for the acquire fence checking readiness, + * so that the acquire point will be valid */ + if (state->syncobj.acquire_point_valid) + weston_syncobj_surface_set_acquire_point(surface, state); + /* Check if this surface is a member of a transaction list already. * If it is, we're not ready to apply this state, so we'll have * to make a new transaction and wait until we are. diff --git a/libweston/syncobj.c b/libweston/syncobj.c new file mode 100644 index 000000000..50892583b --- /dev/null +++ b/libweston/syncobj.c @@ -0,0 +1,571 @@ +/* + * Copyright 2026 Erico Nunes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include +#include "libweston-internal.h" +#include "shared/fd-util.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" + +#include "syncobj.h" + +struct weston_syncobj_timeline { + struct wl_listener timeline_destroy_listener; + int fd; + uint32_t handle; + + struct wl_signal destroy_signal; /* callback argument: this timeline */ +}; + +struct weston_syncobj_acquire_entry { + struct wl_list link; /* weston_syncobj_surface::acquire_entries */ + + int fd; + bool signalled; + struct weston_syncobj_state *owner; + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct wl_event_source *event_source; +}; + +struct weston_syncobj_surface { + struct weston_surface *surface; + struct wl_resource *resource; + struct wl_listener surface_destroy_listener; + + struct wl_list acquire_entries; +}; + +static void +syncobj_timeline_destructor(struct wl_resource *resource) +{ + struct weston_syncobj_timeline *timeline = wl_resource_get_user_data(resource); + + wl_list_remove(&timeline->timeline_destroy_listener.link); + + free(timeline); +} + +static void +syncobj_timeline_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wp_linux_drm_syncobj_timeline_v1_interface weston_syncobj_timeline_interface = { + .destroy = syncobj_timeline_destroy, +}; + +static void +syncobj_timeline_destroy_cb(struct wl_listener *listener, void *data) +{ + struct weston_syncobj_timeline *timeline = + container_of(listener, + struct weston_syncobj_timeline, timeline_destroy_listener); + + timeline->fd = -1; + timeline->handle = 0; +} + +static void +syncobj_manager_import_timeline(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t fd) +{ + struct weston_syncobj_timeline *timeline; + uint32_t handle; + + struct wl_resource *res; + struct weston_compositor *compositor = wl_resource_get_user_data(resource); + + res = wl_resource_create(client, &wp_linux_drm_syncobj_timeline_v1_interface, + wl_resource_get_version(resource), id); + timeline = xzalloc(sizeof *timeline); + timeline->fd = fd; + timeline->timeline_destroy_listener.notify = syncobj_timeline_destroy_cb; + wl_signal_init(&timeline->destroy_signal); + wl_signal_add(&timeline->destroy_signal, &timeline->timeline_destroy_listener); + wl_resource_set_implementation(res, &weston_syncobj_timeline_interface, timeline, + syncobj_timeline_destructor); + + struct weston_renderer *renderer = compositor->renderer; + + assert(renderer && renderer->syncobj_import_timeline); + + if (renderer->syncobj_import_timeline(renderer, fd, &handle) != 0) + return; + + timeline->handle = handle; +} + +static void +acquire_entry_surface_destroy_cb(struct wl_listener *listener, void *data) +{ + struct weston_syncobj_acquire_entry *entry = + container_of(listener, + struct weston_syncobj_acquire_entry, + surface_destroy_listener); + + entry->surface = NULL; +} + +static void +weston_syncobj_surface_cancel_entries(struct weston_syncobj_surface *syncobj_surface) +{ + struct weston_syncobj_acquire_entry *entry, *tmp; + + wl_list_for_each_safe(entry, tmp, &syncobj_surface->acquire_entries, link) { + wl_event_source_remove(entry->event_source); + close(entry->fd); + wl_list_remove(&entry->link); + if (entry->surface) + wl_list_remove(&entry->surface_destroy_listener.link); + if (entry->owner) + entry->owner->acquire_entry = NULL; + free(entry); + } +} + +static void +syncobj_surface_destructor(struct wl_resource *resource) +{ + struct weston_syncobj_surface *syncobj_surface = wl_resource_get_user_data(resource); + + weston_syncobj_surface_cancel_entries(syncobj_surface); + + if (syncobj_surface->surface) { + wl_list_remove(&syncobj_surface->surface_destroy_listener.link); + syncobj_surface->surface->syncobj_surface = NULL; + } + + free(syncobj_surface); +} + +static void +syncobj_surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +syncobj_surface_set_acquire_point(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *timeline, + uint32_t point_hi, + uint32_t point_lo) +{ + struct weston_syncobj_surface *syncobj_surface = wl_resource_get_user_data(resource); + struct weston_syncobj_timeline *syncobj_timeline = wl_resource_get_user_data(timeline); + struct weston_surface *surface = syncobj_surface->surface; + uint64_t point; + + if (!surface) { + wl_resource_post_error(resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + return; + } + + point = ((uint64_t)point_hi << 32ul) | (uint64_t)(point_lo & 0xffffffff); + + surface->pending.syncobj.acquire_point_valid = true; + surface->pending.syncobj.acquire_timeline_handle = syncobj_timeline->handle; + surface->pending.syncobj.acquire_point = point; +} + +static void +syncobj_surface_set_release_point(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *timeline, + uint32_t point_hi, + uint32_t point_lo) +{ + struct weston_syncobj_surface *syncobj_surface = wl_resource_get_user_data(resource); + struct weston_syncobj_timeline *syncobj_timeline = wl_resource_get_user_data(timeline); + struct weston_surface *surface = syncobj_surface->surface; + struct weston_syncobj_release *release; + uint64_t point; + + if (!surface) { + wl_resource_post_error(resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + return; + } + + point = ((uint64_t)point_hi << 32ul) | (uint64_t)(point_lo & 0xffffffff); + + release = xzalloc(sizeof *release); + release->ref_count = 0; + release->release_timeline_handle = syncobj_timeline->handle; + release->release_point = point; + release->fence_fd = -1; + release->renderer = surface->compositor->renderer; + + weston_syncobj_release_reference(&surface->pending.syncobj_release_ref, release); +} + +static const struct wp_linux_drm_syncobj_surface_v1_interface weston_syncobj_surface_interface = { + .set_acquire_point = syncobj_surface_set_acquire_point, + .set_release_point = syncobj_surface_set_release_point, + .destroy = syncobj_surface_destroy, +}; + +static void +syncobj_surface_destroy_cb(struct wl_listener *listener, void *data) +{ + struct weston_syncobj_surface *syncobj_surface = + container_of(listener, + struct weston_syncobj_surface, surface_destroy_listener); + + weston_syncobj_surface_cancel_entries(syncobj_surface); + syncobj_surface->surface = NULL; +} + +static void +syncobj_manager_get_surface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_syncobj_surface *syncobj_surface; + struct weston_surface *surface = wl_resource_get_user_data(surface_resource); + struct wl_resource *res; + + if (surface->syncobj_surface) { + wl_resource_post_error(resource, + WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, + "the surface already has a synchronization object associated"); + return; + } + + res = wl_resource_create(client, &wp_linux_drm_syncobj_surface_v1_interface, + wl_resource_get_version(resource), id); + syncobj_surface = xzalloc(sizeof *syncobj_surface); + syncobj_surface->surface = surface; + syncobj_surface->resource = res; + wl_list_init(&syncobj_surface->acquire_entries); + syncobj_surface->surface_destroy_listener.notify = syncobj_surface_destroy_cb; + wl_signal_add(&surface->destroy_signal, &syncobj_surface->surface_destroy_listener); + wl_resource_set_implementation(res, &weston_syncobj_surface_interface, syncobj_surface, + syncobj_surface_destructor); + surface->syncobj_surface = syncobj_surface; +} + +static void +syncobj_manager_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wp_linux_drm_syncobj_manager_v1_interface syncobj_manager_interface_v1 = { + .destroy = syncobj_manager_destroy, + .get_surface = syncobj_manager_get_surface, + .import_timeline = syncobj_manager_import_timeline, +}; + +static void +bind_syncobj_manager(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + struct weston_compositor *compositor = data; + + resource = wl_resource_create(client, + &wp_linux_drm_syncobj_manager_v1_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &syncobj_manager_interface_v1, + compositor, NULL); +} + +bool +weston_surface_check_pending_syncobj_valid(struct weston_surface *surface) +{ + struct weston_syncobj_surface *syncobj_surface = surface->syncobj_surface; + struct weston_surface_state *pend = &surface->pending; + bool has_acquire = pend->syncobj.acquire_point_valid; + bool has_release = pend->syncobj_release_ref.syncobj_release != NULL; + struct weston_syncobj_release *release; + struct weston_buffer *buffer = NULL; + + if (!syncobj_surface) + return true; + + if (pend->status & WESTON_SURFACE_DIRTY_BUFFER) + buffer = pend->buffer_ref.buffer; + + if (has_acquire && !buffer) { + wl_resource_post_error(syncobj_surface->resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, + "acquire point set but no buffer attached"); + return false; + } + + if (has_release && !buffer) { + wl_resource_post_error(syncobj_surface->resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, + "release point set but no buffer attached"); + return false; + } + + if (buffer && !has_acquire) { + wl_resource_post_error(syncobj_surface->resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT, + "buffer attached but no acquire point set"); + return false; + } + + if (buffer && !has_release) { + wl_resource_post_error(syncobj_surface->resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT, + "buffer attached but no release point set"); + return false; + } + + if (has_acquire && has_release) { + release = pend->syncobj_release_ref.syncobj_release; + + if (pend->syncobj.acquire_timeline_handle == + release->release_timeline_handle && + pend->syncobj.acquire_point >= release->release_point) { + wl_resource_post_error(syncobj_surface->resource, + WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, + "acquire point must be less than release point on same timeline"); + return false; + } + } + + return true; +} + +/** Advertise DRM synchronization object protocol support + * + * \param compositor The compositor to init for. + * \return Zero on success, -1 on failure. + */ +WL_EXPORT int +syncobj_setup(struct weston_compositor *compositor) +{ + if (!compositor->renderer || + !compositor->renderer->syncobj_import_timeline || + !compositor->renderer->syncobj_timeline_set_eventfd) + return -1; + + if (!wl_global_create(compositor->wl_display, + &wp_linux_drm_syncobj_manager_v1_interface, + 1, compositor, + bind_syncobj_manager)) + return -1; + + return 0; +} + +bool +weston_syncobj_surface_state_ready(struct weston_surface *surface, + struct weston_surface_state *state) +{ + if (!state->syncobj.acquire_point_valid) + return true; + if (!state->syncobj.acquire_entry) + return true; + return state->syncobj.acquire_entry->signalled; +} + +static int +acquire_point_eventfd_func(int fd, uint32_t mask, void *data) +{ + struct weston_syncobj_acquire_entry *entry = data; + uint64_t value; + + read(fd, &value, sizeof(value)); + + entry->signalled = true; + + wl_event_source_remove(entry->event_source); + close(entry->fd); + entry->fd = -1; + entry->event_source = NULL; + wl_list_remove(&entry->link); + wl_list_init(&entry->link); + + /* The eventfd may signal after all repaints triggered elsewhere, so + * a repaint must be scheduled here to ensure that the surface will + * actually be updated after the eventfd signals. */ + weston_compositor_schedule_repaint(entry->surface->compositor); + + if (entry->surface) + wl_list_remove(&entry->surface_destroy_listener.link); + entry->surface = NULL; + + return 0; +} + +void +weston_syncobj_surface_set_acquire_point(struct weston_surface *surface, + struct weston_surface_state *state) +{ + struct weston_renderer *renderer = surface->compositor->renderer; + struct weston_syncobj_surface *syncobj_surface = surface->syncobj_surface; + struct weston_syncobj_acquire_entry *entry; + struct wl_event_loop *loop; + int sync_fd; + + assert(renderer && renderer->syncobj_timeline_set_eventfd); + + sync_fd = eventfd(0, EFD_CLOEXEC); + assert(sync_fd >= 0); + + if (renderer->syncobj_timeline_set_eventfd(renderer, + state->syncobj.acquire_timeline_handle, + state->syncobj.acquire_point, + sync_fd) != 0) { + close(sync_fd); + assert(0); + return; + } + + /* The eventfd creation triggers on a client's commit, to ensure that + * there would be time for it to signal and the eventfd be handled + * between the commit and a repaint. + * Since it is possible for a client to submit multiple commits before + * a repaint and an eventfd may already have been added, they need to + * be tracked separately. */ + entry = xzalloc(sizeof *entry); + entry->fd = sync_fd; + entry->signalled = false; + entry->surface = surface; + entry->surface_destroy_listener.notify = acquire_entry_surface_destroy_cb; + wl_signal_add(&surface->destroy_signal, &entry->surface_destroy_listener); + entry->owner = &state->syncobj; + wl_list_insert(&syncobj_surface->acquire_entries, &entry->link); + + loop = wl_display_get_event_loop(surface->compositor->wl_display); + entry->event_source = wl_event_loop_add_fd(loop, sync_fd, + WL_EVENT_READABLE, + acquire_point_eventfd_func, + entry); + + state->syncobj.acquire_entry = entry; +} + +void +weston_syncobj_state_fini(struct weston_syncobj_state *syncobj) +{ + struct weston_syncobj_acquire_entry *entry = syncobj->acquire_entry; + + if (!entry) + return; + + if (entry->event_source) { + wl_event_source_remove(entry->event_source); + close(entry->fd); + wl_list_remove(&entry->link); + } + + if (entry->surface) + wl_list_remove(&entry->surface_destroy_listener.link); + + entry->owner = NULL; + free(entry); + syncobj->acquire_entry = NULL; +} + +void +weston_syncobj_state_move(struct weston_syncobj_state *dst, + struct weston_syncobj_state *src) +{ + *dst = *src; + if (dst->acquire_entry) + dst->acquire_entry->owner = dst; + src->acquire_point_valid = false; + src->acquire_entry = NULL; +} + +static void +weston_syncobj_release_destroy(struct weston_syncobj_release *syncobj_release) +{ + struct weston_renderer *r = syncobj_release->renderer; + + assert(r); + + /* At this time the compositor no longer uses the buffer, but use the + * fence_fd if available as a way to guarantee that no GPU rendering + * job is still pending */ + if (syncobj_release->fence_fd >= 0) { + assert(r->syncobj_timeline_import_syncobj); + r->syncobj_timeline_import_syncobj(r, syncobj_release->release_timeline_handle, + syncobj_release->release_point, + syncobj_release->fence_fd); + close(syncobj_release->fence_fd); + } else { + assert(r->syncobj_timeline_signal); + r->syncobj_timeline_signal(r, syncobj_release->release_timeline_handle, + syncobj_release->release_point); + } + free(syncobj_release); +} + +WL_EXPORT void +weston_syncobj_release_reference(struct weston_syncobj_release_reference *ref, + struct weston_syncobj_release *syncobj_release) +{ + if (syncobj_release == ref->syncobj_release) + return; + + if (ref->syncobj_release) { + ref->syncobj_release->ref_count--; + if (ref->syncobj_release->ref_count == 0) + weston_syncobj_release_destroy(ref->syncobj_release); + } + + if (syncobj_release) + syncobj_release->ref_count++; + + ref->syncobj_release = syncobj_release; +} + +WL_EXPORT void +weston_syncobj_release_move(struct weston_syncobj_release_reference *dest, + struct weston_syncobj_release_reference *src) +{ + weston_syncobj_release_reference(dest, src->syncobj_release); + weston_syncobj_release_reference(src, NULL); +} + diff --git a/libweston/syncobj.h b/libweston/syncobj.h new file mode 100644 index 000000000..d01d0ea31 --- /dev/null +++ b/libweston/syncobj.h @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Erico Nunes + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include "linux-drm-syncobj-v1-server-protocol.h" + +int +syncobj_setup(struct weston_compositor *compositor); + +bool +weston_surface_check_pending_syncobj_valid(struct weston_surface *surface); + +bool +weston_syncobj_surface_state_ready(struct weston_surface *surface, + struct weston_surface_state *state); + +void +weston_syncobj_surface_set_acquire_point(struct weston_surface *surface, + struct weston_surface_state *state); + +void +weston_syncobj_state_fini(struct weston_syncobj_state *syncobj); + +void +weston_syncobj_state_move(struct weston_syncobj_state *dst, + struct weston_syncobj_state *src); + +void +weston_syncobj_release_reference(struct weston_syncobj_release_reference *ref, + struct weston_syncobj_release *syncobj_release); + +void +weston_syncobj_release_move(struct weston_syncobj_release_reference *dest, + struct weston_syncobj_release_reference *src); diff --git a/protocol/meson.build b/protocol/meson.build index 589cb8fa7..8ea2a4501 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,6 +27,7 @@ generated_protocols = [ [ 'ivi-hmi-controller', 'internal' ], [ 'linux-dmabuf', 'unstable', 'v1' ], [ 'linux-explicit-synchronization', 'unstable', 'v1' ], + [ 'linux-drm-syncobj', 'staging', 'v1' ], [ 'presentation-time', 'stable' ], [ 'pointer-constraints', 'unstable', 'v1' ], [ 'relative-pointer', 'unstable', 'v1' ],