diff --git a/Makefile.am b/Makefile.am index 4d86f75..66cc8d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -80,6 +80,12 @@ libnvidia_egl_wayland_la_dmabuf_built_client_headers = \ libnvidia_egl_wayland_la_dmabuf_built_private_protocols = \ linux-dmabuf-unstable-v1-protocol.c +libnvidia_egl_wayland_la_drm_syncobj_built_client_headers = \ + linux-drm-syncobj-v1-client-protocol.h + +libnvidia_egl_wayland_la_drm_syncobj_built_private_protocols = \ + linux-drm-syncobj-v1-protocol.c + libnvidia_egl_wayland_la_presentation_time_built_client_headers = \ presentation-time-client-protocol.h @@ -93,6 +99,8 @@ libnvidia_egl_wayland_la_built_sources = \ $(libnvidia_egl_wayland_la_built_server_headers) \ $(libnvidia_egl_wayland_la_dmabuf_built_client_headers) \ $(libnvidia_egl_wayland_la_dmabuf_built_private_protocols) \ + $(libnvidia_egl_wayland_la_drm_syncobj_built_client_headers) \ + $(libnvidia_egl_wayland_la_drm_syncobj_built_private_protocols) \ $(libnvidia_egl_wayland_la_presentation_time_built_client_headers) \ $(libnvidia_egl_wayland_la_presentation_time_private_protocols) @@ -129,6 +137,12 @@ $(libnvidia_egl_wayland_la_dmabuf_built_private_protocols):%-protocol.c : $(WAYL $(libnvidia_egl_wayland_la_dmabuf_built_client_headers):%-client-protocol.h : $(WAYLAND_PROTOCOLS_DATADIR)/unstable/linux-dmabuf/%.xml $(AM_V_GEN)$(WAYLAND_SCANNER) client-header < $< > $@ +$(libnvidia_egl_wayland_la_drm_syncobj_built_private_protocols):%-protocol.c : $(WAYLAND_PROTOCOLS_DATADIR)/staging/linux-drm-syncobj/%.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) $(WAYLAND_PRIVATE_CODEGEN) < $< > $@ + +$(libnvidia_egl_wayland_la_drm_syncobj_built_client_headers):%-client-protocol.h : $(WAYLAND_PROTOCOLS_DATADIR)/staging/linux-drm-syncobj/%.xml + $(AM_V_GEN)$(WAYLAND_SCANNER) client-header < $< > $@ + $(libnvidia_egl_wayland_la_presentation_time_private_protocols):%-protocol.c : $(WAYLAND_PROTOCOLS_DATADIR)/stable/presentation-time/%.xml $(AM_V_GEN)$(WAYLAND_SCANNER) $(WAYLAND_PRIVATE_CODEGEN) < $< > $@ diff --git a/include/wayland-egldisplay.h b/include/wayland-egldisplay.h index e038668..29318e3 100644 --- a/include/wayland-egldisplay.h +++ b/include/wayland-egldisplay.h @@ -121,6 +121,9 @@ typedef struct WlEglDmaBufFeedbackRec { typedef struct WlEglDisplayRec { WlEglDeviceDpy *devDpy; + /* Supports EGL_ANDROID_native_fence_sync */ + int supports_native_fence_sync; + EGLBoolean ownNativeDpy; struct wl_display *nativeDpy; @@ -128,6 +131,7 @@ typedef struct WlEglDisplayRec { struct wl_eglstream_display *wlStreamDpy; struct wl_eglstream_controller *wlStreamCtl; struct zwp_linux_dmabuf_v1 *wlDmaBuf; + struct wp_linux_drm_syncobj_manager_v1 *wlDrmSyncobj; unsigned int wlStreamCtlVer; struct wp_presentation *wpPresentation; struct wl_event_queue *wlEventQueue; @@ -139,6 +143,9 @@ typedef struct WlEglDisplayRec { WlEglPlatformData *data; + /* DRM device in use */ + int drmFd; + EGLBoolean useInitRefCount; EGLDeviceEXT requestedDevice; diff --git a/include/wayland-eglhandle.h b/include/wayland-eglhandle.h index bd8abaf..9e694f9 100644 --- a/include/wayland-eglhandle.h +++ b/include/wayland-eglhandle.h @@ -108,7 +108,9 @@ typedef struct WlEglPlatformDataRec { PFNEGLCLIENTWAITSYNCKHRPROC clientWaitSync; PFNEGLSIGNALSYNCKHRPROC signalSync; PFNEGLDESTROYSYNCKHRPROC destroySync; + PFNEGLCREATESYNCKHRPROC createSync; PFNEGLSTREAMFLUSHNVPROC streamFlush; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dupNativeFenceFD; /* Used for dma-buf surfaces */ PFNEGLSTREAMIMAGECONSUMERCONNECTNVPROC streamImageConsumerConnect; diff --git a/include/wayland-eglsurface-internal.h b/include/wayland-eglsurface-internal.h index 3af8829..33ccb26 100644 --- a/include/wayland-eglsurface-internal.h +++ b/include/wayland-eglsurface-internal.h @@ -51,6 +51,14 @@ typedef struct WlEglStreamImageRec { struct wl_buffer *buffer; EGLBoolean attached; struct wl_list acquiredLink; + + struct wp_linux_drm_syncobj_timeline_v1 *wlReleaseTimeline; + uint32_t drmSyncobjHandle; + int releasePending; + /* Latest release point the compositor will signal with explicit sync */ + uint64_t releasePoint; + /* Cached acquire EGLSync from acquireImage */ + EGLSyncKHR acquireSync; } WlEglStreamImage; typedef struct WlEglSurfaceCtxRec { @@ -151,6 +159,13 @@ struct WlEglSurfaceRec { EGLBoolean isResized; WlEglDmaBufFeedback feedback; + + /* per-surface Explicit Sync objects */ + struct wp_linux_drm_syncobj_surface_v1 *wlSyncobjSurf; + struct wp_linux_drm_syncobj_timeline_v1 *wlAcquireTimeline; + uint32_t drmSyncobjHandle; + /* Last acquire point used. This starts at 1, zero means invalid. */ + uint64_t syncPoint; }; void wlEglReallocSurface(WlEglDisplay *display, @@ -185,6 +200,9 @@ EGLBoolean wlEglQueryNativeResourceHook(EGLDisplay dpy, EGLint attribute, int *value); +EGLBoolean +wlEglSurfaceCheckReleasePoints(WlEglDisplay *display, WlEglSurface *surface); + EGLBoolean wlEglSendDamageEvent(WlEglSurface *surface, struct wl_event_queue *queue); diff --git a/src/meson.build b/src/meson.build index 56d075b..5a5e4e0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,7 @@ libdrm = dependency('libdrm') wl_protos_dir = wl_protos.get_pkgconfig_variable('pkgdatadir') wl_dmabuf_xml = join_paths(wl_protos_dir, 'unstable', 'linux-dmabuf', 'linux-dmabuf-unstable-v1.xml') wp_presentation_time_xml = join_paths(wl_protos_dir, 'stable', 'presentation-time', 'presentation-time.xml') +wl_drm_syncobj_xml = join_paths(wl_protos_dir, 'staging', 'linux-drm-syncobj', 'linux-drm-syncobj-v1.xml') client_header = generator(prog_scanner, output : '@BASENAME@-client-protocol.h', @@ -65,6 +66,9 @@ src += code.process(wl_dmabuf_xml) src += client_header.process(wp_presentation_time_xml) src += code.process(wp_presentation_time_xml) +src += client_header.process(wl_drm_syncobj_xml) +src += code.process(wl_drm_syncobj_xml) + egl_wayland = library('nvidia-egl-wayland', src, dependencies : [ diff --git a/src/wayland-egldisplay.c b/src/wayland-egldisplay.c index a72c3f4..c3f28cf 100644 --- a/src/wayland-egldisplay.c +++ b/src/wayland-egldisplay.c @@ -32,6 +32,7 @@ #include "wayland-drm-client-protocol.h" #include "wayland-drm.h" #include "presentation-time-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include #include #include @@ -454,6 +455,12 @@ registry_handle_global(void *data, name, &wp_presentation_interface, version); + } else if (strcmp(interface, "wp_linux_drm_syncobj_manager_v1") == 0 && + display->supports_native_fence_sync) { + display->wlDrmSyncobj = wl_registry_bind(registry, + name, + &wp_linux_drm_syncobj_manager_v1_interface, + version); } } @@ -738,6 +745,10 @@ static EGLBoolean terminateDisplay(WlEglDisplay *display, EGLBoolean globalTeard wp_presentation_destroy(display->wpPresentation); display->wpPresentation = NULL; } + if (display->wlDrmSyncobj) { + wp_linux_drm_syncobj_manager_v1_destroy(display->wlDrmSyncobj); + display->wlDrmSyncobj = NULL; + } if (display->wlDmaBuf) { zwp_linux_dmabuf_v1_destroy(display->wlDmaBuf); display->wlDmaBuf = NULL; @@ -926,6 +937,7 @@ EGLDisplay wlEglGetPlatformDisplayExport(void *data, EGLDeviceEXT requestedDevice = EGL_NO_DEVICE_EXT; EGLBoolean usePrimeRenderOffload = EGL_FALSE; EGLBoolean isServerNV; + const char *drmName = NULL; if (platform != EGL_PLATFORM_WAYLAND_EXT) { wlEglSetError(data, EGL_BAD_PARAMETER); @@ -1156,7 +1168,6 @@ EGLDisplay wlEglGetPlatformDisplayExport(void *data, display->primeRenderOffload = EGL_TRUE; } - display->devDpy = wlGetInternalDisplay(pData, eglDevice); if (display->devDpy == NULL) { goto fail; @@ -1168,6 +1179,17 @@ EGLDisplay wlEglGetPlatformDisplayExport(void *data, display->refCount = 1; WL_LIST_INIT(&display->wlEglSurfaceList); + /* Get the DRM device in use */ + drmName = display->data->egl.queryDeviceString(display->devDpy->eglDevice, + EGL_DRM_DEVICE_FILE_EXT); + if (!drmName) { + goto fail; + } + + display->drmFd = open(drmName, O_RDWR | O_CLOEXEC); + if (display->drmFd < 0) { + goto fail; + } // The newly created WlEglDisplay has been set up properly, insert it // in wlEglDisplayList. @@ -1205,6 +1227,7 @@ EGLBoolean wlEglInitializeHook(EGLDisplay dpy, EGLint *major, EGLint *minor) struct wl_display *wrapper = NULL; EGLint err = EGL_SUCCESS; int ret = 0; + const char *dev_exts = NULL; if (!display) { return EGL_FALSE; @@ -1235,6 +1258,11 @@ EGLBoolean wlEglInitializeHook(EGLDisplay dpy, EGLint *major, EGLint *minor) return EGL_FALSE; } + dev_exts = display->data->egl.queryString(display->devDpy->eglDisplay, EGL_EXTENSIONS); + if (dev_exts && wlEglFindExtension("EGL_ANDROID_native_fence_sync", dev_exts)) { + display->supports_native_fence_sync = true; + } + // Set the initCount to 1. If something goes wrong, then terminateDisplay // will clean up and set it back to zero. display->initCount = 1; @@ -1353,6 +1381,7 @@ WlEglDisplay *wlEglAcquireDisplay(EGLDisplay dpy) { static void wlEglUnrefDisplay(WlEglDisplay *display) { if (--display->refCount == 0) { wlEglMutexDestroy(&display->mutex); + close(display->drmFd); free(display); } } diff --git a/src/wayland-eglhandle.c b/src/wayland-eglhandle.c index e0080f1..df01d6e 100644 --- a/src/wayland-eglhandle.c +++ b/src/wayland-eglhandle.c @@ -111,6 +111,8 @@ wlEglCreatePlatformData(int apiMajor, int apiMinor, const EGLExtDriver *driver) GET_PROC(clientWaitSync, eglClientWaitSyncKHR); GET_PROC(signalSync, eglSignalSyncKHR); GET_PROC(destroySync, eglDestroySyncKHR); + GET_PROC(createSync, eglCreateSyncKHR); + GET_PROC(dupNativeFenceFD, eglDupNativeFenceFDANDROID); /* Stream flush */ GET_PROC(streamFlush, eglStreamFlushNV); diff --git a/src/wayland-eglsurface.c b/src/wayland-eglsurface.c index 98f38cf..0c28a4f 100644 --- a/src/wayland-eglsurface.c +++ b/src/wayland-eglsurface.c @@ -24,6 +24,7 @@ #include "wayland-eglstream-client-protocol.h" #include "wayland-eglstream-controller-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include "wayland-eglstream-server.h" #include "wayland-thread.h" #include "wayland-eglutils.h" @@ -42,6 +43,8 @@ #include #include #include +#include +#include #define WL_EGL_WINDOW_DESTROY_CALLBACK_SINCE 3 @@ -154,6 +157,89 @@ EGLint wlEglWaitFrameSync(WlEglSurface *surface) return EGL_SUCCESS; } +static bool +syncobj_import_fd_to_current_point(WlEglDisplay *display, WlEglSurface *surface, + int syncFd) +{ + bool ret = false; + uint32_t tmpSyncobj; + + /* Import our syncfd at a new release point */ + if (drmSyncobjCreate(display->drmFd, 0, &tmpSyncobj) != 0) { + return false; + } + + if (drmSyncobjImportSyncFile(display->drmFd, tmpSyncobj, syncFd) != 0) { + goto end; + } + + if (drmSyncobjTransfer(display->drmFd, surface->drmSyncobjHandle, + surface->syncPoint, tmpSyncobj, 0, 0) != 0) { + goto end; + } + + ret = true; + +end: + drmSyncobjDestroy(display->drmFd, tmpSyncobj); + + return ret; +} + +static bool +send_explicit_sync_points (WlEglDisplay *display, WlEglSurface *surface, + WlEglStreamImage *image) +{ + WlEglPlatformData *data = display->data; + EGLDisplay dpy = display->devDpy->eglDisplay; + int syncFd, err; + uint64_t acquireSyncPoint; + + /* Ignore this unless we are using Explicit Sync */ + if (!surface->wlSyncobjSurf) { + return true; + } + + /* --------------- Get acquire sync fd -------------- */ + syncFd = data->egl.dupNativeFenceFD(dpy, image->acquireSync); + if (syncFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + return false; + } + + /* Clean up our acquire sync object now that we are done with it */ + data->egl.destroySync(dpy, image->acquireSync); + image->acquireSync = EGL_NO_SYNC_KHR; + + err = syncobj_import_fd_to_current_point(display, surface, syncFd); + close(syncFd); + if (!err) { + return false; + } + acquireSyncPoint = surface->syncPoint++; + + /* --------------- Get release EGLSyncKHR -------------- */ + + /* Increment to a new sync point here in the image. */ + image->releasePoint++; + image->releasePending = true; + + /* --------------- Send sync points -------------- */ + + /* Now notify the compositor of our next acquire point */ + wp_linux_drm_syncobj_surface_v1_set_acquire_point(surface->wlSyncobjSurf, + surface->wlAcquireTimeline, + acquireSyncPoint >> 32, + acquireSyncPoint & 0xffffffff); + + /* Now notify the compositor of our next release point */ + wp_linux_drm_syncobj_surface_v1_set_release_point(surface->wlSyncobjSurf, + image->wlReleaseTimeline, + image->releasePoint >> 32, + image->releasePoint & 0xffffffff); + + return true; +} + EGLBoolean wlEglSendDamageEvent(WlEglSurface *surface, struct wl_event_queue *queue) { @@ -183,6 +269,15 @@ wlEglSendDamageEvent(WlEglSurface *surface, struct wl_event_queue *queue) surface->ctx.currentBuffer, surface->dx, surface->dy); + + /* + * Send our explicit sync acquire and release points. This needs to be done + * as part of the surface attach as it is a protocol error to specify these + * points without attaching a buffer in the same commit. + */ + if (!send_explicit_sync_points(surface->wlEglDpy, surface, image)) { + return EGL_FALSE; + } } wl_surface_damage(surface->wlSurface, 0, 0, @@ -473,6 +568,7 @@ destroy_stream_image(WlEglDisplay *display, assert(image->eglImage != EGL_NO_IMAGE_KHR); data->egl.destroyImage(dpy, image->eglImage); image->eglImage = EGL_NO_IMAGE_KHR; + image->releasePoint = 0; if (surface->ctx.currentBuffer == image->buffer) { surface->ctx.currentBuffer = NULL; @@ -487,6 +583,16 @@ destroy_stream_image(WlEglDisplay *display, wl_list_remove(&image->acquiredLink); wl_list_init(&image->acquiredLink); } + + if (image->wlReleaseTimeline) { + wp_linux_drm_syncobj_timeline_v1_destroy(image->wlReleaseTimeline); + drmSyncobjDestroy(display->drmFd, image->drmSyncobjHandle); + if (image->acquireSync != EGL_NO_SYNC_KHR) { + data->egl.destroySync(dpy, image->acquireSync); + image->acquireSync = EGL_NO_SYNC_KHR; + } + image->releasePending = false; + } } static void @@ -938,6 +1044,14 @@ stream_local_buffer_release_callback(void *ptr, struct wl_buffer *buffer) image->attached = EGL_FALSE; if (image->eglImage != EGL_NO_IMAGE_KHR) { + /* + * Release our image back to the stream if explicit sync is not in use + * + * If explicit sync was used, then wl_buffer.release means nothing. We + * will instead have already marked this image for release contingent + * on the release sync getting signaled. This callback doesn't even fire + * in that scenario. + */ data->egl.streamReleaseImage(display->devDpy->eglDisplay, surface->ctx.eglStream, image->eglImage, @@ -958,6 +1072,173 @@ static const struct wl_buffer_listener stream_local_buffer_listener = { stream_local_buffer_release_callback, }; +/* + * Export a syncfd from the timeline at the specified point and make an + * EGLSyncKHR out of it. We can then pass this eglsync to releaseImageNV and + * it will wait for the release point to signal before releasing the image back + * to the screen. + */ +static EGLSyncKHR +get_release_sync(WlEglDisplay *display, WlEglStreamImage *image) +{ + EGLDisplay dpy = display->devDpy->eglDisplay; + WlEglPlatformData *data = display->data; + EGLSyncKHR eglSync = EGL_NO_SYNC_KHR; + int syncFd = -1; + uint32_t tmpSyncobj; + EGLint attribs[3]; + + + /* Import our acquire syncfd at a new acquire point */ + if (drmSyncobjCreate(display->drmFd, 0, &tmpSyncobj) != 0) { + return EGL_NO_SYNC_KHR; + } + + if (drmSyncobjTransfer(display->drmFd, tmpSyncobj, 0, + image->drmSyncobjHandle, image->releasePoint, + 0) != 0) { + goto destroy; + } + + if (drmSyncobjExportSyncFile(display->drmFd, tmpSyncobj, + &syncFd) != 0) { + goto destroy; + } + + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = syncFd; + attribs[2] = EGL_NONE; + eglSync = data->egl.createSync(dpy, EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); + close (syncFd); +destroy: + drmSyncobjDestroy(display->drmFd, tmpSyncobj); + + return eglSync; +} + +/* + * We have committed a frame, and if we are using explicit sync + * we will have registered a release point with the compositor. + * The release point's fence didn't exist then, so we should check + * for any available fences that we should trigger releasing + * images back into the stream with. + * + * This will block if no available buffers have been released. + */ +EGLBoolean +wlEglSurfaceCheckReleasePoints(WlEglDisplay *display, WlEglSurface *surface) +{ + WlEglPlatformData *data = display->data; + EGLDisplay dpy = display->devDpy->eglDisplay; + EGLSyncKHR releaseSync = EGL_NO_SYNC_KHR; + WlEglStreamImage *image = NULL; + uint32_t *syncobjs; + uint64_t *syncPoints; + uint32_t i, firstSignaled, numSyncPoints = 0; + int64_t timeout; + EGLBoolean ret = EGL_FALSE; + + if (!surface->wlSyncobjSurf) { + return EGL_TRUE; + } + + syncobjs = calloc(surface->ctx.numStreamImages, sizeof(uint32_t)); + syncPoints = calloc(surface->ctx.numStreamImages, sizeof(uint64_t)); + if (!syncobjs || !syncPoints) { + return EGL_FALSE; + } + + for (i = 0; i < surface->ctx.numStreamImages; i++) { + pthread_mutex_lock(&surface->ctx.streamImages[i]->mutex); + } + + /* record each release point we are waiting on */ + for (i = 0; i < surface->ctx.numStreamImages; i++) { + syncobjs[i] = surface->ctx.streamImages[i]->drmSyncobjHandle; + + if (surface->ctx.streamImages[i]->releasePending) { + syncPoints[i] = surface->ctx.streamImages[i]->releasePoint; + numSyncPoints++; + } else { + /* + * Use a bogus point for acquired images so we can keep our indices + * the same. This won't affect anything since it will never have a fence + * appear. + */ + syncPoints[i] = UINT64_MAX; + } + } + + if (numSyncPoints == 0) { + goto end; + } + + /* + * Wait for at least one release point to have a fence. We need to block here + * since the streams internal code expects to have at least one buffer placed + * back on the release (internally called returns) queue. + * + * We only wait indefinitely when there are at least two buffers pending. + * One of the pending buffers will be in use by the compositor, and the + * other should have its release point signaled. Without this we might block + * after the first buffer has been committed. + */ + timeout = numSyncPoints >= 2 ? INT64_MAX : 0; + + /* + * The Linux docs say that DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE should be + * used to wait for a fence to appear without waiting on the fence itself. + * Note that there are some bugs with older kernels where this may not + * signal correctly. + */ + if (drmSyncobjTimelineWait(display->drmFd, syncobjs, syncPoints, + surface->ctx.numStreamImages, timeout, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, + &firstSignaled) != 0) { + goto end; + } + + image = surface->ctx.streamImages[firstSignaled]; + + /* Try to get a release point for the first available buffer. */ + releaseSync = get_release_sync(display, image); + if (releaseSync == EGL_NO_SYNC_KHR) { + goto end; + } + + /* + * Pass our newly created release EGLSyncKHR to our eglstream, it + * will wait for it to signal before it releases the image back to + * the stream. Note that wl_buffer.release means nothing with + * linux-drm-syncobj-v1. + */ + ret = data->egl.streamReleaseImage(display->devDpy->eglDisplay, + surface->ctx.eglStream, + image->eglImage, + releaseSync); + /* releaseImage makes a copy, so we destroy ours here */ + data->egl.destroySync(dpy, releaseSync); + + /* + * If we succesfully released the image, Clear our release point so we + * don't repeat this. + */ + if (ret == EGL_TRUE) { + image->releasePending = false; + } + +end: + for (i = 0; i < surface->ctx.numStreamImages; i++) { + pthread_mutex_unlock(&surface->ctx.streamImages[i]->mutex); + } + + free(syncPoints); + free(syncobjs); + + return ret; +} + static EGLint acquire_surface_image(WlEglDisplay *display, WlEglSurface *surface) { @@ -974,12 +1255,33 @@ acquire_surface_image(WlEglDisplay *display, WlEglSurface *surface) EGLint offset; uint32_t i; int fd; + EGLSyncKHR acquireSync = EGL_NO_SYNC_KHR; + const EGLint attribs[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, EGL_NO_NATIVE_FENCE_FD_ANDROID, + EGL_SYNC_STATUS, EGL_SIGNALED, + EGL_NONE, + }; + + if (surface->wlSyncobjSurf) { + /* + * don't flush before acquireImage, we have to pass it in signaled. + * + * acquireImage will reset this, causing the fd to populate. + */ + acquireSync = data->egl.createSync(dpy, EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); + if (acquireSync == EGL_NO_SYNC_KHR) { + return EGL_BAD_SURFACE; + } + } if (!data->egl.streamAcquireImage(dpy, surface->ctx.eglStream, &eglImage, - EGL_NO_SYNC_KHR)) { - return EGL_BAD_SURFACE; + acquireSync)) { + if (acquireSync != EGL_NO_SYNC_KHR) { + goto fail_destroy_sync; + } } for (i = 0; i < surface->ctx.numStreamImages; i++) { @@ -990,11 +1292,13 @@ acquire_surface_image(WlEglDisplay *display, WlEglSurface *surface) } if (!image) { - goto fail_release; + goto fail_destroy_sync; } pthread_mutex_lock(&image->mutex); + image->acquireSync = acquireSync; + if (!image->buffer) { if (!data->egl.exportDMABUFImageQuery(dpy, eglImage, @@ -1058,7 +1362,8 @@ acquire_surface_image(WlEglDisplay *display, WlEglSurface *surface) goto fail_release; } - if (wl_buffer_add_listener(image->buffer, + if (!surface->wlSyncobjSurf && + wl_buffer_add_listener(image->buffer, &stream_local_buffer_listener, image) == -1) { wl_buffer_destroy(image->buffer); @@ -1081,12 +1386,23 @@ acquire_surface_image(WlEglDisplay *display, WlEglSurface *surface) eglImage, EGL_NO_SYNC_KHR); + if (image && image->acquireSync != EGL_NO_SYNC_KHR) { + data->egl.destroySync(dpy, image->acquireSync); + image->acquireSync = EGL_NO_SYNC_KHR; + } + if (image) { /* Release the image lock */ pthread_mutex_unlock(&image->mutex); } return EGL_BAD_SURFACE; + +fail_destroy_sync: + if (acquireSync != EGL_NO_SYNC_KHR) { + data->egl.destroySync(dpy, acquireSync); + } + return EGL_BAD_SURFACE; } static void @@ -1125,34 +1441,94 @@ pop_acquired_image(WlEglSurface *surface) return NULL; } +static int +create_syncobj_timeline(WlEglDisplay *display, uint32_t *drmSyncobjHandleOut) +{ + int ret; + + /* Create a DRM timeline and share it with the compositor */ + if (drmSyncobjCreate(display->drmFd, 0, drmSyncobjHandleOut)) { + return -1; + } + + if (drmSyncobjHandleToFD(display->drmFd, *drmSyncobjHandleOut, &ret)) { + return -1; + } + + return ret; +} + static EGLint -add_surface_image(WlEglDisplay *display, WlEglSurface *surface) +init_surface_image(WlEglDisplay *display, WlEglSurface *surface, + WlEglStreamImage *image) { WlEglPlatformData *data = display->data; EGLDisplay dpy = display->devDpy->eglDisplay; + int drmSyncobjFd = -1; + + image->eglImage = data->egl.createImage(dpy, EGL_NO_CONTEXT, + EGL_STREAM_CONSUMER_IMAGE_NV, + (EGLClientBuffer)surface->ctx.eglStream, + NULL); + if (image->eglImage == EGL_NO_IMAGE_KHR) { + return EGL_BAD_ALLOC; + } + + /* + * Create a per-stream image release timeline. + * + * This is needed since we will be the ones signaling acquire points. If the acquire points + * are on the same timeline as the release points then they will accidentally signal all + * pending release points. + */ + if (surface->wlSyncobjSurf) { + drmSyncobjFd = create_syncobj_timeline(display, &image->drmSyncobjHandle); + if (drmSyncobjFd < 0) { + goto fail; + } + image->acquireSync = EGL_NO_SYNC_KHR; + + /* Get a DRM timeline wl object */ + image->wlReleaseTimeline = + wp_linux_drm_syncobj_manager_v1_import_timeline(display->wlDrmSyncobj, drmSyncobjFd); + if (!image->wlReleaseTimeline) { + goto fail; + } + + close(drmSyncobjFd); + } + + return EGL_SUCCESS; + +fail: + if (image->drmSyncobjHandle) { + drmSyncobjDestroy(display->drmFd, image->drmSyncobjHandle); + } + + if (drmSyncobjFd > 0) { + close(drmSyncobjFd); + } + + data->egl.destroyImage(dpy, image->eglImage); + return EGL_BAD_ALLOC; +} + +static EGLint +add_surface_image(WlEglDisplay *display, WlEglSurface *surface) +{ WlEglStreamImage **newImages; WlEglStreamImage *image; - EGLImageKHR eglImage; + EGLint ret; uint32_t i; for (i = 0; i < surface->ctx.numStreamImages; i++) { image = surface->ctx.streamImages[i]; pthread_mutex_lock(&image->mutex); if ((image->eglImage == EGL_NO_IMAGE_KHR) && !image->buffer) { - eglImage = image->eglImage = - data->egl.createImage(dpy, - EGL_NO_CONTEXT, - EGL_STREAM_CONSUMER_IMAGE_NV, - (EGLClientBuffer)surface->ctx.eglStream, - NULL); + ret = init_surface_image(display, surface, image); pthread_mutex_unlock(&image->mutex); - - if (eglImage != EGL_NO_IMAGE_KHR) { - return EGL_SUCCESS; - } else { - return EGL_BAD_ALLOC; - } + return ret; } pthread_mutex_unlock(&image->mutex); } @@ -1187,14 +1563,8 @@ add_surface_image(WlEglDisplay *display, WlEglSurface *surface) } wl_list_init(&newImages[i]->acquiredLink); - newImages[i]->eglImage = - data->egl.createImage(dpy, - EGL_NO_CONTEXT, - EGL_STREAM_CONSUMER_IMAGE_NV, - (EGLClientBuffer)surface->ctx.eglStream, - NULL); - - if (newImages[i]->eglImage == EGL_NO_IMAGE_KHR) { + + if (init_surface_image(display, surface, newImages[i]) != EGL_SUCCESS) { wlEglMutexDestroy(&newImages[i]->mutex); goto free_image; } @@ -1218,6 +1588,7 @@ EGLint wlEglHandleImageStreamEvents(WlEglSurface *surface) EGLAttrib aux; EGLenum event; EGLint err = EGL_SUCCESS; + EGLint timeout = surface->wlSyncobjSurf ? EGL_FOREVER : 0; if (surface->ctx.wlStreamResource) { /* Not a local stream */ @@ -1225,9 +1596,14 @@ EGLint wlEglHandleImageStreamEvents(WlEglSurface *surface) } while (1) { + /* + * With explicit sync we should block here and not return until we have + * acquired a new image. The stream will not release the image until + * the release point we handed to the compositor signals. + */ err = data->egl.queryStreamConsumerEvent(dpy, surface->ctx.eglStream, - 0, + timeout, &event, &aux); @@ -1247,6 +1623,8 @@ EGLint wlEglHandleImageStreamEvents(WlEglSurface *surface) switch (event) { case EGL_STREAM_IMAGE_AVAILABLE_NV: err = acquire_surface_image(display, surface); + /* Clear our timeout so we exit after all events are handled */ + timeout = 0; break; case EGL_STREAM_IMAGE_ADD_NV: err = add_surface_image(display, surface); @@ -1457,7 +1835,12 @@ static EGLint create_surface_stream_local(WlEglSurface *surface) wl_list_init(&surface->ctx.acquiredImages); - if (!surface->wlBufferEventQueue) { + /* + * Don't enable the buffer release thread when explicit sync is in use. + * In explicit sync we don't care about the delivery of release events, we + * only pay attention to the release points. + */ + if (!surface->wlBufferEventQueue && !surface->wlSyncobjSurf) { /* * Local stream contexts need a private wayland queue used by a separate * thread that can process buffer release events even the application @@ -1651,6 +2034,7 @@ create_surface_context(WlEglSurface *surface) /* Check whether we should use a damage thread */ surface->ctx.useDamageThread = + !surface->wlSyncobjSurf && display->devDpy->exts.stream_fifo_synchronous && display->devDpy->exts.stream_sync && data->egl.queryStream(display->devDpy->eglDisplay, @@ -2151,6 +2535,11 @@ static EGLBoolean wlEglDestroySurface(EGLDisplay dpy, EGLSurface eglSurface) wlEglDestroyFeedback(&surface->feedback); + if (surface->wlSyncobjSurf) { + wp_linux_drm_syncobj_surface_v1_destroy(surface->wlSyncobjSurf); + wp_linux_drm_syncobj_timeline_v1_destroy(surface->wlAcquireTimeline); + } + if (surface->presentFeedbackQueue != NULL) { wl_event_queue_destroy(surface->presentFeedbackQueue); surface->presentFeedbackQueue = NULL; @@ -2247,6 +2636,7 @@ EGLSurface wlEglCreatePlatformWindowSurfaceHook(EGLDisplay dpy, EGLBoolean res = EGL_FALSE; EGLint err = EGL_SUCCESS; EGLint surfType; + int drmSyncobjFd = -1; if (!display) { return EGL_NO_SURFACE; @@ -2319,6 +2709,7 @@ EGLSurface wlEglCreatePlatformWindowSurfaceHook(EGLDisplay dpy, surface->isSurfaceProducer = EGL_TRUE; surface->refCount = 1; surface->isDestroyed = EGL_FALSE; + surface->syncPoint = 1; // FIFO_LENGTH == 1 to set FIFO mode, FIFO_LENGTH == 0 to set MAILBOX mode surface->fifoLength = (display->devDpy->exts.stream_fifo_synchronous && display->devDpy->exts.stream_sync) ? 1 : 0; @@ -2365,6 +2756,28 @@ EGLSurface wlEglCreatePlatformWindowSurfaceHook(EGLDisplay dpy, surface->feedback.unprocessedFeedback = false; } + if (display->wlDrmSyncobj) { + /* Create a DRM timeline and share it with the compositor */ + drmSyncobjFd = create_syncobj_timeline(display, &surface->drmSyncobjHandle); + if (drmSyncobjFd < 0) { + goto fail; + } + + /* Get a per-surface explicit sync object, share our DRM syncobj with the compositor */ + surface->wlSyncobjSurf = + wp_linux_drm_syncobj_manager_v1_get_surface(display->wlDrmSyncobj, surface->wlSurface); + + surface->wlAcquireTimeline = + wp_linux_drm_syncobj_manager_v1_import_timeline(display->wlDrmSyncobj, drmSyncobjFd); + close(drmSyncobjFd); + drmSyncobjFd = -1; + + if (!surface->wlSyncobjSurf || !surface->wlAcquireTimeline) { + err = EGL_BAD_ALLOC; + goto fail; + } + } + err = create_surface_context(surface); if (err != EGL_SUCCESS) { goto fail; @@ -2391,6 +2804,14 @@ EGLSurface wlEglCreatePlatformWindowSurfaceHook(EGLDisplay dpy, return surface; fail: + if (surface->drmSyncobjHandle) { + drmSyncobjDestroy(display->drmFd, surface->drmSyncobjHandle); + } + + if (drmSyncobjFd > 0) { + close(drmSyncobjFd); + } + if (surface) { wlEglDestroySurface(display, surface); } diff --git a/src/wayland-eglswap.c b/src/wayland-eglswap.c index a048815..bf1157d 100644 --- a/src/wayland-eglswap.c +++ b/src/wayland-eglswap.c @@ -148,6 +148,7 @@ EGLBoolean wlEglSwapBuffersWithDamageHook(EGLDisplay eglDisplay, EGLSurface eglS } else { wlEglCreateFrameSync(surface); res = wlEglSendDamageEvent(surface, surface->wlEventQueue); + wlEglSurfaceCheckReleasePoints(display, surface); } }