From 5dd0635b31fcb6b12e9dcb3b908e501f153e7e8b Mon Sep 17 00:00:00 2001
From: Colin Marc <hi@colinmarc.com>
Date: Tue, 26 Mar 2024 11:12:28 +0100
Subject: [PATCH 2/4] vulkan/wsi: implement the Wayland color management
 protocol

This allows applications to use color spaces other than sRGB, if the compositor
supports them.

The color management surface is only created if a non-sRGB and non-passthrough
colorspace is set on the surface, so applications can still use the protocol
directly if they prefer.

Co-authored-by: Xaver Hugl <xaver.hugl@kde.org>
Reviewed-by: Sebastian Wick <sebastian.wick@redhat.com>
Acked-by: Daniel Stone <daniels@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32038>
---
 src/egl/wayland/wayland-drm/meson.build |   1 +
 src/vulkan/wsi/meson.build              |   1 +
 src/vulkan/wsi/wsi_common_wayland.c     | 421 ++++++++++++++++++++++--
 3 files changed, 400 insertions(+), 23 deletions(-)

diff --git a/src/egl/wayland/wayland-drm/meson.build b/src/egl/wayland/wayland-drm/meson.build
index 59ce3442499..d3b6c15ddd2 100644
--- a/src/egl/wayland/wayland-drm/meson.build
+++ b/src/egl/wayland/wayland-drm/meson.build
@@ -48,6 +48,7 @@ wp_protos = {
   'presentation-time': 'stable/presentation-time/presentation-time.xml',
   'tearing-control-v1': 'staging/tearing-control/tearing-control-v1.xml',
   'linux-drm-syncobj-v1': 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml',
+  'color-management-v1': 'staging/color-management/color-management-v1.xml',
 }
 wp_files = {}
 foreach name, xml : wp_protos
diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build
index 6933eb780c4..ba609ab3151 100644
--- a/src/vulkan/wsi/meson.build
+++ b/src/vulkan/wsi/meson.build
@@ -22,6 +22,7 @@ if with_platform_wayland
   files_vulkan_wsi += wp_files['tearing-control-v1']
   links_vulkan_wsi += libloader_wayland_helper
   files_vulkan_wsi += wp_files['linux-drm-syncobj-v1']
+  files_vulkan_wsi += wp_files['color-management-v1']
 endif
 
 if with_platform_windows
diff --git a/src/vulkan/wsi/wsi_common_wayland.c b/src/vulkan/wsi/wsi_common_wayland.c
index d6c1b862324..d4b47c1f1b8 100644
--- a/src/vulkan/wsi/wsi_common_wayland.c
+++ b/src/vulkan/wsi/wsi_common_wayland.c
@@ -47,6 +47,7 @@
 #include "presentation-time-client-protocol.h"
 #include "linux-drm-syncobj-v1-client-protocol.h"
 #include "tearing-control-v1-client-protocol.h"
+#include "color-management-v1-client-protocol.h"
 
 #include <util/cnd_monotonic.h>
 #include <util/compiler.h>
@@ -110,8 +111,13 @@ struct wsi_wl_display {
    struct wp_tearing_control_manager_v1 *tearing_control_manager;
    struct wp_linux_drm_syncobj_manager_v1 *wl_syncobj;
 
+   struct wp_color_manager_v1 *color_manager;
+
    struct dmabuf_feedback_format_table format_table;
 
+   struct u_vector color_primaries;
+   struct u_vector color_transfer_funcs;
+
    /* users want per-chain wsi_wl_swapchain->present_ids.wp_presentation */
    struct wp_presentation *wp_presentation_notwrapped;
    uint32_t wp_presentation_version;
@@ -125,6 +131,9 @@ struct wsi_wl_display {
    /* Formats populated by zwp_linux_dmabuf_v1 or wl_shm interfaces */
    struct u_vector formats;
 
+   /* Additional colorspaces returned by wp_color_management_v1. */
+   struct u_vector colorspaces;
+
    bool sw;
 
    dev_t main_device;
@@ -180,6 +189,11 @@ struct wsi_wl_surface {
    struct dmabuf_feedback dmabuf_feedback, pending_dmabuf_feedback;
 
    struct wp_linux_drm_syncobj_surface_v1 *wl_syncobj_surface;
+
+   struct vk_instance *instance;
+   struct wp_color_management_surface_v1 *color_surface;
+   int color_surface_refcount;
+   VkColorSpaceKHR colorspace;
 };
 
 struct wsi_wl_swapchain {
@@ -230,6 +244,8 @@ struct wsi_wl_swapchain {
       unsigned int refresh_nsec;
    } present_ids;
 
+   VkColorSpaceKHR colorspace;
+
    struct wsi_wl_image images[0];
 };
 VK_DEFINE_NONDISP_HANDLE_CASTS(wsi_wl_swapchain, base.base, VkSwapchainKHR,
@@ -885,6 +901,316 @@ static const struct wl_shm_listener shm_listener = {
    .format = shm_handle_format
 };
 
+static bool
+vector_contains(struct u_vector *vec, unsigned int val)
+{
+   unsigned int *ptr;
+
+   u_vector_foreach(ptr, vec)
+      if (*ptr == val)
+         return true;
+
+   return false;
+}
+
+struct Colorspace {
+   VkColorSpaceKHR colorspace;
+   enum wp_color_manager_v1_primaries primaries;
+   enum wp_color_manager_v1_transfer_function tf;
+};
+struct Colorspace colorspace_mapping[] = {
+   {
+      .colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_BT709_LINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_BT709_NONLINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_BT2020_LINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_HDR10_ST2084_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ,
+   },
+   /* VK_COLOR_SPACE_DOLBYVISION_EXT is left out because it's deprecated */
+   {
+      .colorspace = VK_COLOR_SPACE_HDR10_HLG_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_BT2020,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG,
+   },
+   {
+      .colorspace = VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR,
+   },
+   /* VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT is left out because there's no
+    * exactly matching transfer function in the Wayland protocol */
+   /* VK_COLOR_SPACE_PASS_THROUGH_EXT is handled elsewhere */
+   {
+      .colorspace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT,
+      .primaries = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
+      .tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB,
+   },
+   /* VK_COLOR_SPACE_DISPLAY_NATIVE_AMD isn't supported */
+   /* VK_COLORSPACE_SRGB_NONLINEAR_KHR is just an alias */
+   /* VK_COLOR_SPACE_DCI_P3_LINEAR_EXT is just an alias */
+};
+
+static int
+wsi_wl_display_determine_colorspaces(struct wsi_wl_display *display)
+{
+   u_vector_finish(&display->colorspaces);
+   if (!u_vector_init(&display->colorspaces, 8, sizeof(VkColorSpaceKHR)))
+      return -1;
+
+   /* SRGB_NONLINEAR is always supported. */
+   VkColorSpaceKHR *new_cs = u_vector_add(&display->colorspaces);
+   if (!new_cs)
+      return -1;
+   *new_cs = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+
+   /* as is PASS_THROUGH */
+   new_cs = u_vector_add(&display->colorspaces);
+   if (!new_cs)
+      return -1;
+   *new_cs = VK_COLOR_SPACE_PASS_THROUGH_EXT;
+
+   if (!display->color_manager)
+      return 0;
+
+   struct u_vector *tfs = &display->color_transfer_funcs;
+   struct u_vector *primaries = &display->color_primaries;
+   for (int i = 0; i < ARRAY_SIZE(colorspace_mapping); i++) {
+      if (!vector_contains(primaries, colorspace_mapping[i].primaries))
+         continue;
+      if (!vector_contains(tfs, colorspace_mapping[i].tf))
+         continue;
+      VkColorSpaceKHR *new_cs = u_vector_add(&display->colorspaces);
+      if (!new_cs)
+         return -1;
+      *new_cs = colorspace_mapping[i].colorspace;
+   }
+   return 0;
+}
+
+static void
+color_management_handle_supported_intent(void *data,
+                                         struct wp_color_manager_v1 *color_manager,
+                                         unsigned int intent)
+{
+   /* We only use the perceptual rendering intent, which is always supported. */
+}
+
+static void
+color_management_handle_supported_features(void *data,
+                                           struct wp_color_manager_v1 *color_manager,
+                                           unsigned int feature)
+{
+   /* We don't use any non-default features yet. */
+}
+
+static void
+color_management_handle_supported_tf_named(void *data,
+                                           struct wp_color_manager_v1 *color_manager,
+                                           unsigned int tf)
+{
+   struct wsi_wl_display *display = data;
+   unsigned int *new_tf = u_vector_add(&display->color_transfer_funcs);
+   if (new_tf)
+      *new_tf = tf;
+}
+
+static void
+color_management_handle_supported_primaries_named(void *data,
+                                                  struct wp_color_manager_v1 *color_manager,
+                                                  unsigned int primaries)
+{
+   struct wsi_wl_display *display = data;
+   unsigned int *new_primaries = u_vector_add(&display->color_primaries);
+   if (new_primaries)
+      *new_primaries = primaries;
+}
+
+static void
+color_management_handle_done(void *data, struct wp_color_manager_v1 *color_manager)
+{
+   /* Intentionally left blank */
+}
+
+static const struct wp_color_manager_v1_listener color_manager_listener = {
+   .supported_intent = color_management_handle_supported_intent,
+   .supported_feature = color_management_handle_supported_features,
+   .supported_tf_named = color_management_handle_supported_tf_named,
+   .supported_primaries_named = color_management_handle_supported_primaries_named,
+   .done = color_management_handle_done,
+};
+
+enum image_description_status {
+   undefined,
+   ready,
+   failed,
+};
+
+static void
+color_management_handle_image_desc_failed(void *data,
+                                          struct wp_image_description_v1 *desc,
+                                          unsigned int cause,
+                                          const char *msg)
+{
+   enum image_description_status *status = data;
+   *status = failed;
+}
+
+static void
+color_management_handle_image_desc_ready(void *data,
+                                        struct wp_image_description_v1 *desc,
+                                        unsigned int id)
+{
+   enum image_description_status *status = data;
+   *status = ready;
+}
+
+static const struct wp_image_description_v1_listener image_description_listener = {
+   .failed = color_management_handle_image_desc_failed,
+   .ready = color_management_handle_image_desc_ready,
+};
+
+static bool
+needs_color_surface(VkColorSpaceKHR colorspace)
+{
+   return colorspace != VK_COLOR_SPACE_PASS_THROUGH_EXT;
+}
+
+static void
+wsi_wl_surface_add_color_refcount(struct wsi_wl_surface *wsi_surface)
+{
+   wsi_surface->color_surface_refcount++;
+   if (wsi_surface->color_surface_refcount == 1) {
+      wsi_surface->color_surface =
+         wp_color_manager_v1_get_surface(wsi_surface->display->color_manager, wsi_surface->surface);
+   }
+}
+
+static void
+wsi_wl_surface_remove_color_refcount(struct wsi_wl_surface *wsi_surface)
+{
+   wsi_surface->color_surface_refcount--;
+   if (wsi_surface->color_surface_refcount == 0) {
+      wp_color_management_surface_v1_destroy(wsi_surface->color_surface);
+      wsi_surface->color_surface = NULL;
+   }
+}
+
+static VkResult
+wsi_wl_swapchain_update_colorspace(struct wsi_wl_swapchain *chain)
+{
+   struct wsi_wl_surface *surface = chain->wsi_wl_surface;
+   struct wsi_wl_display *display = surface->display;
+
+   /* we need the color management extension for
+    * everything except sRGB and PASS_THROUGH */
+   if (!display->color_manager) {
+      if (chain->colorspace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ||
+          chain->colorspace == VK_COLOR_SPACE_PASS_THROUGH_EXT) {
+         return VK_SUCCESS;
+      } else {
+         return VK_ERROR_SURFACE_LOST_KHR;
+      }
+   }
+
+   bool new_color_surface = !surface->color_surface;
+   bool needs_color_surface_new = needs_color_surface(chain->colorspace);
+   bool needs_color_surface_old = needs_color_surface(surface->colorspace);
+   if ((new_color_surface || !needs_color_surface_old) && needs_color_surface_new) {
+      wsi_wl_surface_add_color_refcount(surface);
+   } else if (needs_color_surface_old && !needs_color_surface_new) {
+      wsi_wl_surface_remove_color_refcount(surface);
+   }
+
+   if (!new_color_surface && surface->colorspace == chain->colorspace)
+      return VK_SUCCESS;
+
+   /* failure is fatal, so this potentially being wrong
+      in that case doesn't matter */
+   surface->colorspace = chain->colorspace;
+   if (!needs_color_surface_new)
+      return VK_SUCCESS;
+
+   struct wp_image_description_creator_params_v1 *creator =
+      wp_color_manager_v1_create_parametric_creator(display->color_manager);
+
+   if (!creator)
+      return VK_ERROR_SURFACE_LOST_KHR;
+
+   unsigned int primaries = 0;
+   unsigned int tf = 0;
+   for (int i = 0; i < ARRAY_SIZE(colorspace_mapping); i++) {
+      if (colorspace_mapping[i].colorspace == chain->colorspace) {
+         primaries = colorspace_mapping[i].primaries;
+         tf = colorspace_mapping[i].tf;
+      }
+   }
+
+   if (!primaries)
+      return VK_ERROR_SURFACE_LOST_KHR;
+
+   wp_image_description_creator_params_v1_set_primaries_named(creator, primaries);
+   wp_image_description_creator_params_v1_set_tf_named(creator, tf);
+
+   wl_proxy_set_queue((struct wl_proxy *) creator, display->queue);
+
+   struct wp_image_description_v1 *image_desc =
+      wp_image_description_creator_params_v1_create(creator);
+   if (!image_desc)
+      return VK_ERROR_SURFACE_LOST_KHR;
+
+   enum image_description_status status = undefined;
+   wp_image_description_v1_add_listener(image_desc, &image_description_listener, &status);
+
+   while (status == undefined) {
+      int ret = wl_display_dispatch_queue(display->wl_display, display->queue);
+      if (ret < 0)
+         return VK_ERROR_OUT_OF_DATE_KHR;
+   }
+   if (status == failed)
+      return VK_ERROR_SURFACE_LOST_KHR;
+
+   wp_color_management_surface_v1_set_image_description(chain->wsi_wl_surface->color_surface,
+                                                        image_desc,
+                                                        WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
+   wp_image_description_v1_destroy(image_desc);
+
+   return VK_SUCCESS;
+}
+
+
 static void
 presentation_handle_clock_id(void* data, struct wp_presentation *wp_presentation, uint32_t clk_id)
 {
@@ -942,6 +1268,17 @@ registry_handle_global(void *data, struct wl_registry *registry,
       display->commit_timing_manager =
          wl_registry_bind(registry, name, &wp_commit_timing_manager_v1_interface, 1);
    }
+
+   if (strcmp(interface, wp_color_manager_v1_interface.name) == 0) {
+      display->color_manager =
+         wl_registry_bind(registry, name, &wp_color_manager_v1_interface, 1);
+
+      u_vector_init(&display->color_primaries, 8, sizeof(uint32_t));
+      u_vector_init(&display->color_transfer_funcs, 8, sizeof(uint32_t));
+
+      wp_color_manager_v1_add_listener(display->color_manager,
+                                       &color_manager_listener, display);
+   }
 }
 
 static void
@@ -961,6 +1298,10 @@ wsi_wl_display_finish(struct wsi_wl_display *display)
    u_vector_foreach(f, &display->formats)
       u_vector_finish(&f->modifiers);
    u_vector_finish(&display->formats);
+   u_vector_finish(&display->colorspaces);
+   u_vector_finish(&display->color_primaries);
+   u_vector_finish(&display->color_transfer_funcs);
+
    if (display->wl_shm)
       wl_shm_destroy(display->wl_shm);
    if (display->wl_syncobj)
@@ -975,6 +1316,8 @@ wsi_wl_display_finish(struct wsi_wl_display *display)
       wp_commit_timing_manager_v1_destroy(display->commit_timing_manager);
    if (display->tearing_control_manager)
       wp_tearing_control_manager_v1_destroy(display->tearing_control_manager);
+   if (display->color_manager)
+      wp_color_manager_v1_destroy(display->color_manager);
    if (display->wl_display_wrapper)
       wl_proxy_wrapper_destroy(display->wl_display_wrapper);
    if (display->queue)
@@ -1066,9 +1409,14 @@ wsi_wl_display_init(struct wsi_wayland *wsi_wl,
          }
    }
 
-   /* Round-trip again to get formats and modifiers */
+   /* Round-trip again to get formats, modifiers and color properties */
    wl_display_roundtrip_queue(display->wl_display, display->queue);
 
+   if (wsi_wl_display_determine_colorspaces(display) < 0) {
+      result = VK_ERROR_OUT_OF_HOST_MEMORY;
+      goto fail;
+   }
+
    if (wsi_wl->wsi->force_bgra8_unorm_first) {
       /* Find BGRA8_UNORM in the list and swap it to the first position if we
        * can find it.  Some apps get confused if SRGB is first in the list.
@@ -1358,6 +1706,8 @@ wsi_wl_surface_get_formats(VkIcdSurfaceBase *icd_surface,
                            VkSurfaceFormatKHR* pSurfaceFormats)
 {
    VkIcdSurfaceWayland *surface = (VkIcdSurfaceWayland *)icd_surface;
+   struct wsi_wl_surface *wsi_wl_surface =
+      wl_container_of((VkIcdSurfaceWayland *)icd_surface, wsi_wl_surface, base);
    struct wsi_wayland *wsi =
       (struct wsi_wayland *)wsi_device->wsi[VK_ICD_WSI_PLATFORM_WAYLAND];
 
@@ -1369,18 +1719,22 @@ wsi_wl_surface_get_formats(VkIcdSurfaceBase *icd_surface,
    VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormatKHR, out,
                           pSurfaceFormats, pSurfaceFormatCount);
 
-   struct wsi_wl_format *disp_fmt;
-   u_vector_foreach(disp_fmt, &display.formats) {
-      /* Skip formats for which we can't support both alpha & opaque
-       * formats.
-       */
-      if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
-          !(disp_fmt->flags & WSI_WL_FMT_OPAQUE))
-         continue;
+   VkColorSpaceKHR *cs;
+   u_vector_foreach(cs, &display.colorspaces) {
+      struct wsi_wl_format *disp_fmt;
+      u_vector_foreach(disp_fmt, &display.formats) {
+         /* Skip formats for which we can't support both alpha & opaque
+          * formats.
+          */
+         if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
+            !(disp_fmt->flags & WSI_WL_FMT_OPAQUE)) {
+            continue;
+         }
 
-      vk_outarray_append_typed(VkSurfaceFormatKHR, &out, out_fmt) {
-         out_fmt->format = disp_fmt->vk_format;
-         out_fmt->colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+         vk_outarray_append_typed(VkSurfaceFormatKHR, &out, out_fmt) {
+            out_fmt->format = disp_fmt->vk_format;
+            out_fmt->colorSpace = *cs;
+         }
       }
    }
 
@@ -1397,6 +1751,8 @@ wsi_wl_surface_get_formats2(VkIcdSurfaceBase *icd_surface,
                             VkSurfaceFormat2KHR* pSurfaceFormats)
 {
    VkIcdSurfaceWayland *surface = (VkIcdSurfaceWayland *)icd_surface;
+   struct wsi_wl_surface *wsi_wl_surface =
+      wl_container_of((VkIcdSurfaceWayland *)icd_surface, wsi_wl_surface, base);
    struct wsi_wayland *wsi =
       (struct wsi_wayland *)wsi_device->wsi[VK_ICD_WSI_PLATFORM_WAYLAND];
 
@@ -1408,18 +1764,22 @@ wsi_wl_surface_get_formats2(VkIcdSurfaceBase *icd_surface,
    VK_OUTARRAY_MAKE_TYPED(VkSurfaceFormat2KHR, out,
                           pSurfaceFormats, pSurfaceFormatCount);
 
-   struct wsi_wl_format *disp_fmt;
-   u_vector_foreach(disp_fmt, &display.formats) {
-      /* Skip formats for which we can't support both alpha & opaque
-       * formats.
-       */
-      if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
-          !(disp_fmt->flags & WSI_WL_FMT_OPAQUE))
-         continue;
+   VkColorSpaceKHR *cs;
+   u_vector_foreach(cs, &display.colorspaces) {
+      struct wsi_wl_format *disp_fmt;
+      u_vector_foreach(disp_fmt, &display.formats) {
+         /* Skip formats for which we can't support both alpha & opaque
+          * formats.
+          */
+         if (!(disp_fmt->flags & WSI_WL_FMT_ALPHA) ||
+            !(disp_fmt->flags & WSI_WL_FMT_OPAQUE)) {
+            continue;
+         }
 
-      vk_outarray_append_typed(VkSurfaceFormat2KHR, &out, out_fmt) {
-         out_fmt->surfaceFormat.format = disp_fmt->vk_format;
-         out_fmt->surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+         vk_outarray_append_typed(VkSurfaceFormat2KHR, &out, out_fmt) {
+            out_fmt->surfaceFormat.format = disp_fmt->vk_format;
+            out_fmt->surfaceFormat.colorSpace = *cs;
+         }
       }
    }
 
@@ -1515,6 +1875,9 @@ wsi_wl_surface_destroy(VkIcdSurfaceBase *icd_surface, VkInstance _instance,
       dmabuf_feedback_fini(&wsi_wl_surface->pending_dmabuf_feedback);
    }
 
+   if (wsi_wl_surface->color_surface)
+      wp_color_management_surface_v1_destroy(wsi_wl_surface->color_surface);
+
    if (wsi_wl_surface->surface)
       wl_proxy_wrapper_destroy(wsi_wl_surface->surface);
 
@@ -1837,6 +2200,9 @@ wsi_CreateWaylandSurfaceKHR(VkInstance _instance,
    surface->display = pCreateInfo->display;
    surface->surface = pCreateInfo->surface;
 
+   wsi_wl_surface->instance = instance;
+   wsi_wl_surface->colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+
    *pSurface = VkIcdSurfaceBase_to_handle(&surface->base);
 
    return VK_SUCCESS;
@@ -2459,6 +2825,10 @@ wsi_wl_swapchain_queue_present(struct wsi_swapchain *wsi_chain,
              image->base.row_pitches[0] * chain->extent.height);
    }
 
+   VkResult ret = wsi_wl_swapchain_update_colorspace(chain);
+   if (ret != VK_SUCCESS)
+      return ret;
+
    /* For EXT_swapchain_maintenance1. We might have transitioned from FIFO to MAILBOX.
     * In this case we need to let the FIFO request complete, before presenting MAILBOX. */
    while (!chain->legacy_fifo_ready) {
@@ -2815,6 +3185,9 @@ wsi_wl_swapchain_chain_free(struct wsi_wl_swapchain *chain,
       wl_callback_destroy(chain->frame);
    if (chain->tearing_control)
       wp_tearing_control_v1_destroy(chain->tearing_control);
+   if (needs_color_surface(chain->colorspace) && wsi_wl_surface->color_surface) {
+      wsi_wl_surface_remove_color_refcount(wsi_wl_surface);
+   }
 
    /* Only unregister if we are the non-retired swapchain, or
     * we are a retired swapchain and memory allocation failed,
@@ -2979,6 +3352,8 @@ wsi_wl_surface_create_swapchain(VkIcdSurfaceBase *icd_surface,
                                                           WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC);
    }
 
+   chain->colorspace = pCreateInfo->imageColorSpace;
+
    enum wsi_wl_buffer_type buffer_type;
    struct wsi_base_image_params *image_params = NULL;
    struct wsi_cpu_image_params cpu_image_params;
-- 
2.49.0

