SDL: wayland: Use a separate frame callback for setting the surface damage region

From 78698a0ba2269ce54f82293e11807e2c492d9937 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 17 May 2022 12:37:16 -0400
Subject: [PATCH] wayland: Use a separate frame callback for setting the
 surface damage region

Previously, the surface damage region was being set in the same callback used for preventing render hangs in the GL backend when the surface was not visible.  This was not ideal, as the callback was never fired in the case of using a different render backend or having a swap interval of 0.  Use a separate frame callback for setting the surface damage region to ensure that it fires reliably, regardless of the backend being used or swap interval.
---
 src/video/wayland/SDL_waylandopengles.c |  6 +--
 src/video/wayland/SDL_waylandwindow.c   | 59 ++++++++++++++++++-------
 src/video/wayland/SDL_waylandwindow.h   |  7 +--
 3 files changed, 49 insertions(+), 23 deletions(-)

diff --git a/src/video/wayland/SDL_waylandopengles.c b/src/video/wayland/SDL_waylandopengles.c
index 4c834fd8645..26626ee5189 100644
--- a/src/video/wayland/SDL_waylandopengles.c
+++ b/src/video/wayland/SDL_waylandopengles.c
@@ -140,9 +140,9 @@ Wayland_GLES_SwapWindow(_THIS, SDL_Window *window)
 
             /* wl_display_prepare_read_queue() will return -1 if the event queue is not empty.
              * If the event queue is empty, it will prepare us for our SDL_IOReady() call. */
-            if (WAYLAND_wl_display_prepare_read_queue(display, data->frame_event_queue) != 0) {
+            if (WAYLAND_wl_display_prepare_read_queue(display, data->gles_swap_frame_event_queue) != 0) {
                 /* We have some pending events. Check if the frame callback happened. */
-                WAYLAND_wl_display_dispatch_queue_pending(display, data->frame_event_queue);
+                WAYLAND_wl_display_dispatch_queue_pending(display, data->gles_swap_frame_event_queue);
                 continue;
             }
 
@@ -163,7 +163,7 @@ Wayland_GLES_SwapWindow(_THIS, SDL_Window *window)
 
             /* We have events. Read and dispatch them. */
             WAYLAND_wl_display_read_events(display);
-            WAYLAND_wl_display_dispatch_queue_pending(display, data->frame_event_queue);
+            WAYLAND_wl_display_dispatch_queue_pending(display, data->gles_swap_frame_event_queue);
         }
         SDL_AtomicSet(&data->swap_interval_ready, 0);
     }
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 2c70d40553a..8be9cd9874e 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -426,27 +426,44 @@ SetFullscreen(SDL_Window *window, struct wl_output *output, SDL_bool commit)
     }
 }
 
-static const struct wl_callback_listener surface_frame_listener;
+const struct wl_callback_listener surface_damage_frame_listener;
 
 static void
-handle_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
+surface_damage_frame_done(void *data, struct wl_callback *cb, uint32_t time)
 {
-    SDL_WindowData *wind = (SDL_WindowData *) data;
-    SDL_AtomicSet(&wind->swap_interval_ready, 1);  /* mark window as ready to present again. */
+    SDL_WindowData *wind = (SDL_WindowData *)data;
 
+    /* Manually set the damage region when using a viewport. */
     if (!SDL_RectEmpty(&wind->viewport_rect)) {
         wl_surface_damage(wind->surface, wind->viewport_rect.x, wind->viewport_rect.y,
                           wind->viewport_rect.w, wind->viewport_rect.h);
     }
 
+    wl_callback_destroy(cb);
+    wind->surface_damage_frame_callback = wl_surface_frame(wind->surface);
+    wl_callback_add_listener(wind->surface_damage_frame_callback, &surface_damage_frame_listener, data);
+}
+
+const struct wl_callback_listener surface_damage_frame_listener = {
+    surface_damage_frame_done
+};
+
+static const struct wl_callback_listener gles_swap_frame_listener;
+
+static void
+gles_swap_frame_done(void *data, struct wl_callback *cb, uint32_t time)
+{
+    SDL_WindowData *wind = (SDL_WindowData *) data;
+    SDL_AtomicSet(&wind->swap_interval_ready, 1);  /* mark window as ready to present again. */
+
     /* reset this callback to fire again once a new frame was presented and compositor wants the next one. */
-    wind->frame_callback = wl_surface_frame(wind->frame_surface_wrapper);
+    wind->gles_swap_frame_callback = wl_surface_frame(wind->gles_swap_frame_surface_wrapper);
     wl_callback_destroy(cb);
-    wl_callback_add_listener(wind->frame_callback, &surface_frame_listener, data);
+    wl_callback_add_listener(wind->gles_swap_frame_callback, &gles_swap_frame_listener, data);
 }
 
-static const struct wl_callback_listener surface_frame_listener = {
-    handle_surface_frame_done
+static const struct wl_callback_listener gles_swap_frame_listener = {
+    gles_swap_frame_done
 };
 
 
@@ -1838,13 +1855,17 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
      * window isn't visible.
      */
     if (window->flags & SDL_WINDOW_OPENGL) {
-        data->frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display);
-        data->frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface);
-        WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->frame_surface_wrapper, data->frame_event_queue);
-        data->frame_callback = wl_surface_frame(data->frame_surface_wrapper);
-        wl_callback_add_listener(data->frame_callback, &surface_frame_listener, data);
+        data->gles_swap_frame_event_queue = WAYLAND_wl_display_create_queue(data->waylandData->display);
+        data->gles_swap_frame_surface_wrapper = WAYLAND_wl_proxy_create_wrapper(data->surface);
+        WAYLAND_wl_proxy_set_queue((struct wl_proxy *)data->gles_swap_frame_surface_wrapper, data->gles_swap_frame_event_queue);
+        data->gles_swap_frame_callback = wl_surface_frame(data->gles_swap_frame_surface_wrapper);
+        wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data);
     }
 
+    /* Fire a callback when the compositor wants a new frame to set the surface damage region. */
+    data->surface_damage_frame_callback = wl_surface_frame(data->surface);
+    wl_callback_add_listener(data->surface_damage_frame_callback, &surface_damage_frame_listener, data);
+
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
     if (c->surface_extension) {
         data->extended_surface = qt_surface_extension_get_extended_surface(
@@ -2107,10 +2128,14 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
 
         SDL_free(wind->outputs);
 
-        if (wind->frame_callback) {
-            WAYLAND_wl_event_queue_destroy(wind->frame_event_queue);
-            WAYLAND_wl_proxy_wrapper_destroy(wind->frame_surface_wrapper);
-            wl_callback_destroy(wind->frame_callback);
+        if (wind->gles_swap_frame_callback) {
+            WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue);
+            WAYLAND_wl_proxy_wrapper_destroy(wind->gles_swap_frame_surface_wrapper);
+            wl_callback_destroy(wind->gles_swap_frame_callback);
+        }
+
+        if (wind->surface_damage_frame_callback) {
+            wl_callback_destroy(wind->surface_damage_frame_callback);
         }
 
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index e77cb80a516..703ec53da45 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -42,9 +42,10 @@ typedef struct {
     SDL_Window *sdlwindow;
     SDL_VideoData *waylandData;
     struct wl_surface *surface;
-    struct wl_callback *frame_callback;
-    struct wl_event_queue *frame_event_queue;
-    struct wl_surface *frame_surface_wrapper;
+    struct wl_callback *gles_swap_frame_callback;
+    struct wl_event_queue *gles_swap_frame_event_queue;
+    struct wl_surface *gles_swap_frame_surface_wrapper;
+    struct wl_callback *surface_damage_frame_callback;
 
     union {
 #ifdef HAVE_LIBDECOR_H