Browse Source

libobs,libobs-opengl: Add gs_sync_t

This change adds a new type named gs_sync_t and related functions to X11
and Wayland EGL backends to abstract DRM syncobjs and their various
uses.

Signed-off-by: Doğukan Korkmaztürk <[email protected]>
Doğukan Korkmaztürk 1 year ago
parent
commit
68ca91b21d

+ 86 - 0
docs/sphinx/reference-libobs-graphics-graphics.rst

@@ -1082,6 +1082,91 @@ Texture Functions
 
 ---------------------
 
+.. function:: bool gs_query_sync_capabilities(void)
+
+   **only Linux, FreeBSD, DragonFly:** Checks if the use of synchronization objects is supported
+
+   :rtype:  bool
+   :return: *true* if supported, *false* otherwise
+
+---------------------
+
+.. function:: gs_sync_t *gs_sync_create(void)
+
+   **only Linux, FreeBSD, DragonFly:** Creates a synchronization object
+
+   Inserts a fence command into the command stream of the bound context and
+   creates a synchronization object that is signalled when all the commands
+   preceding the inserted fence are executed.
+
+   :rtype: gs_sync_t*
+
+---------------------
+
+.. function:: gs_sync_t *gs_sync_create_from_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point)
+
+   **only Linux, FreeBSD, DragonFly:** Creates a synchronization object
+
+   Creates a synchronization object that is signalled when the specified
+   timeline point of the given DRM syncobj is signalled.
+
+   :param syncobj_fd:     A file descriptor that is referencing a DRM syncobj
+   :param timeline_point: Timeline point of the DRM syncobj
+   :rtype:                gs_sync_t*
+
+---------------------
+
+.. function:: void gs_sync_destroy(gs_sync_t *sync)
+
+   **only Linux, FreeBSD, DragonFly:** Destroys a synchronization object
+
+   If the given synchronization object is in use, it will be marked for
+   destruction and will be destroyed later when it is no longer referenced.
+
+   :param sync: Synchronization object
+
+---------------------
+
+.. function:: bool gs_sync_export_syncobj_timeline_point(gs_sync_t *sync, int syncobj_fd, uint64_t timeline_point)
+
+   **only Linux, FreeBSD, DragonFly:** Exports a synchronization object to a DRM syncobj timeline point
+
+   Creates a DRM syncobj timeline point that is signalled when the given
+   synchronization object is signalled.
+
+   :param sync:           Synchronization object
+   :param syncobj_fd:     A file descriptor that is referencing a DRM syncobj
+   :param timeline_point: Timeline point of the DRM syncobj
+   :rtype:                bool
+   :return:               *true* if the synchronization object is exported successfully, *false* otherwise
+
+---------------------
+
+.. function:: bool gs_sync_signal_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point)
+
+   **only Linux, FreeBSD, DragonFly:** Signals a DRM syncobj timeline point
+
+   :param syncobj_fd:     A file descriptor that is referencing a DRM syncobj
+   :param timeline_point: Timeline point of the DRM syncobj
+   :rtype:                bool
+   :return:               *true* if the timeline point is signalled successfully, *false* otherwise
+
+---------------------
+
+.. function:: bool gs_sync_wait(gs_sync_t *sync)
+
+   **only Linux, FreeBSD, DragonFly:** Wait for a synchronization object to be signalled
+
+   Blocks the execution of the commands that would be inserted into the command
+   stream of the bound context until the given synchronization object is
+   signalled.
+
+   :param sync: Synchronization object
+   :rtype:      bool
+   :return:     *true* if successful, *false* otherwise
+
+---------------------
+
 .. function:: gs_texture_t *gs_texture_create_from_iosurface(void *iosurf)
 
    **macOS only:** Creates a texture from an IOSurface.
@@ -1586,4 +1671,5 @@ Graphics Types
 .. type:: struct gs_shader           gs_shader_t
 .. type:: struct gs_shader_param     gs_sparam_t
 .. type:: struct gs_device           gs_device_t
+.. type:: void                       gs_sync_t
 .. type:: struct graphics_subsystem  graphics_t

+ 2 - 0
libobs-opengl/CMakeLists.txt

@@ -8,6 +8,7 @@ if(NOT TARGET OBS::glad)
 endif()
 
 if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
+  find_package(Libdrm REQUIRED)
   find_package(X11 REQUIRED)
   find_package(Xcb REQUIRED xcb)
   find_package(X11-xcb REQUIRED)
@@ -54,6 +55,7 @@ target_link_libraries(
     OBS::glad
     "$<$<PLATFORM_ID:Darwin>:$<LINK_LIBRARY:FRAMEWORK,Cocoa.framework>>"
     "$<$<PLATFORM_ID:Darwin>:$<LINK_LIBRARY:FRAMEWORK,IOSurface.framework>>"
+    $<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:Libdrm::Libdrm>
     $<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:xcb::xcb>
     $<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:X11::x11-xcb>
     $<$<AND:$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>,$<BOOL:${ENABLE_WAYLAND}>>:OpenGL::EGL>

+ 115 - 0
libobs-opengl/gl-egl-common.c

@@ -26,6 +26,9 @@
 
 #include <linux/types.h>
 #include <asm/ioctl.h>
+#include <xf86drm.h>
+#include <unistd.h>
+#include <fcntl.h>
 typedef unsigned int drm_handle_t;
 
 #else
@@ -392,6 +395,95 @@ bool gl_egl_query_dmabuf_modifiers_for_format(EGLDisplay egl_display, uint32_t d
 	return true;
 }
 
+bool gl_egl_query_sync_capabilities(int drm_fd)
+{
+	uint64_t syncobjCap = 0;
+	uint64_t syncobjTimelineCap = 0;
+
+	drmGetCap(drm_fd, DRM_CAP_SYNCOBJ, &syncobjCap);
+	drmGetCap(drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &syncobjTimelineCap);
+	return syncobjCap && syncobjTimelineCap && (EGL_ANDROID_native_fence_sync > 0);
+}
+
+gs_sync_t *gl_egl_create_sync(EGLDisplay egl_display)
+{
+	gs_sync_t *sync = eglCreateSync(egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
+	glFlush();
+	return sync;
+}
+
+gs_sync_t *gl_egl_create_sync_from_syncobj_timeline_point(EGLDisplay egl_display, int drm_fd, int syncobj_fd,
+							  uint64_t timeline_point)
+{
+	EGLSync sync;
+	int syncfile_fd = -1;
+	uint32_t syncobj_handle = 0;
+	uint32_t temp_handle = 0;
+
+	drmSyncobjFDToHandle(drm_fd, syncobj_fd, &syncobj_handle);
+	drmSyncobjTimelineWait(drm_fd, &syncobj_handle, &timeline_point, 1, INT64_MAX,
+			       DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, NULL);
+	drmSyncobjCreate(drm_fd, 0, &temp_handle);
+	drmSyncobjTransfer(drm_fd, temp_handle, 0, syncobj_handle, timeline_point, 0);
+	drmSyncobjExportSyncFile(drm_fd, temp_handle, &syncfile_fd);
+	drmSyncobjDestroy(drm_fd, temp_handle);
+	drmSyncobjDestroy(drm_fd, syncobj_handle);
+
+	const EGLAttrib attributes[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, syncfile_fd, EGL_NONE};
+
+	sync = eglCreateSync(egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, attributes);
+	if (sync == EGL_NO_SYNC) {
+		blog(LOG_ERROR, "Unable to create an EGLSync object from a syncfile fd");
+		close(syncfile_fd);
+	}
+
+	return sync;
+}
+
+bool gl_egl_sync_export_syncobj_timeline_point(EGLDisplay egl_display, gs_sync_t *sync, int drm_fd, int syncobj_fd,
+					       uint64_t timeline_point)
+{
+	uint32_t syncobj_handle = 0;
+	uint32_t temp_handle = 0;
+
+	int gs_sync_fd = eglDupNativeFenceFDANDROID(egl_display, sync);
+	if (gs_sync_fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
+		return false;
+	}
+
+	drmSyncobjFDToHandle(drm_fd, syncobj_fd, &syncobj_handle);
+	drmSyncobjCreate(drm_fd, 0, &temp_handle);
+	drmSyncobjImportSyncFile(drm_fd, temp_handle, gs_sync_fd);
+	drmSyncobjTransfer(drm_fd, syncobj_handle, timeline_point, temp_handle, 0, 0);
+	drmSyncobjDestroy(drm_fd, temp_handle);
+	drmSyncobjDestroy(drm_fd, syncobj_handle);
+	close(gs_sync_fd);
+
+	return true;
+}
+
+bool gl_egl_sync_signal_syncobj_timeline_point(int drm_fd, int syncobj_fd, uint64_t timeline_point)
+{
+	uint32_t syncobj_handle = 0;
+	bool result;
+
+	drmSyncobjFDToHandle(drm_fd, syncobj_fd, &syncobj_handle);
+	result = drmSyncobjTimelineSignal(drm_fd, &syncobj_handle, &timeline_point, 1) == 0;
+	drmSyncobjDestroy(drm_fd, syncobj_handle);
+
+	return result;
+}
+
+void gl_egl_device_sync_destroy(EGLDisplay egl_display, gs_sync_t *sync)
+{
+	eglDestroySync(egl_display, sync);
+}
+
+bool gl_egl_sync_wait(EGLDisplay egl_display, gs_sync_t *sync)
+{
+	return eglWaitSync(egl_display, sync, 0);
+}
+
 const char *gl_egl_error_to_string(EGLint error_number)
 {
 	switch (error_number) {
@@ -445,3 +537,26 @@ const char *gl_egl_error_to_string(EGLint error_number)
 		break;
 	}
 }
+
+int get_drm_render_node_fd(EGLDisplay egl_display)
+{
+	EGLAttrib attrib;
+	EGLDeviceEXT device;
+	const char *drm_render_node_path;
+
+	if (eglQueryDisplayAttribEXT(egl_display, EGL_DEVICE_EXT, &attrib) != EGL_TRUE) {
+		return -1;
+	}
+	device = (EGLDeviceEXT)attrib;
+	drm_render_node_path = eglQueryDeviceStringEXT(device, EGL_DRM_RENDER_NODE_FILE_EXT);
+	if (drm_render_node_path == NULL) {
+		return -1;
+	}
+
+	return open(drm_render_node_path, O_RDWR | O_CLOEXEC);
+}
+
+void close_drm_render_node_fd(int fd)
+{
+	close(fd);
+}

+ 20 - 0
libobs-opengl/gl-egl-common.h

@@ -6,6 +6,10 @@
 
 const char *gl_egl_error_to_string(EGLint error_number);
 
+int get_drm_render_node_fd(EGLDisplay egl_display);
+
+void close_drm_render_node_fd(int fd);
+
 struct gs_texture *gl_egl_create_dmabuf_image(EGLDisplay egl_display, unsigned int width, unsigned int height,
 					      uint32_t drm_format, enum gs_color_format color_format, uint32_t n_planes,
 					      const int *fds, const uint32_t *strides, const uint32_t *offsets,
@@ -24,3 +28,19 @@ struct gs_texture *gl_egl_create_texture_from_pixmap(EGLDisplay egl_display, uin
 bool gl_egl_enum_adapters(EGLDisplay display, bool (*callback)(void *param, const char *name, uint32_t id),
 			  void *param);
 uint32_t gs_get_adapter_count();
+
+bool gl_egl_query_sync_capabilities(int drm_fd);
+
+gs_sync_t *gl_egl_create_sync(EGLDisplay egl_display);
+
+gs_sync_t *gl_egl_create_sync_from_syncobj_timeline_point(EGLDisplay egl_display, int drm_fd, int syncobj_fd,
+							  uint64_t timeline_point);
+
+void gl_egl_device_sync_destroy(EGLDisplay egl_display, gs_sync_t *sync);
+
+bool gl_egl_sync_export_syncobj_timeline_point(EGLDisplay egl_display, gs_sync_t *sync, int drm_fd, int syncobj_fd,
+					       uint64_t timeline_point);
+
+bool gl_egl_sync_signal_syncobj_timeline_point(int drm_fd, int syncobj_fd, uint64_t timeline_point);
+
+bool gl_egl_sync_wait(EGLDisplay egl_display, gs_sync_t *sync);

+ 36 - 0
libobs-opengl/gl-nix.c

@@ -170,3 +170,39 @@ struct gs_texture *device_texture_create_from_pixmap(gs_device_t *device, uint32
 {
 	return gl_vtable->device_texture_create_from_pixmap(device, width, height, color_format, target, pixmap);
 }
+
+bool device_query_sync_capabilities(gs_device_t *device)
+{
+	return gl_vtable->device_query_sync_capabilities(device);
+}
+
+gs_sync_t *device_sync_create(gs_device_t *device)
+{
+	return gl_vtable->device_sync_create(device);
+}
+
+gs_sync_t *device_sync_create_from_syncobj_timeline_point(gs_device_t *device, int syncobj_fd, uint64_t timeline_point)
+{
+	return gl_vtable->device_sync_create_from_syncobj_timeline_point(device, syncobj_fd, timeline_point);
+}
+
+void device_sync_destroy(gs_device_t *device, gs_sync_t *sync)
+{
+	return gl_vtable->device_sync_destroy(device, sync);
+}
+
+bool device_sync_export_syncobj_timeline_point(gs_device_t *device, gs_sync_t *sync, int syncobj_fd,
+					       uint64_t timeline_point)
+{
+	return gl_vtable->device_sync_export_syncobj_timeline_point(device, sync, syncobj_fd, timeline_point);
+}
+
+bool device_sync_signal_syncobj_timeline_point(gs_device_t *device, int syncobj_fd, uint64_t timeline_point)
+{
+	return gl_vtable->device_sync_signal_syncobj_timeline_point(device, syncobj_fd, timeline_point);
+}
+
+bool device_sync_wait(gs_device_t *device, gs_sync_t *sync)
+{
+	return gl_vtable->device_sync_wait(device, sync);
+}

+ 16 - 0
libobs-opengl/gl-nix.h

@@ -67,4 +67,20 @@ struct gl_winsys_vtable {
 								void *pixmap);
 	bool (*device_enum_adapters)(gs_device_t *device, bool (*callback)(void *param, const char *name, uint32_t id),
 				     void *param);
+
+	bool (*device_query_sync_capabilities)(gs_device_t *device);
+
+	gs_sync_t *(*device_sync_create)(gs_device_t *device);
+
+	gs_sync_t *(*device_sync_create_from_syncobj_timeline_point)(gs_device_t *device, int syncobj_fd,
+								     uint64_t timeline_point);
+
+	void (*device_sync_destroy)(gs_device_t *device, gs_sync_t *sync);
+
+	bool (*device_sync_export_syncobj_timeline_point)(gs_device_t *device, gs_sync_t *sync, int syncobj_fd,
+							  uint64_t timeline_point);
+
+	bool (*device_sync_signal_syncobj_timeline_point)(gs_device_t *device, int syncobj_fd, uint64_t timeline_point);
+
+	bool (*device_sync_wait)(gs_device_t *device, gs_sync_t *sync);
 };

+ 2 - 0
libobs-opengl/gl-subsystem.h

@@ -658,6 +658,8 @@ struct gs_device {
 	struct fbo_info *cur_fbo;
 };
 
+typedef void *gs_sync;
+
 extern struct fbo_info *get_fbo(gs_texture_t *tex, uint32_t width, uint32_t height);
 
 extern void gl_update(gs_device_t *device);

+ 67 - 0
libobs-opengl/gl-wayland-egl.c

@@ -90,6 +90,8 @@ struct gl_platform {
 	EGLDisplay display;
 	EGLConfig config;
 	EGLContext context;
+
+	int drm_fd;
 };
 
 struct gl_windowinfo *gl_wayland_egl_windowinfo_create(const struct gs_init_data *info)
@@ -227,6 +229,11 @@ static struct gl_platform *gl_wayland_egl_platform_create(gs_device_t *device, u
 		goto fail_load_egl;
 	}
 
+	plat->drm_fd = get_drm_render_node_fd(plat->display);
+	if (plat->drm_fd < 0) {
+		blog(LOG_WARNING, "Unable to open DRM render node.");
+	}
+
 	goto success;
 
 fail_load_egl:
@@ -247,6 +254,7 @@ static void gl_wayland_egl_platform_destroy(struct gl_platform *plat)
 	if (plat) {
 		egl_context_destroy(plat);
 		eglTerminate(plat->display);
+		close_drm_render_node_fd(plat->drm_fd);
 		bfree(plat);
 	}
 }
@@ -381,6 +389,58 @@ static bool gl_wayland_egl_enum_adapters(gs_device_t *device,
 	return gl_egl_enum_adapters(device->plat->display, callback, param);
 }
 
+static bool gl_wayland_egl_device_query_sync_capabilities(gs_device_t *device)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_query_sync_capabilities(plat->drm_fd);
+}
+
+static gs_sync_t *gl_wayland_egl_device_sync_create(gs_device_t *device)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_create_sync(plat->display);
+}
+
+static gs_sync_t *gl_wayland_egl_device_sync_create_from_syncobj_timeline_point(gs_device_t *device, int syncobj_fd,
+										uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_create_sync_from_syncobj_timeline_point(plat->display, plat->drm_fd, syncobj_fd, timeline_point);
+}
+
+static void gl_wayland_egl_device_sync_destroy(gs_device_t *device, gs_sync_t *sync)
+{
+	struct gl_platform *plat = device->plat;
+
+	gl_egl_device_sync_destroy(plat->display, sync);
+}
+
+static bool gl_wayland_egl_device_sync_export_syncobj_timeline_point(gs_device_t *device, gs_sync_t *sync,
+								     int syncobj_fd, uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_export_syncobj_timeline_point(plat->display, sync, plat->drm_fd, syncobj_fd, timeline_point);
+}
+
+static bool gl_wayland_egl_device_sync_signal_syncobj_timeline_point(gs_device_t *device, int syncobj_fd,
+								     uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_signal_syncobj_timeline_point(plat->drm_fd, syncobj_fd, timeline_point);
+}
+
+static bool gl_wayland_egl_device_sync_wait(gs_device_t *device, gs_sync_t *sync)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_wait(plat->display, sync);
+}
+
 static const struct gl_winsys_vtable egl_wayland_winsys_vtable = {
 	.windowinfo_create = gl_wayland_egl_windowinfo_create,
 	.windowinfo_destroy = gl_wayland_egl_windowinfo_destroy,
@@ -401,6 +461,13 @@ static const struct gl_winsys_vtable egl_wayland_winsys_vtable = {
 	.device_query_dmabuf_modifiers_for_format = gl_wayland_egl_device_query_dmabuf_modifiers_for_format,
 	.device_texture_create_from_pixmap = gl_wayland_egl_device_texture_create_from_pixmap,
 	.device_enum_adapters = gl_wayland_egl_enum_adapters,
+	.device_query_sync_capabilities = gl_wayland_egl_device_query_sync_capabilities,
+	.device_sync_create = gl_wayland_egl_device_sync_create,
+	.device_sync_create_from_syncobj_timeline_point = gl_wayland_egl_device_sync_create_from_syncobj_timeline_point,
+	.device_sync_destroy = gl_wayland_egl_device_sync_destroy,
+	.device_sync_export_syncobj_timeline_point = gl_wayland_egl_device_sync_export_syncobj_timeline_point,
+	.device_sync_signal_syncobj_timeline_point = gl_wayland_egl_device_sync_signal_syncobj_timeline_point,
+	.device_sync_wait = gl_wayland_egl_device_sync_wait,
 };
 
 const struct gl_winsys_vtable *gl_wayland_egl_get_winsys_vtable(void)

+ 67 - 0
libobs-opengl/gl-x11-egl.c

@@ -83,6 +83,7 @@ struct gl_platform {
 	EGLContext context;
 	EGLSurface pbuffer;
 	bool close_xdisplay;
+	int drm_fd;
 };
 
 /* The following utility function is copied verbatim from GLX code. */
@@ -342,6 +343,11 @@ static struct gl_platform *gl_x11_egl_platform_create(gs_device_t *device, uint3
 		goto fail_load_gl;
 	}
 
+	plat->drm_fd = get_drm_render_node_fd(plat->edisplay);
+	if (plat->drm_fd < 0) {
+		blog(LOG_WARNING, "Unable to open DRM render node.");
+	}
+
 	goto success;
 
 fail_make_current:
@@ -367,6 +373,7 @@ static void gl_x11_egl_platform_destroy(struct gl_platform *plat)
 	eglTerminate(plat->edisplay);
 	if (plat->close_xdisplay)
 		XCloseDisplay(plat->xdisplay);
+	close_drm_render_node_fd(plat->drm_fd);
 	bfree(plat);
 }
 
@@ -571,6 +578,59 @@ static bool gl_x11_egl_enum_adapters(gs_device_t *device, bool (*callback)(void
 	return gl_egl_enum_adapters(device->plat->edisplay, callback, param);
 }
 
+static bool gl_x11_egl_device_query_sync_capabilities(gs_device_t *device)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_query_sync_capabilities(plat->drm_fd);
+}
+
+static gs_sync_t *gl_x11_egl_device_sync_create(gs_device_t *device)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_create_sync(plat->edisplay);
+}
+
+static gs_sync_t *gl_x11_egl_device_sync_create_from_syncobj_timeline_point(gs_device_t *device, int syncobj_fd,
+									    uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_create_sync_from_syncobj_timeline_point(plat->edisplay, plat->drm_fd, syncobj_fd, timeline_point);
+}
+
+static void gl_x11_egl_device_sync_destroy(gs_device_t *device, gs_sync_t *sync)
+{
+	struct gl_platform *plat = device->plat;
+
+	gl_egl_device_sync_destroy(plat->edisplay, sync);
+}
+
+static bool gl_x11_egl_device_sync_export_syncobj_timeline_point(gs_device_t *device, gs_sync_t *sync, int syncobj_fd,
+								 uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_export_syncobj_timeline_point(plat->edisplay, sync, plat->drm_fd, syncobj_fd,
+							 timeline_point);
+}
+
+static bool gl_x11_egl_device_sync_signal_syncobj_timeline_point(gs_device_t *device, int syncobj_fd,
+								 uint64_t timeline_point)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_signal_syncobj_timeline_point(plat->drm_fd, syncobj_fd, timeline_point);
+}
+
+static bool gl_x11_egl_device_sync_wait(gs_device_t *device, gs_sync_t *sync)
+{
+	struct gl_platform *plat = device->plat;
+
+	return gl_egl_sync_wait(plat->edisplay, sync);
+}
+
 static const struct gl_winsys_vtable egl_x11_winsys_vtable = {
 	.windowinfo_create = gl_x11_egl_windowinfo_create,
 	.windowinfo_destroy = gl_x11_egl_windowinfo_destroy,
@@ -591,6 +651,13 @@ static const struct gl_winsys_vtable egl_x11_winsys_vtable = {
 	.device_query_dmabuf_modifiers_for_format = gl_x11_egl_device_query_dmabuf_modifiers_for_format,
 	.device_texture_create_from_pixmap = gl_x11_egl_device_texture_create_from_pixmap,
 	.device_enum_adapters = gl_x11_egl_enum_adapters,
+	.device_query_sync_capabilities = gl_x11_egl_device_query_sync_capabilities,
+	.device_sync_create = gl_x11_egl_device_sync_create,
+	.device_sync_create_from_syncobj_timeline_point = gl_x11_egl_device_sync_create_from_syncobj_timeline_point,
+	.device_sync_destroy = gl_x11_egl_device_sync_destroy,
+	.device_sync_export_syncobj_timeline_point = gl_x11_egl_device_sync_export_syncobj_timeline_point,
+	.device_sync_signal_syncobj_timeline_point = gl_x11_egl_device_sync_signal_syncobj_timeline_point,
+	.device_sync_wait = gl_x11_egl_device_sync_wait,
 };
 
 const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void)

+ 16 - 0
libobs/graphics/device-exports.h

@@ -150,6 +150,22 @@ EXPORT bool device_query_dmabuf_modifiers_for_format(gs_device_t *device, uint32
 EXPORT gs_texture_t *device_texture_create_from_pixmap(gs_device_t *device, uint32_t width, uint32_t height,
 						       enum gs_color_format color_format, uint32_t target,
 						       void *pixmap);
+
+EXPORT bool device_query_sync_capabilities(gs_device_t *device);
+
+EXPORT gs_sync_t *device_sync_create(gs_device_t *device);
+
+EXPORT gs_sync_t *device_sync_create_from_syncobj_timeline_point(gs_device_t *device, int syncobj_fd,
+								 uint64_t timeline_point);
+
+EXPORT void device_sync_destroy(gs_device_t *device, gs_sync_t *sync);
+
+EXPORT bool device_sync_export_syncobj_timeline_point(gs_device_t *device, gs_sync_t *sync, int syncobj_fd,
+						      uint64_t timeline_point);
+
+EXPORT bool device_sync_signal_syncobj_timeline_point(gs_device_t *device, int syncobj_fd, uint64_t timeline_point);
+
+EXPORT bool device_sync_wait(gs_device_t *device, gs_sync_t *sync);
 #endif
 
 #ifdef __cplusplus

+ 7 - 0
libobs/graphics/graphics-imports.c

@@ -242,6 +242,13 @@ bool load_graphics_imports(struct gs_exports *exports, void *module, const char
 	GRAPHICS_IMPORT(device_query_dmabuf_capabilities);
 	GRAPHICS_IMPORT(device_query_dmabuf_modifiers_for_format);
 	GRAPHICS_IMPORT(device_texture_create_from_pixmap);
+	GRAPHICS_IMPORT(device_query_sync_capabilities);
+	GRAPHICS_IMPORT(device_sync_create);
+	GRAPHICS_IMPORT(device_sync_create_from_syncobj_timeline_point);
+	GRAPHICS_IMPORT(device_sync_destroy);
+	GRAPHICS_IMPORT(device_sync_export_syncobj_timeline_point);
+	GRAPHICS_IMPORT(device_sync_signal_syncobj_timeline_point);
+	GRAPHICS_IMPORT(device_sync_wait);
 #endif
 
 	return success;

+ 9 - 0
libobs/graphics/graphics-internal.h

@@ -264,6 +264,15 @@ struct gs_exports {
 	struct gs_texture *(*device_texture_create_from_pixmap)(gs_device_t *device, uint32_t width, uint32_t height,
 								enum gs_color_format color_format, uint32_t target,
 								void *pixmap);
+	bool (*device_query_sync_capabilities)(gs_device_t *device);
+	gs_sync_t *(*device_sync_create)(gs_device_t *device);
+	gs_sync_t *(*device_sync_create_from_syncobj_timeline_point)(gs_device_t *device, int syncobj_fd,
+								     uint64_t timeline_point);
+	void (*device_sync_destroy)(gs_device_t *device, gs_sync_t *sync);
+	bool (*device_sync_export_syncobj_timeline_point)(gs_device_t *device, gs_sync_t *sync, int syncobj_fd,
+							  uint64_t timeline_point);
+	bool (*device_sync_signal_syncobj_timeline_point)(gs_device_t *device, int syncobj_fd, uint64_t timeline_point);
+	bool (*device_sync_wait)(gs_device_t *device, gs_sync_t *sync);
 #endif
 };
 

+ 52 - 0
libobs/graphics/graphics.c

@@ -1371,6 +1371,58 @@ gs_texture_t *gs_texture_create_from_pixmap(uint32_t width, uint32_t height, enu
 								   target, pixmap);
 }
 
+bool gs_query_sync_capabilities(void)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_query_sync_capabilities(graphics->device);
+}
+
+gs_sync_t *gs_sync_create(void)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_create(graphics->device);
+}
+
+gs_sync_t *gs_sync_create_from_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_create_from_syncobj_timeline_point(graphics->device, syncobj_fd,
+										timeline_point);
+}
+
+void gs_sync_destroy(gs_sync_t *sync)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_destroy(graphics->device, sync);
+}
+
+bool gs_sync_export_syncobj_timeline_point(gs_sync_t *sync, int syncobj_fd, uint64_t timeline_point)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_export_syncobj_timeline_point(graphics->device, sync, syncobj_fd,
+									   timeline_point);
+}
+
+bool gs_sync_signal_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_signal_syncobj_timeline_point(graphics->device, syncobj_fd,
+									   timeline_point);
+}
+
+bool gs_sync_wait(gs_sync_t *sync)
+{
+	graphics_t *graphics = thread_graphics;
+
+	return graphics->exports.device_sync_wait(graphics->device, sync);
+}
+
 #endif
 
 gs_texture_t *gs_cubetexture_create(uint32_t size, enum gs_color_format color_format, uint32_t levels,

+ 14 - 0
libobs/graphics/graphics.h

@@ -299,6 +299,7 @@ typedef struct gs_effect_technique gs_technique_t;
 typedef struct gs_effect_pass gs_epass_t;
 typedef struct gs_effect_param gs_eparam_t;
 typedef struct gs_device gs_device_t;
+typedef void gs_sync_t;
 typedef struct graphics_subsystem graphics_t;
 
 /* ---------------------------------------------------
@@ -899,6 +900,19 @@ EXPORT bool gs_query_dmabuf_modifiers_for_format(uint32_t drm_format, uint64_t *
 EXPORT gs_texture_t *gs_texture_create_from_pixmap(uint32_t width, uint32_t height, enum gs_color_format color_format,
 						   uint32_t target, void *pixmap);
 
+EXPORT bool gs_query_sync_capabilities(void);
+
+EXPORT gs_sync_t *gs_sync_create(void);
+
+EXPORT gs_sync_t *gs_sync_create_from_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point);
+
+EXPORT void gs_sync_destroy(gs_sync_t *sync);
+
+EXPORT bool gs_sync_export_syncobj_timeline_point(gs_sync_t *sync, int syncobj_fd, uint64_t timeline_point);
+
+EXPORT bool gs_sync_signal_syncobj_timeline_point(int syncobj_fd, uint64_t timeline_point);
+
+EXPORT bool gs_sync_wait(gs_sync_t *sync);
 #endif
 
 /* inline functions used by modules */