Merge branch 'wl-backend-color-management' into 'master'

Draft: backend/wayland: add color-management-v1 support

See merge request wlroots/wlroots!5156
This commit is contained in:
Simon Ser 2026-01-31 14:09:53 +00:00
commit f8715ab798
6 changed files with 263 additions and 17 deletions

View file

@ -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);

View file

@ -12,6 +12,7 @@ wlr_files += files(
)
client_protos = [
'color-management-v1',
'drm',
'linux-dmabuf-v1',
'linux-drm-syncobj-v1',

View file

@ -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);

View file

@ -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;
};

View file

@ -0,0 +1,9 @@
#ifndef TYPES_WLR_COLOR_MANAGEMENT_V1_H
#define TYPES_WLR_COLOR_MANAGEMENT_V1_H
#include <wlr/types/wlr_color_management_v1.h>
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

View file

@ -10,6 +10,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 2
@ -1016,8 +1017,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_COMPOUND_POWER_2_4:
return WLR_COLOR_TRANSFER_FUNCTION_SRGB;
@ -1030,10 +1030,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) {
@ -1051,18 +1058,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) {