File mutter-implement-linux-drm-syncobj-v1.patch of Package mutter

diff --git a/cogl/cogl/cogl-context-private.h b/cogl/cogl/cogl-context-private.h
index 502dda72b..dca006653 100644
--- a/cogl/cogl/cogl-context-private.h
+++ b/cogl/cogl/cogl-context-private.h
@@ -311,3 +311,6 @@ _cogl_context_set_current_projection_entry (CoglContext *context,
 void
 _cogl_context_set_current_modelview_entry (CoglContext *context,
                                            CoglMatrixEntry *entry);
+
+void
+_cogl_context_update_sync (CoglContext *context);
diff --git a/cogl/cogl/cogl-context.c b/cogl/cogl/cogl-context.c
index f7d276825..7df82a93f 100644
--- a/cogl/cogl/cogl-context.c
+++ b/cogl/cogl/cogl-context.c
@@ -458,6 +458,28 @@ _cogl_context_set_current_modelview_entry (CoglContext *context,
   context->current_modelview_entry = entry;
 }
 
+void
+_cogl_context_update_sync (CoglContext *context)
+{
+  const CoglWinsysVtable *winsys = _cogl_context_get_winsys (context);
+
+  if (!winsys->update_sync)
+    return;
+
+  winsys->update_sync (context);
+}
+
+int
+cogl_context_get_latest_sync_fd (CoglContext *context)
+{
+  const CoglWinsysVtable *winsys = _cogl_context_get_winsys (context);
+
+  if (!winsys->get_sync_fd)
+    return -1;
+
+  return winsys->get_sync_fd (context);
+}
+
 CoglGraphicsResetStatus
 cogl_get_graphics_reset_status (CoglContext *context)
 {
diff --git a/cogl/cogl/cogl-context.h b/cogl/cogl/cogl-context.h
index 8ad691420..847da576a 100644
--- a/cogl/cogl/cogl-context.h
+++ b/cogl/cogl/cogl-context.h
@@ -368,4 +368,18 @@ cogl_context_timestamp_query_get_time_ns (CoglContext        *context,
 COGL_EXPORT int64_t
 cogl_context_get_gpu_time_ns (CoglContext *context);
 
+/**
+ * cogl_context_get_latest_sync_fd
+ * @context: a #CoglContext pointer
+ *
+ * This function is used to get support for waiting on previous
+ * GPU work through sync fds. It will return a sync fd which will
+ * signal when the previous work has completed.
+ *
+ * Return value: sync fd for latest GPU submission if available,
+ * returns -1 if not.
+ */
+COGL_EXPORT int
+cogl_context_get_latest_sync_fd (CoglContext *context);
+
 G_END_DECLS
diff --git a/cogl/cogl/winsys/cogl-onscreen-egl.c b/cogl/cogl/winsys/cogl-onscreen-egl.c
index 49514ff23..e7cea0443 100644
--- a/cogl/cogl/winsys/cogl-onscreen-egl.c
+++ b/cogl/cogl/winsys/cogl-onscreen-egl.c
@@ -309,6 +309,9 @@ cogl_onscreen_egl_swap_buffers_with_damage (CoglOnscreen  *onscreen,
                                         COGL_FRAMEBUFFER (onscreen),
                                         COGL_FRAMEBUFFER_STATE_BIND);
 
+  /* Update our "latest" sync fd to contain all previous work */
+  _cogl_context_update_sync (context);
+
   if (cogl_has_feature (context, COGL_FEATURE_ID_TIMESTAMP_QUERY))
     {
       info->gpu_time_before_buffer_swap_ns =
diff --git a/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h b/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h
index 494d5ea39..105055c1a 100644
--- a/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h
+++ b/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h
@@ -156,6 +156,15 @@ COGL_WINSYS_FEATURE_FUNCTION (EGLBoolean, eglDestroySync,
                               (EGLDisplay dpy,
                                EGLSyncKHR sync))
 COGL_WINSYS_FEATURE_END ()
+
+COGL_WINSYS_FEATURE_BEGIN (native_fence_sync,
+                           "ANDROID\0",
+                           "native_fence_sync\0",
+                           COGL_EGL_WINSYS_FEATURE_NATIVE_FENCE_SYNC)
+COGL_WINSYS_FEATURE_FUNCTION (EGLint, eglDupNativeFenceFD,
+                              (EGLDisplay dpy,
+                               EGLSyncKHR sync))
+COGL_WINSYS_FEATURE_END ()
 #endif
 
 COGL_WINSYS_FEATURE_BEGIN (surfaceless_context,
diff --git a/cogl/cogl/winsys/cogl-winsys-egl-private.h b/cogl/cogl/winsys/cogl-winsys-egl-private.h
index c1136c6d8..39b37d8a5 100644
--- a/cogl/cogl/winsys/cogl-winsys-egl-private.h
+++ b/cogl/cogl/winsys/cogl-winsys-egl-private.h
@@ -101,6 +101,7 @@ typedef enum _CoglEGLWinsysFeature
   COGL_EGL_WINSYS_FEATURE_FENCE_SYNC                    =1L<<5,
   COGL_EGL_WINSYS_FEATURE_SURFACELESS_CONTEXT           =1L<<6,
   COGL_EGL_WINSYS_FEATURE_CONTEXT_PRIORITY              =1L<<7,
+  COGL_EGL_WINSYS_FEATURE_NATIVE_FENCE_SYNC             =1L<<9,
 } CoglEGLWinsysFeature;
 
 typedef struct _CoglRendererEGL
@@ -119,6 +120,9 @@ typedef struct _CoglRendererEGL
   /* vtable for platform specific parts */
   const CoglWinsysEGLVtable *platform_vtable;
 
+  /* Sync for latest submitted work */
+  EGLSyncKHR sync;
+
   /* Function pointers for EGL specific extensions */
 #define COGL_WINSYS_FEATURE_BEGIN(a, b, c, d)
 
diff --git a/cogl/cogl/winsys/cogl-winsys-egl-x11.c b/cogl/cogl/winsys/cogl-winsys-egl-x11.c
index af4df1814..c3fce8f92 100644
--- a/cogl/cogl/winsys/cogl-winsys-egl-x11.c
+++ b/cogl/cogl/winsys/cogl-winsys-egl-x11.c
@@ -243,6 +243,7 @@ _cogl_winsys_renderer_connect (CoglRenderer *renderer,
   xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
 
   egl_renderer->platform_vtable = &_cogl_winsys_egl_vtable;
+  egl_renderer->sync = EGL_NO_SYNC_KHR;
 
   if (!_cogl_xlib_renderer_connect (renderer, error))
     goto error;
diff --git a/cogl/cogl/winsys/cogl-winsys-egl.c b/cogl/cogl/winsys/cogl-winsys-egl.c
index 28aa0a268..89a432a6f 100644
--- a/cogl/cogl/winsys/cogl-winsys-egl.c
+++ b/cogl/cogl/winsys/cogl-winsys-egl.c
@@ -482,6 +482,9 @@ _cogl_winsys_display_destroy (CoglDisplay *display)
 
   g_return_if_fail (egl_display != NULL);
 
+  if (egl_renderer->sync != EGL_NO_SYNC_KHR)
+    egl_renderer->pf_eglDestroySync (egl_renderer->edpy, egl_renderer->sync);
+
   cleanup_context (display);
 
   if (egl_renderer->platform_vtable->display_destroy)
@@ -614,6 +617,37 @@ _cogl_winsys_fence_destroy (CoglContext *context, void *fence)
 
   renderer->pf_eglDestroySync (renderer->edpy, fence);
 }
+
+static int
+_cogl_winsys_get_sync_fd (CoglContext *context)
+{
+  CoglRendererEGL *renderer = context->display->renderer->winsys;
+  int fd;
+
+  if (!renderer->pf_eglDupNativeFenceFD)
+    return -1;
+
+  fd = renderer->pf_eglDupNativeFenceFD (renderer->edpy, renderer->sync);
+  if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID)
+    return -1;
+
+  return fd;
+}
+
+static void
+_cogl_winsys_update_sync (CoglContext *context)
+{
+  CoglRendererEGL *renderer = context->display->renderer->winsys;
+
+  if (!renderer->pf_eglDestroySync || !renderer->pf_eglCreateSync)
+    return;
+
+  if (renderer->sync != EGL_NO_SYNC_KHR)
+    renderer->pf_eglDestroySync (renderer->edpy, renderer->sync);
+
+  renderer->sync = renderer->pf_eglCreateSync (renderer->edpy,
+        EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
+}
 #endif
 
 static CoglWinsysVtable _cogl_winsys_vtable =
@@ -636,6 +670,8 @@ static CoglWinsysVtable _cogl_winsys_vtable =
     .fence_add = _cogl_winsys_fence_add,
     .fence_is_complete = _cogl_winsys_fence_is_complete,
     .fence_destroy = _cogl_winsys_fence_destroy,
+    .get_sync_fd = _cogl_winsys_get_sync_fd,
+    .update_sync = _cogl_winsys_update_sync,
 #endif
   };
 
diff --git a/cogl/cogl/winsys/cogl-winsys-private.h b/cogl/cogl/winsys/cogl-winsys-private.h
index d414cada0..e40c6b1b4 100644
--- a/cogl/cogl/winsys/cogl-winsys-private.h
+++ b/cogl/cogl/winsys/cogl-winsys-private.h
@@ -144,6 +144,12 @@ typedef struct _CoglWinsysVtable
   (*fence_destroy) (CoglContext *ctx,
                     void        *fence);
 
+  void
+  (*update_sync) (CoglContext *ctx);
+
+  int
+  (*get_sync_fd) (CoglContext *ctx);
+
 } CoglWinsysVtable;
 
 typedef const CoglWinsysVtable *(*CoglWinsysVtableGetter) (void);
diff --git a/config.h.meson b/config.h.meson
index d319aa344..517f20837 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -115,3 +115,6 @@
 
 /* Whether the Xwayland has -enable-ei-portal option */
 #mesondefine HAVE_XWAYLAND_ENABLE_EI_PORTAL
+
+/* Supports eventfd */
+#mesondefine HAVE_EVENTFD
diff --git a/meson.build b/meson.build
index 5530cebe7..ed131266a 100644
--- a/meson.build
+++ b/meson.build
@@ -516,6 +516,8 @@ endif
 
 cc.compiles('void main (void) { __builtin_ffsl (0); __builtin_popcountl (0); }')
 
+have_eventfd = cc.has_header('sys/eventfd.h')
+
 cdata = configuration_data()
 cdata.set_quoted('GETTEXT_PACKAGE', gettext_package)
 cdata.set_quoted('VERSION', meson.project_version())
@@ -541,6 +543,7 @@ cdata.set('HAVE_STARTUP_NOTIFICATION', have_startup_notification)
 cdata.set('HAVE_INTROSPECTION', have_introspection)
 cdata.set('HAVE_PROFILER', have_profiler)
 cdata.set('HAVE_LIBDISPLAY_INFO', have_libdisplay_info)
+cdata.set('HAVE_EVENTFD', have_eventfd)
 
 if have_x11_client
   xkb_base = xkeyboard_config_dep.get_variable('xkb_base')
diff --git a/src/meson.build b/src/meson.build
index ca2ef166c..d4bfb143a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -589,6 +589,8 @@ if have_wayland
     'core/meta-service-channel.h',
     'wayland/meta-cursor-sprite-wayland.c',
     'wayland/meta-cursor-sprite-wayland.h',
+    'wayland/meta-drm-timeline.c',
+    'wayland/meta-drm-timeline.h',
     'wayland/meta-pointer-confinement-wayland.c',
     'wayland/meta-pointer-confinement-wayland.h',
     'wayland/meta-pointer-lock-wayland.c',
@@ -641,6 +643,8 @@ if have_wayland
     'wayland/meta-wayland-keyboard.h',
     'wayland/meta-wayland-legacy-xdg-foreign.c',
     'wayland/meta-wayland-legacy-xdg-foreign.h',
+    'wayland/meta-wayland-linux-drm-syncobj.c',
+    'wayland/meta-wayland-linux-drm-syncobj.h',
     'wayland/meta-wayland-outputs.c',
     'wayland/meta-wayland-outputs.h',
     'wayland/meta-wayland-pointer.c',
@@ -1080,6 +1084,7 @@ if have_wayland
     ['xdg-output', 'unstable', 'v1', ],
     ['xdg-shell', 'stable', ],
     ['xwayland-keyboard-grab', 'unstable', 'v1', ],
+    ['linux-drm-syncobj-v1', 'private', ],
   ]
   if have_wayland_eglstream
     wayland_eglstream_protocols_dir = wayland_eglstream_protocols_dep.get_variable('pkgdatadir')
diff --git a/src/wayland/meta-drm-timeline.c b/src/wayland/meta-drm-timeline.c
new file mode 100644
index 000000000..6d86fd19f
--- /dev/null
+++ b/src/wayland/meta-drm-timeline.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Austin Shafer <ashafer@nvidia.com>
+ */
+
+/**
+ * MetaDrmTimeline
+ *
+ * MetaDrmTimeline is a helper for handling DRM syncobj operations. It
+ * can import DRM syncobjs and export eventfds at a particular point.
+ *
+ * This is heavily inspired by wlroot's wlr_render_timeline, written by
+ * Simon Ser.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <xf86drm.h>
+#include <glib/gstdio.h>
+#ifdef HAVE_EVENTFD
+#include <sys/eventfd.h>
+#endif
+
+#include "meta/util.h"
+#include "wayland/meta-drm-timeline.h"
+
+enum
+{
+  PROP_0,
+
+  PROP_DRM_FD,
+  PROP_SYNCOBJ_FD,
+
+  N_PROPS
+};
+
+typedef struct _MetaDrmTimeline
+{
+  GObject parent;
+
+  int drm;
+  int drm_syncobj_fd;
+  uint32_t drm_syncobj;
+} MetaDrmTimeline;
+
+static GParamSpec *obj_props[N_PROPS];
+
+static void initable_iface_init (GInitableIface *initable_iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (MetaDrmTimeline, meta_drm_timeline, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                      initable_iface_init))
+
+static void
+meta_drm_timeline_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  MetaDrmTimeline *timeline = META_DRM_TIMELINE (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRM_FD:
+      g_value_set_int (value, timeline->drm);
+      break;
+    case PROP_SYNCOBJ_FD:
+      g_value_set_int (value, timeline->drm_syncobj_fd);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+meta_drm_timeline_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  MetaDrmTimeline *timeline = META_DRM_TIMELINE (object);
+  int fd;
+
+  switch (prop_id)
+    {
+    case PROP_DRM_FD:
+      fd = g_value_get_int (value);
+      timeline->drm = fcntl (fd, F_DUPFD_CLOEXEC, 0);
+      break;
+    case PROP_SYNCOBJ_FD:
+      fd = g_value_get_int (value);
+      timeline->drm_syncobj_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static gboolean
+meta_drm_timeline_initable_init (GInitable     *initable,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+  MetaDrmTimeline *timeline = META_DRM_TIMELINE (initable);
+
+  if (drmSyncobjFDToHandle (timeline->drm,
+                            timeline->drm_syncobj_fd,
+                            &timeline->drm_syncobj) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Failed to import DRM syncobj");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = meta_drm_timeline_initable_init;
+}
+
+MetaDrmTimeline *
+meta_drm_timeline_import_syncobj (int       fd,
+                                  int       drm_syncobj,
+                                  GError  **error)
+{
+  MetaDrmTimeline *timeline = g_initable_new (META_TYPE_DRM_TIMELINE,
+                                              NULL, error,
+                                              "drm-fd", fd,
+                                              "syncobj-fd", drm_syncobj,
+                                              NULL);
+
+  return timeline;
+}
+
+int
+meta_drm_timeline_get_eventfd (MetaDrmTimeline *timeline,
+                               uint64_t         sync_point,
+                               GError         **error)
+{
+  g_autofd int fd = -1;
+
+#ifdef HAVE_EVENTFD
+  fd = eventfd (0, EFD_CLOEXEC);
+  if (fd < 0)
+    return -1;
+
+  if (drmSyncobjEventfd (timeline->drm, timeline->drm_syncobj,
+                         sync_point, fd, 0) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_SUPPORTED,
+                   "DRM_IOCTL_SYNCOBJ_EVENTFD: Failed to export eventfd");
+      return -1;
+    }
+#endif
+
+  return g_steal_fd (&fd);
+}
+
+gboolean
+meta_drm_timeline_set_sync_point (MetaDrmTimeline *timeline,
+                                  uint64_t         sync_point,
+                                  int              sync_fd,
+                                  GError         **error)
+{
+  uint32_t tmp;
+
+  /* Import our syncfd at a new release point */
+  if (drmSyncobjCreate (timeline->drm, 0, &tmp) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_SUPPORTED,
+                   "Failed to create temporary syncobj");
+      return FALSE;
+    }
+
+  if (drmSyncobjImportSyncFile (timeline->drm, tmp, sync_fd) != 0)
+    goto end;
+
+  if (drmSyncobjTransfer (timeline->drm, timeline->drm_syncobj,
+                          sync_point, tmp, 0, 0) != 0)
+    goto end;
+
+  drmSyncobjDestroy (timeline->drm, tmp);
+  return TRUE;
+
+end:
+  drmSyncobjDestroy (timeline->drm, tmp);
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_NOT_SUPPORTED,
+               "Failed to import syncfd at specified point");
+  return FALSE;
+}
+
+static void
+meta_drm_timeline_finalize (GObject *object)
+{
+  MetaDrmTimeline *timeline = META_DRM_TIMELINE (object);
+
+  drmSyncobjDestroy (timeline->drm, timeline->drm_syncobj);
+  g_clear_fd (&timeline->drm_syncobj_fd, NULL);
+  g_clear_fd (&timeline->drm, NULL);
+
+  G_OBJECT_CLASS (meta_drm_timeline_parent_class)->finalize (object);
+}
+
+static void
+meta_drm_timeline_init (MetaDrmTimeline *timeline)
+{
+  timeline->drm = -1;
+  timeline->drm_syncobj_fd = -1;
+  timeline->drm_syncobj = -1;
+}
+
+static void
+meta_drm_timeline_class_init (MetaDrmTimelineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = meta_drm_timeline_get_property;
+  object_class->set_property = meta_drm_timeline_set_property;
+  object_class->finalize = meta_drm_timeline_finalize;
+
+  obj_props[PROP_DRM_FD] =
+    g_param_spec_int ("drm-fd",
+                      NULL,
+                      NULL,
+                      0, INT_MAX, 0,
+                      G_PARAM_READWRITE |
+                      G_PARAM_CONSTRUCT_ONLY |
+                      G_PARAM_STATIC_STRINGS);
+
+  obj_props[PROP_SYNCOBJ_FD] =
+    g_param_spec_int ("syncobj-fd",
+                      NULL,
+                      NULL,
+                      0, INT_MAX, 0,
+                      G_PARAM_READWRITE |
+                      G_PARAM_CONSTRUCT_ONLY |
+                      G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, obj_props);
+}
diff --git a/src/wayland/meta-drm-timeline.h b/src/wayland/meta-drm-timeline.h
new file mode 100644
index 000000000..8206e3066
--- /dev/null
+++ b/src/wayland/meta-drm-timeline.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Austin Shafer <ashafer@nvidia.com>
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <stdint.h>
+
+#define META_TYPE_DRM_TIMELINE (meta_drm_timeline_get_type ())
+G_DECLARE_FINAL_TYPE (MetaDrmTimeline, meta_drm_timeline,
+                      META, DRM_TIMELINE, GObject);
+
+typedef struct _MetaDrmTimeline MetaDrmTimeline;
+
+MetaDrmTimeline * meta_drm_timeline_create (int      fd,
+                                            GError **error);
+
+MetaDrmTimeline * meta_drm_timeline_import_syncobj (int       fd,
+                                                    int       drm_syncobj,
+                                                    GError  **error);
+
+int meta_drm_timeline_get_eventfd (MetaDrmTimeline *timeline,
+                                   uint64_t         sync_point,
+                                   GError         **error);
+
+gboolean meta_drm_timeline_set_sync_point (MetaDrmTimeline *timeline,
+                                           uint64_t         sync_point,
+                                           int              sync_fd,
+                                           GError         **error);
diff --git a/src/wayland/meta-wayland-buffer.c b/src/wayland/meta-wayland-buffer.c
index 7a9858010..ae928e843 100644
--- a/src/wayland/meta-wayland-buffer.c
+++ b/src/wayland/meta-wayland-buffer.c
@@ -49,6 +49,7 @@
 #include "wayland/meta-wayland-buffer.h"
 
 #include <drm_fourcc.h>
+#include <glib/gstdio.h>
 
 #include "backends/meta-backend-private.h"
 #include "clutter/clutter.h"
@@ -56,6 +57,8 @@
 #include "meta/util.h"
 #include "wayland/meta-wayland-dma-buf.h"
 #include "wayland/meta-wayland-private.h"
+#include "wayland/meta-drm-timeline.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #ifdef HAVE_NATIVE_BACKEND
 #include "backends/native/meta-drm-buffer-gbm.h"
@@ -661,12 +664,43 @@ meta_wayland_buffer_inc_use_count (MetaWaylandBuffer *buffer)
 void
 meta_wayland_buffer_dec_use_count (MetaWaylandBuffer *buffer)
 {
+  MetaContext *context = meta_wayland_compositor_get_context (buffer->compositor);
+  MetaBackend *backend = meta_context_get_backend (context);
+  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
+  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
+  MetaWaylandSyncPoint *sync_point;
+  g_autoptr(GError) error = NULL;
+  g_autofd int sync_fd = -1;
+
   g_return_if_fail (buffer->use_count > 0);
 
   buffer->use_count--;
 
   if (buffer->use_count == 0 && buffer->resource)
-    wl_buffer_send_release (buffer->resource);
+    {
+      wl_buffer_send_release (buffer->resource);
+
+      sync_fd = cogl_context_get_latest_sync_fd (cogl_context);
+      if (sync_fd < 0)
+        {
+          meta_topic (META_DEBUG_WAYLAND, "Invalid Sync Fd returned by COGL");
+          return;
+        }
+
+      for (int i = 0; i < buffer->release_points->len; i++)
+        {
+          sync_point = g_ptr_array_index (buffer->release_points, i);
+          if (!meta_wayland_sync_timeline_set_sync_point (sync_point->timeline,
+                                                          sync_point->sync_point,
+                                                          sync_fd,
+                                                          &error))
+            {
+              g_warning ("Failed to import sync point: %s", error->message);
+            }
+        }
+      g_ptr_array_remove_range (buffer->release_points, 0,
+                                buffer->release_points->len);
+    }
 }
 
 gboolean
@@ -875,6 +909,7 @@ meta_wayland_buffer_finalize (GObject *object)
 
   clear_tainted_scanout_onscreens (buffer);
   g_clear_pointer (&buffer->tainted_scanout_onscreens, g_hash_table_unref);
+  g_clear_pointer (&buffer->release_points, g_ptr_array_unref);
 
   g_clear_object (&buffer->egl_image.texture);
 #ifdef HAVE_WAYLAND_EGLSTREAM
@@ -893,6 +928,7 @@ meta_wayland_buffer_finalize (GObject *object)
 static void
 meta_wayland_buffer_init (MetaWaylandBuffer *buffer)
 {
+  buffer->release_points = g_ptr_array_new_with_free_func (g_free);
 }
 
 static void
diff --git a/src/wayland/meta-wayland-buffer.h b/src/wayland/meta-wayland-buffer.h
index 2b50c2539..2ce3a2c9b 100644
--- a/src/wayland/meta-wayland-buffer.h
+++ b/src/wayland/meta-wayland-buffer.h
@@ -80,6 +80,8 @@ struct _MetaWaylandBuffer
   } single_pixel;
 
   GHashTable *tainted_scanout_onscreens;
+
+  GPtrArray *release_points;
 };
 
 #define META_TYPE_WAYLAND_BUFFER (meta_wayland_buffer_get_type ())
diff --git a/src/wayland/meta-wayland-dma-buf.c b/src/wayland/meta-wayland-dma-buf.c
index 79a0f1409..bd155cdae 100644
--- a/src/wayland/meta-wayland-dma-buf.c
+++ b/src/wayland/meta-wayland-dma-buf.c
@@ -39,6 +39,8 @@
 
 #include <drm_fourcc.h>
 #include <glib/gstdio.h>
+#include <linux/dma-buf.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -54,6 +56,7 @@
 #include "wayland/meta-wayland-buffer.h"
 #include "wayland/meta-wayland-private.h"
 #include "wayland/meta-wayland-versions.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #ifdef HAVE_NATIVE_BACKEND
 #include "backends/native/meta-drm-buffer-gbm.h"
@@ -846,10 +849,11 @@ typedef struct _MetaWaylandDmaBufSource
   gpointer user_data;
 
   gpointer fd_tags[META_WAYLAND_DMA_BUF_MAX_FDS];
+  int owned_sync_fd[META_WAYLAND_DMA_BUF_MAX_FDS];
 } MetaWaylandDmaBufSource;
 
 static gboolean
-meta_wayland_dma_buf_fd_readable (int fd)
+is_fd_readable (int fd)
 {
   GPollFD poll_fd;
 
@@ -880,11 +884,16 @@ meta_wayland_dma_buf_source_dispatch (GSource     *base,
   for (i = 0; i < META_WAYLAND_DMA_BUF_MAX_FDS; i++)
     {
       gpointer fd_tag = source->fd_tags[i];
+      int fd;
 
       if (!fd_tag)
         continue;
 
-      if (!meta_wayland_dma_buf_fd_readable (dma_buf->fds[i]))
+      fd = source->owned_sync_fd[i];
+      if (fd < 0)
+        fd = dma_buf->fds[i];
+
+      if (!is_fd_readable (fd))
         {
           ready = FALSE;
           continue;
@@ -892,6 +901,7 @@ meta_wayland_dma_buf_source_dispatch (GSource     *base,
 
       g_source_remove_unix_fd (&source->base, fd_tag);
       source->fd_tags[i] = NULL;
+      g_clear_fd (&source->owned_sync_fd[i], NULL);
     }
 
   if (!ready)
@@ -918,6 +928,7 @@ meta_wayland_dma_buf_source_finalize (GSource *base)
         {
           g_source_remove_unix_fd (&source->base, fd_tag);
           source->fd_tags[i] = NULL;
+          g_clear_fd (&source->owned_sync_fd[i], NULL);
         }
     }
 
@@ -929,6 +940,47 @@ static GSourceFuncs meta_wayland_dma_buf_source_funcs = {
   .finalize = meta_wayland_dma_buf_source_finalize
 };
 
+static MetaWaylandDmaBufSource *
+create_source (MetaWaylandBuffer               *buffer,
+               MetaWaylandDmaBufSourceDispatch  dispatch,
+               gpointer                         user_data)
+{
+  MetaWaylandDmaBufSource *source;
+  int i;
+
+  source =
+    (MetaWaylandDmaBufSource *) g_source_new (&meta_wayland_dma_buf_source_funcs,
+                                              sizeof (*source));
+  g_source_set_name ((GSource *) source, "[mutter] DmaBuf readiness source");
+
+  source->buffer = g_object_ref (buffer);
+  source->dispatch = dispatch;
+  source->user_data = user_data;
+
+  for (i = 0; i < META_WAYLAND_DMA_BUF_MAX_FDS; i++)
+    source->owned_sync_fd[i] = -1;
+
+  return source;
+}
+
+static int
+get_sync_file (int dma_buf_fd)
+{
+  struct dma_buf_export_sync_file dbesf = { .flags = DMA_BUF_SYNC_READ };
+  int ret;
+
+  do
+    {
+      ret = ioctl (dma_buf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &dbesf);
+    }
+  while (ret == -1 && errno == EINTR);
+
+  if (ret == 0)
+    return dbesf.fd;
+
+  return -1;
+}
+
 /**
  * meta_wayland_dma_buf_create_source:
  * @buffer: A #MetaWaylandBuffer object
@@ -962,18 +1014,15 @@ meta_wayland_dma_buf_create_source (MetaWaylandBuffer               *buffer,
       if (fd < 0)
         break;
 
-      if (meta_wayland_dma_buf_fd_readable (fd))
+      if (is_fd_readable (fd))
         continue;
 
       if (!source)
-        {
-          source =
-            (MetaWaylandDmaBufSource *) g_source_new (&meta_wayland_dma_buf_source_funcs,
-                                                      sizeof (*source));
-          source->buffer = g_object_ref (buffer);
-          source->dispatch = dispatch;
-          source->user_data = user_data;
-        }
+        source = create_source (buffer, dispatch, user_data);
+
+      source->owned_sync_fd[i] = get_sync_file (fd);
+      if (source->owned_sync_fd[i] >= 0)
+        fd = source->owned_sync_fd[i];
 
       source->fd_tags[i] = g_source_add_unix_fd (&source->base, fd, G_IO_IN);
     }
@@ -984,6 +1033,39 @@ meta_wayland_dma_buf_create_source (MetaWaylandBuffer               *buffer,
   return &source->base;
 }
 
+GSource *
+meta_wayland_drm_syncobj_create_source (MetaWaylandBuffer                *buffer,
+                                        MetaWaylandSyncobjTimeline       *timeline,
+                                        uint64_t                          sync_point,
+                                        MetaWaylandDmaBufSourceDispatch   dispatch,
+                                        gpointer                          user_data)
+{
+  MetaWaylandDmaBufSource *source = NULL;
+  g_autofd int sync_fd = -1;
+  g_autoptr(GError) error = NULL;
+
+  sync_fd = meta_wayland_sync_timeline_get_eventfd (timeline, sync_point, &error);
+  if (sync_fd < 0)
+    {
+      g_warning ("Failed to get sync fd: %s", error->message);
+      return NULL;
+    }
+
+  if (is_fd_readable (sync_fd))
+    {
+      return NULL;
+    }
+
+  source = create_source (buffer, dispatch, user_data);
+  if (!source)
+    return NULL;
+
+  source->fd_tags[0] = g_source_add_unix_fd (&source->base, sync_fd, G_IO_IN);
+  source->owned_sync_fd[0] = g_steal_fd (&sync_fd);
+
+  return &source->base;
+}
+
 static void
 buffer_params_create_common (struct wl_client   *client,
                              struct wl_resource *params_resource,
diff --git a/src/wayland/meta-wayland-dma-buf.h b/src/wayland/meta-wayland-dma-buf.h
index 1c03acb32..eb39f9c7c 100644
--- a/src/wayland/meta-wayland-dma-buf.h
+++ b/src/wayland/meta-wayland-dma-buf.h
@@ -63,6 +63,13 @@ meta_wayland_dma_buf_create_source (MetaWaylandBuffer               *buffer,
                                     MetaWaylandDmaBufSourceDispatch  dispatch,
                                     gpointer                         user_data);
 
+GSource *
+meta_wayland_drm_syncobj_create_source (MetaWaylandBuffer                *buffer,
+                                        MetaWaylandSyncobjTimeline       *timeline,
+                                        uint64_t                          sync_point,
+                                        MetaWaylandDmaBufSourceDispatch   dispatch,
+                                        gpointer                          user_data);
+
 CoglScanout *
 meta_wayland_dma_buf_try_acquire_scanout (MetaWaylandDmaBufBuffer *dma_buf,
                                           CoglOnscreen            *onscreen);
diff --git a/src/wayland/meta-wayland-linux-drm-syncobj.c b/src/wayland/meta-wayland-linux-drm-syncobj.c
new file mode 100644
index 000000000..24b908c41
--- /dev/null
+++ b/src/wayland/meta-wayland-linux-drm-syncobj.c
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2023 NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Austin Shafer <ashafer@nvidia.com>
+ */
+
+#include "config.h"
+
+#include "backends/native/meta-backend-native-types.h"
+#include "backends/native/meta-device-pool.h"
+#include "backends/native/meta-renderer-native.h"
+#include "meta/util.h"
+#include "wayland/meta-wayland-buffer.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
+#include "wayland/meta-wayland-private.h"
+#include "cogl/cogl-egl.h"
+#include <fcntl.h>
+#include <glib/gstdio.h>
+
+typedef struct _MetaWaylandDrmSyncobjManager
+{
+  GObject parent;
+
+  int drm;
+} MetaWaylandDrmSyncobjManager;
+
+typedef struct _MetaWaylandSyncobjSurface
+{
+  GObject parent;
+
+  struct wl_resource *resource;
+  MetaWaylandSurface *surface;
+  gulong surface_destroy_handler_id;
+} MetaWaylandSyncobjSurface;
+
+typedef struct _MetaWaylandSyncobjTimeline
+{
+  GObject parent;
+
+  MetaDrmTimeline *drm_timeline;
+} MetaWaylandSyncobjTimeline;
+
+#define META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER (meta_wayland_drm_syncobj_manager_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandDrmSyncobjManager, meta_wayland_drm_syncobj_manager,
+                      META, WAYLAND_DRM_SYNCOBJ_MANAGER, GObject)
+
+#define META_TYPE_WAYLAND_SYNCOBJ_SURFACE (meta_wayland_syncobj_surface_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandSyncobjSurface, meta_wayland_syncobj_surface,
+                      META, WAYLAND_SYNCOBJ_SURFACE, GObject)
+
+#define META_TYPE_WAYLAND_SYNCOBJ_TIMELINE (meta_wayland_syncobj_timeline_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandSyncobjTimeline, meta_wayland_syncobj_timeline,
+                      META, WAYLAND_SYNCOBJ_TIMELINE, GObject)
+
+#define META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER (meta_wayland_drm_syncobj_manager_get_type ())
+G_DEFINE_FINAL_TYPE (MetaWaylandDrmSyncobjManager, meta_wayland_drm_syncobj_manager,
+                     G_TYPE_OBJECT)
+
+#define META_TYPE_WAYLAND_SYNCOBJ_SURFACE (meta_wayland_syncobj_surface_get_type ())
+G_DEFINE_FINAL_TYPE (MetaWaylandSyncobjSurface, meta_wayland_syncobj_surface,
+                     G_TYPE_OBJECT)
+
+#define META_TYPE_WAYLAND_SYNCOBJ_TIMELINE (meta_wayland_syncobj_timeline_get_type ())
+G_DEFINE_FINAL_TYPE (MetaWaylandSyncobjTimeline, meta_wayland_syncobj_timeline,
+                     G_TYPE_OBJECT)
+
+G_DEFINE_FINAL_TYPE (MetaWaylandSyncPoint, meta_wayland_sync_point, G_TYPE_OBJECT);
+
+static GQuark quark_syncobj_surface;
+
+static void
+meta_wayland_sync_point_set (MetaWaylandSyncPoint      **sync_point_ptr,
+                             MetaWaylandSyncobjTimeline *syncobj_timeline,
+                             uint32_t                    point_hi,
+                             uint32_t                    point_lo)
+{
+  MetaWaylandSyncPoint *sync_point;
+
+  if (!*sync_point_ptr)
+    *sync_point_ptr = g_object_new (META_TYPE_WAYLAND_SYNC_POINT, NULL);
+
+  sync_point = *sync_point_ptr;
+  g_set_object (&sync_point->timeline, syncobj_timeline);
+  sync_point->sync_point = (uint64_t)point_hi << 32 | point_lo;
+}
+
+static void
+meta_wayland_sync_point_finalize (GObject *object)
+{
+  MetaWaylandSyncPoint *sync = META_WAYLAND_SYNC_POINT (object);
+
+  g_object_unref (sync->timeline);
+
+  G_OBJECT_CLASS (meta_wayland_sync_point_parent_class)->finalize (object);
+}
+
+static void
+meta_wayland_sync_point_init (MetaWaylandSyncPoint *sync)
+{
+}
+
+static void
+meta_wayland_sync_point_class_init (MetaWaylandSyncPointClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_wayland_sync_point_finalize;
+}
+
+static void
+syncobj_timeline_handle_resource_destroy (struct wl_resource *resource)
+{
+  MetaWaylandSyncobjTimeline *syncobj_timeline =
+    wl_resource_get_user_data (resource);
+  g_object_unref (syncobj_timeline);
+}
+
+static void
+meta_wayland_syncobj_timeline_finalize (GObject *object)
+{
+  MetaWaylandSyncobjTimeline *syncobj_timeline =
+    META_WAYLAND_SYNCOBJ_TIMELINE (object);
+
+  g_clear_object (&syncobj_timeline->drm_timeline);
+
+  G_OBJECT_CLASS (meta_wayland_syncobj_timeline_parent_class)->finalize (object);
+}
+
+static void
+meta_wayland_syncobj_timeline_class_init (MetaWaylandSyncobjTimelineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_wayland_syncobj_timeline_finalize;
+}
+
+static void
+meta_wayland_syncobj_timeline_init (MetaWaylandSyncobjTimeline *syncobj_timeline)
+{
+  syncobj_timeline->drm_timeline = NULL;
+}
+
+static void
+syncobj_timeline_handle_destroy (struct wl_client   *client,
+                                 struct wl_resource *resource)
+{
+  wl_resource_destroy (resource);
+}
+
+static const struct wp_linux_drm_syncobj_timeline_v1_interface
+  syncobj_timeline_implementation =
+{
+  syncobj_timeline_handle_destroy,
+};
+
+gboolean
+meta_wayland_sync_timeline_set_sync_point (MetaWaylandSyncobjTimeline  *timeline,
+                                           uint64_t                     sync_point,
+                                           int                          sync_fd,
+                                           GError                     **error)
+{
+  return meta_drm_timeline_set_sync_point (timeline->drm_timeline,
+                                           sync_point,
+                                           sync_fd,
+                                           error);
+}
+
+int
+meta_wayland_sync_timeline_get_eventfd (MetaWaylandSyncobjTimeline  *timeline,
+                                        uint64_t                     sync_point,
+                                        GError                     **error)
+{
+  return meta_drm_timeline_get_eventfd (timeline->drm_timeline,
+                                        sync_point,
+                                        error);
+}
+
+static void
+syncobj_surface_handle_destroy (struct wl_client   *client,
+                                struct wl_resource *resource)
+{
+  wl_resource_destroy (resource);
+}
+
+static void
+syncobj_surface_handle_set_acquire_point (struct wl_client   *client,
+                                          struct wl_resource *resource,
+                                          struct wl_resource *timeline_resource,
+                                          uint32_t            point_hi,
+                                          uint32_t            point_lo)
+{
+  MetaWaylandSyncobjSurface *syncobj_surface = wl_resource_get_user_data (resource);
+  MetaWaylandSurface *surface = syncobj_surface->surface;
+  MetaWaylandSyncobjTimeline *syncobj_timeline =
+    wl_resource_get_user_data (timeline_resource);
+
+  if (!surface)
+    {
+      wl_resource_post_error (resource,
+                              WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE,
+                              "Underlying surface object has been destroyed");
+      return;
+    }
+
+  meta_wayland_sync_point_set (&surface->pending_state->drm_syncobj.acquire,
+                               syncobj_timeline,
+                               point_hi,
+                               point_lo);
+}
+
+static void syncobj_surface_handle_set_release_point (struct wl_client   *client,
+                                                      struct wl_resource *resource,
+                                                      struct wl_resource *timeline_resource,
+                                                      uint32_t            point_hi,
+                                                      uint32_t            point_lo)
+{
+  MetaWaylandSyncobjSurface *syncobj_surface = wl_resource_get_user_data (resource);
+  MetaWaylandSurface *surface = syncobj_surface->surface;
+  MetaWaylandSyncobjTimeline *syncobj_timeline =
+    wl_resource_get_user_data (timeline_resource);
+
+  if (!surface)
+    {
+      wl_resource_post_error (resource,
+                              WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE,
+                              "Underlying surface object has been destroyed");
+      return;
+    }
+
+  meta_wayland_sync_point_set (&surface->pending_state->drm_syncobj.release,
+                               syncobj_timeline,
+                               point_hi,
+                               point_lo);
+}
+
+static const struct wp_linux_drm_syncobj_surface_v1_interface
+  syncobj_surface_implementation =
+{
+  syncobj_surface_handle_destroy,
+  syncobj_surface_handle_set_acquire_point,
+  syncobj_surface_handle_set_release_point,
+};
+
+static void
+syncobj_surface_resource_destroyed (MetaWaylandSurface        *surface,
+                                    MetaWaylandSyncobjSurface *syncobj_surface)
+{
+  g_clear_signal_handler (&syncobj_surface->surface_destroy_handler_id,
+                          syncobj_surface->surface);
+
+  g_object_set_qdata (G_OBJECT (syncobj_surface->surface),
+                      quark_syncobj_surface,
+                      NULL);
+
+  syncobj_surface->surface = NULL;
+}
+
+static void
+syncobj_surface_destructor (struct wl_resource *resource)
+{
+  MetaWaylandSyncobjSurface *syncobj_surface =
+    wl_resource_get_user_data (resource);
+
+  if (syncobj_surface->surface)
+    syncobj_surface_resource_destroyed (syncobj_surface->surface, syncobj_surface);
+
+  g_object_unref (syncobj_surface);
+}
+
+static void
+meta_wayland_syncobj_surface_class_init (MetaWaylandSyncobjSurfaceClass *klass)
+{
+}
+
+static void
+meta_wayland_syncobj_surface_init (MetaWaylandSyncobjSurface *syncobj_surface)
+{
+}
+
+static void
+drm_syncobj_manager_handle_destroy (struct wl_client   *client,
+                                    struct wl_resource *resource)
+{
+  wl_resource_destroy (resource);
+}
+
+static void
+drm_syncobj_manager_handle_get_surface (struct wl_client   *client,
+                                        struct wl_resource *resource,
+                                        uint32_t            id,
+                                        struct wl_resource *surface_resource)
+{
+  MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource);
+  MetaWaylandSyncobjSurface *syncobj_surface =
+    g_object_get_qdata (G_OBJECT (surface), quark_syncobj_surface);
+  struct wl_resource *sync_resource;
+
+  if (syncobj_surface)
+    {
+      wl_resource_post_error (surface_resource,
+                              WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS,
+                              "DRM Syncobj surface object already created for surface %d",
+                              wl_resource_get_id (surface_resource));
+      return;
+    }
+
+  sync_resource =
+    wl_resource_create (client,
+                        &wp_linux_drm_syncobj_surface_v1_interface,
+                        wl_resource_get_version (resource),
+                        id);
+  if (sync_resource == NULL)
+    {
+      wl_resource_post_no_memory (resource);
+      return;
+    }
+
+  syncobj_surface = g_object_new (META_TYPE_WAYLAND_SYNCOBJ_SURFACE, NULL);
+  syncobj_surface->surface = surface;
+  syncobj_surface->surface_destroy_handler_id =
+    g_signal_connect (surface,
+                      "destroy",
+                      G_CALLBACK (syncobj_surface_resource_destroyed),
+                      syncobj_surface);
+
+  g_object_set_qdata (G_OBJECT (surface),
+                      quark_syncobj_surface,
+                      syncobj_surface);
+
+  wl_resource_set_implementation (sync_resource,
+                                  &syncobj_surface_implementation,
+                                  syncobj_surface,
+                                  syncobj_surface_destructor);
+  syncobj_surface->resource = sync_resource;
+}
+
+static void
+drm_syncobj_manager_handle_import_timeline (struct wl_client   *client,
+                                            struct wl_resource *resource,
+                                            uint32_t            id,
+                                            int                 drm_syncobj_fd)
+{
+  MetaWaylandDrmSyncobjManager *drm_syncobj = wl_resource_get_user_data (resource);
+  g_autoptr (GError) error = NULL;
+  g_autoptr (MetaDrmTimeline) drm_timeline = NULL;
+  g_autoptr (MetaWaylandSyncobjTimeline) syncobj_timeline = NULL;
+  struct wl_resource *timeline_resource;
+
+  drm_timeline = meta_drm_timeline_import_syncobj (drm_syncobj->drm,
+                                                   drm_syncobj_fd,
+                                                   &error);
+  close (drm_syncobj_fd);
+  if (!drm_timeline)
+    {
+      wl_resource_post_error (resource,
+                              WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE,
+                              "Failed to import DRM syncobj: %s",
+                              error->message);
+      return;
+    }
+
+  syncobj_timeline = g_object_new (META_TYPE_WAYLAND_SYNCOBJ_TIMELINE, NULL);
+
+  timeline_resource = wl_resource_create (client,
+                                          &wp_linux_drm_syncobj_timeline_v1_interface,
+                                          wl_resource_get_version (resource),
+                                          id);
+  if (timeline_resource == NULL)
+    {
+      wl_resource_post_no_memory (resource);
+      return;
+    }
+
+  syncobj_timeline->drm_timeline = g_steal_pointer (&drm_timeline);
+  wl_resource_set_implementation (timeline_resource,
+                                  &syncobj_timeline_implementation,
+                                  g_steal_pointer (&syncobj_timeline),
+                                  syncobj_timeline_handle_resource_destroy);
+}
+
+static const struct wp_linux_drm_syncobj_manager_v1_interface
+  drm_syncobj_manager_implementation =
+{
+  drm_syncobj_manager_handle_destroy,
+  drm_syncobj_manager_handle_get_surface,
+  drm_syncobj_manager_handle_import_timeline,
+};
+
+static void
+meta_wayland_drm_syncobj_manager_finalize (GObject *object)
+{
+  MetaWaylandDrmSyncobjManager *drm_syncobj =
+    META_WAYLAND_DRM_SYNCOBJ_MANAGER (object);
+
+  g_clear_fd (&drm_syncobj->drm, NULL);
+
+  G_OBJECT_CLASS (meta_wayland_drm_syncobj_manager_parent_class)->finalize (object);
+}
+
+static void
+meta_wayland_drm_syncobj_manager_class_init (MetaWaylandDrmSyncobjManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_wayland_drm_syncobj_manager_finalize;
+
+  quark_syncobj_surface = g_quark_from_static_string ("drm-syncobj-quark");
+}
+
+static void
+meta_wayland_drm_syncobj_manager_init (MetaWaylandDrmSyncobjManager *drm_syncobj)
+{
+  drm_syncobj->drm = -1;
+}
+
+static void
+drm_syncobj_manager_bind (struct wl_client *client,
+                          void             *user_data,
+                          uint32_t          version,
+                          uint32_t          id)
+{
+  MetaWaylandDrmSyncobjManager *drm_syncobj_manager = user_data;
+  struct wl_resource *resource;
+
+  resource = wl_resource_create (client,
+                                 &wp_linux_drm_syncobj_manager_v1_interface,
+                                 version,
+                                 id);
+  wl_resource_set_implementation (resource,
+                                  &drm_syncobj_manager_implementation,
+                                  drm_syncobj_manager,
+                                  NULL);
+}
+
+static MetaWaylandDrmSyncobjManager *
+meta_wayland_drm_syncobj_manager_new (MetaWaylandCompositor *compositor,
+                                      GError               **error)
+{
+  MetaContext *context =
+    meta_wayland_compositor_get_context (compositor);
+  MetaBackend *backend = meta_context_get_backend (context);
+  MetaEgl *egl = meta_backend_get_egl (backend);
+  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
+  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
+  EGLDisplay egl_display = cogl_egl_context_get_egl_display (cogl_context);
+  MetaWaylandDrmSyncobjManager *drm_syncobj_manager;
+  EGLDeviceEXT egl_device;
+  g_autofd int drm_fd = -1;
+  EGLAttrib attrib;
+  uint64_t timeline_supported = false;
+  const char *device_path = NULL;
+
+  g_assert (backend && egl && clutter_backend && cogl_context && egl_display);
+
+  if (!meta_egl_query_display_attrib (egl, egl_display,
+                                      EGL_DEVICE_EXT, &attrib,
+                                      error))
+    return NULL;
+
+  egl_device = (EGLDeviceEXT) attrib;
+
+  if (meta_egl_egl_device_has_extensions (egl, egl_device, NULL,
+                                          "EGL_EXT_device_drm_render_node",
+                                          NULL))
+    {
+      if (!meta_egl_query_device_string (egl, egl_device,
+                                         EGL_DRM_RENDER_NODE_FILE_EXT,
+                                         &device_path, error))
+        return NULL;
+    }
+
+  if (!device_path &&
+      meta_egl_egl_device_has_extensions (egl, egl_device, NULL,
+                                          "EGL_EXT_device_drm",
+                                          NULL))
+    {
+      if (!meta_egl_query_device_string (egl, egl_device,
+                                         EGL_DRM_DEVICE_FILE_EXT,
+                                         &device_path, error))
+        return NULL;
+    }
+
+  if (!device_path)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_SUPPORTED,
+                   "Failed to find EGL device to initialize linux-drm-syncobj-v1");
+      return NULL;
+    }
+
+  drm_fd = open (device_path, O_RDWR | O_CLOEXEC);
+  if (drm_fd < 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Failed to open DRM device %s",
+                   device_path);
+      return NULL;
+    }
+
+  if (drmGetCap (drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &timeline_supported) != 0
+      || !timeline_supported)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_SUPPORTED,
+                   "Failed to check DRM syncobj timeline capability");
+      return NULL;
+    }
+
+#ifdef HAVE_EVENTFD
+  if (drmSyncobjEventfd (drm_fd, 0, 0, -1, 0) != -1 || errno != ENOENT)
+#endif
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_SUPPORTED,
+                   "drmSyncobjEventfd failed: linux-drm-syncobj requires eventfd support");
+      return NULL;
+    }
+
+  drm_syncobj_manager = g_object_new (META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER, NULL);
+  drm_syncobj_manager->drm = g_steal_fd (&drm_fd);
+
+  if (!wl_global_create (compositor->wayland_display,
+                         &wp_linux_drm_syncobj_manager_v1_interface,
+                         1,
+                         drm_syncobj_manager,
+                         drm_syncobj_manager_bind))
+    {
+      g_error ("Failed to create wp_linux_drm_syncobj_manager_v1_interface global");
+    }
+
+  return drm_syncobj_manager;
+}
+
+void
+meta_wayland_drm_syncobj_init (MetaWaylandCompositor *compositor)
+{
+  g_autoptr (GError) error = NULL;
+  MetaWaylandDrmSyncobjManager *manager =
+    meta_wayland_drm_syncobj_manager_new (compositor, &error);
+
+  if (!manager)
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+        {
+          meta_topic (META_DEBUG_WAYLAND, "Disabling explicit sync: %s",
+                      error->message);
+        }
+       else
+        {
+          g_warning ("Failed to create linux-drm-syncobj-manager: %s",
+                     error->message);
+        }
+      return;
+    }
+
+  g_object_set_data_full (G_OBJECT (compositor), "-meta-wayland-drm-syncobj-manager",
+                          manager,
+                          g_object_unref);
+}
+
+/*
+ * Validate that the appropriate acquire and release points have been set
+ * for this surface.
+ */
+bool
+meta_wayland_surface_explicit_sync_validate (MetaWaylandSurface      *surface,
+                                             MetaWaylandSurfaceState *state)
+{
+  MetaWaylandSyncobjSurface *syncobj_surface = g_object_get_qdata (G_OBJECT (surface),
+                                                                   quark_syncobj_surface);
+
+  if (!syncobj_surface)
+    return TRUE;
+
+  if (state->buffer)
+    {
+      if (state->buffer->type != META_WAYLAND_BUFFER_TYPE_DMA_BUF)
+        {
+          wl_resource_post_error (syncobj_surface->resource,
+                                  WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_UNSUPPORTED_BUFFER,
+                                  "Explicit Sync only supported on dmabuf buffers");
+          return FALSE;
+        }
+
+      if (!state->drm_syncobj.acquire)
+        {
+          wl_resource_post_error (syncobj_surface->resource,
+                                  WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
+                                  "No Acquire point provided");
+          return FALSE;
+        }
+
+      if (!state->drm_syncobj.release)
+        {
+          wl_resource_post_error (syncobj_surface->resource,
+                                  WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT,
+                                  "No Release point provided");
+          return FALSE;
+        }
+
+      if (state->drm_syncobj.acquire->timeline == state->drm_syncobj.release->timeline &&
+          state->drm_syncobj.acquire->sync_point >= state->drm_syncobj.release->sync_point)
+        {
+          wl_resource_post_error (syncobj_surface->resource,
+                                  WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS,
+                                  "Invalid Release and Acquire point combination");
+          return FALSE;
+        }
+    }
+  else if (state->drm_syncobj.acquire || state->drm_syncobj.release)
+    {
+      wl_resource_post_error (syncobj_surface->resource,
+                              WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER,
+                              "Release or Acquire point set but no buffer attached");
+      return FALSE;
+    }
+
+  return TRUE;
+}
diff --git a/src/wayland/meta-wayland-linux-drm-syncobj.h b/src/wayland/meta-wayland-linux-drm-syncobj.h
new file mode 100644
index 000000000..cfe361b7c
--- /dev/null
+++ b/src/wayland/meta-wayland-linux-drm-syncobj.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Austin Shafer <ashafer@nvidia.com>
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#include "wayland/meta-wayland-types.h"
+#include "wayland/meta-drm-timeline.h"
+
+#include "linux-drm-syncobj-v1-server-protocol.h"
+
+#define META_TYPE_WAYLAND_SYNC_POINT (meta_wayland_sync_point_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandSyncPoint,
+                      meta_wayland_sync_point,
+                      META, WAYLAND_SYNC_POINT,
+                      GObject)
+
+typedef struct _MetaWaylandSyncPoint {
+  GObject parent;
+
+  MetaWaylandSyncobjTimeline *timeline;
+  uint64_t sync_point;
+} MetaWaylandSyncPoint;
+
+bool
+meta_wayland_surface_explicit_sync_validate (MetaWaylandSurface      *surface,
+                                             MetaWaylandSurfaceState *state);
+
+void
+meta_wayland_drm_syncobj_init (MetaWaylandCompositor *compositor);
+
+gboolean
+meta_wayland_sync_timeline_set_sync_point (MetaWaylandSyncobjTimeline  *timeline,
+                                           uint64_t                     sync_point,
+                                           int                          sync_fd,
+                                           GError                     **error);
+
+int
+meta_wayland_sync_timeline_get_eventfd (MetaWaylandSyncobjTimeline  *timeline,
+                                        uint64_t                     sync_point,
+                                        GError                     **error);
diff --git a/src/wayland/meta-wayland-surface.c b/src/wayland/meta-wayland-surface.c
index d42f31828..8eaf4c924 100644
--- a/src/wayland/meta-wayland-surface.c
+++ b/src/wayland/meta-wayland-surface.c
@@ -51,6 +51,7 @@
 #include "wayland/meta-wayland-viewporter.h"
 #include "wayland/meta-wayland-xdg-shell.h"
 #include "wayland/meta-window-wayland.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #ifdef HAVE_XWAYLAND
 #include "wayland/meta-xwayland-private.h"
@@ -421,6 +422,9 @@ meta_wayland_surface_state_set_default (MetaWaylandSurfaceState *state)
   wl_list_init (&state->presentation_feedback_list);
 
   state->xdg_popup_reposition_token = 0;
+
+  state->drm_syncobj.acquire = NULL;
+  state->drm_syncobj.release = NULL;
 }
 
 static void
@@ -441,6 +445,8 @@ meta_wayland_surface_state_clear (MetaWaylandSurfaceState *state)
   MetaWaylandFrameCallback *cb, *next;
 
   g_clear_object (&state->texture);
+  g_clear_object (&state->drm_syncobj.acquire);
+  g_clear_object (&state->drm_syncobj.release);
 
   g_clear_pointer (&state->surface_damage, cairo_region_destroy);
   g_clear_pointer (&state->buffer_damage, cairo_region_destroy);
@@ -605,6 +611,11 @@ meta_wayland_surface_state_merge_into (MetaWaylandSurfaceState *from,
       to->xdg_positioner = g_steal_pointer (&from->xdg_positioner);
       to->xdg_popup_reposition_token = from->xdg_popup_reposition_token;
     }
+
+  g_set_object (&to->drm_syncobj.acquire, from->drm_syncobj.acquire);
+  g_clear_object (&from->drm_syncobj.acquire);
+  g_set_object (&to->drm_syncobj.release, from->drm_syncobj.release);
+  g_clear_object (&from->drm_syncobj.release);
 }
 
 static void
@@ -924,10 +935,14 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface)
   MetaWaylandBuffer *buffer = pending->buffer;
   MetaWaylandTransaction *transaction;
   MetaWaylandSurface *subsurface_surface;
+  MetaWaylandSyncPoint *release_point = pending->drm_syncobj.release;
 
   COGL_TRACE_BEGIN_SCOPED (MetaWaylandSurfaceCommit,
                            "WaylandSurface (commit)");
 
+  if (!meta_wayland_surface_explicit_sync_validate (surface, pending))
+    return;
+
   if (buffer)
     {
       g_autoptr (GError) error = NULL;
@@ -953,6 +968,9 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface)
 
       pending->texture = g_object_ref (surface->protocol_state.texture);
 
+      if (release_point)
+        g_ptr_array_add (buffer->release_points, g_object_ref (release_point));
+
       g_object_ref (buffer);
       meta_wayland_buffer_inc_use_count (buffer);
     }
diff --git a/src/wayland/meta-wayland-surface.h b/src/wayland/meta-wayland-surface.h
index 3e8db5a87..459e3c04a 100644
--- a/src/wayland/meta-wayland-surface.h
+++ b/src/wayland/meta-wayland-surface.h
@@ -29,6 +29,7 @@
 #include "meta/meta-cursor-tracker.h"
 #include "wayland/meta-wayland-pointer-constraints.h"
 #include "wayland/meta-wayland-types.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #define META_TYPE_WAYLAND_SURFACE (meta_wayland_surface_get_type ())
 G_DECLARE_FINAL_TYPE (MetaWaylandSurface,
@@ -134,6 +135,12 @@ struct _MetaWaylandSurfaceState
   /* xdg_popup */
   MetaWaylandXdgPositioner *xdg_positioner;
   uint32_t xdg_popup_reposition_token;
+
+  /* Explicit Synchronization */
+  struct {
+    MetaWaylandSyncPoint *acquire;
+    MetaWaylandSyncPoint *release;
+  } drm_syncobj;
 };
 
 struct _MetaWaylandDragDestFuncs
diff --git a/src/wayland/meta-wayland-transaction.c b/src/wayland/meta-wayland-transaction.c
index 9b999bfa9..7750b2e9f 100644
--- a/src/wayland/meta-wayland-transaction.c
+++ b/src/wayland/meta-wayland-transaction.c
@@ -26,6 +26,7 @@
 #include "wayland/meta-wayland.h"
 #include "wayland/meta-wayland-buffer.h"
 #include "wayland/meta-wayland-dma-buf.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #define META_WAYLAND_TRANSACTION_NONE ((void *)(uintptr_t) G_MAXSIZE)
 
@@ -287,6 +288,17 @@ meta_wayland_transaction_dma_buf_dispatch (MetaWaylandBuffer *buffer,
   meta_wayland_transaction_maybe_apply (transaction);
 }
 
+static void
+ensure_buf_sources (MetaWaylandTransaction *transaction)
+{
+  if (!transaction->buf_sources)
+    {
+      transaction->buf_sources =
+        g_hash_table_new_full (NULL, NULL, NULL,
+                               (GDestroyNotify) g_source_destroy);
+    }
+}
+
 static gboolean
 meta_wayland_transaction_add_dma_buf_source (MetaWaylandTransaction *transaction,
                                              MetaWaylandBuffer      *buffer)
@@ -303,12 +315,35 @@ meta_wayland_transaction_add_dma_buf_source (MetaWaylandTransaction *transaction
   if (!source)
     return FALSE;
 
-  if (!transaction->buf_sources)
-    {
-      transaction->buf_sources =
-        g_hash_table_new_full (NULL, NULL, NULL,
-                               (GDestroyNotify) g_source_destroy);
-    }
+  ensure_buf_sources (transaction);
+
+  g_hash_table_insert (transaction->buf_sources, buffer, source);
+  g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  return TRUE;
+}
+
+static gboolean
+meta_wayland_transaction_add_drm_syncobj_source (MetaWaylandTransaction *transaction,
+                                                 MetaWaylandBuffer      *buffer,
+                                                 MetaWaylandSyncPoint   *acquire)
+{
+  GSource *source;
+
+  if (transaction->buf_sources &&
+      g_hash_table_contains (transaction->buf_sources, buffer))
+    return FALSE;
+
+  source = meta_wayland_drm_syncobj_create_source (buffer,
+                                                   acquire->timeline,
+                                                   acquire->sync_point,
+                                                   meta_wayland_transaction_dma_buf_dispatch,
+                                                   transaction);
+  if (!source)
+    return FALSE;
+
+  ensure_buf_sources (transaction);
 
   g_hash_table_insert (transaction->buf_sources, buffer, source);
   g_source_attach (source, NULL);
@@ -335,8 +370,11 @@ meta_wayland_transaction_commit (MetaWaylandTransaction *transaction)
         {
           MetaWaylandBuffer *buffer = entry->state->buffer;
 
-          if (buffer &&
-              meta_wayland_transaction_add_dma_buf_source (transaction, buffer))
+          if ((entry->state->drm_syncobj.acquire &&
+               meta_wayland_transaction_add_drm_syncobj_source (transaction, buffer,
+                                                                entry->state->drm_syncobj.acquire))
+              || (buffer &&
+                  meta_wayland_transaction_add_dma_buf_source (transaction, buffer)))
             maybe_apply = FALSE;
         }
     }
diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h
index c14b340f4..2b5a967ab 100644
--- a/src/wayland/meta-wayland-types.h
+++ b/src/wayland/meta-wayland-types.h
@@ -64,6 +64,8 @@ typedef struct _MetaWaylandActivation MetaWaylandActivation;
 
 typedef struct _MetaWaylandDmaBufManager MetaWaylandDmaBufManager;
 
+typedef struct _MetaWaylandSyncobjTimeline MetaWaylandSyncobjTimeline;
+
 typedef struct _MetaWaylandXdgPositioner MetaWaylandXdgPositioner;
 
 typedef struct _MetaXWaylandManager MetaXWaylandManager;
diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
index a2bd7d2ab..a3569af53 100644
--- a/src/wayland/meta-wayland.c
+++ b/src/wayland/meta-wayland.c
@@ -50,6 +50,7 @@
 #include "wayland/meta-wayland-tablet-manager.h"
 #include "wayland/meta-wayland-transaction.h"
 #include "wayland/meta-wayland-xdg-foreign.h"
+#include "wayland/meta-wayland-linux-drm-syncobj.h"
 
 #ifdef HAVE_XWAYLAND
 #include "wayland/meta-wayland-x11-interop.h"
@@ -808,6 +809,7 @@ meta_wayland_compositor_new (MetaContext *context)
   meta_wayland_activation_init (compositor);
   meta_wayland_transaction_init (compositor);
   meta_wayland_idle_inhibit_init (compositor);
+  meta_wayland_drm_syncobj_init (compositor);
 
 #ifdef HAVE_WAYLAND_EGLSTREAM
   {
diff --git a/src/wayland/protocol/linux-drm-syncobj-v1.xml b/src/wayland/protocol/linux-drm-syncobj-v1.xml
new file mode 100644
index 000000000..2c491eaf4
--- /dev/null
+++ b/src/wayland/protocol/linux-drm-syncobj-v1.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="linux_drm_syncobj_v1">
+  <copyright>
+    Copyright 2016 The Chromium Authors.
+    Copyright 2017 Intel Corporation
+    Copyright 2018 Collabora, Ltd
+    Copyright 2021 Simon Ser
+
+    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.
+  </copyright>
+
+  <description summary="protocol for providing explicit synchronization">
+    This protocol allows clients to request explicit synchronization for
+    buffers. It is tied to the Linux DRM synchronization object framework.
+
+    Synchronization refers to co-ordination of pipelined operations performed
+    on buffers. Most GPU clients will schedule an asynchronous operation to
+    render to the buffer, then immediately send the buffer to the compositor
+    to be attached to a surface.
+
+    With implicit synchronization, ensuring that the rendering operation is
+    complete before the compositor displays the buffer is an implementation
+    detail handled by either the kernel or userspace graphics driver.
+
+    By contrast, with explicit synchronization, DRM synchronization object
+    timeline points mark when the asynchronous operations are complete. When
+    submitting a buffer, the client provides a timeline point which will be
+    waited on before the compositor accesses the buffer, and another timeline
+    point that the compositor will signal when it no longer needs to access the
+    buffer contents for the purposes of the surface commit.
+
+    Linux DRM synchronization objects are documented at:
+    https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#drm-sync-objects
+
+    Warning! The protocol described in this file is currently in the testing
+    phase. Backward compatible changes may be added together with the
+    corresponding interface version bump. Backward incompatible changes can
+    only be done by creating a new major version of the extension.
+  </description>
+
+  <interface name="wp_linux_drm_syncobj_manager_v1" version="1">
+    <description summary="global for providing explicit synchronization">
+      This global is a factory interface, allowing clients to request
+      explicit synchronization for buffers on a per-surface basis.
+
+      See wp_linux_drm_syncobj_surface_v1 for more information.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy explicit synchronization factory object">
+        Destroy this explicit synchronization factory object. Other objects
+        shall not be affected by this request.
+      </description>
+    </request>
+
+    <enum name="error">
+      <entry name="surface_exists" value="0"
+        summary="the surface already has a synchronization object associated"/>
+      <entry name="invalid_timeline" value="1"
+        summary="the timeline object could not be imported"/>
+    </enum>
+
+    <request name="get_surface">
+      <description summary="extend surface interface for explicit synchronization">
+        Instantiate an interface extension for the given wl_surface to provide
+        explicit synchronization.
+
+        If the given wl_surface already has an explicit synchronization object
+        associated, the surface_exists protocol error is raised.
+
+        Graphics APIs, like EGL or Vulkan, that manage the buffer queue and
+        commits of a wl_surface themselves, are likely to be using this
+        extension internally. If a client is using such an API for a
+        wl_surface, it should not directly use this extension on that surface,
+        to avoid raising a surface_exists protocol error.
+      </description>
+      <arg name="id" type="new_id" interface="wp_linux_drm_syncobj_surface_v1"
+        summary="the new synchronization surface object id"/>
+      <arg name="surface" type="object" interface="wl_surface"
+        summary="the surface"/>
+    </request>
+
+    <request name="import_timeline">
+      <description summary="import a DRM syncobj timeline">
+        Import a DRM synchronization object timeline.
+
+        If the FD cannot be imported, the invalid_timeline error is raised.
+      </description>
+      <arg name="id" type="new_id" interface="wp_linux_drm_syncobj_timeline_v1"/>
+      <arg name="fd" type="fd" summary="drm_syncobj file descriptor"/>
+    </request>
+  </interface>
+
+  <interface name="wp_linux_drm_syncobj_timeline_v1" version="1">
+    <description summary="synchronization object timeline">
+      This object represents an explicit synchronization object timeline
+      imported by the client to the compositor.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the timeline">
+        Destroy the synchronization object timeline. Other objects are not
+        affected by this request, in particular timeline points set by
+        set_acquire_point and set_release_point are not unset.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="wp_linux_drm_syncobj_surface_v1" version="1">
+    <description summary="per-surface explicit synchronization">
+      This object is an add-on interface for wl_surface to enable explicit
+      synchronization.
+
+      Each surface can be associated with only one object of this interface at
+      any time.
+
+      Explicit synchronization is guaranteed to be supported for buffers
+      created with any version of the linux-dmabuf protocol. Compositors are
+      free to support explicit synchronization for additional buffer types.
+      If at surface commit time the attached buffer does not support explicit
+      synchronization, an unsupported_buffer error is raised.
+
+      As long as the wp_linux_drm_syncobj_surface_v1 object is alive, the
+      compositor may ignore implicit synchronization for buffers attached and
+      committed to the wl_surface. The delivery of wl_buffer.release events
+      for buffers attached to the surface becomes undefined.
+
+      Clients must set both acquire and release points if and only if a
+      non-null buffer is attached in the same surface commit. See the
+      no_buffer, no_acquire_point and no_release_point protocol errors.
+
+      If at surface commit time the acquire and release DRM syncobj timelines
+      are identical, the acquire point value must be strictly less than the
+      release point value, or else the conflicting_points protocol error is
+      raised.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the surface synchronization object">
+        Destroy this surface synchronization object.
+
+        Any timeline point set by this object with set_acquire_point or
+        set_release_point since the last commit may be discarded by the
+        compositor. Any timeline point set by this object before the last
+        commit will not be affected.
+      </description>
+    </request>
+
+    <enum name="error">
+      <entry name="no_surface" value="1"
+        summary="the associated wl_surface was destroyed"/>
+      <entry name="unsupported_buffer" value="2"
+        summary="the buffer does not support explicit synchronization"/>
+      <entry name="no_buffer" value="3" summary="no buffer was attached"/>
+      <entry name="no_acquire_point" value="4"
+        summary="no acquire timeline point was set"/>
+      <entry name="no_release_point" value="5"
+        summary="no release timeline point was set"/>
+      <entry name="conflicting_points" value="6"
+        summary="acquire and release timeline points are in conflict"/>
+    </enum>
+
+    <request name="set_acquire_point">
+      <description summary="set the acquire timeline point">
+        Set the timeline point that must be signalled before the compositor may
+        sample from the buffer attached with wl_surface.attach.
+
+        The 64-bit unsigned value combined from point_hi and point_lo is the
+        point value.
+
+        The acquire point is double-buffered state, and will be applied on the
+        next wl_surface.commit request for the associated surface. Thus, it
+        applies only to the buffer that is attached to the surface at commit
+        time.
+
+        If an acquire point has already been attached during the same commit
+        cycle, the new point replaces the old one.
+
+        If the associated wl_surface was destroyed, a no_surface error is
+        raised.
+
+        If at surface commit time there is a pending acquire timeline point set
+        but no pending buffer attached, a no_buffer error is raised. If at
+        surface commit time there is a pending buffer attached but no pending
+        acquire timeline point set, the no_acquire_point protocol error is
+        raised.
+      </description>
+      <arg name="timeline" type="object" interface="wp_linux_drm_syncobj_timeline_v1"/>
+      <arg name="point_hi" type="uint" summary="high 32 bits of the point value"/>
+      <arg name="point_lo" type="uint" summary="low 32 bits of the point value"/>
+    </request>
+
+    <request name="set_release_point">
+      <description summary="set the release timeline point">
+        Set the timeline point that must be signalled by the compositor when it
+        has finished its usage of the buffer attached with wl_surface.attach
+        for the relevant commit.
+
+        Once the timeline point is signaled, and assuming the associated buffer
+        is not pending release from other wl_surface.commit requests, no
+        additional explicit or implicit synchronization with the compositor is
+        required to safely re-use the buffer.
+
+        Note that clients cannot rely on the release point being always
+        signaled after the acquire point: compositors may release buffers
+        without ever reading from them. In addition, the compositor may use
+        different presentation paths for different commits, which may have
+        different release behavior. As a result, the compositor may signal the
+        release points in a different order than the client committed them.
+
+        Because signaling a timeline point also signals every previous point,
+        it is generally not safe to use the same timeline object for the
+        release points of multiple buffers. The out-of-order signaling
+        described above may lead to a release point being signaled before the
+        compositor has finished reading. To avoid this, it is strongly
+        recommended that each buffer should use a separate timeline for its
+        release points.
+
+        The 64-bit unsigned value combined from point_hi and point_lo is the
+        point value.
+
+        The release point is double-buffered state, and will be applied on the
+        next wl_surface.commit request for the associated surface. Thus, it
+        applies only to the buffer that is attached to the surface at commit
+        time.
+
+        If a release point has already been attached during the same commit
+        cycle, the new point replaces the old one.
+
+        If the associated wl_surface was destroyed, a no_surface error is
+        raised.
+
+        If at surface commit time there is a pending release timeline point set
+        but no pending buffer attached, a no_buffer error is raised. If at
+        surface commit time there is a pending buffer attached but no pending
+        release timeline point set, the no_release_point protocol error is
+        raised.
+      </description>
+      <arg name="timeline" type="object" interface="wp_linux_drm_syncobj_timeline_v1"/>
+      <arg name="point_hi" type="uint" summary="high 32 bits of the point value"/>
+      <arg name="point_lo" type="uint" summary="low 32 bits of the point value"/>
+    </request>
+  </interface>
+</protocol>
openSUSE Build Service is sponsored by