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' ],