From ab5d3148b98df01d7505d3d5b20f5f9217fdb076 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 21 Sep 2025 01:38:35 +0200 Subject: [PATCH 1/2] color_management_v1: expose try_* enum converters in internal header This will be useful for the Wayland backend. --- include/types/wlr_color_management_v1.h | 9 +++++++++ types/wlr_color_management_v1.c | 25 +++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 include/types/wlr_color_management_v1.h diff --git a/include/types/wlr_color_management_v1.h b/include/types/wlr_color_management_v1.h new file mode 100644 index 000000000..7fdd50d72 --- /dev/null +++ b/include/types/wlr_color_management_v1.h @@ -0,0 +1,9 @@ +#ifndef TYPES_WLR_COLOR_MANAGEMENT_V1_H +#define TYPES_WLR_COLOR_MANAGEMENT_V1_H + +#include + +uint32_t transfer_function_try_to_wlr(enum wp_color_manager_v1_transfer_function tf); +uint32_t named_primaries_try_to_wlr(enum wp_color_manager_v1_primaries primaries); + +#endif diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index c69a69c81..ca9773e41 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -9,6 +9,7 @@ #include "color-management-v1-protocol.h" #include "render/color.h" +#include "types/wlr_color_management_v1.h" #include "util/mem.h" #define COLOR_MANAGEMENT_V1_VERSION 1 @@ -984,8 +985,7 @@ void wlr_color_manager_v1_set_surface_preferred_image_description( } } -enum wlr_color_transfer_function -wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_function tf) { +uint32_t transfer_function_try_to_wlr(enum wp_color_manager_v1_transfer_function tf) { switch (tf) { case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: return WLR_COLOR_TRANSFER_FUNCTION_SRGB; @@ -998,10 +998,17 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_ case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: return WLR_COLOR_TRANSFER_FUNCTION_BT1886; default: - abort(); + return 0; } } +enum wlr_color_transfer_function +wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_function tf) { + uint32_t wlr_tf = transfer_function_try_to_wlr(tf); + assert(wlr_tf != 0); + return wlr_tf; +} + enum wp_color_manager_v1_transfer_function wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf) { switch (tf) { @@ -1019,18 +1026,24 @@ wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function abort(); } -enum wlr_color_named_primaries -wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries) { +uint32_t named_primaries_try_to_wlr(enum wp_color_manager_v1_primaries primaries) { switch (primaries) { case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: return WLR_COLOR_NAMED_PRIMARIES_SRGB; case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: return WLR_COLOR_NAMED_PRIMARIES_BT2020; default: - abort(); + return 0; } } +enum wlr_color_named_primaries +wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries) { + uint32_t wlr_primaries = named_primaries_try_to_wlr(primaries); + assert(wlr_primaries != 0); + return wlr_primaries; +} + enum wp_color_manager_v1_primaries wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries) { switch (primaries) { From a981089065e93799490739e497f717a13decee04 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 21 Sep 2025 01:39:21 +0200 Subject: [PATCH 2/2] backend/wayland: add color-management-v1 support --- backend/wayland/backend.c | 53 +++++++++++ backend/wayland/meson.build | 1 + backend/wayland/output.c | 181 +++++++++++++++++++++++++++++++++--- include/backend/wayland.h | 11 +++ 4 files changed, 235 insertions(+), 11 deletions(-) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 14a783b67..595314c9d 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -18,7 +18,9 @@ #include "backend/wayland.h" #include "render/drm_format_set.h" #include "render/pixel_format.h" +#include "types/wlr_color_management_v1.h" +#include "color-management-v1-client-protocol.h" #include "drm-client-protocol.h" #include "linux-dmabuf-v1-client-protocol.h" #include "linux-drm-syncobj-v1-client-protocol.h" @@ -348,6 +350,49 @@ static const struct wl_shm_listener shm_listener = { .format = shm_handle_format, }; +static void color_manager_v1_handle_supported_intent(void *data, + struct wp_color_manager_v1 *color_manager_v1, uint32_t render_intent) { + // This space is intentionally left blank +} + +static void color_manager_v1_handle_supported_feature(void *data, + struct wp_color_manager_v1 *color_manager_v1, uint32_t feature) { + struct wlr_wl_backend *wl = data; + switch (feature) { + case WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC: + wl->color_manager_v1_features.parametric = true; + break; + case WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: + wl->color_manager_v1_features.set_mastering_display_primaries = true; + break; + } +} + +static void color_manager_v1_handle_supported_tf_named(void *data, + struct wp_color_manager_v1 *color_manager_v1, uint32_t tf) { + struct wlr_wl_backend *wl = data; + wl->supported_tfs |= transfer_function_try_to_wlr(tf); +} + +static void color_manager_v1_handle_supported_primaries_named(void *data, + struct wp_color_manager_v1 *color_manager_v1, uint32_t primaries) { + struct wlr_wl_backend *wl = data; + wl->supported_primaries |= named_primaries_try_to_wlr(primaries); +} + +static void color_manager_v1_handle_done(void *data, + struct wp_color_manager_v1 *color_manager_v1) { + // This space is intentionally left blank +} + +static const struct wp_color_manager_v1_listener color_manager_v1_listener = { + .supported_intent = color_manager_v1_handle_supported_intent, + .supported_feature = color_manager_v1_handle_supported_feature, + .supported_tf_named = color_manager_v1_handle_supported_tf_named, + .supported_primaries_named = color_manager_v1_handle_supported_primaries_named, + .done = color_manager_v1_handle_done, +}; + static void registry_global(void *data, struct wl_registry *registry, uint32_t name, const char *iface, uint32_t version) { struct wlr_wl_backend *wl = data; @@ -419,6 +464,11 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(iface, wp_linux_drm_syncobj_manager_v1_interface.name) == 0) { wl->drm_syncobj_manager_v1 = wl_registry_bind(registry, name, &wp_linux_drm_syncobj_manager_v1_interface, 1); + } else if (strcmp(iface, wp_color_manager_v1_interface.name) == 0) { + wl->color_manager_v1 = wl_registry_bind(registry, name, + &wp_color_manager_v1_interface, 1); + wp_color_manager_v1_add_listener(wl->color_manager_v1, + &color_manager_v1_listener, wl); } } @@ -553,6 +603,9 @@ static void backend_destroy(struct wlr_backend *backend) { if (wl->viewporter) { wp_viewporter_destroy(wl->viewporter); } + if (wl->color_manager_v1) { + wp_color_manager_v1_destroy(wl->color_manager_v1); + } free(wl->drm_render_name); free(wl->activation_token); xdg_wm_base_destroy(wl->xdg_wm_base); diff --git a/backend/wayland/meson.build b/backend/wayland/meson.build index b606a9bb9..919a88789 100644 --- a/backend/wayland/meson.build +++ b/backend/wayland/meson.build @@ -12,6 +12,7 @@ wlr_files += files( ) client_protos = [ + 'color-management-v1', 'drm', 'linux-dmabuf-v1', 'linux-drm-syncobj-v1', diff --git a/backend/wayland/output.c b/backend/wayland/output.c index fb4d1f914..f0d3d5cd1 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -18,8 +18,10 @@ #include "backend/wayland.h" #include "render/pixel_format.h" +#include "types/wlr_color_management_v1.h" #include "types/wlr_output.h" +#include "color-management-v1-client-protocol.h" #include "linux-dmabuf-v1-client-protocol.h" #include "linux-drm-syncobj-v1-client-protocol.h" #include "presentation-time-client-protocol.h" @@ -35,7 +37,8 @@ static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | WLR_OUTPUT_STATE_WAIT_TIMELINE | - WLR_OUTPUT_STATE_SIGNAL_TIMELINE; + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; static size_t last_output_num = 0; @@ -532,6 +535,9 @@ static void output_layer_handle_addon_destroy(struct wlr_addon *addon) { struct wlr_wl_output_layer *layer = wl_container_of(addon, layer, addon); wlr_addon_finish(&layer->addon); + if (layer->color_management_surface_v1 != NULL) { + wp_color_management_surface_v1_destroy(layer->color_management_surface_v1); + } if (layer->viewport != NULL) { wp_viewport_destroy(layer->viewport); } @@ -545,15 +551,27 @@ static const struct wlr_addon_interface output_layer_addon_impl = { .destroy = output_layer_handle_addon_destroy, }; -static struct wlr_wl_output_layer *get_or_create_output_layer( +static struct wlr_wl_output_layer *get_output_layer( struct wlr_wl_output *output, struct wlr_output_layer *wlr_layer) { assert(output->backend->subcompositor != NULL); - struct wlr_wl_output_layer *layer; struct wlr_addon *addon = wlr_addon_find(&wlr_layer->addons, output, &output_layer_addon_impl); - if (addon != NULL) { - layer = wl_container_of(addon, layer, addon); + if (addon == NULL) { + return NULL; + } + + struct wlr_wl_output_layer *layer = wl_container_of(addon, layer, addon); + return layer; +} + +static void set_surface_image_description(struct wlr_wl_output *output, + struct wp_color_management_surface_v1 *color_management_surface); + +static struct wlr_wl_output_layer *get_or_create_output_layer( + struct wlr_wl_output *output, struct wlr_output_layer *wlr_layer) { + struct wlr_wl_output_layer *layer = get_output_layer(output, wlr_layer); + if (layer != NULL) { return layer; } @@ -579,6 +597,12 @@ static struct wlr_wl_output_layer *get_or_create_output_layer( layer->viewport = wp_viewporter_get_viewport(output->backend->viewporter, layer->surface); } + if (output->backend->color_manager_v1 != NULL) { + layer->color_management_surface_v1 = + wp_color_manager_v1_get_surface(output->backend->color_manager_v1, layer->surface); + set_surface_image_description(output, layer->color_management_surface_v1); + } + return layer; } @@ -606,7 +630,6 @@ static void output_layer_unmap(struct wlr_wl_output_layer *layer) { } wl_surface_attach(layer->surface, NULL, 0, 0); - wl_surface_commit(layer->surface); layer->mapped = false; } @@ -627,7 +650,7 @@ static void damage_surface(struct wl_surface *surface, } } -static bool output_layer_commit(struct wlr_wl_output *output, +static bool output_layer_update(struct wlr_wl_output *output, struct wlr_wl_output_layer *layer, const struct wlr_output_layer_state *state) { if (state->layer->dst_box.x != state->dst_box.x || @@ -671,12 +694,11 @@ static bool output_layer_commit(struct wlr_wl_output *output, wl_surface_attach(layer->surface, buffer->wl_buffer, 0, 0); damage_surface(layer->surface, state->damage); - wl_surface_commit(layer->surface); layer->mapped = true; return true; } -static bool commit_layers(struct wlr_wl_output *output, +static bool update_layers(struct wlr_wl_output *output, struct wlr_output_layer_state *layers, size_t layers_len) { if (output->backend->subcompositor == NULL) { return true; @@ -702,7 +724,7 @@ static bool commit_layers(struct wlr_wl_output *output, prev_layer->surface); } - if (!output_layer_commit(output, layer, &layers[i])) { + if (!output_layer_update(output, layer, &layers[i])) { return false; } @@ -712,6 +734,17 @@ static bool commit_layers(struct wlr_wl_output *output, return true; } +static void commit_layers(struct wlr_wl_output *output) { + struct wlr_output_layer *wlr_layer; + wl_list_for_each(wlr_layer, &output->wlr_output.layers, link) { + struct wlr_wl_output_layer *layer = get_output_layer(output, wlr_layer); + if (layer == NULL) { + continue; + } + wl_surface_commit(layer->surface); + } +} + static void unmap_callback_handle_done(void *data, struct wl_callback *callback, uint32_t cb_data) { struct wlr_wl_output *output = data; @@ -723,6 +756,97 @@ static const struct wl_callback_listener unmap_callback_listener = { .done = unmap_callback_handle_done, }; +static int32_t encode_cie1931_coord(float value) { + return round(value * 1000 * 1000); +} + +static struct wp_image_description_v1 *create_image_description_v1( + struct wlr_wl_backend *wl, + const struct wlr_output_image_description *img_desc) { + struct wp_image_description_creator_params_v1 *param_creator = + wp_color_manager_v1_create_parametric_creator(wl->color_manager_v1); + + wp_image_description_creator_params_v1_set_tf_named(param_creator, + wlr_color_manager_v1_transfer_function_from_wlr(img_desc->transfer_function)); + wp_image_description_creator_params_v1_set_primaries_named(param_creator, + wlr_color_manager_v1_primaries_from_wlr(img_desc->primaries)); + + const struct wlr_color_primaries *mast_primaries = + &img_desc->mastering_display_primaries; + bool has_mast_primaries = memcmp(mast_primaries, + &(struct wlr_color_primaries){0}, sizeof(*mast_primaries)) != 0; + if (wl->color_manager_v1_features.set_mastering_display_primaries && + has_mast_primaries) { + wp_image_description_creator_params_v1_set_mastering_display_primaries( + param_creator, + encode_cie1931_coord(mast_primaries->red.x), + encode_cie1931_coord(mast_primaries->red.y), + encode_cie1931_coord(mast_primaries->green.x), + encode_cie1931_coord(mast_primaries->green.y), + encode_cie1931_coord(mast_primaries->blue.x), + encode_cie1931_coord(mast_primaries->blue.y), + encode_cie1931_coord(mast_primaries->white.x), + encode_cie1931_coord(mast_primaries->white.y)); + } + if (wl->color_manager_v1_features.set_mastering_display_primaries && + img_desc->mastering_luminance.min != 0 && + img_desc->mastering_luminance.max != 0) { + wp_image_description_creator_params_v1_set_mastering_luminance( + param_creator, + round(img_desc->mastering_luminance.min * 10000), + round(img_desc->mastering_luminance.max)); + } + + if (img_desc->max_cll != 0) { + wp_image_description_creator_params_v1_set_max_cll(param_creator, + img_desc->max_cll); + } + if (img_desc->max_fall != 0) { + wp_image_description_creator_params_v1_set_max_cll(param_creator, + img_desc->max_fall); + } + + return wp_image_description_creator_params_v1_create(param_creator); +} + +static void set_surface_image_description(struct wlr_wl_output *output, + struct wp_color_management_surface_v1 *color_management_surface) { + if (output->img_desc != NULL) { + wp_color_management_surface_v1_set_image_description(color_management_surface, + output->img_desc, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + } else { + wp_color_management_surface_v1_unset_image_description(color_management_surface); + } +} + +static void update_image_description(struct wlr_wl_output *output, + const struct wlr_output_image_description *img_desc) { + if (output->img_desc != NULL) { + wp_image_description_v1_destroy(output->img_desc); + output->img_desc = NULL; + } + + if (img_desc != NULL) { + output->img_desc = create_image_description_v1(output->backend, img_desc); + } + + assert(output->color_management_surface_v1 != NULL); + set_surface_image_description(output, output->color_management_surface_v1); + + if (output->cursor.color_management_surface_v1 != NULL) { + set_surface_image_description(output, output->cursor.color_management_surface_v1); + } + + struct wlr_output_layer *wlr_layer; + wl_list_for_each(wlr_layer, &output->wlr_output.layers, link) { + struct wlr_wl_output_layer *layer = get_output_layer(output, wlr_layer); + if (layer == NULL) { + continue; + } + set_surface_image_description(output, layer->color_management_surface_v1); + } +} + static bool output_commit(struct wlr_output *wlr_output, const struct wlr_output_state *state) { struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); @@ -852,10 +976,18 @@ static bool output_commit(struct wlr_output *wlr_output, const struct wlr_output } if ((state->committed & WLR_OUTPUT_STATE_LAYERS) && - !commit_layers(output, state->layers, state->layers_len)) { + !update_layers(output, state->layers, state->layers_len)) { return false; } + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + update_image_description(output, state->image_description); + } + + if (state->committed & (WLR_OUTPUT_STATE_LAYERS | WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)) { + commit_layers(output); + } + if (pending_enabled) { if (output->frame_callback != NULL) { wl_callback_destroy(output->frame_callback); @@ -914,6 +1046,11 @@ static bool output_set_cursor(struct wlr_output *wlr_output, if (output->cursor.surface == NULL) { output->cursor.surface = wl_compositor_create_surface(backend->compositor); + if (backend->color_manager_v1 != NULL) { + output->cursor.color_management_surface_v1 = + wp_color_manager_v1_get_surface(backend->color_manager_v1, output->cursor.surface); + set_surface_image_description(output, output->cursor.color_management_surface_v1); + } } struct wl_surface *surface = output->cursor.surface; @@ -958,6 +1095,9 @@ static void output_destroy(struct wlr_output *wlr_output) { wl_list_remove(&output->link); + if (output->cursor.color_management_surface_v1) { + wp_color_management_surface_v1_destroy(output->cursor.color_management_surface_v1); + } if (output->cursor.surface) { wl_surface_destroy(output->cursor.surface); } @@ -976,12 +1116,18 @@ static void output_destroy(struct wlr_output *wlr_output) { wl_callback_destroy(output->unmap_callback); } + if (output->img_desc != NULL) { + wp_image_description_v1_destroy(output->img_desc); + } if (output->drm_syncobj_surface_v1) { wp_linux_drm_syncobj_surface_v1_destroy(output->drm_syncobj_surface_v1); } if (output->zxdg_toplevel_decoration_v1) { zxdg_toplevel_decoration_v1_destroy(output->zxdg_toplevel_decoration_v1); } + if (output->color_management_surface_v1) { + wp_color_management_surface_v1_destroy(output->color_management_surface_v1); + } if (output->xdg_toplevel) { xdg_toplevel_destroy(output->xdg_toplevel); } @@ -1132,6 +1278,19 @@ static struct wlr_wl_output *output_create(struct wlr_wl_backend *backend, output->backend = backend; wl_list_init(&output->presentation_feedbacks); + if (backend->color_manager_v1) { + output->color_management_surface_v1 = wp_color_manager_v1_get_surface(backend->color_manager_v1, output->surface); + if (!output->color_management_surface_v1) { + wlr_log(WLR_ERROR, "Failed to get color management surface"); + return NULL; + } + } + + if (backend->color_manager_v1_features.parametric) { + wlr_output->supported_primaries = backend->supported_primaries; + wlr_output->supported_transfer_functions = backend->supported_tfs; + } + wl_proxy_set_tag((struct wl_proxy *)output->surface, &surface_tag); wl_surface_set_user_data(output->surface, output); diff --git a/include/backend/wayland.h b/include/backend/wayland.h index e24eb9fdf..05d8be4a2 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -53,7 +53,14 @@ struct wlr_wl_backend { struct xdg_activation_v1 *activation_v1; struct wl_subcompositor *subcompositor; struct wp_viewporter *viewporter; + struct wp_color_manager_v1 *color_manager_v1; char *drm_render_name; + uint32_t supported_tfs; // bitfield of enum wlr_color_transfer_function + uint32_t supported_primaries; // bitfield of enum wlr_color_named_primaries + struct { + bool parametric; + bool set_mastering_display_primaries; + } color_manager_v1_features; }; struct wlr_wl_buffer { @@ -90,6 +97,7 @@ struct wlr_wl_output_layer { struct wl_surface *surface; struct wl_subsurface *subsurface; struct wp_viewport *viewport; + struct wp_color_management_surface_v1 *color_management_surface_v1; bool mapped; }; @@ -106,6 +114,8 @@ struct wlr_wl_output { struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1; struct wp_linux_drm_syncobj_surface_v1 *drm_syncobj_surface_v1; + struct wp_color_management_surface_v1 *color_management_surface_v1; + struct wp_image_description_v1 *img_desc; struct wl_list presentation_feedbacks; char *title; @@ -129,6 +139,7 @@ struct wlr_wl_output { struct { struct wlr_wl_pointer *pointer; struct wl_surface *surface; + struct wp_color_management_surface_v1 *color_management_surface_v1; int32_t hotspot_x, hotspot_y; } cursor; };