Skip to content

Commit

Permalink
egl-swap: support eglSwapBuffersWithDamage with damage_thread
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chergert committed Mar 15, 2022
1 parent 7d6c362 commit a233052
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 3 deletions.
27 changes: 27 additions & 0 deletions include/wayland-eglsurface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
_Atomic EGLint head;
_Atomic EGLint tail;
WlEglStreamDamage frames[WL_EGL_STREAM_DAMAGE_BUFFER_N_FRAMES];
} WlEglStreamDamageBuffer;

typedef struct WlEglSurfaceCtxRec {
EGLBoolean isOffscreen;
EGLSurface eglSurface;
Expand Down Expand Up @@ -79,6 +95,8 @@ typedef struct WlEglSurfaceCtxRec {
uint32_t numStreamImages;

struct wl_list link;

WlEglStreamDamageBuffer damageBuffer;
} WlEglSurfaceCtx;

typedef struct WlEglSurfaceRec {
Expand Down Expand Up @@ -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);

Expand Down
95 changes: 92 additions & 3 deletions src/wayland-eglsurface.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <stdatomic.h>

#define WL_EGL_WINDOW_DESTROY_CALLBACK_SINCE 3

Expand Down Expand Up @@ -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++;
}

Expand Down Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions src/wayland-eglswap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a233052

Please sign in to comment.