From 7d6c362e9974aa547645b954e6f05597c7508769 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 3 Mar 2022 03:14:00 -0800 Subject: [PATCH 1/2] egl-swap: provide damage rectangles to wl_surface Previously, wlEglSendDamageEvent used the entire surface as damage to the compositor but in the wrong coordinate system. wl_surface_damage() requires coordinates for the surface which could be scaled while wl_surface_damage_buffer() expects buffer coordinates which is what surface->width and surface->height represent. This ensures that the parameters to eglSwapBuffersWithDamage() are passed along to the compositor as well. The coordinate system is flipped between eglSwapBuffersWithDamage() and wl_surface_damage_buffer() which is handled as well. Signed-off-by: Christian Hergert --- include/wayland-eglsurface.h | 4 +++- src/wayland-eglsurface.c | 25 +++++++++++++++++++++---- src/wayland-eglswap.c | 4 ++-- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/include/wayland-eglsurface.h b/include/wayland-eglsurface.h index c4f0d02..5a4b41f 100644 --- a/include/wayland-eglsurface.h +++ b/include/wayland-eglsurface.h @@ -160,7 +160,9 @@ EGLBoolean wlEglQueryNativeResourceHook(EGLDisplay dpy, int *value); EGLBoolean wlEglSendDamageEvent(WlEglSurface *surface, - struct wl_event_queue *queue); + struct wl_event_queue *queue, + EGLint *rects, + EGLint n_rects); void wlEglCreateFrameSync(WlEglSurface *surface); EGLint wlEglWaitFrameSync(WlEglSurface *surface); diff --git a/src/wayland-eglsurface.c b/src/wayland-eglsurface.c index 78e1fa6..16eae97 100644 --- a/src/wayland-eglsurface.c +++ b/src/wayland-eglsurface.c @@ -161,9 +161,13 @@ EGLint wlEglWaitFrameSync(WlEglSurface *surface) } EGLBoolean -wlEglSendDamageEvent(WlEglSurface *surface, struct wl_event_queue *queue) +wlEglSendDamageEvent(WlEglSurface *surface, + struct wl_event_queue *queue, + EGLint *rects, + EGLint n_rects) { struct wl_display *wlDpy = surface->wlEglDpy->nativeDpy; + EGLint i; if (surface->ctx.wlStreamResource) { /* Attach same buffer to indicate new content for the surface is @@ -191,8 +195,21 @@ wlEglSendDamageEvent(WlEglSurface *surface, struct wl_event_queue *queue) surface->dy); } - wl_surface_damage(surface->wlSurface, 0, 0, - surface->width, surface->height); + if (n_rects > 0 && + (wl_proxy_get_version((struct wl_proxy *)surface->wlSurface) >= + WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)) { + for (i = 0; i < n_rects; i++) { + int y = rects[i*4+1] + rects[i*4+3]; + wl_surface_damage_buffer(surface->wlSurface, + rects[i*4], + surface->height - y, + rects[i*4+2], + rects[i*4+3]); + } + } else { + wl_surface_damage(surface->wlSurface, 0, 0, UINT32_MAX, UINT32_MAX); + } + wl_surface_commit(surface->wlSurface); surface->ctx.isAttached = EGL_TRUE; @@ -257,7 +274,7 @@ damage_thread(void *args) data->egl.streamFlush(display->devDpy->eglDisplay, surface->ctx.eglStream); } - ok = wlEglSendDamageEvent(surface, queue); + ok = wlEglSendDamageEvent(surface, queue, NULL, 0); surface->ctx.framesProcessed++; } diff --git a/src/wayland-eglswap.c b/src/wayland-eglswap.c index 22d3267..74e8e3a 100644 --- a/src/wayland-eglswap.c +++ b/src/wayland-eglswap.c @@ -125,7 +125,7 @@ EGLBoolean wlEglSwapBuffersWithDamageHook(EGLDisplay eglDisplay, EGLSurface eglS if (surface->ctx.useDamageThread) { surface->ctx.framesProduced++; } else { - res = wlEglSendDamageEvent(surface, surface->wlEventQueue); + res = wlEglSendDamageEvent(surface, surface->wlEventQueue, rects, n_rects); } } wlEglCreateFrameSync(surface); @@ -285,7 +285,7 @@ EGLBoolean wlEglPostPresentExport(WlEglSurface *surface) { if (surface->ctx.useDamageThread) { surface->ctx.framesProduced++; } else { - res = wlEglSendDamageEvent(surface, surface->wlEventQueue); + res = wlEglSendDamageEvent(surface, surface->wlEventQueue, NULL, 0); } wlEglCreateFrameSync(surface); From e6de8a095837f3061e42278090a30bcf258e5d59 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 3 Mar 2022 17:28:09 -0800 Subject: [PATCH 2/2] egl-swap: support eglSwapBuffersWithDamage with damage_thread If we have a damage thread, we were previously ignoring all of the damage rectangles provided by the client. This uses a small lock-free ring-buffer between the producer and consumer threads to pass the damage region to the damage thread where they will ultimately be passed to the underlying EGL implementation. This fixes GTK 4 sending appropriate damage regions with NVIDIA when EGL is used on Wayland. --- include/wayland-eglsurface.h | 27 ++++++++++ src/wayland-eglsurface.c | 95 ++++++++++++++++++++++++++++++++++-- src/wayland-eglswap.c | 6 +++ 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/include/wayland-eglsurface.h b/include/wayland-eglsurface.h index 5a4b41f..f155227 100644 --- a/include/wayland-eglsurface.h +++ b/include/wayland-eglsurface.h @@ -34,6 +34,9 @@ extern "C" { #endif +#define WL_EGL_STREAM_DAMAGE_BUFFER_N_FRAMES 3 +#define WL_EGL_STREAM_DAMAGE_BUFFER_N_RECTS 32 + typedef struct WlEglStreamImageRec { /* Pointer back to the parent surface for use in Wayland callbacks */ struct WlEglSurfaceRec *surface; @@ -52,6 +55,19 @@ typedef struct WlEglStreamImageRec { struct wl_list acquiredLink; } WlEglStreamImage; +typedef struct WlEglStreamDamageRec { + EGLuint64KHR frameNumber; + EGLint n_rects; + EGLint _padding; + EGLint rects[4 * WL_EGL_STREAM_DAMAGE_BUFFER_N_RECTS]; +} WlEglStreamDamage; + +typedef struct WlEglStreamDamageBufferRec { + volatile EGLint head; + volatile EGLint tail; + WlEglStreamDamage frames[WL_EGL_STREAM_DAMAGE_BUFFER_N_FRAMES]; +} WlEglStreamDamageBuffer; + typedef struct WlEglSurfaceCtxRec { EGLBoolean isOffscreen; EGLSurface eglSurface; @@ -79,6 +95,8 @@ typedef struct WlEglSurfaceCtxRec { uint32_t numStreamImages; struct wl_list link; + + WlEglStreamDamageBuffer damageBuffer; } WlEglSurfaceCtx; typedef struct WlEglSurfaceRec { @@ -164,6 +182,15 @@ EGLBoolean wlEglSendDamageEvent(WlEglSurface *surface, EGLint *rects, EGLint n_rects); +void wlEglInitializeStreamDamageBuffer(WlEglStreamDamageBuffer *buffer); +EGLBoolean wlEglPutStreamDamage(WlEglStreamDamageBuffer *buffer, + EGLuint64KHR frameNumber, + EGLint *rects, + EGLint n_rects); +EGLBoolean wlEglGetStreamDamageForFrame(WlEglStreamDamageBuffer *buffer, + EGLuint64KHR frameNumber, + WlEglStreamDamage *damage); + void wlEglCreateFrameSync(WlEglSurface *surface); EGLint wlEglWaitFrameSync(WlEglSurface *surface); diff --git a/src/wayland-eglsurface.c b/src/wayland-eglsurface.c index 16eae97..4667cec 100644 --- a/src/wayland-eglsurface.c +++ b/src/wayland-eglsurface.c @@ -40,6 +40,7 @@ #include #include #include +#include #define WL_EGL_WINDOW_DESTROY_CALLBACK_SINCE 3 @@ -268,13 +269,20 @@ damage_thread(void *args) if (ok) { // If there's an unprocessed frame ready, send damage event - if (surface->ctx.framesFinished != - surface->ctx.framesProcessed) { + if (surface->ctx.framesFinished != surface->ctx.framesProcessed) { + WlEglStreamDamage damage; + if (display->devDpy->exts.stream_flush) { data->egl.streamFlush(display->devDpy->eglDisplay, surface->ctx.eglStream); } - ok = wlEglSendDamageEvent(surface, queue, NULL, 0); + + if (wlEglGetStreamDamageForFrame(&surface->ctx.damageBuffer, surface->ctx.framesProcessed, &damage)) { + ok = wlEglSendDamageEvent(surface, queue, damage.rects, damage.n_rects); + } else { + ok = wlEglSendDamageEvent(surface, queue, NULL, 0); + } + surface->ctx.framesProcessed++; } @@ -2223,3 +2231,84 @@ EGLBoolean wlEglQueryNativeResourceHook(EGLDisplay dpy, wlExternalApiUnlock(); return res; } + +void +wlEglInitializeStreamDamageBuffer(WlEglStreamDamageBuffer *buffer) +{ + memset (buffer, 0, sizeof *buffer); +} + +EGLBoolean +wlEglGetStreamDamageForFrame(WlEglStreamDamageBuffer *buffer, + EGLuint64KHR frameNumber, + WlEglStreamDamage *damage) +{ + EGLint tail = buffer->tail; + + atomic_thread_fence (memory_order_acquire); + + for (;;) { + if (buffer->head == tail) { + return EGL_FALSE; + } + + if (buffer->frames[tail].frameNumber > frameNumber) { + /* We must have dropped our desired frame damage on the floor + * because there was not space or it had too many rectangles. + */ + return EGL_FALSE; + } + + /* Avoid copying empty rectangles */ + memcpy (damage, &buffer->frames[tail], + offsetof (WlEglStreamDamage, rects) + buffer->frames[tail].n_rects * sizeof(EGLint) * 4); + + tail++; + if (tail == WL_EGL_STREAM_DAMAGE_BUFFER_N_FRAMES) { + tail = 0; + } + + atomic_thread_fence (memory_order_release); + + buffer->tail = tail; + + if (damage->frameNumber == frameNumber) { + return EGL_TRUE; + } + } + + /* Not reached */ +} + +EGLBoolean +wlEglPutStreamDamage(WlEglStreamDamageBuffer *buffer, + EGLuint64KHR frameNumber, + EGLint *rects, + EGLint n_rects) +{ + EGLint head = buffer->head + 1; + + if (n_rects == 0 || n_rects > WL_EGL_STREAM_DAMAGE_BUFFER_N_RECTS) { + return EGL_FALSE; + } + + if (head == WL_EGL_STREAM_DAMAGE_BUFFER_N_FRAMES) { + head = 0; + } + + atomic_thread_fence (memory_order_acquire); + + if (head != buffer->tail) { + buffer->frames[buffer->head].frameNumber = frameNumber; + buffer->frames[buffer->head].n_rects = n_rects; + memcpy (buffer->frames[buffer->head].rects, rects, sizeof (EGLint) * 4 * n_rects); + + atomic_thread_fence (memory_order_release); + + buffer->head = head; + + return EGL_TRUE; + } + + return EGL_FALSE; +} diff --git a/src/wayland-eglswap.c b/src/wayland-eglswap.c index 74e8e3a..c219128 100644 --- a/src/wayland-eglswap.c +++ b/src/wayland-eglswap.c @@ -123,6 +123,12 @@ EGLBoolean wlEglSwapBuffersWithDamageHook(EGLDisplay eglDisplay, EGLSurface eglS if (res) { if (surface->ctx.useDamageThread) { + /* Failure due to a full ring-buffer results in a full-surface + * composition by the damage thread. + */ + wlEglPutStreamDamage(&surface->ctx.damageBuffer, + surface->ctx.framesProduced, + rects, n_rects); surface->ctx.framesProduced++; } else { res = wlEglSendDamageEvent(surface, surface->wlEventQueue, rects, n_rects);