SDL: wayland: Ensure that color descriptions are always retrieved

From fab52b578fb6752cea693cb3ecafcd16db90f775 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 26 Feb 2025 11:14:55 -0500
Subject: [PATCH] wayland: Ensure that color descriptions are always retrieved

---
 src/video/wayland/SDL_waylandcolor.c  | 159 +++++++++++++++++++-------
 src/video/wayland/SDL_waylandcolor.h  |  14 +--
 src/video/wayland/SDL_waylandvideo.c  |  20 +---
 src/video/wayland/SDL_waylandvideo.h  |   1 +
 src/video/wayland/SDL_waylandwindow.c |  14 +--
 src/video/wayland/SDL_waylandwindow.h |   2 +
 6 files changed, 135 insertions(+), 75 deletions(-)

diff --git a/src/video/wayland/SDL_waylandcolor.c b/src/video/wayland/SDL_waylandcolor.c
index 148a3416c1451..dfc69bb5f433e 100644
--- a/src/video/wayland/SDL_waylandcolor.c
+++ b/src/video/wayland/SDL_waylandcolor.c
@@ -27,31 +27,87 @@
 #include "SDL_waylandvideo.h"
 #include "SDL_waylandwindow.h"
 #include "color-management-v1-client-protocol.h"
+#include "../../events/SDL_windowevents_c.h"
 
 typedef struct Wayland_ColorInfoState
 {
     struct wp_image_description_v1 *wp_image_description;
     struct wp_image_description_info_v1 *wp_image_description_info;
-    Wayland_ColorInfo *info;
 
-    bool result;
+    union
+    {
+        SDL_WindowData *window_data;
+        SDL_DisplayData *display_data;
+    };
+
+    enum
+    {
+        WAYLAND_COLOR_OBJECT_TYPE_WINDOW,
+        WAYLAND_COLOR_OBJECT_TYPE_DISPLAY
+    } object_type;
+
+    SDL_HDROutputProperties HDR;
+
+    // The ICC fd is only valid if the size is non-zero.
+    int icc_fd;
+    Uint32 icc_size;
+
+    bool deferred_event_processing;
 } Wayland_ColorInfoState;
 
+static void Wayland_CancelColorInfoRequest(Wayland_ColorInfoState *state)
+{
+    if (state) {
+        if (state->wp_image_description_info) {
+            wp_image_description_info_v1_destroy(state->wp_image_description_info);
+            state->wp_image_description_info = NULL;
+        }
+        if (state->wp_image_description) {
+            wp_image_description_v1_destroy(state->wp_image_description);
+            state->wp_image_description = NULL;
+        }
+    }
+}
+
+void Wayland_FreeColorInfoState(Wayland_ColorInfoState *state)
+{
+    if (state) {
+        Wayland_CancelColorInfoRequest(state);
+
+        switch (state->object_type) {
+        case WAYLAND_COLOR_OBJECT_TYPE_WINDOW:
+            state->window_data->color_info_state = NULL;
+            break;
+        case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY:
+            state->display_data->color_info_state = NULL;
+            break;
+        }
+
+        SDL_free(state);
+    }
+}
+
 static void image_description_info_handle_done(void *data,
                                                struct wp_image_description_info_v1 *wp_image_description_info_v1)
 {
     Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
-
-    if (state->wp_image_description_info) {
-        wp_image_description_info_v1_destroy(state->wp_image_description_info);
-        state->wp_image_description_info = NULL;
-    }
-    if (state->wp_image_description) {
-        wp_image_description_v1_destroy(state->wp_image_description);
-        state->wp_image_description = NULL;
+    Wayland_CancelColorInfoRequest(state);
+
+    switch (state->object_type) {
+        case WAYLAND_COLOR_OBJECT_TYPE_WINDOW:
+        {
+            SDL_SetWindowHDRProperties(state->window_data->sdlwindow, &state->HDR, true);
+            if (state->icc_size) {
+                state->window_data->icc_fd = state->icc_fd;
+                state->window_data->icc_size = state->icc_size;
+                SDL_SendWindowEvent(state->window_data->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
+            }
+        } break;
+        case WAYLAND_COLOR_OBJECT_TYPE_DISPLAY:
+        {
+            SDL_copyp(&state->display_data->HDR, &state->HDR);
+        } break;
     }
-
-    state->result = true;
 }
 
 static void image_description_info_handle_icc_file(void *data,
@@ -60,8 +116,8 @@ static void image_description_info_handle_icc_file(void *data,
 {
     Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
 
-    state->info->icc_fd = icc;
-    state->info->icc_size = icc_size;
+    state->icc_fd = icc;
+    state->icc_size = icc_size;
 }
 
 static void image_description_info_handle_primaries(void *data,
@@ -102,7 +158,7 @@ static void image_description_info_handle_luminances(void *data,
                                                      uint32_t reference_lum)
 {
     Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
-    state->info->HDR.HDR_headroom = (float)max_lum / (float)reference_lum;
+    state->HDR.HDR_headroom = (float)max_lum / (float)reference_lum;
 }
 
 static void image_description_info_handle_target_primaries(void *data,
@@ -157,13 +213,18 @@ static void PumpColorspaceEvents(Wayland_ColorInfoState *state)
 
     // Run the image description sequence to completion in its own queue.
     struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display);
-    WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
+    if (state->deferred_event_processing) {
+        WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description_info, queue);
+    } else {
+        WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
+    }
 
     while (state->wp_image_description) {
         WAYLAND_wl_display_dispatch_queue(vid->display, queue);
     }
 
     WAYLAND_wl_event_queue_destroy(queue);
+    Wayland_FreeColorInfoState(state);
 }
 
 static void image_description_handle_failed(void *data,
@@ -172,9 +233,11 @@ static void image_description_handle_failed(void *data,
                                             const char *msg)
 {
     Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
+    Wayland_CancelColorInfoRequest(state);
 
-    wp_image_description_v1_destroy(state->wp_image_description);
-    state->wp_image_description = NULL;
+    if (state->deferred_event_processing) {
+        Wayland_FreeColorInfoState(state);
+    }
 }
 
 static void image_description_handle_ready(void *data,
@@ -186,6 +249,10 @@ static void image_description_handle_ready(void *data,
     // This will inherit the queue of the factory image description object.
     state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description);
     wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data);
+
+    if (state->deferred_event_processing) {
+        PumpColorspaceEvents(state);
+    }
 }
 
 static const struct wp_image_description_v1_listener image_description_listener = {
@@ -193,32 +260,44 @@ static const struct wp_image_description_v1_listener image_description_listener
     image_description_handle_ready
 };
 
-bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info)
+void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing)
 {
-    Wayland_ColorInfoState state;
-    SDL_zero(state);
-    state.info = info;
-
-    state.wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback);
-    wp_image_description_v1_add_listener(state.wp_image_description, &image_description_listener, &state);
-
-    PumpColorspaceEvents(&state);
-
-    return state.result;
+    // Cancel any pending request, as it is out-of-date.
+    Wayland_FreeColorInfoState(window_data->color_info_state);
+    Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState));
+
+    if (state) {
+        window_data->color_info_state = state;
+        state->window_data = window_data;
+        state->object_type = WAYLAND_COLOR_OBJECT_TYPE_WINDOW;
+        state->deferred_event_processing = defer_event_processing;
+        state->wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback);
+        wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
+
+        if (!defer_event_processing) {
+            PumpColorspaceEvents(state);
+        }
+    }
 }
 
-bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info)
+void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing)
 {
-    Wayland_ColorInfoState state;
-    SDL_zero(state);
-    state.info = info;
-
-    state.wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output);
-    wp_image_description_v1_add_listener(state.wp_image_description, &image_description_listener, &state);
-
-    PumpColorspaceEvents(&state);
-
-    return state.result;
+    // Cancel any pending request, as it is out-of-date.
+    Wayland_FreeColorInfoState(display_data->color_info_state);
+    Wayland_ColorInfoState *state = SDL_calloc(1, sizeof(Wayland_ColorInfoState));
+
+    if (state) {
+        display_data->color_info_state = state;
+        state->display_data = display_data;
+        state->object_type = WAYLAND_COLOR_OBJECT_TYPE_DISPLAY;
+        state->deferred_event_processing = defer_event_processing;
+        state->wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output);
+        wp_image_description_v1_add_listener(state->wp_image_description, &image_description_listener, state);
+
+        if (!defer_event_processing) {
+            PumpColorspaceEvents(state);
+        }
+    }
 }
 
 #endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/src/video/wayland/SDL_waylandcolor.h b/src/video/wayland/SDL_waylandcolor.h
index 6125374b85019..bef5d8c3e0569 100644
--- a/src/video/wayland/SDL_waylandcolor.h
+++ b/src/video/wayland/SDL_waylandcolor.h
@@ -26,16 +26,10 @@
 
 #include "../SDL_sysvideo.h"
 
-typedef struct Wayland_ColorInfo
-{
-    SDL_HDROutputProperties HDR;
+struct Wayland_ColorInfoState;
 
-    // The ICC fd is only valid if the size is non-zero.
-    int icc_fd;
-    Uint32 icc_size;
-} Wayland_ColorInfo;
-
-extern bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info);
-extern bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info);
+extern void Wayland_FreeColorInfoState(struct Wayland_ColorInfoState *state);
+extern void Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, bool defer_event_processing);
+extern void Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, bool defer_event_processing);
 
 #endif // SDL_waylandcolor_h_
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 853c05eb1f90d..43019535f524c 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -1118,21 +1118,12 @@ static const struct wl_output_listener output_listener = {
     display_handle_description // Version 4
 };
 
-static void Wayland_GetOutputColorInfo(SDL_DisplayData *display)
-{
-    Wayland_ColorInfo info;
-    SDL_zero(info);
-
-    if (Wayland_GetColorInfoForOutput(display, &info)) {
-        SDL_copyp(&display->HDR, &info.HDR);
-    }
-}
-
 static void handle_output_image_description_changed(void *data,
                                                     struct wp_color_management_output_v1 *wp_color_management_output_v1)
 {
+    SDL_DisplayData *display = (SDL_DisplayData *)data;
     // wl_display.done is called after this event, so the display HDR status will be updated there.
-    Wayland_GetOutputColorInfo(data);
+    Wayland_GetColorInfoForOutput(display, false);
 }
 
 static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = {
@@ -1171,7 +1162,7 @@ static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
     if (data->videodata->wp_color_manager_v1) {
         data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output);
         wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data);
-        Wayland_GetOutputColorInfo(data);
+        Wayland_GetColorInfoForOutput(data, true);
     }
     return true;
 }
@@ -1191,6 +1182,7 @@ static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event)
         SDL_free(display_data->wl_output_name);
 
         if (display_data->wp_color_management_output) {
+            Wayland_FreeColorInfoState(display_data->color_info_state);
             wp_color_management_output_v1_destroy(display_data->wp_color_management_output);
         }
 
@@ -1221,7 +1213,7 @@ static void Wayland_FinalizeDisplays(SDL_VideoData *vid)
 
 static void Wayland_init_xdg_output(SDL_VideoData *d)
 {
-    for(int i = 0; i < d->output_count; ++i) {
+    for (int i = 0; i < d->output_count; ++i) {
         SDL_DisplayData *disp = d->output_list[i];
         disp->xdg_output = zxdg_output_manager_v1_get_xdg_output(disp->videodata->xdg_output_manager, disp->output);
         zxdg_output_v1_add_listener(disp->xdg_output, &xdg_output_listener, disp);
@@ -1234,7 +1226,7 @@ static void Wayland_InitColorManager(SDL_VideoData *d)
         SDL_DisplayData *disp = d->output_list[i];
         disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output);
         wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp);
-        Wayland_GetOutputColorInfo(disp);
+        Wayland_GetColorInfoForOutput(disp, true);
     }
 }
 
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 5b5ee24c0fa2c..8cde64c9acfd9 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -118,6 +118,7 @@ struct SDL_DisplayData
     SDL_DisplayID display;
     SDL_VideoDisplay placeholder;
     int wl_output_done_count;
+    struct Wayland_ColorInfoState *color_info_state;
 };
 
 // Needed here to get wl_surface declaration, fixes GitHub#4594
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index c06f72ad0a382..212fd54f3535f 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -1656,17 +1656,7 @@ static void feedback_surface_preferred_changed(void *data,
                                                uint32_t identity)
 {
     SDL_WindowData *wind = (SDL_WindowData *)data;
-    Wayland_ColorInfo info;
-    SDL_zero(info);
-
-    if (Wayland_GetColorInfoForWindow(wind, &info)) {
-        SDL_SetWindowHDRProperties(wind->sdlwindow, &info.HDR, true);
-        if (info.icc_size) {
-            wind->icc_fd = info.icc_fd;
-            wind->icc_size = info.icc_size;
-            SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
-        }
-    }
+    Wayland_GetColorInfoForWindow(wind, false);
 }
 
 static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = {
@@ -2626,6 +2616,7 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
         if (c->wp_color_manager_v1) {
             data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface);
             wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data);
+            Wayland_GetColorInfoForWindow(data, true);
         } else if (c->frog_color_management_factory_v1) {
             data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface);
             frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data);
@@ -3092,6 +3083,7 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
         }
 
         if (wind->wp_color_management_surface_feedback) {
+            Wayland_FreeColorInfoState(wind->color_info_state);
             wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback);
         }
 
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 601589a2d34f0..619fd79e6d7d5 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -118,6 +118,8 @@ struct SDL_WindowData
     struct frog_color_managed_surface *frog_color_managed_surface;
     struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
 
+    struct Wayland_ColorInfoState *color_info_state;
+
     SDL_AtomicInt swap_interval_ready;
 
     SDL_DisplayData **outputs;